面向流:向流数据进行操作。流是单向的。可以把流理解为水流,水流在管道中只能流向一个方向。
NIO:面向缓冲区
NIO 中的通道:可以把它理解为铁路,铁路(通道)本身并没有传输功能,它就是用来连接的,但可以通过火车(工具) 来进行数据的传输。这个火车(工具) 就叫缓冲区。而是是双向的,火车可以在轨道上两头走的。
通道:负责连接。
缓冲区:负责存储数据。
第一个区别是针对于IO 与NIO 本身而言的。
第二第三个区别是针对于网络编程(Socket) 而言的
缓冲区:
Buffer: 底层就是一个数组
put() : 写模式
filp(): 切换成读模式
clear():
position 和limit 回到最初状态,无法知道buffer 中数据情况,所以就无法正确读取数据。
remaining : limit - position
直接缓冲区与非直接缓冲区:
API 上的详细介绍:
当应用程序向磁盘空间发送IO 请求(read(), write()),不是直接通过应用程序直接把数据读/写到磁盘文件中的,而是通过一些过程:
内核地址空间:操作系统的内存空间
用户地址空间:JVM 的地址空间。
传统IO 操作 和通过allocate()分配的缓冲区都是用的非直接缓冲区。
其实中间的copy 操作是没有必要的,所以NIO 中就直接在物理内存中开辟Buffer 内存空间,应用程序和物理磁盘直接面向这个Buffer 来进行读/写数据。
所以NIO 的效率比传统IO 的效率要高。因为省去了copy 操作。
弊端:1.不安全 2. 因为缓冲区直接建立到物理内存中,所以是比JVM 上建立Buufer消耗大,销毁的消耗也大。 3. 当数据写到物理内存映射文件(Buffer)中时,数据什么时候到达目的地(物理磁盘/ 应用程序),是由操作系统说了算,所以这里数据不易控制。
如果数据能长时间在内存进行操作,或者大量大数据不想在JVM 中开辟空间,就可以放到这个物理内存映射文件中。
这个物理内存什么时候释放? 当GC 回收的时候,回收到应用程序对这个物理内存的引用时,这是这个物理内存才会被回收。
从源码角度看直接缓冲区和非直接缓冲区的区别:
非直接缓冲区底层就是一个分配在堆空间的数组。
直接缓冲区分配方法:
点进去是看不了的了。
使用API 判断是不是直接缓冲区。
通道:
源节点和目标节点的连接
当我们发起读写请求时,CPU 会把目标物理磁盘中的数据,拿到内存中进行运算。
最早期的IO 接口,全都是由CPU 独立负责的,当我们应用程序发起读写请求时,CPU 的占用率就变得很高。以至于CPU 不能做其他工作。没有高效的利用CPU。
后来做了改良:在内存和IO 接口之间搞了一个DMA(直接存储器)。这是当应用系统对操作系统发送读写请求时,DMA 会向CPU 申请权限(资源),CPU 给了它权限以后,IO 接口的操(读写操作)作就全权由DMA 来负责操作。这样CPU 就能有更多资源去执行其他工作。
传统的数据传输方式:DMA(就是我们所说的IO 流)
如果应用程序发起大量的读写请求时,DMA 会不断的向CPU 申请资源,然后不断的建立DMA 总线,如果DMA 总线过多,会造成总线冲突问题。如果出现这个问题,也会影响性能。
继续改良:
把DMA 改良成通道的方式(DMA 还是存在的)。通道的好处:它是一个完全独立的处理器,专门用于IO 操作的,有自己的命令。(它依然附属于CPU)这样就和CPU 直接分开了,当进行IO 操作时不需要向CPU 申请资源。
其实通道和DMA 处理IO 请求的方式是差不多的,但是对于大量的IO 读写操作来说,因为通道独立于CPU,而DMA 仍需向CPU 申请资源,所以此时的效率通道会高一点。
实例:
1.利用通道完成文件的复制(非直接缓冲区)
通过流的方式来获取通道,很少用到,只是体现了流对通道的支持
I/O 操作完不要忘记关闭流
因为流本来就具有读写功能,所以对应的通道具有读写功能,但这也是非直接内存中的Buffer 才有的功能。
直接内存中的通道不具备读写数据功能,只能通过Buffer 来进行数据读写
2.使用直接缓冲区完成文件的复制(物理内存映射文件)
先建立目标通道:获取数据的通道跟传输数据的通道
Paths: 目标通道的路径。可以用拼串的形式把路径分开写。
StandardOpenOption:可以对通道进行权限控制,作用就是:当Buffer 对通道进行操作的时候,如果权限不符合,就会直接报错。
比如传输数据的通道,有两个个权限:CREATE ,CREATE_NEW 。CREATE :当Paths 路径下有相同名字的文件,就再创建一个相同名字的文件。CREATE_NEW :当Paths 路径下有相同名字的文件,会直接报错
通道打开,并且授予对应的权限以后,开始创建对应通道的内存映射文件(这个内存映射文件就是直接字节缓冲区。这个缓冲区在物理内存中。而且这个缓冲区只有ByteBuffer)。
注意! 通道的内存映射文件权限一定要和对应通道的权限一样,不然会报错。比如outMappedBuf 内存映射文件权限是read_write。它对应的通道的权限只是write。所以这样运行会报错。
修改通道权限:
一个通道对应一个字节缓冲区,那么在复制文件的时候需要用到一个中间的字节数据在作为中间载具。把读取通道中的缓冲区(inMappedBuf)的数据写到字节数据中,在把字节数组中的数据写到写通道(outMappedBuf)的缓冲区中。
整体代码:
从直接内存中进行读写操作是比非直接内存中进行读写操作快很多的,但同时也有问题:
文件已经读写完了,程序还没运行完(虽然是绿色,但还是没运行完,我们的时间还没打印),同时CPU 占用率还很高。
正常结束是这样的:
也就是说文件已经读写完毕了,但程序不仅没那么快结束,就是JVM一直引用着物理内存的那个缓冲区(也就是垃圾回收机制没有及时释放掉对直接内存的引用),导CPU 占用率一下过高。
总结:操作直接内存缓冲区,效率虽高,但并不是所有的时候都很稳定(占用CPU)。原因就是:当我们把数据从程序写到物理内存的映射文件(缓冲区)时,这时候这些数据就不归程序管了。什么时候把数据写到磁盘,就是操作系统的事了。跟我们写的程序无关。所以如果垃圾回收机制不及时释放掉对物理内存的内存文件的引用,程序还无法停下来。
建议:如果你希望你的缓冲区存放的时间长,那就存放在物理内存中。
通道之间的数据传输:
下面这种和上面得到的效果都是一样的。只是通道之间的数据传输方向:To, From 的区别而已。都能实现对文件的复制。
目前还都是I/O 和NIO 的区别,还没有涉及网络编程。