05、Netty 源码解析 - 通道Channel

一、基本介绍

1、NIO 的通道类似于流,但有些区别如下:

  • 通道可以同时进行读写,而流只能读或者只能写
  • 通道可以实现异步读写数据
  • 通道可以从缓冲读数据,也可以写数据到缓冲

 2、BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。

3、Channel 在 NIO 中是一个接口
public interface Channel extends Closeable

4、常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel

5、FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写。

二、常用方法

2.1 FileChannel 类

FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有:

方法 描述
int read(ByteBuffer dst) 从通道读取数据并放到缓冲区中
int write(ByteBuffer src) 把缓冲区的数据写到通道中
long transferFrom(ReadableByteChannel src, long position, long count) 从目标通道中复制数据到当前通道
long transferTo(long position, long count, WritableByteChannel target) 把数据从当前通道复制给目标通道

三、应用案例

3.1 本地文件写数据

案例要求

1、使用 ByteBuffer(缓冲)和 FileChannel(通道),将"Hello,World!",写入到 file01.txt 中
2、文件不存在就创建
3、代码演示

public class NIOFileChannel01 {

    public static void main(String[] args) throws IOException {

        String str = "Hello,World!";

        // 创建一个输出流->channel
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");

        // 通过 fileOutputStream 获取对应的 FileChannel
        // 这个 fileChannel 真实类型是 FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();

        // 创建一个缓冲区 ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 将 str 放入到 byteBuffer
        byteBuffer.put(str.getBytes());

        // 对 byteBuffer 进行反转(flip)
        byteBuffer.flip();

        // 将 byteBuffer 数据写入到 fileChannel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
    
}

3.2 本地文件读数据

案例要求

1、使用 ByteBuffer(缓冲)和 Channel(通道),将 file01.txt 中的数据读入到程序,并显示在控制台
2、假定文件已经存在
3、代码演示

public class NIOFileChannel02 {

    public static void main(String[] args) throws IOException {

        // 创建文件的输入流
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        // 通过 fileInputStream 获取对应的 FileChannel->FileChannelImpl
        FileChannel fileChannel = fileInputStream.getChannel();

        // 创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());

        // 将通道的数据读入到 byteBuffer 中
        fileChannel.read(byteBuffer);

        // 将 byteBuffer 中的字节数据转成字符串
        System.out.println(new String(byteBuffer.array()));

        fileInputStream.close();
    }

}

3.3 使用一个 Buffer 完成文件读取

案例要求

1、使用 FileChannel(通道)和 方法 read、write,完成文件的拷贝
2、拷贝一个文本文件 1.txt,放在项目下即可 2.txt
3、全程只使用一个 Buffer

 

4、代码演示

public class NIOFileChannel03 {

    public static void main(String[] args) throws IOException {

        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

        while (true){ // 循环读取

            // 这里有一个重要的操作,一定不要忘了
            /*public final Buffer clear() {
                position = 0;
                limit = capacity;
                mark = -1;
                return this;
            }*/
            byteBuffer.clear(); // 清空 buffer

            int read = fileChannel01.read(byteBuffer);
            System.out.println("read =" + read);
            System.out.println(new String(byteBuffer.array()));

            if(read == -1){ // 表示读取完毕
                break;
            }

            // 将 buffer 中的数据写入到 fileChannel02 --> 2.txt
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }

        // 关闭流
        fileInputStream.close();
        fileOutputStream.close();
    }

}

3.4 使用transferFrom拷贝文件

案例要求

1、使用 FileChannel(通道)和 方法 transferFrom,完成文件的拷贝
2、拷贝一张图片
3、代码演示

public class NIOFileChannel04 {

    public static void main(String[] args) throws IOException {

        // 创建相关流
        FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");

        // 获取各个流对应的filechannel
        FileChannel sourceCh = fileInputStream.getChannel();
        FileChannel destCh = fileOutputStream.getChannel();

        // 使用 transferFrom 完成拷贝
        destCh.transferFrom(sourceCh,0,sourceCh.size());

        // 关闭通道和流
        sourceCh.close();
        destCh.close();
        fileInputStream.close();
        fileOutputStream.close();
    }

}