java基础-IO

2018-1-2 by Atlas

1. 分类

  • 分类标准-面向数据类型

字符流:主要操作char类型数据,包含Writer、Reader及其子类。
字节流:主要操作byte类型数据,包含OutputStream、InputStream及其子类。
字节便于计算机读写,字符便于人类识别,数据从字节到字符,再从字符到字节,完成人类和计算机之间的交流,也帮助人与人之间的交流。

  • 分类标准-面向数据流向

输入流:包含InputStream、Reader及其子类
输出流:包含OutputStream、Writer及其子类。
IO流的一个特点就是对称性,不论字节、字符,还是输入、输出,都是对称的,数据进进出出完成交流,本文不用代码举例描述也非常容易理解和记忆,也是对称的功劳,实际上接下来java序列化和解序列化将会形象的体会到。
整个IO类族是Writer、Reader及其子类和OutputStream、InputStream及其子类的组合拓展和完善的。

2. 常用类

  • InputStream

InputStream是所有输入字节流的父类,是一个抽象类。
ByteInputStream、FileInputStream是基本的介质流,分别从Byte数组、本地文件中读取数据。
ObjectInputStream和FilterInputStream及其子类都是装饰流。

  • OutputStream

OutputStream是所有输出字节流的父类,是一个抽象类。
ByteOutputStream、FIleOutputStream是基本的介质流,分别往Byte数组、本地文件中写入数据。
ObjectOutputStream和FilterOutputStream及其子类都是装饰流。

  • Reader

Reader是所有字符输入流的父类,是一个抽象类。
CharArrayReader、StringReader分别从Char数组、String中读取数据。
PipedReader从与其它线程共享的管道中读取数据。
BufferedReader、FilterReader及其子类都是装饰流。

  • Writer

Writer是所有字符输出流的父类,是一个抽象类。
CharArrayWriter、StringWriter分别往Char数组、String中写入数据。
PipedWriter往与其他线程共享的管道中写入数据。
BufferedWriter、FilterWriter及其子类都是装饰流。

  • InputStreamReader

InputStreamReader是一个连接字节输入流和字符输入流的桥梁,FileReader是实现此功能的工具类。

  • OutputStreamWriter

OutputStreamWriter是一个连接字节输出流和字符输出流的桥梁,FileWriter是实现此功能的工具类。

3. 理解IO原理

  • 用户态和内核态

对于操作系统而言,JVM只是一个用户进程,处于用户态空间中。而处于用户态空间的进程是不能直接操作底层的硬件的。而IO操作就需要操作底层的硬件,比如磁盘。因此,IO操作必须得借助内核的帮助才能完成(中断,trap),即:会有用户态到内核态的切换。

  • 用户缓冲和内核缓冲

优势:我们写代码 new byte[] 数组时,一般是都是“随意” 创建一个“任意大小”的数组。比如,new byte[128]、new byte[1024]、new byte[4096]....但是,对于磁盘块的读取而言,每次访问磁盘读数据时,并不是读任意大小的数据的,而是:每次读一个磁盘块或者若干个磁盘块(这是因为访问磁盘操作代价是很大的,而且我们也相信局部性原理) 因此,就需要有一个“中间缓冲区”--即内核缓冲区。先把数据从磁盘读到内核缓冲区中,然后再把数据从内核缓冲区搬到用户缓冲区。这也是为什么我们总感觉到第一次read操作很慢,而后续的read操作却很快的原因吧。因为,对于后续的read操作而言,它所需要读的数据很可能已经在内核缓冲区了,此时只需将内核缓冲区中的数据拷贝到用户缓冲区即可,并未涉及到底层的读取磁盘操作,当然就快了。
劣势:当需要传输的数据远远大于内核缓冲区的大小时,内核缓冲区就会成为瓶颈,原因是它已经起不到“缓冲”的功能。

  • 普通IO处理流程

普通IO处理流程

1)用户程序创建一个缓冲区。
2)当执行到read()方法时,其实底层是发生了很多操作的:
①内核给磁盘控制器发命令说:我要读磁盘上的某某块磁盘块上的数据。
②在DMA的控制下,把磁盘上的数据读入到内核缓冲区。
③内核把数据从内核缓冲区复制到用户缓冲区。

  • NIO处理流程

NIO处理流程

1)内核空间的 buffer 与 用户空间的 buffer 都映射到同一块 物理内存区域。
2)对文件的操作不需要再发read 或者 write 系统调用。
3)当用户进程访问“内存映射文件”地址时,自动产生缺页错误,然后由底层的OS负责将磁盘上的数据送到内存。
使用内存映射缓冲区来操作文件,它比普通的IO操作读文件要快得多。甚至比使用文件通道(FileChannel)操作文件 还要快。因为,使用内存映射缓冲区操作文件时,没有显示的系统调用(read,write),而且OS还会自动缓存一些文件页(memory page)

  • 经典读文件

大多数WEB应用程序执行的一系列操作:接受用户请求--->从本地磁盘读数据--->数据进入内核缓冲区--->用户缓冲区--->内核缓冲区--->用户缓冲区--->socket发送数据。

经典读文件

①第一次上下文切换发生在 read()方法执行,表示服务器要去磁盘上读文件了,这会导致一个 sys_read()的系统调用。此时由用户态切换到内核态,完成的动作是:DMA把磁盘上的数据读入到内核缓冲区中(这也是第一次拷贝)。
②第二次上下文切换发生在read()方法的返回(这也说明read()是一个阻塞调用),表示数据已经成功从磁盘上读到内核缓冲区了。此时,由内核态返回到用户态,完成的动作是:将内核缓冲区中的数据拷贝到用户缓冲区(这是第二次拷贝)。
③第三次上下文切换发生在 send()方法执行,表示服务器准备把数据发送出去了。此时,由用户态切换到内核态,完成的动作是:将用户缓冲区中的数据拷贝到内核缓冲区(这是第三次拷贝)。
④第四次上下文切换发生在 send()方法的返回【这里的send()方法可以异步返回,所谓异步返回就是:线程执行了send()之后立即从send()返回,剩下的数据拷贝及发送就交给底层操作系统实现了】。此时,由内核态返回到用户态,完成的动作是:将内核缓冲区中的数据送到 protocol engine.(这是第四次拷贝)。

  • zerocopy transferTo() 读文件

IO操作需要数据频繁地在内核缓冲区和用户缓冲区之间拷贝,而zerocopy技术可以减少这种拷贝的次数,同时也降低了上下文切换(用户态与内核态之间的切换)的次数。
zerocopy技术的目标就是提高IO密集型JAVA应用程序的性能。

zerocopy transferTo() 读文件

1)DMA将数据从磁盘读入 Read buffer中(第一次数据拷贝)。
2)还是在内核空间中,将数据从Read buffer 拷贝到 Socket buffer(第二次数据拷贝)。
3)最终再将数据从 Socket buffer 拷贝到 NIC buffer(第三次数据拷贝)。
4)再从内核态返回到用户态。
三次数据拷贝和二次上下文切换。

  • 增强的 zerocopy transferTo() 读文件

如果底层的网络硬件以及操作系统支持,还可以进一步减少数据拷贝次数 以及 CPU干预次数。

增强的 zerocopy transferTo() 读文件

1)用户程序执行 transferTo()方法,导致一次系统调用,从用户态切换到内核态。
2)DMA将数据从磁盘中拷贝到Read buffer用一个描述符标记此次待传输数据的地址以及长度。
3)DMA直接把数据从Read buffer 传输到 NIC buffer。

参考文献:JAVAIO以及NIO理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章