Java NIO之通道Channel
1 2 3 4 5 6 7 8 9 10 | public interface Channel extends Closeable { /** * Tells whether or not this channel is open. */ public boolean isOpen(); /** * Closes this channel. */ public void close() throws IOException; } |
Channel 经常翻译为通道,类似 IO 中的流,用于读取和写入。它与 Buffer 打交道,读操作的时候将Channel 中的数据填充到 Buffer 中,而写操作时将 Buffer 中的数据写入到 Channel 中。所有的 NIO 操作始于通道,通道是数据来源或数据写入的目的地,主要地,我们将关心 java.nio 包中实现的以下几个 Channel:
-
FileChannel:文件通道,用于文件的读和写
-
DatagramChannel:用于 UDP 连接的接收和发送
-
SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
-
ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求
1 2 3 4 5 6 7 8 9 10 11 | public interface ReadableByteChannel extends Channel { public int read(ByteBuffer dst) throws IOException; } public interface WritableByteChannel extends Channel { public int write(ByteBuffer src) throws IOException; } public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { } |
1 2 3 4 5 6 7 8 9 | public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel { ... } public abstract class AbstractSelectableChannel extends SelectableChannel { ... } |
可以看出,socket通道类从SelectableChannel类引申而来,从SelectableChannel引申而来的类可以和支持有条件的选择的选择器(Selectors)一起使用。将非阻塞I/O和选择器组合起来可以使开发者的程序利用多路复用I/O。
1 2 3 4 5 6 7 | //初始化 FileInputStream inputStream = new FileInputStream( new File( "/data.txt" )); FileChannel fileChannel = inputStream.getChannel(); //读取文件内容 ByteBuffer buffer = ByteBuffer.allocate( 1024 ); int num = fileChannel.read(buffer); |
FileChannel对象是线程安全的,多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的。影响通道位置或者影响文件的操作都是单线程的,如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待,并发行为也会受到底层操作系统或文件系统的影响。
1 2 3 4 5 6 7 8 | ByteBuffer buffer = ByteBuffer.allocate( 1024 ); buffer.put( "随机写入一些内容到 Buffer 中" .getBytes()); // Buffer 切换为读模式 buffer.flip(); while (buffer.hasRemaining()) { // 将 Buffer 中的内容写入文件 fileChannel.write(buffer); } |
5、使用文件通道读写数据
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 | public class TestFileChannelWriteAndRead { public static void main(String[] args) throws IOException { File file = new File( "D:/Files.txt" ); RandomAccessFile raf = new RandomAccessFile(file, "rw" ); FileChannel fc = raf.getChannel(); ByteBuffer bb = ByteBuffer.allocate( 10 ); String str = "abcdefghij" ; System.out.println( "开始向渠道写入数据" ); bb.put(str.getBytes()); bb.flip(); fc.write(bb); // bb.clear(); // fc.close(); // // File file1 = new File("D:/Files.txt"); // FileInputStream fis = new FileInputStream(file1); // FileChannel fc1 = fis.getChannel(); // ByteBuffer bb1 = ByteBuffer.allocate(35); System.out.println( "渠道开始读取数据" ); fc.read(bb); bb.flip(); while (bb.hasRemaining()) { System.out.print(( char )bb.get()); } bb.clear(); fc.close(); } } 输出: 开始向渠道写入数据 渠道开始读取数据 abcdefghij |
文件通道必须通过一个打开的RandomAccessFile、FileInputStream、FileOutputStream获取到,因此这里使用FileInputStream来获取FileChannel。接着只要使用read方法将内容读取到缓冲区内即可,缓冲区内有了数据,就可以使用前文对于缓冲区的操作读取数据了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 打开一个 TCP 连接: SocketChannel socketChannel = SocketChannel.open( new InetSocketAddress( "https://www.zhihu.com" , 80 )); 当然了,上面的这行代码等价于下面的两行: // 打开一个通道 SocketChannel socketChannel = SocketChannel.open(); // 发起连接 socketChannel.connect( new InetSocketAddress( "https://www.zhihu.com" , 80 )); SocketChannel 的读写和 FileChannel 没什么区别,就是操作缓冲区。 // 读取数据 socketChannel.read(buffer); // 写入数据到网络连接中 while (buffer.hasRemaining()) { socketChannel.write(buffer); } |
1 2 3 4 5 6 7 8 | // 实例化 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 监听 8080 端口 serverSocketChannel.socket().bind( new InetSocketAddress( 8080 )); while ( true ) { // 一旦有一个 TCP 连接进来,就对应创建一个 SocketChannel 进行处理 SocketChannel socketChannel = serverSocketChannel.accept(); } |
SocketChannel 它不仅仅是 TCP 客户端,它代表的是一个网络通道,可读可写。ServerSocketChannel 不和 Buffer 打交道了,因为它并不实际处理数据,它一旦接收到请求后,实例化 SocketChannel,之后在这个连接通道上的数据传递它就不管了,因为它需要继续监听端口,等待下一个连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 监听端口: DatagramChannel channel = DatagramChannel.open(); channel.socket().bind( new InetSocketAddress( 9090 )); ByteBuffer buf = ByteBuffer.allocate( 48 ); channel.receive(buf); 发送数据: String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate( 48 ); buf.put(newData.getBytes()); buf.flip(); int bytesSent = channel.send(buf, new InetSocketAddress( "jenkov.com" , 80 )); |
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术