Channel是什么
Java NIO中的所有I/O操作都基于Channel对象,就像流操作都要基于Stream对象一样,因此很有必要先了解Channel是什么。以下内容摘自JDK 1.8的文档。
A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.
从上述内容可知,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,Channel是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。这和传统IO中的Stream有点类似,并且Channel根据不同的I/O操作对应有不同的实现,常用的有如下几个:
- FileChannel:读写文件
- DatagramChannel: UDP协议网络通信
- SocketChannel:TCP协议网络通信
- ServerSocketChannel:监听Socket
Channel和Stream相比有如下几个特性:
- Channel是双向的,既可以用来进行读操作也可以进行写操作,而Stream是单向的(所以分
InputStream
和OutputStream
)。 - Channel本身并不能访问数据,因为它只是一个通道,Channel的读和写都需要配和Buffer来操作,可以从Channel里读取数据到Buffer中,也可以把Buffer中的数据写入Channel。
- Channel可以异步地读写。
Channel的使用
获取通道有如下三种方式:
对支持通道的对象调用getChannel() 方法。
支持通道的类如下:
本地 IO:`FileInputStream`/`FileOutputStream` `RandomAccessFile`
网络IO:
`Socket` `ServerSocket` `DatagramSocket`
在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法
open()
打开并返回指定通道。- 在 JDK 1.7 中的 NIO.2 的 Files 工具类的静态方法
newByteChannel()
可以创建FileChannel
实例。
如下是FileChannel
的使用示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81public class FileChannalTest {
//利用通道完成文件的复制(非直接缓冲区)
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("C:/Users/liaosi/Desktop/1.jpg");
fos = new FileOutputStream("C:/Users/liaosi/Desktop/2.jpg");
//获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将通道中的数据读取写到缓冲区中
while (inChannel.read(buffer) != 1) {
//切换缓冲区为读模式
buffer.flip();
//将缓冲区中的数据写入通道中
outChannel.write(buffer);
//清空缓冲区
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inChannel != null) {
inChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (outChannel != null) {
outChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//通道之间直接传输数据(直接缓冲区)
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("C:/Users/liaosi/Desktop/1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("C:/Users/liaosi/Desktop/4.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//数据从一个通道传输到另外一个通道
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
}
}
SocketChannel
SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:
- 调用
SocketChannel
的open()
方法。 ServerSocketChannel
接受到一个连接后,会创建一个SocketChannel。
连接socket
SocketChannel
可以通过configureBlocking
方法设置为阻塞和非阻塞两种方式,默认是阻塞的。SocketChannel
通过connect
方法连接到一个socket,如果是阻塞的SocketChannel
,connect
方法会一直阻塞直到连接建立或者发生IO异常,如果是非阻塞的SocketChannel
,这个方法会立即返回,如果连接已经成功建立则返回true,否则返回false,后续可以再调用finishConnect()
来完成连接操作。
建立连接之后,SocketChannel
可以通过write
向Buffer中写入数据,通过read
方法从 Buffer 中读取数据,read()
方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了末尾。
ServerSocketChannel
前面的SocketChannel
是可以主动发起连接到socket的通道,ServerSocketChannel
则是用来监听到socket的连接和接收连接的通道。ServerSocketChannel
可以被无参的open()方法创建。但是改方法只是创建了一个ServerSocketChannel
对象,并没有进行绑定操作,仍需要调用bind()方法进行绑定,使之监听指定端口上的套接字。未进行绑定的ServerSocketChannel
调用accept()
,将会抛出NotYetBoundException异常
。1
2
3
4
5
6
7
8//1.创建ServerSocketChannel
ServerSocketChannel channel = ServerSocketChannel.open();
//2.绑定到本机网络接口
channel.bind(new InetSocketAddress(8091));
//3.SocketChannel socketChannel = channel.accept();
SocketChannel socketChannel = channel.accept();
accept
方法返回SocketChannel套接字通道,用于读取请求数据和写入响应数据。ServerSocketChannel
可以通过configureBlocking
方法设置成阻塞或非阻塞状态,ServerSocketChannel的阻塞和非阻塞体现在:
- 阻塞模式:在调用
accept
方法后,将阻塞直到有新的socket连接时返回SocketChannel对象,代表新建立的套接字通道。 - 非阻塞模式:在调用
accept
方法后,如果无连接建立,则返回null;如果有连接,则返回SocketChannel。
起始Java NIO中Channel最大的用处是在网络端,特别是服务端,可以通过非阻塞的方式增加应用程序的并发量,ServerSocketChannel
一般会配合Selector
使用以获取更好的并发性能,具体的使用方式在Selector
那一节中再做介绍。