Java NIO之通道Channel
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 对应的服务端,用于监听某个端口进来的请求
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 { }
public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel { ... } public abstract class AbstractSelectableChannel extends SelectableChannel { ... }
可以看出,socket通道类从SelectableChannel类引申而来,从SelectableChannel引申而来的类可以和支持有条件的选择的选择器(Selectors)一起使用。将非阻塞I/O和选择器组合起来可以使开发者的程序利用多路复用I/O。
//初始化 FileInputStream inputStream = new FileInputStream(new File("/data.txt")); FileChannel fileChannel = inputStream.getChannel(); //读取文件内容 ByteBuffer buffer = ByteBuffer.allocate(1024); int num = fileChannel.read(buffer);
FileChannel对象是线程安全的,多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的。影响通道位置或者影响文件的操作都是单线程的,如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待,并发行为也会受到底层操作系统或文件系统的影响。
ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("随机写入一些内容到 Buffer 中".getBytes()); // Buffer 切换为读模式 buffer.flip(); while(buffer.hasRemaining()) { // 将 Buffer 中的内容写入文件 fileChannel.write(buffer); }
5、使用文件通道读写数据
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方法将内容读取到缓冲区内即可,缓冲区内有了数据,就可以使用前文对于缓冲区的操作读取数据了。
打开一个 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); }
// 实例化 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 监听 8080 端口 serverSocketChannel.socket().bind(new InetSocketAddress(8080)); while (true) { // 一旦有一个 TCP 连接进来,就对应创建一个 SocketChannel 进行处理 SocketChannel socketChannel = serverSocketChannel.accept(); }
SocketChannel 它不仅仅是 TCP 客户端,它代表的是一个网络通道,可读可写。ServerSocketChannel 不和 Buffer 打交道了,因为它并不实际处理数据,它一旦接收到请求后,实例化 SocketChannel,之后在这个连接通道上的数据传递它就不管了,因为它需要继续监听端口,等待下一个连接。
监听端口: 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));