在上一篇我们讲了 Buffer,它在整个 NIO 体系中就像一速度很快的汽车,负责装载数据的,但是光有车没有路可不行啊!而 Channel 就好比高速路,连接了起点(源文件)与终点(目标文件)。所以这么看来,通道Channel 和流Stream 很是相似啊!
在 Java1.4之前的 I/O 系统中,提供的都是面向流的I/O系统,系统一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。面向流的I/O 速度非常慢。后来在Java 1.4 中推出了NIO,这是一个面向块的I/O 系统,系统以块的方式处理处理,每一个操作在一步中产生或者消费一个数据库,按块处理要比按字节处理数据快的多。
这里说的“块”就是缓冲区 Buffer。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的Buffer。对于读同样不会直接从通道中读取字节,而是将数据从通道读入Buffer,再从缓冲区获取这个字节。
读/写 ==> Buffer <=> Channel <=> File
另外,流一般是单向的(读||写),而通道是双向的(读&&写)
在NIO 中,根据读取的file不同,提供了多种通道对象,而所有的通道对象都实现了 Channel 接口。
public interface Channel extends Closeable {
// 通道是否开启
public boolean isOpen();
// 关闭通道
public void close() throws IOException;
}
跟 Stream 体系一样,Channel 的子类非常多,有着一套完善的继承体系:
FileChannel 用于文件的数据读写;DatagramChannel用于UDP的数据读写;SocketChannel用于TCP的数据读写;ServerSocketChannel允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel。下面我们就来具体看看这几个 Channel 如何使用…
1.FileChannel
Java NIO FileChannel是连接文件的通道。使用FileChannel,可以从文件中读取数据和将数据写入文件。使用之前,FileChannel必须被打开,但是无法直接打开FileChannel,需要通过InputStream,OutputStream或RandomAccessFile获取FileChannel。
1.使用 NIO读取数据
任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO 读取数据可以分为下面三个步骤:
- 从 FileInputStream 获取 Channel
- 创建 Buffer
- 将数据从 Channel 读取到 Buffer 中
public class FileInputDemo {
public static void main(String[] args) throws IOException {
// 1.创建FileInputStream,获取Channel
FileInputStream fin = new FileInputStream("C://test.txt");
FileChannel channel = fin.getChannel();
// 2.创建Buffer
ByteBuffer buffer = ByteBuffer.allocate(10);
// 3.从Channel读取数据到Buffer
channel.read(buffer);
// 注:这里记住要flip先锁定,最后要clear解锁
buffer.flip();
while (buffer.hasRemaining()) {
byte b = buffer.get();
System.out.println((char)b);
}
fin.close();
}
}
2.使用 NIO写入数据
使用 NIO 写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:
- 从 FileInputStream 获取 Channel
- 创建 Buffer
- 将数据从Channel 写入到 Buffer 中
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 1.从FileInputStream 获取 Channel。
FileOutputStream fout = new FileOutputStream("C://test.txt");
FileChannel fc = fout.getChannel();
// 2.创建Buffer
ByteBuffer buffer = ByteBuffer.allocate(10);
// 3.向Buffer写入数据,然后通过Channel写入
for (int i = 0; i < buffer.capacity(); i++) {
buffer.put((byte)i);
}
// 注:这里记住要flip先锁定,最后要clear解锁
buffer.flip();
fc.write(buffer);
buffer.clear();
}
}
2.ServerSocketChannel
Java NIO中的 ServerSocketChannel是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在java.nio.channels包中。
创建 ServerSocketChannel,并监听80端口:
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(80));
3.SocketChannel
SocketChannel 是一种面向流连接只sockets套接字的可选择通道,用来连接 Socket 套接字,处理网络I/O的通道,基于TCP连接传输,实现了可选择通道,可以被多路复用的。
打开一个 SocketChannel 并连接某台服务器:
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("https://www.test.com", 80));
非阻塞的 Channel
在文章上面的继承关系图中,我们可以看到 ServerSocketChannel 和 SocketChannel 继承了SelectableChannel抽象类。SelectableChannel 的意思是"可选择"通道,也就是说我们可以选择该通道是阻塞模式或者非阻塞模式
-
Blocking:默认。跟 BIO 的 InputStream 一样,在 read() 或者 write() 时如果没有数据,则会让当前线程陷入阻塞等待状态。
-
NoBlocking:需要手动设置
channel.configureBlocking(false),它的实现需要选择器 Selector 的支持。在使用时我们首先需要将 Channel 及相应事件注册到 Selector,这样 Selector 就相当于一个身处在最外层的大管家,只有连接主动触发了注册的事件,大管家才会让当前线程去读/写 Channel。换个角度去看,线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
PS:关于 Selector 我们放在下一篇去说…






还没有评论,来说两句吧...