【Java知识点】关于I/O你需要知道的基础
基础知识
IO流简介
IO,即(Input/Output),也就是输入和输出。IO流在Java中分为输入流和输出流,根据数据的处理方式又分为字节流和字符流。
- 为什么IO操作有字节流和字符流?
- 在不知道编码类型的情况下,字节流操作容易出现乱码。
- 字符流要通过Java虚拟机转换得到的,过程比较耗时。
- 音频文件、图片等媒体文件用字节流较好,文字用字符流较好。
字节流
InputStream(字节输入流)
java.io.InputStream抽象类是所有字节输入流的父类。
- 作用
- 用于从源头(通常是文件)读取数据(字节信息)到内存中。
FileInputStream
- 作用
- 常用的字节输入流对象,可以指定文件路径,可以直接读取单字节数据,也可以读取至字节数组中。
- 一般不直接单独使用,常与BufferedInputStream(字节缓冲输入流)搭配,如下面这段代码。
1
2
3
4
5// 新建一个 BufferedInputStream 对象
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("inputFile.txt"));
// 读取文件的内容并复制到 String 对象中
String result = new String(bufferedInputStream.readAllBytes());
System.out.println(result);
DataInputStream
- 作用
- 用于读取指定类型数据,例如int、boolean等,不能单独使用,必须结合FileInputStream
1
2
3
4
5
6
7FileInputStream fileInputStream = new FileInputStream("inputFile.txt");
//必须将fileInputStream作为构造参数才能使用
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
//可以读取任意具体的类型数据
dataInputStream.readBoolean();
dataInputStream.readInt();
dataInputStream.readUTF();
- 用于读取指定类型数据,例如int、boolean等,不能单独使用,必须结合FileInputStream
还有ObjectInputStream(反序列化,读取对象)等..
OutputStream(字节输出流)
java.io.OutputStream抽象类是所有字节输出流的父类。
- 作用
- 用于将数据(字节信息)写入到目的地(通常是文件)
FileOutputStream、DataOutputStream、ObjectOutputStream与Input差不多
字符流
Reader(字符输入流)
java.io.Reader抽象类是所有字符输入流的父类。常用的FileReader(读取字符文件)继承自InputStreamReader(字节流转换为字符流的桥梁),InputStreamReader继承自Reader。
- 作用
- 用于从源头(通常是文件)读取数据(字符信息)到内存中。
Writer(字符输出流)
java.io.Writer抽象类是所有字节输出流的父类。常用的FileWriter(写入字符到文件)继承自OutputStreamWriter(字符流转换为字节李的桥梁),继承自OutputStreamWriter继承自Writer。
- 作用
- 常用于将数据(字符信息)写入到目的地(通常是文件)
字节缓冲流
- 是什么
- 采用装饰器模式增强了InputStream和OutputStream子类对象的功能。有BufferedInputStream和BufferedOutputStream
- 在调用write(int b)和read()这两个一次只读取一个字节的方法时,由于字节缓冲流内部有缓冲区,会将读取到的字节先存放在缓冲区。
- 为什么
- 由于IO操作十分消耗性能,所以采用缓冲流将数据加载至缓冲区,一次性读取/写入多个字节从而避免频繁的IO操作,提高传输效率。缓冲区本质上是一个字节数组,大小默认为8192字节,可自定义。
字符缓冲流
BufferedReader (字符缓冲输入流)和 BufferedWriter(字符缓冲输出流)类似于BufferedInputStream(字节缓冲输入流)和BufferedOutputStream(字节缓冲输入流)。
打印流
- PrintStream(字节打印流),是OutputStream的子类。
- PrintWriter(字符打印流),是Writer的子类
- System.out实际上就是获取一个PrintStream对象,System.out.print就是调用PrintStream对象的write方法。
随机访问流
- 作用
- 支持跳转到文件任意位置进行读写的RandomAccessFile。在rw模式下若文件指针位置已有数据使用write方法会覆盖。
- 常用于实现大文件的断点续传
- 模式
- r —— 只读模式
- rw —— 读写模式
- rws —— 相对于rw,rws 同步更新对“文件的内容”或“元数据”的修改到外部存储设备。
- rwd —— rwd 同步更新对“文件的内容”的修改到外部存储设备。
- 常用方法
- seek(long pos) —— 设置文件指针偏移量,距离开头pos个字节。
- getFilePointer() —— 获取文件指针当前位置。
设计模式
装饰器模式
装饰器模式(Decorator)可以在不改变原有对象的情况下拓展其功能。
- 特点
- 通过组合替代继承来扩展原始类的功能,通过实现或者继承该类的抽象父类或者实现的接口,在一些继承关系比较复杂的场景更加实用,例如I/O场景。
- 必须有一个被装饰的对象(作为成员变量)
- 必须拥有与被装饰对象相同的接口(多态调用,扩展需要)
- 它可以给被装饰对象添加额外的功能
- 可以对原始类嵌套使用多个装饰器,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口
- 这段话看着很难理解,实际上就可以理解为辅助和ADC,都是英雄单位,辅助英雄通过各种技能来增强ADC的作用。
适配器模式
适配器(Adapter Pattern)模式主要用于接口互不兼容的类的协调工作,可以联想到日常使用的电源适配器,比如游戏机的欧版电源需要适配器才能在中国用。
- 适配者(Adaptee)
- 定义
- 被适配的对象或者类
- 定义
- 适配器(Adapter)
- 定义
- 作用于适配者的对象或者类
- 分类
- 对象适配器
- 通过组合关系来实现
- 类适配器
- 通过继承关系来实现
- 对象适配器
- 定义
- 案例
- IO中的字符流就是基于适配器模式来做的(和字节流不同)
- 通过对象适配器,将字节流对象适配成一个字符流对象,这样我们可以直接通过字节流对象来读取或者写入字符数据
- InputStreamReader 和 OutputStreamWriter 就是两个适配器(Adapter), 同时,它们两个也是字节流和字符流之间的桥梁。InputStreamReader 使用 **StreamDecoder(流解码器)对字节进行解码,实现字节流到字符流的转换, OutputStreamWriter 使用StreamEncoder(流编码器)**对字符进行编码,实现字符流到字节流的转换。
- 与装饰器模式的区别
- 装饰器模式
- 更侧重于动态地增强原始类的功能,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。
- 适配器模式
- 更侧重于让接口不兼容而不能交互的类可以一起工作。当我们调用适配器对应的方法时,适配器内部会调用适配者类或者和适配类相关的类的方法,这个过程透明的。类似于Type-C转USB的转接头,即插即用。
- 适配器和适配者不惜要继承相同的抽象类或者实现相同的接口。
- 装饰器模式
工厂模式
工厂模式用于创建对象,NIO(Non-Blocking IO,非阻塞)中大量用到了工厂模式。意图在于定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
- 案例
- 可参考 菜鸟教程
观察者模式
主打一个监听,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所以依赖于它的对象都得到通知并被自动更新。
常见的I/O模型
BIO(Blocking I/O)
BIO属于同步阻塞IO模型
- 同步阻塞IO模型中,应用程序发起accept()、read()、write()、connect()调用后,会一直阻塞,直到相关的操作等待完成之后才能继续后续代码处理。
- 在低访问量情况下,程序能够正常运行,然而面对高并发的情况下,这个传统的BIO模型就无能为力。虽然可以创建多线程来面对高并发,但是系统的性能会大打折扣。
NIO(Non-blocking/New I/O)
NIO中的N不单纯是New的意思,也可以理解为Non-blocking。Java中的NIO。属于同步非阻塞模型,一个线程可以处理多个请求连接。对于高负载、高并发的应用,应使用NIO。
- 适用于链接数目比较多且链接时间短的架构。
- 核心组件
- Channel(通道)
- 是某一个实体(硬件设备/文件/网络套接字/程序)和操作系统底层I/O进行通信的桥梁
- channel是双向的,可以读数据,也可以写数据。类似流但是流是单向的。
- Selector(多路复用器/选择器)
- 是单线程处理多个请求的核心组件
- 服务端客户端会轮询调用selector的select()方法获取事件,此方法是一个阻塞方法,如果当前没有事件产生,就会阻塞。有事件产生例如新客户端连接等,select()方法会结束,此时从selector中就能获取一个SelectionKey的集合,每个元素代表一个事件,循环处理所有事件。
- 非阻塞并不是完全不阻塞,只是将阻塞放到了selector端。非阻塞体现在拉取到事件后,比如读取服务端的响应数据一定可以读取到。
- Buffer(缓冲区)
- NIO中对数据进行读写都会通过缓冲区。
- 不是单单byte数组,是经过封装的。
- 有capacity、position、limit这三个重要的变量。
- Channel(通道)
AIO(Asynchronous I/O)
AIO也就是NIO2。NIO的改进版,是异步IO模型。
异步IO是基于事件和回调机制实现的,也即是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。