java.nio(JDK1.4)
Java NIO: Channels and Buffers(通道和缓冲区)
标准的 IO 基于字节流和字符流进行操作的,而 NIO 是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer
- 异步I/O 是指用户程序发起IO请求后,不等待数据,同时操作系统内核负责I/O操作把数据从内核拷贝到用户程序的缓冲区后通知应用程序。数据拷贝是由操作系统内核完成,用户程序从一开始就没有等待数据,发起请求后不参与任何IO操作,等内核通知完成。
- 同步I/O 就是非异步IO的情况,也就是用户程序要参与把数据拷贝到程序缓冲区(例如java的InputStream读字节流过程)。
- *同步IO里的非阻塞* 是指用户程序发起IO操作请求后不等待数据,而是调用会立即返回一个标志信息告知条件不满足,数据未准备好,从而用户请求程序继续执行其它任务。执行完其它任务,用户程序会主动轮询查看IO操作条件是否满足,如果满足,则用户程序亲自参与拷贝数据动作。
在这里需要强调的是NIO不是真正意义上的异步,它只是非阻塞的。
缓冲区 Buffer
缓冲区本质上是一个 可以读写数据的内存块,可以理解成是一个 容器对象( 含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer
常用 Bytebuffer
Buffer 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:
通道channel
NIO 的通道**类似于流,**但有些区别如下:
- 通道可以同时进行读写(通道是全双工的),而流只能读或者只能写
- 通道可以实现异步读写数据
- 通道可以从缓冲读数据,也可以写数据到缓冲
从类图可以看出,实际上Channel可以分为两大类:用于网络读写的SelectableChannel和用于文件操作的FileChannel。.
多路复用器 selector
Selector 允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用 Selector 就会很方便。例如,在一个聊天服务器中。
简单来讲,
- Selector会不断地轮询注册在其上的Channel,
- 如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,
- 然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作
一旦向 Selector 注册了一或多个通道,就**可以调用几个重载的 select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)**已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。
一个多路复用器Selector可以同时轮询多个Channel,**由于JDK使用了epoll()代替传统的select实现(unix网络编程卷1中有讲到),**所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端,这确实是个非常巨大的进步。
使用注意
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
注意第二行,从通道读取字节到 ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。读到的数据可能不是完整的。
public class Program {
static public void main( String args[] ) throws Exception {
FileInputStream fin = new FileInputStream("c:\\test.txt");
// 获取通道
FileChannel fc = fin.getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取数据到缓冲区
fc.read(buffer);
buffer.flip();//将指针复位,读写切换!!!
while (buffer.remaining()>0) {
byte b = buffer.get();
System.out.print(((char)b));
}
fin.close();
}
}
从channel中读取数据到buffer中,用buffer的get方法获取数据(put方法可以存入数据)
NI0可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
通道(channel)中的数据总是要先读到一个 Buffer,或者总是要从一个 Buffer 中写入。使用 Buffer 读写数据一般遵循以下四个步骤:
- 写入数据到 Buffer
- 调用 flip()方法//将写变成读
- 从 Buffer 中读取数据
- 调用 clear()方法或者 compact()方法
其他解释
pipe:两个线程间通信。管道由一对通道组成:一**个可写入的 sink 通道和一个可读取的source 通道。**一旦将某些字节写入接收器通道,就可以按照与写入时完全相同的顺序从源通道中读取这些字节。
IO和NIO的区别
面向流与面向缓冲
- Java NI0和I0之间第一个最大的区别是,I0是面向流的,NI0是面向缓冲区的。Java I0面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。Java NI0的缓冲导向方法略有不同。**数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。**但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞与非阻塞 IO
- Java IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。JavaNIO 的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
选择器(Selectors)
- Java NIO 的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。(避免了多线程之间,上下文切换造成的开销)