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));

浙公网安备 33010602011771号