NIO之Channel通道

Channel通道

通过Channel可以跟文件或者Socket进行数据的传输。Channel 可以双向传输数据,不能直接访问数据,只能与Buffer交互。把Channel中的数据读到 Buffer 中,程序从Buffer中读取数据; 写操作时, 程序把数据写入 Buffer 中,再把buffer中的数据写入到 Channel 中。如果把Channel比作是一座桥,则Buffer可以看做是在桥上跑的汽车。通过汽车可以运输桥两边的货物。

java.nio.channels.Channel是一个接口,常用的实现类有:

  • FileChannel:读写文件的通道
  • SocketChannel/ ServerSocketChannel:读写 Socket 套接字中的数据
  • DatagramChannel:通过 UDP 读写网络数据

下面利用Channel和Buffer实现文件数据读取和写入。

package com.monkey1024;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/*
    使用Channel和Buffer实现文件读取和写入
*/
public class Channel01 {
    public static void main(String[] args) throws Exception {
        //创建文件输入流和文件输出流对象
        try (FileInputStream fis = new FileInputStream("D:/monkey1024/a.txt");
             FileOutputStream fos = new FileOutputStream("D:/monkey1024/b.txt");

             //获取Channel对象
             FileChannel fisChannel = fis.getChannel();
             FileChannel fosChannel = fos.getChannel()) {


            //创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(16);

            int read = fisChannel.read(buffer);

            //如果是不等于-1,则表示没有读完
            while (read != -1) {
                //翻转
                buffer.flip();
                //将buffer输出到channel
                fosChannel.write(buffer);
                //将position设置为0
                buffer.clear();
                //重写读取数据到buffer中
                read = fisChannel.read(buffer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

虚拟内存映射

FileChannel 中有一个map方法,该方法会对硬盘的一个文件进行虚拟内存映射,将该映射封装为一个MappedByteBuffer对象。建立好虚拟内存映射之后,该文件不会从硬盘读到内存中,类似DirectByteBuffer 。通过虚拟内存映射效率要高一些。该方式底层是基于内存映射(mmap)的实现。

package com.monkey1024;

import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/*
    虚拟内存映射
 */
public class Channel02 {
    public static void main(String[] args) {
        try (//利用open方法获取Channel对象
             FileChannel fisChannel = FileChannel.open(Paths.get("D:/monkey1024/a.txt"), StandardOpenOption.READ);
             FileChannel fosChannel = FileChannel.open(Paths.get("D:/monkey1024/b.txt"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE)) {

            //获取虚拟内存映射对象
            MappedByteBuffer fisMappedByteBuffer = fisChannel.map(FileChannel.MapMode.READ_ONLY, 0, fisChannel.size());
            MappedByteBuffer fosMappedByteBuffer = fosChannel.map(FileChannel.MapMode.READ_WRITE, 0, fisChannel.size());

            //移动虚拟内存映射的数据
            byte[] bytes = new byte[fisMappedByteBuffer.limit()];
            fisMappedByteBuffer.get(bytes);
            fosMappedByteBuffer.put(bytes);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Channel至Channel之间的传输

在进行数据移动的时候,可以直接通过Channel至Channel之间进行传输,而不用Buffer来传递数据。该方式底层都是基于 sendfile 实现数据传输。

这里涉及到了FileChannel中的两个方法:

  • transferFrom 从哪里获取传输的数据
  • transferTo 将数据传输到哪里

下面示例分别利用了上述两个方法进行文件的传输

package com.monkey1024;

import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/*
    使用Channel和channel的传输
*/
public class Channel03 {
    public static void main(String[] args) throws Exception {
        //获取Channel对象
        try (FileChannel fisChannel = FileChannel.open(Paths.get("D:/monkey1024/a.txt"), StandardOpenOption.READ);
             FileChannel fosChannel = FileChannel.open(Paths.get("D:/monkey1024/b.txt"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW)) {

            //将fisChannel中的数据传输到fosChannel
            //fisChannel.transferTo(fisChannel.position(),fisChannel.size(),fosChannel);

            //从fisChannel中传输数据至fosChannel
            fosChannel.transferFrom(fisChannel, fisChannel.position(), fisChannel.size());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

分散 (Scatter) 和聚集 (Gather)

Scatter 是指从 Channel 通道中读取数据,把这些数据按顺序分散写入到多个 Buffer 缓冲区中。

Gather 是指在写操作时,将多个 Buffer 缓冲区的数据写入到同一个 Channel 中。

Scatter、Gather 经常用于需要将传输的数据分开处理的场景。

下面示例演示使用Scatter和Gather将一个文件中的内容拷贝到另一个文件中。

package com.monkey1024;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/*
    分散 (Scatter) 和聚集 (Gather)
 */
public class Channel03 {
    public static void main(String[] args) {
        try (//利用open方法获取Channel对象
             FileChannel fisChannel = FileChannel.open(Paths.get("D:/monkey1024/a.txt"), StandardOpenOption.READ);
             FileChannel fosChannel = FileChannel.open(Paths.get("D:/monkey1024/b.txt"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW)) {

            //创建缓冲区
            ByteBuffer buffer1 = ByteBuffer.allocate(10);
            ByteBuffer buffer2 = ByteBuffer.allocate(10);

            ByteBuffer[] buffers = {buffer1, buffer2};
            //分散读取
            long read = fisChannel.read(buffers);

            while (read != -1) {
                //翻转buffer
                for (int i = 0; i < buffers.length ; i++) {
                    buffers[i].flip();
                    System.out.println(new String(buffers[i].array(),0,buffers[i].limit()));
                }
                //聚集写入
                fosChannel.write(buffers);

                //将position设置为0
                for (int i = 0; i < buffers.length ; i++) {
                    buffers[i].clear();
                }

                //分散读取
                read = fisChannel.read(buffers);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}