【Java】IO流

本文详细介绍了Java中的IO流,包括FileReader/FileWriter的字符流操作,File类的功能,缓冲流的使用,转换流在不同数据类型间的转换,以及打印流和数据流的基本应用。还特别讲解了对象流的序列化与反序列化,强调了对象必须实现Serializable接口才能被序列化,并讨论了版本控制和transient关键字在序列化中的作用。
摘要由CSDN通过智能技术生成

字符流

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()方法得到一个字符串,并将字符串转换为字符,放入缓冲区再经过转换流输出到给定的输出流上。

img

数据流

数据流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关键字,使得某些属性不参与序列化,取消这些不必要保存的属性,可以节省数据空间占用以及减少序列化时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值