channel 和 stream 的区别

1,586 阅读4分钟

前言

  • 之前学习 netty 的时候,一直觉得这两者很相似,当时又不懂具体区别在哪,只知道在使用 NIO 编程的时候,先获得一个 stream 然后通过 stream 获得一个 channel, 最后通过 Buffer 进行操作,本章主要区别下两者的使用

1. stream

image.png

  • 大体上,分为字节流、字符流,内部可以再次划分为 输入流和输出流
@Test
    public void test04() throws IOException {
        byte[] bytes = {12,21,34,11,21};
        FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test.txt");
        // 写入二进制文件,直接打开会出现乱码
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }

    @Test
    public void test05() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test.txt");
        int c;
        // 读取写入的二进制文件,输出字节数组
        while ((c = fileInputStream.read()) != -1) {
            System.out.print(c);
        }
    }

  • 传统的 IO 流 大概是这样子的:

image.png

  • 我们需要把磁盘文件或者网络文件中的数据读取到程序中来,我们需要建立一个用于传输数据的管道,原来我们传输数据面对的直接就是管道里面一个个字节数据的流动(我们弄了一个 byte 数组,来回进行数据传递),所以说原来的 IO 它面对的就是管道里面的一个数据流动,所以我们说原来的 IO 是面向流的

  • 我们说传统的 IO 还有一个特点就是,它是单向的。解释一下就是:如果说我们想把目标地点的数据读取到程序中来,我们需要建立一个管道,这个管道我们称为输入流。相应的,如果如果我们程序中有数据想要写到目标地点去,我们也得再建立一个管道,这个管道我们称为输出流。所以我们说传统的 IO 流是单向的

2. NIO

@Slf4j
public class ChannelDemo1 {
    public static void main(String[] args) {
        try (RandomAccessFile file = new RandomAccessFile("helloword/data.txt", "rw")) {
            FileChannel channel = file.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(10);
            do {
                // 向 buffer 写入
                int len = channel.read(buffer);
                log.debug("读到字节数:{}", len);
                //读取到的位置,如果返回是-1, 表示没有内容了,直接返回
                if (len == -1) {
                    break;
                }
                // 切换 buffer 读模式
                buffer.flip();
                while(buffer.hasRemaining()) {
                    log.debug("{}", (char)buffer.get());
                }
                // 切换 buffer 写模式
                buffer.clear();
            } while (true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

image.png

  • 我们说只要是 IO ,那么就是为了完成数据传输的。

  • 即便你用 NIO ,它也是为了数据传输,所以你要想完成数据传输,你也得建立一个用于传输数据的通道,这个通道你不能把它理解为之前的水流了,但是你可以把它理解为铁路,铁路本身是不能完成运输的,铁路要想完成运输它必须依赖火车,说白了这个通道就是为了连接目标地点和源地点。所以注意通道本身不能传输数据,要想传输数据必须要有缓冲区,这个缓冲区你就可以完全把它理解为火车,比如说你现在想把程序中的数据写到文件中,那么你就可以把数据都写到缓冲区,然后缓冲区通过通道进行传输,最后再把数据从缓冲区拿出来写到文件中,你想把文件中的数据传数到程序中,也是一个道理,把数据写到缓冲区,缓冲区通过通道进行传输,到程序中把数据拿出来。所以我们说原来的 IO 单向的现在的缓冲区是双向的,这种传输数据的方式也叫面向缓冲区。总结一下,就是通道只负责连接,缓冲区才负责存储数据。

小结

  • channel是双向的,既可以读,也可以写。而stream是单向的,一个stream不可能同时读同时写,这样也从java规范中不允许的,因为inputstream和outputstream都是抽象类,一个类不可能同时继承两个抽象类。

  • 应用程序通过channel读取外部数据(比如文件),首先得获取这个数据对应的channel,然后通过channel将数据写入的buffer当中,不管这个buffer是堆内缓冲区还是直接缓冲区。应用程序操作的是这个buffer中的数据

  • 而应用程序通过stream读取外部数据(比如文件),首先获取这个文件对应的stream,然后直接从stream中获取对应的数据

  • channel可以在异步非阻塞变成中使用,在读取数据的过程中会产生相应的时事件,而stream只能在阻塞编程中使用。

  • 在文件操作中,channel可以通过FileStream的getChannel方法获得,实现了nio与oio的相互转换。

参考