FileChannel

FileChannel用于连接文件来读取和写入数据,它总是以阻塞模式运行,不支持调用configureBlocking方法设置为非阻塞类型。FileChannel不能够直接创建对象使用,它需要通过InputStream、outputStream或者RandomAccessFile来进行获取。例如:

  1. @Test
  2. public void test() throws FileNotFoundException {
  3. FileInputStream inputStream = new FileInputStream("test.txt");
  4. FileChannel fileChannel = inputStream.getChannel();
  5. System.out.println(fileChannel);
  6. RandomAccessFile accessFile = new RandomAccessFile("test.txt", "rw");
  7. FileChannel channel = accessFile.getChannel();
  8. System.out.println(channel);
  9. FileOutputStream outputStream = new FileOutputStream("out.txt");
  10. FileChannel channel1 = outputStream.getChannel();
  11. System.out.println(channel1);
  12. }

Channel通常需要配合Buffer一起使用,可以将Channel中的数据读取到Buffer中,也可以将Buffer中的数据写入到Channel中。使用Buffer前需要先为Buffer分配一定的内存空间,然后调用Channel的read方法将数据读取到Buffer中。当Channel使用完毕后,调用close方法主动的关闭Channel。

  1. public static void main(String[] args) throws IOException {
  2. RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
  3. FileChannel channel = file.getChannel(); // 获取Channel
  4. ByteBuffer buffer = ByteBuffer.allocate(48); // 分配Buffer空间
  5. int bytesRead = channel.read(buffer); // 读取数据
  6. while(bytesRead != -1){
  7. buffer.flip();
  8. while(buffer.hasRemaining()){
  9. System.out.println((char) buffer.get());
  10. }
  11. buffer.clear();
  12. }
  13. file.close();
  14. channel.close();
  15. }

如果想要往Channel中写入数据,需要调用Channel的write方法,例如:

  1. @Test
  2. public void channelWrite() throws IOException {
  3. ByteBuffer byteBuffer = ByteBuffer.allocate(24);
  4. for (int i = 0; i < 10; i++) {
  5. byteBuffer.put((byte) i);
  6. }
  7. FileOutputStream outputStream = new FileOutputStream("out.txt");
  8. FileChannel channel = outputStream.getChannel();
  9. channel.write(byteBuffer);
  10. System.out.println(channel.position());
  11. channel.close();
  12. outputStream.close();
  13. }

除了read和write方法外,FileChannel还提供了一些其他的方法:

  • size:获取和Channel连接的文件的大小
  • truncate:根据给定的长度截断文件
  • force:强制清除Channel未写入到磁盘中的剩余数据

SocketChannel

SocketChannel用于连接TCP Socket,NIO提供了两种方法来创建SocketChannel:

  • 自行打开一个SocketChannel来连接到网络上的服务器
  • 当ServerSocketChannel获取到一个连接后可以创建一个SocketChannel

当SocketChannel使用完毕后,需要自行调用close方法关闭。

例如:

  1. @Test
  2. public void SocketChannelOpen() throws IOException {
  3. SocketChannel channel = SocketChannel.open();
  4. channel.connect(new InetSocketAddress("http://baidu.com", 80));
  5. // 其他操作
  6. channel.close();
  7. }

同样也可以将SocketChannel中的数据读取到Buffer中,做法和FileChannel类似:

  1. @Test
  2. public void SocketChannelOpen() throws IOException {
  3. SocketChannel channel = SocketChannel.open();
  4. channel.connect(new InetSocketAddress("https://baidu.com", 80));
  5. ByteBuffer byteBuffer = ByteBuffer.allocate(48);
  6. int read = channel.read(byteBuffer);
  7. System.out.println(read);
  8. channel.close();
  9. }

如果想要往SocketChannel中写入数据,同样调用write方法实现。

不同于FileChannel一个地方在于,SocketChannel支持调用configureBlocking(false)方法来实现非阻塞模式,从而实现异步的调用connect、read和write方法。

对于connect方法来说,如果它是以异步模式运行,那么调用connect不必等到连接建立后才返回。如果想要知道连接是否已经建立,可以调用finishConnect方法判断。例如:

  1. cketChannel.configureBlocking(false); // 以非阻塞模式运行
  2. socketChannel.connect(new InetSocketAddress("http://baidu.com", 80));
  3. while(! socketChannel.finishConnect() ){ // 不断自旋,检查连接是否建立
  4. // ...
  5. }

对于write方法来说,如果它是以非阻塞模式运行,那么不必真正的写入数据才返回,因此,程序需要使用循环的方式来调用write方法。

对于read方法来说,如果它是以非阻塞模式运行,那么不必真正的读取到数据才返回。如果程序想判断是否真的读到了数据,可以从方法的int型返回值获知。

如果数据传输结束,调用close方法来关闭SocketChannel。


ServerSocketChannel

ServerSocketChannel用于监听可能到来的TCP连接,例如;

  1. ServerSocketChannel channel = ServerSocketChannel.open();
  2. channel.socket().bind(new InetSocketAddress(9999));
  3. while(true){
  4. SocketChannel socketChannel = channel.accept();
  5. // ....
  6. }

如果想要创建一个ServerSocketChannel,可以调用它的open方法实现,如上所示。当然,ServerSocketChannel使用完毕之后,也需要调用close方法自行关闭。

通过调用accept方法来监听到来的TCP连接,当连接到达时会返回一个SocketChannel。因此,如果监听开始后没有连接到达,那么accept方法会一直处于阻塞状态,如上所示。

当然,ServerSocketChannel也可以以非阻塞模式运行,此时accept方法调用后会立即返回。如果此时有连接到达,那么返回的SocketChannel不为null,否则为null。

  1. ServerSocketChannel channel = ServerSocketChannel.open();
  2. channel.socket().bind(new InetSocketAddress(9999));
  3. channel.configureBlocking(false);
  4. while(true){
  5. SocketChannel socketChannel = channel.accept();
  6. if(socketChannel != null){
  7. // ...
  8. }
  9. // ....
  10. }

DataGramChannel

DataGramChannel可以用来发送和接收UDP数据报,由于UDP是无连接的,因此程序无法像其他类型的Channel来读取和写入数据。类似的,通过调用open方法来获取DataGramChannel,通过receive方法可以获取数据报,如下所示:

  1. DataGramChannel channel = DataGramChannel.open();
  2. channel.socket().bind(new InetSocketAddress(9999));
  3. ByteBuffer bufferr = ByteBuffer.allocate(24);
  4. buffer.clear();
  5. channel.receive(buffer);

receive方法获取到的数据报将被写入到Buffer中,如果数据量超过了Buffer的容量,那么多余的数据报将被直接丢弃。如果想要发送UDP数据报,可以调用send方法实现,如下所示:

  1. DataGramChannel channel = DataGramChannel.open();
  2. ByteBuffer buffer = ByteBuffer.allocate(48);
  3. buf.put(....);
  4. buffer.flip();
  5. int bytesSent = channel.send(buffer, new InetSocketAddress("http://www.baidu.com", 80));

通过调用connect方法可以和一个特定的地址建立”连接”,它不是类似于TCP一样真正的连接,只是支持Channel只能向这个地址发送数据报或者接收数据报。

  1. channel.connect(new InetSocketAddress("http://www.baidu.com", 80));

连接建立后,可以调用read和write方法,但是它无法保证数据的成功交付。

  1. int bytesRad = channel.read(buffer);
  2. int bytesWritten = channel.write(buf);

使用完毕后,调用close方法关闭Channel。