今天看到一篇文章,其中提到了NIO,之前没使用过,今天拿出来学习下,做个个人分享。
IO指的是流式处理,I对用的是Input,O对应的是OutPut,也就是我们常用的IO。IO流的数据来源可以是键盘,文件,网络等等,下面是其类图的划分,具体这些类和api该怎么使用就不说了。
这里有个注意点:字节流和字符流是两个不同的概念。图为输出的示意,出入与之相反。
从源码上看(这里只列出InputStreamReader)
字节流其实也是字节的升级,底层都需要通过字节流来读取文件等的字节信息,这个也不难理解,因为对于程序而言,文件都是以字节码的形式存在的,不是现成的字符,底层设备接收的永远是字节。
字节流和字符的主要的区别就是,字符流在读取字节流后会放到缓冲区(可以理解为在内存中开辟的一段特殊的独立的内存区域),如果直接使用使用字节流读取,比如我们需要读取文件中字符(不是字节)就需要不停的循环读取,然后转成我们需要的字符信息,而使用字符流读取的时候,reader会将字节先放到缓冲区,然后将字节转换成字符。
不管是字节流还是字符流,他们都是阻塞的,并且它是单向的,什么意思,当读取一个文件或者一段内容的时候,read或者readLine方法只会按照输入结束或者输入可用或者异常或者等才会退出读取,否则会一直等待读取,不会执行下面的语句,同时规定的是输出流那就只能输出,不能输入,同样的如果是输入流那就只能输入不能输出。
NIO指的是new io 即新的IO,他注重的是缓冲区的使用,其主要核心包括:channel(通道)、buffer(缓冲)、和Selector(选择器)。
主要流程为:channel提供读取网络或者文件内容的通道(channel本省不读取),然后由在缓冲区读取内容,缓冲区读取内容时可以前后移动(这是一个比较重要的特点),Selector则可以监听多个channel,即多路复用,既然是多路复用的,那么它就不会存在单线程阻塞的问题,同一个线程中可能有许多的channel,那么即使某个通道暂时阻塞也不会影响下面的程序执行,并且channel作为通道它不具备先进先出的特点,就是说它可以是双向的不像流只能是单向的。个人认为,NIO是IO在使用缓冲区的基础上的一种全面升级,同时由于其支持多通道的特点,比较适合用于网络IO(多线程、双向通信)
关于阻塞和非阻塞
阻塞是相对与CPU而言的,阻塞及对当前线程挂起,不能执行下面的程序,非阻塞则当前线程仍然是执行状态,并没有让出CPU让其去做其他事情。
io是阻塞的:使用一个线程去进行io去读取的时候会监听读取内容,如果没有读写完,则线程一直是阻塞状态,CPU转而去做其他事情,那么当在多线程的情况下,cpu需要不停的为线程做上下文切换,而这些上下文的切换是由于阻塞引起的(即大多是以阻塞而告终),并不能很好节约cpu的开销。
nio是非阻塞的,即nio使用channel和selector在一个线程中完成多个任务,由一个线程负责处理所有io事件,它使用的事件机制驱动,而不是监听机制驱动,事件到了才会触发,但是它有个缺点,每一次的数据处理都是对缓冲区进行的,那么就会有一个问题,在数据处理之前必须要判断缓冲区的数据是否完整或者已经读取完毕(也就是说需要先将所有数据读取到缓冲区才能进行下一次操作),如果没有,假设数据只读取了一部分,那么对不完整的数据处理没有任何意义。所以每次数据处理之前都要检测缓冲区数据。
阻塞和非阻塞对性能影响
NIO和IO的主要区别
使用场景
如果是要管理打开成千上万的连接,而连接的数据量比较小,比如聊天室,可以选择使用NIO,一个线程处理多个channel。并且,NIO比较适合于网络io的获取。
如果是连接数少,但是每个连接的数据量比较大,那么使用io。
为什么?
缓冲区的大小会影响整个程序的性能,如果缓冲区过大,那么机器或者虚拟机的其它能使用资源就会相对较小,如果在并发连接的情况下,开一个线程和开一万个线程对cpu的影响是很大的。
缓冲区
其实就是内存中的一个特殊空间,这个空间中存放的是临时数据,即当我们需要写入写出文件或者网络数据时,先写到缓冲区,然后再由缓冲区写出或者写入,这样大大降低了程序与IO或者网络的交互次数,比如写出一个文件或者给客户端返回一行文字,我们肯定是等文件全部编辑完成了才能输出到文件里面,而不是打一个字就写一次,这样性能是及其差的,而这里的等待输入完成的过程就是将先输入的放到缓冲区,然后再一并flush。
直接缓冲区和非直接缓冲区
直接缓冲区:
在java中直接缓冲区使用allocateDirect()方法来分配或者使用FileChannel 的map()方法来创建(返回MappedByteBuffer),这块空间映射到内核地址空间和用户地址空间,应用程序与磁盘之间的数据存取之间通过这块直接申请的物理内存进行。也可以理解为直接内存,它可以提高文件的读写速度,但是它的分配创建是需要消耗很大资源的,并且不被jvm管理(但是创建它的对象是被jvm管理,可以手动销毁创建这块区域的对象,直接内存也会随之被回收)。
非直接缓冲区:
allocate() 方法分配缓冲区,并且可以被jvm内存机制管理,其主要原理是将os(操作系统)管理的文件,读取到内核地址空间,但是此时程序本身是不会直接读取的,这里的内湖地址空间是操作系统层面的,之后,NIO会从内核地址空间将信息复制到用户自己创建的零时缓冲区,然后程序再进行读写操作,这里的读写就是在用户的零时缓冲区,非直接缓冲区也可以理解为需要中间缓冲区来进行数据的拷贝操作,那么这里就会有两个问题:
1.安全性(及时性):因为读写不是程序直接操作,而是通过中间缓冲区的复制,那么当写操作执行完毕,此时数据时存在内核空间地址的,此后的写操作就是操作系统层面的,操作系统什么时候写就要看它什么时候被分配到CPU资源,而这个过程中是有可能断开或者被侵入的。
2.性能:因为需要不停的复制,就会多出许多额外的开销
NIO使用的是什么缓存:这个需要看是如何使用NIO的如果是allocate()就是非直接内存,如果是allocateDirect()那就是直接缓冲,也可以使用来判断时候的是否是直接缓冲区。