IO流简述
IO流即输入输出流,通过IO可以完成硬盘文件的读和写,输入输出是相对于内存而言的。

IO流的分类
有多种分类方式:
一种方式是按照流的方向进行分类:
以内存作为参照物,
往内存中去,叫输入(Input)读(Read)
从内存中出来,叫输出(output)或者写(Write)
另一种方式是按照读取薮据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取1个字节byte(8bit),这种流是万能的,
什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频等
假设文件file.txt,采用字节流的话是这样读的:
a中国bc张三fe
第一次读:一个字节,正好读到'a'
第二次读:一个字节,正好读到'中'字符的一半
第三次读:一个字节,正好读到'中'字符的另外一半
有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,
这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
假设文件file.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读:'a'字符('a'字符在windows系统中占用1个字节。)
第二次读:'中'字符(中字符在windows系统中占用2个字节。)
Java中的IO流
Java中IO流都已经写好了,我们需要掌握java中的各种流及其操作。java中IO流主要有四大家族(都是抽象类):java.io.InputStream(字节输入流)、java.io.OutputStream(字节输出流)、java.io.Reader(字符输入流)、java.io.Writer(字符输入流)。以Reader、Writer结尾的都是字符流,以Stream结尾的都是字节流。
所有的流都实现了java.io.Closeable接口,都有close()方法,都是可关闭的。流是内存和硬盘之间的通道,用完一定要关,减少资源损耗。所有输出流都实现了java.io.Flushable接口,都有flush()方法,都是可刷新的。刷新表示将通道中剩余未输出的数据强行输出完(清空管道)。输出流在最终输出之后一定要flush(),避免数据丢失。
java.io包下需要掌握的流有16个:
文件专属:
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
***重点掌握 FileInputStream和FileOutputStream,其余查文档。
FileInputStream
要点
java.io.FileInputStream:文件输入流,万能的,任何文件类型都可以读,以字节方式完成输入操作(硬盘-->内存)。
int read():一次读一个字节
int read(byte[] b):一次最多读取b.length个字节,减少硬盘和内存的交互,提高程序效率。
int available():返回流当中剩余的没有读到的字节数量
long skip(long n):跳过n个字节不读
实例一
public class FileInputStreamTest01 {
public static void main(String[] args) {
//创建文件字节输入流对象
FileInputStream fis=null;
//文件绝对路径:E:\java\test(IDEA会自动将\编程\\转义
try {
// fis=new FileInputStream("E:\\java\\test.txt");
//写成这样也可以
fis=new FileInputStream("E:/java/test.txt");
//开始读
/*
int readData=0;
//read()返回值尾-1则读到文件末尾,一个字节一个字节地读
//返回值是读到的字节
//内存和硬盘交互过于频繁,大量时间都花费在数据传输上
while((readData=fis.read())!=-1){
System.out.println(readData);
}
*/
byte[] bytes=new byte[4];//准备一个4个字节长度的byte数组abcdef
//这个方法返回值是读字节数量,不是文件数据本身。
//没读到数据则返回-1
/*
int readCount=fis.read(bytes);
System.out.println(readCount);//4
System.out.println(new String(bytes));//abcd
System.out.println(new String(bytes,0,readCount));//abcd
readCount=fis.read(bytes);
System.out.println(readCount);//2
//将bytes数组中所有数据转成字符串
System.out.println(new String(bytes));//efcd
//读多少转多少
System.out.println(new String(bytes,0,readCount));//ef
*/
int readCount=0;
while((readCount=fis.read(bytes)) != -1){
System.out.println(new String(bytes,0,readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//确保流关闭
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
实例二
public class FileInputStreamTest02 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
fis=new FileInputStream("E:/java/test.txt");
/*
int readByte = fis.read();
System.out.println("剩下多少个字节没有读:"+fis.available());//5
//这种方式不太适合太大的文件,应为byte[]数组不能太大
byte[] bytes = new byte[fis.available()];
//不需要循环,一次读完文件所有剩下数据
int readCount=fis.read(bytes);
System.out.println(new String(bytes));//bcdef
*/
//skip跳过几个字节不读
fis.skip(3);
System.out.println(fis.read());//100
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileOutputStream
要点
实例
public class FileOutputStreamTest01 {
public static void main(String[] args) {
FileOutputStream fos=null;
try {
//文件不存在会新建
//这种方式会将文件清空然后再写,谨慎使用!
//fos=new FileOutputStream("tempfile.txt");
//以追加的方式在文件末尾写入,不会清空原文件内容。
fos = new FileOutputStream("Chapter23/src/tempfile",true);
byte[] bytes ={97,98,99,100};
//将bytes数组的全部写入
fos.write(bytes);
//将bytes数组的一部分写入
fos.write(bytes,0,2);
String s = "我是一个中国人,我骄傲!!";
//将字符串转换成字符串数组
byte[] bs = s.getBytes();
fos.write(bs);
//写完一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文件复制
要点
使用FileInputeStream + FileOutputStream完成文件拷贝,拷贝过程是一边读一边写 使用以上的字节流拷贝文件的时候,文件类型随意。

拷贝文件
public class Copy01 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建一个输入流对象
fis=new FileInputStream("E:\\动漫剪辑素材\\场景\\studio_video_1626758642687.mp4");
//创建一个输出流对象
fos = new FileOutputStream("E:\\java\\dom.mp4");
//边读边写
byte[] bytes = new byte[1024*1024];//1MB
int readCount=0;
while((readCount=fis.read(bytes))!=-1){
fos.write(bytes,0,readCount);
}
//刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//分开try catch,一起try其中一个出异常可能影响另一个的关闭
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileReader和FileWriter
要点
FileReader:
文件输入流,只能读取普通文本,一次读取一个字符,用法和FileInputStream相同,只不过存储数据用char[]数组,而不是byte[]数组。读取文本内容时,比较方便快捷。
FileWriter:
文件输出流,只能输出普通文本,一次输出一个字符,用法和FileOutputStream相同,只不过存储数据用char[]数组,而不是byte[]数组。写入文本内容时,比较方便快捷。
能用记事本打开编辑的都是普通文本文件不仅限于.txt文件。
缓冲流
要点
BufferedReader:
带有缓冲区的输入流,使用这个流时不需要自定义char数组或byte数组,自带缓冲区。
BufferedWriter:
带有缓冲的字符输出流。
OutputStreamWriter和OutputStreamReader:转换流
实例一
public class BufferedReaderTest01 {
public static void main(String[] args) throws IOException {
//当一个流的构造方法中需要一个流的时候,这个北传进来的流叫节点流
//外部负责包装的流叫包装流/处理流
//FileReader是结点流,BufferReader是包装流/处理流
FileReader reader=new FileReader("Copy01.java");
BufferedReader br = new BufferedReader(reader);
String s=null;
while((s =br.readLine())!=null){
System.out.println(s);//默认读一个文本行但不带换行符
System.out.println("\n");
}
//关闭流
//对于包装流来说,只需要关闭最外层流,里面的流会自动关闭。(可以看源码)
br.close();
/*
//字节流
FileInputStream in = new FileInputStream("Copy01.java");
//通过转换流转换
InputStreamReader reader1 = new InputStreamReader(in);
//这个构造方法只能传一个字符流,不能传字节流
BufferedReader br1 = new BufferedReader(reader);
*/
BufferedReader br1=new BufferedReader(new InputStreamReader(new FileInputStream("Copy1.java")));
String line=null;
while(((line=br1.readLine())!=null)){
System.out.println(line);
}
br1.close();;
}
}
实例二
public class BufferWriterTest {
public static void main(String[] args) throws IOException {
//带有缓冲区的字符输出流
//BufferedWriter out = new BufferedWriter(new FileWriter("copy"));
//以追加方式写
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy",true)) );
//开始写
out.write("hello world");
out.write("\n");
out.write("hello kitty");
//刷新
out.flush();
//关闭最外层流
out.close();
}
}
数据流
java.io.DataOutputStream:数据专属流。
DateOutputStream写的文件,只能使用DataInputStream去读,并且读的时候需要知道写入的顺序,按写入的顺序读,才可以正常取出数据,可用于加密(用的少)。
标准输出流
要点
java.io.PrintStream:标准的字节输出流,默认输出到控制台。标准输出流不需要手动关闭。实例
public class Logger {
/*
记录日志
* */
public static void log(String msg){
try {
//指向一个日志文件
PrintStream out=new PrintStream(new FileOutputStream("log.txt",true));
//改变输出方向
System.setOut(out);
//日期当前时间
Date nowTIme = new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:ss SSS");
String strTime = sdf.format(nowTIme);
System.out.println(strTime+":"+msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
public class LogTest {
public static void main(String[] args) {
Logger.log(msg:"调用了System类的gc()方法,建议启动垃圾回收")
Logger.log(msg:"用户尝试登录,验证失败");
}
}
File类简述
要点
File类的常用方法:
方法很多,查文档就好了。listFiles()方法获取当前目录下所有的子文件。
实例
public class FileTest01 {
public static void main(String[] args) throws Exception {
//创建一个File对象
File f1 = new File( pathname: “D:\file”);
//判断是否存在
System.out.println(f1.exists());
//如果:\file不存在,则以文件的形式创建出来
/*if(!f1.exists()) {
//以文件形式新建
f1. createNewFile();
}*/
//如果D:\file不存在 ,则以目录的形式创建出来
/*if(!f1. exists()) {
//以目录的形式新建。
f1. mkdir();
}*/
//创建多重目录
File f2 = new File( pathname: "D:/a/b/c/d/e/f");
/*if(!f2.exists()) {
//多重目录的形式新建。
f2. mkdirs();
}*/
File f3 = new File( pathname: "D:\\course\\学习方法.txt");
//获取文件的父路径
String parentPath = f3.getParent();
System.out.println(parentPath); //D: \course
File parentFile = f3.getParentFile();
System.out.prihtln("获取绝对路径:"+ parentFile.getAbsolutePath());
File f4 = new File( pathname: "copy");
System.out.println("绝对路径:"+ f4.getAbsolutePath());
File f5 = new File("pathname: "D:\\course\\asdf.txt");
System.out.println(f5.getName());//asdf
//判断是否是一个目录
System.out.println(f5.isDirectory()); // false
//判断是否是一个文件
System.out.println(f5.isFile()); // true
//获取文件最后一次修改时间
long haoMiao = f5.lastModified(); //这个毫秒是从1970年到现在的总毫秒数。
//将总毫秒数转换成日期
Date time = new Date(haoMiao);
SimpleDateFormat sdf = new SimpleDateFormat( pattern: "yyyy-MM-dd HH:mm:ss SSs");
String strTime = sdf.format(time);
System.out.println(strTime);
//获取文件大小
System.out.println(f5.length());
// File[] listFiles()
//获取当前目录下所有的子文件。
File f= new File( pathname: "D:\\course");
File[] files = f.listFiles();
// foreach
for(File file : files){
//System.out.println(file.getAbsolutePath());
System.out.println(file.getName());
}
}
}
对象流
序列化和反序列化

要点
1、参与序列化和反序列化的对象,必须实现Serializable接口。
2、注意:通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable {
}
这个接口当中什么代码都没有。
那么它起到一个什么作用呢?
起到标识的作用,标志的作用,Serializable这个标志接口是给java虚拟机参考的,
java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。
序列化版本号有什么用?
Java语言区分类机制:
①首先通过类名进行比对,如果类名不同则不是同一个类
②如果类名相同,则通过序列化版本号区分,如果序列化版本号不同则不是同一个类,否则为同一个类。
不同人开发编写了类名相同的不同类(这两个了类都实现了Serializable接口),这时候就可以通过序列化版本号来区分这两个类。
自动生成序列化版本号的缺陷?
一旦代码确定之后不能进行后续的修改,因为只要修改,必然会重新编译生成全新的序列化版本号,
这个时候java虚拟机会认为这是一个全新的类。
凡是一个类实现了Serializable接口,建议将该类的序列号版本号手动写死( private static final long serialVersionUID = ),不建议自动生成。Idea可以为类生成固定的序列化版本号。
序列化版本号不一致报错:
InvalidClassException: Student;
local class incompatible: stream classdesc serialVersionUID = 412425857988772568,
local class serialVersionUID = 3891390038314571630
3、序列化多个对象:将对象放在集合当中,序列化集合。
4、transient关键字:游离的,不参与序列化。在对象属性前加transient关键字表示该属性不参与序列化操作,反序列化时该属性值为null。
序列化的实现
public class ObjectOutputStreamTest {
public static void main(String[] args) throws IOException {
//创建java对象
Student s = new Student(1111,"zhangsan");
//序列化对象
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("students"));
oos.writeObject(s);
oos.flush();
oos.close();
List<Student> studentList = new ArrayList<>();
studentList.add(new Student(001,"wangwu"));
studentList.add((new Student(002,"lisi")));
studentList.add(s);
//序列化studentList(List已经实现Serializable接口),批量序列化student对象
ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("students2"));
oos1.writeObject(studentList);
oos1.flush();
oos1.close();
}
}
反序列化
public class ObjectInputStreamTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("students"));
//开始反序列化,读
Object obj=ois.readObject();
//反序列化回来是一个学生对象
System.out.println(obj);//Student{no=1111, name='zhangsan'}
ois.close();
//反序列化集合
ObjectInputStream ois1=new ObjectInputStream(new FileInputStream("students2"));
//Object obj1=ois.readObject();
// System.out.println(obj1 instanceof List);//true
List<Student> studentList = (List<Student>) ois1.readObject();
for (Student student : studentList){
System.out.println(student);
}
ois1.close();
}
}
IO + Properties
要点
经常改变的数据可以单独写到一个文件中,使用程序动态读取。
将来只需呀修改这个文件的内容,不需要改动java代码,不需要重新编译,服务器也不需要重启,
就可以拿到动态的信息。以上机制的文件称为配置文件。
并且当配置文件的内容格式为:
key=value;
时,这种配置文件叫属性配置文件。属性配置文件以.properties结尾,但这不是必须的。
属性配置文件中#代表注释,key重复则value会自动覆盖,等号两端不要有空格。
Properties对象专门存放属性配置文件数据。
实例
public class IOPropertiesTest {
public static void main(String[] args) throws IOException {
/*
Properties是一个Map集合,key和value都是String类型。
将userinfo文件中的数据加载到Properties对象中。
* */
//新建一个输入流
FileReader reader = new FileReader("chapter23/userinfo");
//新建一个map集合
Properties pro=new Properties();
//调用Properties对象的load方法将文件中数据加载到map集合中
pro.load(reader);//文件中的数据顺着管道以键值对的方式加载到map集合
String username=pro.getProperty("username");
System.out.println(username);
//有更简单的方法:直接用Bundle属性类,这里不说了
}
}