NIO概念
NIO是Java New IO的简称,其中的N可以理解为Non-blocking,不单纯是New,是解决高并发、I/O高性能的有效方式,在jdk1.4里提供的新api,NIO提供了一种完全不同的操作方式,NIO支持面向缓冲区的、基于通道的IO操作。新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写,新增了满足NIO的功能。

NIO特性
Sun官方标榜的特性如下:
- 为所有的原始类型提供(Buffer)缓存支持。
- 字符集编码解码解决方案。
- Channel:一个新的原始I/O抽象。
- 支持锁和内存映射文件的访问接口。
- 提供多路(non-bloking)非阻塞式的高伸缩性网络I/O.
NIO创建目的
NIO的创建目的是为了让Java程序可以实现高速I/O而无需编写自定义的本机代码。
NIO将最耗时的I/O操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。
流(IO/BIO)与块(NIO)的比较
| IO/BIO | NIO | AIO | |
|---|---|---|---|
| 基于对象 | 面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented)、 选择器(Selectors) | 面向缓冲区(Buffer Oriented) |
| 客户端:线程数 | 1:1 | M:1 | M:0 |
| 阻塞类型 | 阻塞(Blocking IO) | 非阻塞(Non Blocking IO) | 非阻塞(Non Blocking IO) |
| 同步类型 | 同步 | 同步 | 异步 |
| 编程难度 | 简单 | 非常复杂 | 复杂 |
| 调试难度 | 简单 | 复杂 | 复杂 |
| 可靠性 | 非常差 | 高 | 高 |
| 吞吐量 | 低 | 高 | 高 |
原来的I/O库(在java.io.*中)与NIO最重要的区别是数据打包和传输的方式。
- 原来的I/O以流的方式处理数据,而NIO以块的方式处理数据。
- 面向流的I/O系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的I/O通常相当慢。
- 面向块的I/O系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快的多。但是面向块的I/O缺少一些面向流的I/O所具有的优雅性和简单性。
补充:(AIO)
JDK7引入了AsynchronousI/O,即AIO。在进行I/O编程中,常用到两种模式:Reactor和Proactor。Java的NIO就是Reactor,当有事件触发时,服务器端得到通知,进行相应的处理。
AIO即NIO2.0,叫做异步不阻塞的IO。AIO引入了异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,
**特点:**先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
目前AIO还没有广泛应用,Netty也是基于NIO,而不是AIO。
NIO核心组件
- Channel(通道)
- Buffer(缓冲区)
- Selector(选择器)

图解说明:
- 每个Channel都会对应一个Buffer。
- Selector对应一个线程,一个线程对应多个Channel(连接)。
- 该图反应了有三个Channel注册到该Selector。
- 程序切换到哪个Channel是由时间决定的,Event就是一个重要的概念。
- Selector会根据不同的事件,在各个通道上切换。
- Buffer就是一个内存块,底层是有一个数组。
- 数据的读取写入是通过Buffer,BIO中要么是输入流,或者是输出流,不能双向,但是NIO的Buffer是可以读也可以写,需要flip()方法切换Channel是双向的,可以返回底层操作系统的情况,比如Linux,底层的操作系统通道就是双向的。

NIO核心------缓冲区(Buffer)
缓冲区是包在一个对象内的基本数据元素数组。由java.nio包定义的,所有缓冲区都是Buffer抽象类的子类。
Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息。
缓冲区的四个属性
容量(Capacity)
缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
上界(Limit)
缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。
位置(Position)
下一个要被读或写的元素的索引。位置会自动由相应的get()和put()函数更新。
标记(Mark)
一个备忘位置。调用mark()来设定 mark = position。调用reset()设定 position = mark。标记在设定前是未定义的(undefined)。
注:0 <= mark <= position <= limit <= capacity
缓冲区的分类
- 字节缓冲区(ByteBuffer)
- 字符缓冲区(CharBuffer)
- 双精浮点型(double)缓冲区(DoubleBuffer)
- 单精浮点型(float)缓冲区(FloatBuffer)
- 整型(int)缓冲区(IntBuffer)
- 长整型(long)缓冲区(LongBuffer)
- 短整型(short)缓冲区(ShortBuffer)
上述缓冲区管理方式几乎一致,都是通过**allocate()**来获取缓冲区。
缓冲区存取数据的核心方法
- 存 :通过相关Buffer类的**put()**方法进行操作。
- 取 :通过相关Buffer类的**get()**方法进行操作。
新的缓冲区是由分配或包装操作创建的。分配操作创建一个缓冲区对象并分配一个私有的空间来储存容量大小的数据元素。包装操作创建一个缓冲区对象但是不分配任何空间来储存数据元素。它使用您所提供的数组作为存储空间来储存缓冲区中的数据元素。
通过allocate()或者wrap()函数创建的缓冲区通常都是间接的。
作者:筱白爱学习!!
欢迎关注转发评论点赞沟通,您的支持是筱白的动力!