IO
四大顶流
- 所有以Reader结尾的都是输入字符流;所有以Writer结尾的都是输出字符流;
- 四大流都实现了Closeable接口,说明都可以关闭,都有close()方法。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭。不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭.
- 只有输出流实现了
Flushable
接口。养成一个好习惯,输出流在最终输出之后,一定要记得flush()
刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。注意:如果没有flush()可能会导致丢失数据.
文件专属流
FileInputStream FileOutputStream FileReader FileWriter
- FileInputStream 万能流, 任何类型的文件都可以以字节读入到内存,但是一次读取一个字节byte ,这样内存和硬盘交互太频繁,基本上时间资源都耗费在交互上面了。标准模版:
FileInputStream fis = null;
try {
fis = new FileInputStream("src/Test.java");
// 这种available()方法开辟空间不适合大文件
byte[] bytes = new byte[fis.available()];
int readcount;
// read方法可以接收一个byte数组,每次读取byte数组长度的字节,并返回实际读到的字节数,因为有可能
// 文件最后一次读取时读不满 byte.length 个字节。当 readcount=-1时,表示读不到数据了。
while((readcount = fis.read(bytes)) != -1) {
// String又一个构造方法是可以传入一个byte数组,并设置从哪个索引开始,将多少个字节数组元素转换为
// String
System.out.println(new String(bytes, 0, readcount));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
FileInputStream 其他常用方法:
available()
返回当前流中文件剩余没有读到的字节数量skip(n)
跳过n个字节不读
- FileOutputStream
FileOutputStream fos =null;
try {
// 如果该文件不存在则创建文件,如果存在则会覆盖原有内容
fos = new FileOutputStream("/Users/ymy/test.txt");
// 加上true后保证是在原有内容上追加
fos = new FileOutputStream("/Users/ymy/test.txt", true);
byte[] bytes = {101, 102, 23, 33};
// 将整个字节数组写入
fos.write(bytes);
// 从0索引开始写入2个元素
fos.write(bytes, 0, 2);
// 写完之后一定要刷新
fos.flush();
} catch (IOException e) {
e.printStackTrace();
}
- FileReader 只能读取文本文件
FileReader fr = null;
try {
fr = new FileReader("/Users/ymy/test.txt");
char[] chars = new char[6];
int readCount = 0;
while ((readCount = fr.read(chars)) != -1)
System.out.println(new String(chars, 0, readCount));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- FileWriter 只能输出普通文本
FileWriter fw = null;
try {
// 逻辑和FileOutputStream一样
fw = new FileWriter("/Users/ymy/test.txt", true);
// FileWriter 支持4种写入方式
char[] chars = {'我', '是', '中', '国', '人'};
fw.write(chars);
fw.write(chars, 0, 3);
fw.write(new String("哈哈哈哈"));
fw.write(new String("一二三四"), 0, 3);
fw.write("aaa");
// 输出流一定要记得刷新
fw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
FileReader 和 FileWriter 只能处理普通文本文件
转换流
InputStreamReader OutputStreamWriter
缓冲流
BufferedReader BufferedWriter BufferedInputStream BufferedOutputStream
- BufferedReader 带有缓冲区的字符输入流。使用这个流的时候不需要自定Xchar数组,或者说不需要自定byte数组。自带缓冲。比较方便写代码。
BufferedReader br = null;
try {
// 当一个流的构造方法中需要- -个流的时候,这个被传进来的流叫做:节点流。
// 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
// 像当前这个程序来说: FileReader就是-一个节点流。BufferedReader 就是包装流/处理流。
br = new BufferedReader(new FileReader("/Users/ymy/test.java"));
String s = null;
// readline()方法读取一个文本行,但不会读取换行符。
while ((s = br.readLine()) != null)
System.out.println(s);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
// 关闭流。对于包装流来说,只需要关闭最外层流就行,里面的结点流会自动关闭
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- BufferedWriter 一样,构造器接收一个
Writer
类型的对象。其他操作和FileWriter
一样
数据流
DataInputStream DataOutputStream
- DataOutputStream :数据专属的流。这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通文本文档。 (这个文件使用记事本打不开。 )
标准输出流
PrintWriter PrintStream
- PrintStream 标准的字节输出流。默认输出到控制台。并且该流不需要手动关闭close() 。我们最经常用的方法
System.out.println()
中,System.out
返回的就是一个 PrintStream 类的对象。
PrintStream out = System.out;
out.println("hahah");
还可以将标准输出流的输出方向修改为某个文件。不过setOut方法要接收一个PrintStream类对象。
这个就是实现日志记录的方法。
try {
PrintStream printStream = new PrintStream(new FileOutputStream("log.txt"));
System.setOut(printStream);
printStream.println("调用了方法");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
对象流
ObjectInputStream ObjectOutputStream
序列化: Serialize java对象存储到文件中。 将java对象的状态保存下来的过程。
反序列化: DeSerialize将硬盘上的数据重新恢复到内存当中,恢复成java对象。
- 如果要让自定义类可序列化和反序列化,一定要实现
Serializable
接口 - 通过源代码发现, Serializable接口只是一个标志接口:这个接口当中什么代码都没有。那么它起到一个什么作用呢?起到标识的作用,标志的作用, jqva虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
- Serial izable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成
一个序列化版本号。
序列化单一对象
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("students"));
Student s1 = new Student("abc");
oos.writeObject(s1);
oos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
反序列化单一对象
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("students"));
Object student = ois.readObject();
System.out.println(student);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
序列化多个对象 此时要将多个对象放在集合中,并且该集合也需要实现 Serializable
接口
List<Student> list = new ArrayList<>();
list.add(new Student("qwe"));
list.add(new Student("xyz"));
list.add(new Student("jkl"));
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("objects"));
oos.writeObject(list);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
反序列化多个对象 反序列化的结果也是放在一个集合中
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("objects"));
List<Student> list = (List<Student>) ois.readObject();
System.out.println(list);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
transient
修饰的属性表示不参与序列化
序列化版本号
java语言中是采用什么机制来区分类的?
- 首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
- 如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。
Java,虚拟机看到serializable接口之后,会自动生成一个序列化版本号。这里没有手动写出来, java虚拟机会默认提供这个序列化版本号。建议将序列化版本号手动的写出来。不建议自动生成。
A编写了一个类: com.abc.java.bean.Student implements Serializable
B编写了一个类: com.abc.java.bean.Student implements Serializable
不同的人编写了同一个类,但“这两个类确实不是同-一个类”。这个时候序列化版本就起上作用了。对于java,虚拟机来说, java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不-样。所以区分开了。( 这是自动生成序列化版本号的好处)
这种自动生成序列化版本号有什么缺陷?
这种自动生成的序列化版本号缺点是:一旦代码确定之后 ,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号。这个时候java虚拟机会认为这是一个全新的类。( 这样就不好了! )
最终结论:凡是一个类实现了Serial izable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变, java虚拟机会认为是同-一个类。
文件类File
一个File对象有可能对应的是目录,也可能是文件。File只是一个路径名的抽象表示形式。File类中有很多常用方法。