字符流
FileReader和FileWriter
使用char[]承接内容,一个位置放入一个字符。
不支持resset()和字节流一样
使用.getEncoding()获取编码格式
写入使用.append()链式调用
写入后使用flush()刷新
字符流更适合读文本文件,字节流可以读更多类型的文件
File类
可以是文件或者文件夹,快速获得文件信息
public static void main(String[] args) {
File file = new File("test.txt"); //直接创建文件对象,可以是相对路径,也可以是绝对路径
System.out.println(file.exists()); //此文件是否存在
System.out.println(file.length()); //获取文件的大小
System.out.println(file.isDirectory()); //是否为一个文件夹
System.out.println(file.canRead()); //是否可读
System.out.println(file.canWrite()); //是否可写
System.out.println(file.canExecute()); //是否可执行
}
通过File对象,我们就能快速得到文件的所有信息,如果是文件夹,还可以获取文件夹内部的文件列表等内容:
File file = new File("/");
System.out.println(Arrays.toString(file.list())); //快速获取文件夹下的文件名称列表
for (File f : file.listFiles()){ //所有子文件的File对象
System.out.println(f.getAbsolutePath()); //获取文件的绝对路径
}
如果我们希望读取某个文件的内容,可以直接将File作为参数传入字节流或是字符流:
File file = new File("test.txt");
try (FileInputStream inputStream = new FileInputStream(file)){ //直接做参数
System.out.println(inputStream.available());
}catch (IOException e){
e.printStackTrace();
}
file.mkdir可以创建没有的文件夹 file.mkdirs递归地创建多级文件夹
缓冲流
BufferedInputStream的父类是FileInputStream,并且同名方法实质上调用的也是传入的FileInputStream类的方法,这种写法成为装饰者模式,在调用之前进行一些额外的操作
缓冲区默认8192个字节/字符
I/O操作一般不能重复读取内容(比如键盘发送的信号,主机接收了就没了),而缓冲流提供了缓冲机制,一部分内容可以被暂时保存,BufferedInputStream支持reset()
和mark()
操作。
使用mark(int readlimit)标记一个位置,在使用reset(int readlimit)之后,文件流会重新从这个位置开始读
BufferedReader缓冲字符流,操作上没有太多区别
以下三种情况会将缓冲区内容送入目标:
- 缓冲区满
- flush
- close
转换流
有时会遇到这样一个很麻烦的问题:读取的是一个字符串或是一个个字符,但是我只能往一个OutputStream里输出,但是OutputStream又只支持byte类型,如果要往里面写入内容,进行数据转换就会很麻烦,可以通过new另一种IO类来进行转换流。
public static void main(String[] args) {
try(OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("test.txt"))){ //虽然给定的是FileOutputStream,但是现在支持以Writer的方式进行写入
writer.write("lbwnb"); //以操作Writer的样子写入OutputStream
}catch (IOException e){
e.printStackTrace();
}
}
还有InputStreamReader
打印流
System.out
就是一个打印流PrintStream 继承自FilterOutputStream
存在自动刷新机制,且不会抛出任何异常,有内部检查机制checkError()
我们平时使用的println
方法就是PrintStream中的方法,它会直接打印基本数据类型或是调用对象的toString()
方法得到一个字符串,并将字符串转换为字符,放入缓冲区再经过转换流输出到给定的输出流上。
数据流
数据流DataInputStream也是FilterInputStream的子类,同样采用装饰者模式,最大的不同是它支持基本数据类型的直接读取:
public static void main(String[] args) {
try (DataInputStream dataInputStream = new DataInputStream(new FileInputStream("test.txt"))){
System.out.println(dataInputStream.readBoolean()); //直接将数据读取为任意基本数据类型
}catch (IOException e) {
e.printStackTrace();
}
}
用于写入基本数据类型:
public static void main(String[] args) {
try (DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("output.txt"))){
dataOutputStream.writeBoolean(false);
}catch (IOException e) {
e.printStackTrace();
}
}
写入的是二进制数据,并不是写入的字符串,使用DataInputStream可以读取,一般他们是配合一起使用的。
对象流
基本数据类型能够读取和写入基本数据类型,对象也支持。ObjectOutputStream不仅支持基本数据类型,通过对对象的序列化操作,以某种格式保存对象,来支持对象类型的IO。
- 它不是继承自FilterInputStream的。
- 对象的类必须实现Serializable接口才能被序列化
public static void main(String[] args) {
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("output.txt"));
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("output.txt"))){
People people = new People("lbw");
outputStream.writeObject(people);
outputStream.flush();
people = (People) inputStream.readObject();
System.out.println(people.name);
}catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
static class People implements Serializable{ //必须实现Serializable接口才能被序列化
String name;
public People(String name){
this.name = name;
}
}
在后续的操作中,有可能会使得这个类的一些结构发生变化,如新增成员属性,而原来保存的数据只适用于之前版本的这个类,因此我们需要一种方法来区分类的不同版本:
static class People implements Serializable{
private static final long serialVersionUID = 123456; //在序列化时,会被自动添加这个属性,它代表当前类的版本,我们也可以手动指定版本。
String name;
public People(String name){
this.name = name;
}
}
当发生版本不匹配时,会无法反序列化为对象:
java.io.InvalidClassException: com.test.Main_base$People; local class incompatible: stream classdesc serialVersionUID = 123456, local class serialVersionUID = 1234567
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2003)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1850)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2160)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1667)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
at com.test.Main_base.main(Main_base.java:27)
如果我们不希望某些属性参与到序列化中进行保存,我们可以添加transient
关键字:
transient int score;
在一些JDK内部的源码中,也存在大量的transient关键字,使得某些属性不参与序列化,取消这些不必要保存的属性,可以节省数据空间占用以及减少序列化时间。