上文讲到Java NIO
一些基本概念。在标准的IO
中,都是基于字节流/字符流进行数据操做的,而在NIO
中则是是基于Channel
和Buffer
进行操做,其中的Channel
的虽然模拟了流的概念,实则大不相同。服务器
本文将详细阐述NIO
中的通道Channel
的概念和具体的用法。网络
区别 | Stream | Channel |
---|---|---|
是否支持异步 | 不支持 | 支持 |
是否支持双向数据传输 | 不支持,只能单向 | 支持,既能够从通道读取数据,也能够向通道写入数据 |
是否结合Buffer使用 | 不 | 必须结合Buffer使用 |
性能 | 较低 | 较高 |
Channel
用于在字节缓冲区和位于通道另外一侧的服务(一般是文件或者套接字)之间以便有效的进行数据传输。借助通道,能够用最小的总开销来访问操做系统自己的I/O
服务。dom
须要注意的是Channel必须结合Buffer使用,应用程序不能直接向通道中读/写数据,也就是缓冲区充当着应用程序和通道数据流动的转换的角色。异步
查看Channel
的源码。全部的接口都实现于Channel
接口,从接口上来看,全部的通道都有这两种操做:检查通道的开启状态和关闭通道。socket
1 |
public interface Channel extends Closeable { |
广义上来讲通道能够被分为两类:文件I/O
和网络I/O
,也就是文件通道和套接字通道。若是分的更细致一点则是:工具
TCP
读写网络数据;TCP
链接,并对每一个连接建立对应的SocketChannel
;UDP
读写网络中的数据。通道既能够是单向的也能够是双向的。只实现ReadableByteChannel
接口中的read()
方法或者只实现WriteableByteChannel
接口中的write()
方法的通道皆为单向通道,同时实现ReadableByteChannel
和WriteableByteChannel
为双向通道,好比ByteChannel
。性能
1 |
public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { |
对于Socket
通道来讲,它们一直是双向的,而对于FileChannel
来讲,它一样实现了ByteChannel
,可是经过FileInputStream
的getChannel()
获取的FileChannel
只具备文件的只读权限。测试
注意:调用FileChannel的write()方法会抛出了NonWriteChannelException异常。this
通道的工做模式有两种:阻塞或非阻塞。在非阻塞模式下,调用的线程不会休眠,请求的操做会马上返回结果;在阻塞模式下,调用的线程会产生休眠。编码
除FileChannel
不能运行在非阻塞模式下,其他的通道均可阻塞运行也能够以非阻塞的方式运行。
另外从SelectableChannel
引伸出的类能够和支持有条件选择的Selector
结合使用,进而充分利用多路复用的I/O
(Multiplexed I/O
)来提升性能。
SelectableChannel
的源码中有如下几个抽象方法,能够看出支持配置两种工做模式:
1 |
public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel { |
对于Socket
通道类来讲,一般与Selector
共同使用以提升性能。须要注意的是通道不能被同时使用,一个打开的通道表明着与一个特定I/O
服务进行链接并封装了该链接的状态,通道一旦关闭,该链接便会断开。
通道的close()
比较特殊,不管在通道时在阻塞模式下仍是非阻塞模式下,因为close()
方法的调用而致使底层I/O
的关闭均可能会形成线程的暂时阻塞。在一个已关闭的通道上调用close()
并无任何意义,只会当即返回。
对于Socket通道来讲存在直接建立新Socket通道的方法,而对于文件通道来讲,升级以后的FileInputStream、FileOutputStream和RandomAccessFile提供了getChannel()方法来获取通道。
Java NIO
中的FileChannel
是一个链接到文件的通道,能够经过文件通道读写文件。文件通道老是阻塞式的,所以FileChannel没法设置为非阻塞模式。
(一). 文件写操做:
1 |
public static void testWriteOnFileChannel() { |
(二). 文件读操做:
1 |
public static void testReadOnFileChannel() { |
文件读写测试:
1 |
public static void main(String[] args) { |
(一). transferFrom()的使用
FileChannel
的transferFrom()
方法能够将数据从源通道传输到FileChannel
中。下面是一个简单的例子:
1 |
public static void testTransferFrom(){ |
(二). transferTo()的使用
transferTo()
方法将数据从FileChannel
传输到目标channel
中。下面是一个简单的例子:
1 |
public static void testTransferTo() { |
Java NIO
中的ServerSocketChannel
是一个能够监听新进来的TCP链接的通道。它相似ServerSocket
,要注意的是和DatagramChannel
和SocketChannel
不一样,ServerSocketChannel
自己不具有传输数据的能力,而只是负责监听传入的链接和建立新的SocketChannel
。
(一). 建立ServerSocketChannel
经过ServerSocketChannel.open()
方法来建立一个新的ServerSocketChannel
对象,该对象关联了一个未绑定ServerSocket
的通道。经过调用该对象上的socket()
方法能够获取与之关联的ServerSocket
。
1 |
ServerSocketChannel socketChannel = ServerSocketChannel.open(); |
(二). 为ServerSocketChannel绑定监听端口号
在JDK 1.7
以前,ServerSocketChannel
没有bind()
方法,所以须要经过他关联的的socket
对象的socket()
来绑定。
1 |
// JDK1.7以前 |
从JDK1.7
及之后,能够直接经过ServerSocketChannel
的bind()
方法来绑定端口号。
1 |
// JDK1.7以后 |
(三). 设置ServerSocketChannel
的工做模式
ServerSocketChannel
底层默认采用阻塞的工做模式,它提供了一个configureBlocking()
方法,容许配置ServerSocketChannel
以非阻塞方式运行。
1 |
// 设置为非阻塞模式 |
进一步查看configureBlocking
源码以下:
1 |
public final SelectableChannel configureBlocking(boolean block) throws IOException { |
Javadoc解释configureBlocking()方法用于调整底层通道的工做模式,即阻塞和非阻塞,默认是阻塞工做模式。
若是block设置为true,直接返回当前的阻塞式的通道;若是block设置为false,configureBlocking()方法会调用implConfigureBlocking()方法。这里implConfigureBlocking()是由ServerSocketChannelImpl
实现,最终调用了IOUtil中的native方法configureBlocking()。
(四). 监听新进来的链接
经过ServerSocketChannel.accept()
方法监听新进来的链接,这里须要根据configureBlocking()
的配置区分两种工做模式的使用:
accept()
方法返回的时候,它返回一个包含新链接的SocketChannel
,不然accept()
方法会一直阻塞到有新链接到达。accept()
会当即返回null
,该模式下一般不会仅仅监听一个链接,所以需在while
循环中调用accept()
方法.阻塞模式:
1 |
while(true) { |
非阻塞模式:
1 |
while(true) { |
(五). 关闭ServerSocketChannel
经过调用ServerSocketChannel.close()
方法来关闭ServerSocketChannel
。
1 |
serverSocketChannel.close(); |
(一). 阻塞模式
代码示例:
1 |
public static void blockingTest() throws IOException { |
运行结果:
(二). 非阻塞模式
代码示例:
1 |
public static void nonBlockingTest() throws IOException { |
运行结果:
Java NIO
中的SocketChannel
是一个链接到TCP
网络套接字的通道,它是Socket
类的对等类。
一般SocketChannel
在客户端向服务器发起链接请求,每一个SocketChannel
对象建立时都关联一个对等的Socket
对象。一样SocketChannel
也能够运行在非阻塞模式下。
SocketChannel
建立的方式有两种:
SocketChannel
并链接到某台服务器上;ServerSocketChannel
时,服务端会建立一个SocketChannel
。(一). 建立SocketChannel
经过SocketChannel
的静态方法open()
建立SocketChannel
对象。此时通道虽然打开,但并未创建链接。此时若是进行I/O
操做会抛出NotYetConnectedException
异常。
1 |
SocketChannel socketChannel = SocketChannel.open(); |
(二). 链接指定服务器
经过SocketChannel
对象的connect()
链接指定地址。该通道一旦链接,将保持链接状态直到被关闭。可经过isConnected()
来肯定某个SocketChannel
当前是否已链接。
若是在客户端的SocketChannel
阻塞模式下,即服务器端的ServerSocketChannel
也为阻塞模式:
1 |
socketChannel.connect(new InetSocketAddress("127.0.0.1", 25000)); |
两点须要注意:其一,SocketChannel须要经过configureBlocking()设置为非阻塞模式;其二,非阻塞模式下,connect()方法调用后会异步返回,为了肯定链接是否创建,须要调用finishConnect()的方法。
1 |
socketChannel.configureBlocking(false); |
(三). 从SocketChannel读数据
利用SocketChannel
对象的read()
方法将数据从SocketChannel
读取到Buffer
。
1 |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); |
(四). 向SocketChannel写数据
利用SocketChannel
对象的write()
将Buffer
的数据写入SocketChannel
。
1 |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); |
(五). 关闭SocketChannel
利用SocketChannel
对象的close()
方法关闭SocketChannel
。
1 |
socketChannel.close(); |
(一). 阻塞模式
代码示例:
1 |
public static void blockingWrite() throws Exception { |
服务端打印结果:
(一). 非阻塞模式
代码示例:
1 |
public static void nonBlockingWrite() throws Exception { |
服务端打印结果:
Java NIO
中的DatagramChannel
是一个能收发UDP
包的通道,其底层实现为DatagramSocket + Selector
。DatagramChannel
能够调用socket()
方法获取对等DatagramSocket
对象。DatagramChannel
对象既能够充当服务端(监听者),也能够充当客户端(发送者)。若是须要新建立的通道负责监听,那么该通道必须绑定一个端口(或端口组):
数据报发送方:
1 |
public static void main(String[] args) throws Exception { |
数据报接收方:
1 |
public static void main(String[] args) throws Exception { |
先运行DatagramChannelReceiveTest
,再运行DatagramChannelSendTest
,观察控制台输出:
数据报发送方:
数据报接收方:
NIO
通道提供了一个便捷的通道类Channels
,其中定义了几种静态的工厂方法以简化通道和流转换。其中经常使用的方法以下:
方法 | 返回 | 描述 |
---|---|---|
newChannel(InputStream in) | ReadableByteChannel | 返回一个将从给定的输入流读取数据的通道。 |
newChannel(OutputStream out) | WritableByteChannel | 返回一个将向给定的输出流写入数据的通道。 |
newInputStream(ReadableByteChannel ch) | InputStream | 返回一个将从给定的通道读取字节的流。 |
newOutputStream(WritableByteChannel ch) | OutputStream | 返回一个将向给定的通道写入字节的流。 |
newReader(ReadableByteChannel ch, CharsetDecoder dec, int minBufferCap) | Reader | 返回一个reader,它将从给定的通道读取字节并依据提供的字符集名称对读取到的字节进行解码。 |
newReader(ReadableByteChannel ch, String csName) | Reader | 返回一个reader,它将从给定的通道读取字节并依据提供的字符集名称将读取到的字节解码成字符。 |
newWriter(WritableByteChannel ch, CharsetEncoder dec, int minBufferCap) | Writer | 返回一个writer,它将使用提供的字符集名称对字符编码并写到给定的通道中。 |
newWriter(WritableByteChannel ch, String csName) | Writer | 返回一个writer,它将依据提供的字符集名称对字符编码并写到给定的通道中。 |
本文针对NIO
中的通道的作了详细的介绍,对于文件通道FileChannel
,网络通道SocketChannel
、ServerSocketChannel
和DatagramChannel
进行了实战演示。
篇幅较长,可见NIO
提供的原生的通道API
在使用上并非太容易。