摘自并发编程网:http://ifeve.com/overview/
仅作自己学习……
1. FileChannel
Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。
在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。
调用多个read()方法之一从FileChannel中读取数据。首先,分配一个Buffer,从FileChannel中读取的数据将被读到Buffer中,该方法将从数据从FileChannel读取到Buffer中,read方法返回的int值代表有多少字节被读到了Buffer中,如果返回-1,表示到了文件末尾。
使用FileChannel.write()方法是在while循环中调用的。因为无法保证write方法一次能向FileChannel中写入多少个字节,需要重复调用,直到Buffer中已经没有尚未写入通道的字节。
在用完FileChannel后必须将其关闭。
有时可能需要在FileChannel的某个特定位置进行数据的读写操作,就可以通过调用position()方法获取FileChannel的当前位置,也可以通过调用position(long pos)来设置FileChannel的当前位置。如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志。如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。
FileChannel的size方法将返回该实例所关联的文件大小。使用FileChannel的truncate方法可以截取一个文件,截取文件时,文件指定长度后面的部分将会被删除。FileChannel的force方法将通道里尚未写入磁盘的数据强制写到磁盘中。出于性能考虑,操作系统会将数据缓存在内存中,所以无法保证写入到Channel中的数据一定会即时写入到磁盘中,要保证这一点,必须要使用force方法。force中的Boolean参数指定是否同时将文件元数据(权限信息等)写入到磁盘中。
2. SocketChannel
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:
- 打开一个SocketChannel并连接到互联网上的某台服务器。
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(IPAddress, PORT));
- 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。
在使用完SocketChannel后调用close方法关闭。
如果要从SocketChannel中读取,调用read方法。首先,要分配一个Buffer,从SocketChannel中读到的数据将会放到这个Buffer中;read方法返回的int值表示读了多少字节进入到Buffer中,如果返回的是-1,表示已经读到流的末尾(连接关闭)。
写数据到SocketChannel中调用write方法,该方法以一个Buffer作为参数。其调用也是在一个while循环中,write方法无法保证写多少个字节到SocketChannel中,所以我们要重复调用write直到Buffer没有要写的字节为止。
可以设置SocketChannel在非阻塞模式下运行,此时调用connect,该方法可能会在连接建立之前就已经提前返回了。同理,非阻塞模式下的write和read方法可能会在尚未写入或读取任何内容后就返回了。
非阻塞模式与选择器搭配会工作得更好,通过将一个或者多个SocketChannel注册到Selector,可以询问哪个通道已经准备好读取/写入等。
3. ServerSocketChannel
Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样,ServerSocketChannel类在 java.nio.channels包中。
调用 ServerSocketChannel.open() 静态方法来打开ServerSocketChannel,调用close来进行关闭和清理资源。
通过 ServerSocketChannel.accept() 方法监听新进来的连接,当accept()方法返回的时候,它返回一个包含新进来的连接的SocketChannel,accept()方法会一直阻塞到有新连接到达。
ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。因此,需要检查返回的SocketChannel是否是null。
4. DatagramChannel
Java NIO中的DatagramChannel是一个能收发UDP包的通道,UDP是无连接的网络协议,不能像其它通道那样读取和写入,它发送和接收的是数据包。
调用DatagramChannel.open()
方法并绑定对应的端口来接收数据包,
使用
receive
方法从
DatagramChannel
中读取数据,
receive
方法会将接收到的数据包复制到指定
Buffer
,如果
Buffer
容纳不下收到的所有数据,多出来的数据将会被抛弃。
通过调用
send
方法来从
DatagramChannel
发送数据。因为服务器可能并没有监控发送数据的端口,
UDP
也不会先进行连接的建立,因此可能发送数据包丢失的现象。
可以将
DatagramChannel
连接到网络中的特定地址,由于
UDP
是无连接的,连接到特定地址只会锁住
DatagramChannel
,让其在特定地址收发数据。
5. Pipe
Java NIO Pipe是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写入到sink通道,从source通道中读取。
通过Pipe.open()方法来打开管道,返回一个Pipe对象。如果想要向管道写数据,需要访问sink通道,执行pipe.sink()方法,向返回的SinkChannel中写入数据。
如果想要从管道中读取数据,需要访问source通道,执行pipe.source()方法,从返回的SourceChannel中读取数据。
当前,Channel的读取和写入都要和Buffer打交道。
6. Java NIO与IO
6.1 主要区别
IO |
NIO |
面向流 |
面向缓冲 |
阻塞IO |
非阻塞IO |
无 |
选择器 |
6.2 面向流和面向缓冲
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。Java NIO的缓冲导向方法略有不同,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
6.3 阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read()或write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
6.4 选择器
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
6.5 NIO和IO影响应用程序的设计
使用NIO的API调用时看起来与使用IO时有所不同,IO是仅从一个InputStream逐字节读取,而NIO是数据必须先读入缓冲区再处理。
IO的数据读取模型:
NIO的数据读取模型:
NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有连接,可能是一个优势。如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。