通俗易懂的来讲,个人比较喜欢把io理解成对于文件中的数据进行读取或者修改的一种途径。
关于io的那些具体代码该如何编写,本文不打算进行具体展示,因为代码案例很多在网上一搜就有,所以直接忽略。
(作为程序员,基本的搜索能力还是要有的)
个人对于io的认识
每次读取数据都开启一个通道,然后用一个载体去读取文件里面的内容,然后将文件里面的数据运输回到程序里面。
那么载体的种类可能为字节,也可能是以缓冲的形式来读取数据,因此这个时候又分为了面向字节和面向缓冲的形式来读取数据。
面向字节读取数据
如图所示,一次运输一个字节,所以效率会比较底下。常见的字节流都是以这种方式读取和修改数据信息的。
FileInputStream,ObjectInPutStream,PipedOutputStream,ByteArrayInputPutStream...
面向缓冲读取数据
面向缓冲的形式对数据内容读取的效率会较高,原因在于buffer会预先读取一定的字节内容,传输到程序中,读取效率会有所提升。
IO操作的时候计算机内部到底发生了什么?
在较早的计算机里面,读取磁盘汇总的数据的请求通常都是由一个程序发起的,首先会请求用户的地址空间(这里面存储了buffer数据)再去请求内核地址空间的内容。用户的内核地址空间和os的内核地址空间之间进行数据copy。内核地址空间需要去请求文件的io接口,读取数据。
但是每次内核地址空间去请求文件的io接口的时候,都需要cpu分出精力来处理io请求,因此会对cpu造成较大的压力。
于是随着技术的进步,后边出现了一种技术叫做dma总线技术,将io接口请求的任务分配给了dma和内存处理,减轻了cpu的压力。
假设当大量的请求发送过来之后,dma每次运行的时候都需要向cpu申请资源,创建dma总线。但是如果dma总线过多,容易发生冲突,因此会有性能影响。(申请资源需要排队)
因此后边又有了技术改善,dma拥有了自己的内存,专门用于进行io操作,有自己的命令和传输方式,不需要向cpu申请资源。
非直接缓冲区
jdk早期版本的是数据读取过程中,实际上是需要经历如下的一个过程
java程序并非直接就有权利去读取磁盘里面的数据,需要通过磁盘文件的io接口将数据传输到内核地址的缓存区,然后通过拷贝的形式将数据拷贝到用户地址的缓存区,再从用户地址里面的缓存区域进行数据的拷贝,从而实现io的数据读取。(通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间)
直接缓冲区
在jdk1.4之后出现了new io,也就是我们常说的nio,它的运行效率高的原因之一就是使用了直接缓冲区。让jvm直接将部分的内存存储在了物理内存里面,避免了原来的jvm和os之间进行数据copy的性能消耗。
但是这样也有一定的缺点,那就是数据的安全性没有给os来进行管理,有一定的危险(例如病毒程序的篡改)。有时候程序里面的大对象存储也会使用堆外内存来保存,也是这种原理。(不占用jvm内存)如下图所示:
阻塞和非阻塞
Java的Io的各种流是阻塞的,当某个线程执行io这类操作的时候,都会进入一个堵塞的状态,直到有相应的数据读取或者全部写入完毕之后才阻塞结束。
随着jdk的发展,逐渐出现了nio技术,这是一种基于非阻塞模式的处理手段。当有线程进行数据的读写的时候,会有单独的线程来进行对于多个管道的输入输出管理。
同步io vs. 异步io
同步I/O:当有一连串的io请求需要处理的时候,无法并发的执行,需要进行排队等候。
异步I/O:用户线程发起I/O请求后仍然继续执行,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
字节流和字符流
早期版本的jdk里面提供了字节流和字符流两种类型,字符流在处理数据的时候是基于字符的形式,比原始的字节速度要高些,但是在处理一些类似于图片这类的数据的时候却无从下手。(因为图片的编码格式和常规的字符流编码格式不一致)。因此字符流和字节流各有各的优势,并不能说被任一方所替代。
总结
个人觉得io是java程序猿向高端进阶的一门比较吃力的学问了,从原始的bio发展到现在的nio,aio。netty,mina等一系列高性能io框架出来之后,市场的实际应用份额也越来越高。包括现有的很多rpc框架,例如说阿里的dubbo,名声赫赫的Tomcat,Jetty等服务器,很多都是基于io的技术基础进行升级研发的。