IO
输入Input:数据输入到计算机内存的过程 。
输出Output:数据输出到外部存储的过程。
IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
四个抽象类基类:输入流的基类---InputStream【字节输入】、Reader【字符输入】
输出流的基类---OutputStream【字节输出】、Writer【字符输出】
字节流
InputStream(字节输入流)
从源头(通常是文件)读取数据(字节信息)到内存中,java.io.InputStream常用方法如下:
read() | 返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回 -1 ,表示文件结束。 |
read(byte b[ ]) | 等价于 read(b, 0, b.length),从输入流中读取一些字节存储到数组 b 中,返回读取的字节数(无则-1) |
read(byte b[], int off, int len) | off---偏移量;len---要读取的最大字节数 |
skip(long n) | 忽略n个字节,返回实际忽略的字节数 |
available() | 返回输入流中可以读取的字节数 |
close() | 关闭并释放资源 |
Java9新增 readAllBytes() 读取输入流中的所有字节,返回字节数组 readNBytes(byte[] b, int off, int len) 阻塞直到读取 transferTo(OutputStream out) 将所有字节从一个输入流传递到一个输出流 |
FileInputStream 是一个比较常用的字节输入流对象,可直接指定文件路径,可以直接读取单字节数据,也可以读取至字节数组中。通常配合 BufferedInputStream(字节缓冲输入流)使用。
InputStream fis = new FileInputStream("input.txt")
BufferedInputStream bufferedInputStream = new BufferedInputStream(fis);
// 读取文件的内容并复制到 String 对象中
String result = new String(bufferedInputStream.readAllBytes());
OutputStream(字节输出流)
将数据(字节信息)写入到目的地(通常是文件),常用方法:write(int b)、write(byte b[ ])、write(byte[] b, int off, int len)、flush()【刷新此输出流并强制写出所有缓冲的输出字节】、close()
同输入对应FileOutputStream与BufferedOutputStream
字符流
因为 字符流由Java虚拟机将字节转换得到,耗时;乱码问题等,IO流提供了一个直接操作字符的接口,方便字符操作,在涉及字符时使用,默认Unicode编码。
Reader(字符输入流)
从源头(通常是文件)读取数据(字符信息/文本)到内存中,常用方法如下:
read()、read(char[] cbuf)、read(char[] cbuf, int off, int len)、skip(long n)、close()
InputStreamReader 是字节流转换为字符流的桥梁extends Reader,其子类 FileReader 是基于该基础上的封装,可以直接操作字符文件。
try (FileReader fileReader = new FileReader("input.txt");) {
int content;
long skip = fileReader.skip(3);
System.out.println("The actual number of bytes skipped:" + skip);
System.out.print("The content read from file:");
while ((content = fileReader.read()) != -1) {
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
Writer(字符输出流)
将数据(字符信息)写入到目的地(通常是文件),常用方法:
write(int c)、write(char[] cbuf)、write(char[] cbuf, int off, int len)、write(String str)、write(String str, int off, int len)、append(CharSequence csq)【将指定的字符序列附加到指定的 Writer 对象并返回该 Writer 对象】、append(char c)、flush()、close()
同输入OutputStreamWriter 是字符流转换为字节流的桥梁extends Writer,其子类 FileWriter 是基于该基础上的封装,可以直接将字符写入到文件。
字节/字符缓冲流
将数据加载至缓冲区,一次性读取/写入多个字节,避免频繁的 IO 操作,如通过 BufferedInputStream(字节缓冲输入流)来增强 FileInputStream 的功能。
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))
BufferedReader (字符缓冲输入流)和 BufferedWriter(字符缓冲输出流)功能类似,内部都维护了一个字节数组作为缓冲区。
打印流
System.out.print("Hello!") ,System.out 实际是用于获取一个 PrintStream 对象,print方法实际调用的是 PrintStream 对象的 write 方法。PrintStream 属于字节打印流,与之对应的是 PrintWriter (字符打印流)。PrintStream 是 OutputStream 的子类,PrintWriter 是 Writer 的子类。
随机访问流
RandomAccessFile 支持随意跳转到文件的任意位置进行读写,可以指定 mode(读写模式)。
RandomAccessFile randomAccessFile = new RandomAccessFile(new File("input.txt"), "rw");
四种IO设计模式
装饰器(Decorator)模式
装饰器模式用组合代替继承扩展功能,适用于继承复杂的IO流。
对于字节流,FilterInputStream/FilterOutputStream是核心,其BufferedXxx、DataXxx等子类增强FileXxx等基础流。
适配器(Adapter Pattern)模式
适配器模式协调接口不兼容的类,像电源转接头。分对象适配(组合)与类适配(继承)。IO中,InputStreamReader/OutputStreamWriter是对象适配器,把字节流转字符流,分别用StreamDecoder/StreamEncoder编解码。
工厂模式
用于创建对象,NIO 中大量用到了工厂模式,比如 Files 类的 newInputStream 方法用于创建 InputStream 对象(静态工厂)、 Paths 类的 get 方法创建 Path 对象(静态工厂)、ZipFileSystem 类的 getPath 的方法创建 Path 对象(简单工厂)。
观察者模式
NIO 中的文件目录监听服务使用到了观察者模式,基于 WatchService 接口【观察者】和 Watchable 接口【被观察者】实现。
补充:Java NIO(1.4)= Non-blocking,Channel+Selector+Buffer,面向缓冲、通道,高并发网络首选。
IO模型
开发常接触的两种IO为磁盘 IO(读写文件) 和 网络 IO(网络请求和响应),应用程序发起 IO 调用,内核真正执行 IO。
同步阻塞模型 BIO (Blocking I/O)
应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。无法处理高并发
同步非阻塞模型
read没数据立即返回,线程空档做别的事,再回来轮询,避免久堵;但高频轮询耗CPU。
I/O 多路复用模型
先 select/epoll 问哪些内核数据就绪,再阻塞 read,减少无效系统调用,省 CPU。
信号驱动IO
应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。
异步IO
相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。
NIO
弥补了同步阻塞 I/O 的不足,它在标准 Java 代码中提供了非阻塞、面向缓冲、基于通道的 I/O,可以使用少量的线程来处理多个连接,大大提高了 I/O 效率和并发。
传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
零拷贝
计算机执行 IO 操作时,CPU 不需要将数据从一个存储区域复制到另一个存储区域,从而可以减少上下文切换以及 CPU 的拷贝时间,主要解决操作系统在处理 I/O 操作时频繁复制数据的问题。零拷贝的常见实现技术有: mmap+write、sendfile和 sendfile + DMA gather copy 。
传统
mmap+write【mmap() 替换 read(),将内核缓冲区里的数据「映射」到用户空间】
sendfile【替代前面的 read() 和 write() 这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销】
DMA gather copy【网卡的 SG-DMA 控制器,缓冲区只将描述符和数据长度传到socket缓冲区,数据直接拷贝到网卡缓冲区】