• channel和Stream的对比


    这篇文章主要想总结下NIO的channel的传统io中的stream的差别在哪。网上找了很多文章,都感觉只是说了概念。然后自己大概看了下源码,结合概念,整理一下。有些地方可能不是很准确,也希望可以给点意见,互相学习。

    这里不讲异步方面的东西,只是想单纯讲一下stream和channel在操作内存时的一些差异。

    让我产生问题的来源主要有俩个:

    1. 从概念上解读,stream是按照字节去处理的,看起来就像是水流一样,一个接一个。而channel是按照数据块来处理的。那么bufferedStream呢?加了buffer后是不是也是按照数据块来处理呢。那么这时,bufferedStream和channel的性能区别是在哪里呢?

    2. 网上发现了一些文章,通过拷贝文件的实验来对比channel和stream的性能。stream使用的是bufferedStream。结果是channel的性能要比stream快1/3。当然我没有做实验去验证,只是通过代码的解读来理解下为什么性能会有这么大的差别。

    首先需要引入一些背景知识,用户的线程是如何读入和写出文件的。下面这个图是从一本书上截下来的,简单的说明下读取文件时的流程。

    1. 磁盘的controller把数据从磁盘拷贝到系统内核区。

    2.然后cpu把数据从系统内核拷贝的用户的内存区。

    3.系统对内存块的操作是按数据块操作的,这也是NIO的一个重要的概念,操作数据时尽量和操作系统相吻合,来提高内存操作的效率。

     

    然后再分别分析一下channel和stream在操作内存时分别的步骤,就可以比较清晰的看出俩者的差别。

    一 channel:

    首先总结一下,这也是一个大家都知道的概念。channel的输入端和输出端都是byteBuffer。在内存操作的时候,也是以数据块为单位来进行数据移动。下面具体说下输出的步骤:

    1. 如果我们使用的是directBuffer。 那么会直接调用native方法,把整块的内存写出到磁盘。

    2.如果我们使用的是堆内的buffer,java会创建一个临时的directBuffer,把堆内buffer的数据拷贝到临时的 directBuffer。然后调用native方法把临时的 directBuffer的内容整块的写入磁盘。

    下面贴一下源码来看一下:

    static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
            if(var1 instanceof DirectBuffer) {
                // 如果是directBuffer,直接整块写入磁盘
                return writeFromNativeBuffer(var0, var1, var2, var4);
            } else {
                int var5 = var1.position();
                int var6 = var1.limit();
    
                assert var5 <= var6;
    
                int var7 = var5 <= var6?var6 - var5:0;
                // 创建临时directBuffer
                ByteBuffer var8 = Util.getTemporaryDirectBuffer(var7);
    
                int var10;
                try {
                    var8.put(var1);
                    var8.flip();
                    var1.position(var5);
                    // 直接将临沭directBuffer整块写入磁盘
                    int var9 = writeFromNativeBuffer(var0, var8, var2, var4);
                    if(var9 > 0) {
                        var1.position(var5 + var9);
                    }
    
                    var10 = var9;
                } finally {
                    Util.offerFirstTemporaryDirectBuffer(var8);
                }
    
                return var10;
            }
        }

    二 stream

    首先也说一下概念,stream是按照字节,一个一个操作内存的。但是,真的是这样的吗?

    先看下FileOutputStream这个类:

        public void write(byte b[], int off, int len) throws IOException {
            writeBytes(b, off, len, append);
        }
    
        private native void writeBytes(byte b[], int off, int len, boolean append)
            throws IOException;

    可以看到,这里也是调用native方法,把整个byte块都写入了磁盘。具体native方法是怎么实现的,哪位大大可以帮忙分析一下?

    再看下BufferedOutPutStream这个类,主要看下write方法和flushBuffer方法

    public synchronized void write(byte b[], int off, int len) throws IOException {
    if (len >= buf.length) {
    /* If the request length exceeds the size of the output buffer,
    flush the output buffer and then write the data directly.
    In this way buffered streams will cascade harmlessly. */
    flushBuffer();
    out.write(b, off, len);
    return;
    }
    if (len > buf.length - count) {
    flushBuffer();
    }
    // 写入内部的buffer区
    System.arraycopy(b, off, buf, count, len);
    count += len;
    }

    /** Flush the internal buffer */ private void flushBuffer() throws IOException { if (count > 0) { 调用代理的outputStream的写出方法 out.write(buf, 0, count); count = 0; } }

    所以,从这个角度来看,stream和channel都是直接把整个数据块对底层进行写入的。

    那么,stream真的比channel慢吗? 值得怀疑。 哪位大佬可以帮忙解下惑呢?

  • 相关阅读:
    rxjs 学习实践笔记
    封装localStorage、sessionStorage设置,获取,移除方法
    Angular实现类似vuex状态管理功能、全局数据管理与同步更新
    关于RxJS 处理多个Http请求 串行与并行方法
    vue插件汇总
    vue使用videojs控制后台m3u8数据请求
    NPOI winform读取Excel
    //邮件发送:
    定时任务框架Quartz.net
    系统日志控件 Log4NET
  • 原文地址:https://www.cnblogs.com/zhaoxinshanwei/p/9636769.html
Copyright © 2020-2023  润新知