Buffer和Channel

那年仲夏 提交于 2019-12-13 06:40:19

    在细讲Buffer和Channel前,我们先对几个概念梳理一下。我们在做网络编程的时候时常听到三种东西:BIO,NIO,AIO。 BIO是java中普通的IO包,jdk1.4时出现了NIO,N表时New,所以其实就是新版的IO,jdk1.7时出现了AIO,也叫NIO2.0。那这三种IO有什么区别呢?IO是同步阻塞IO,NIO是同步非阻塞IO,AIO是异步非阻塞IO。这里有几个名词,同步和异步,阻塞和非阻塞,我们举个例子:
    我们在烧水,当水还没开这一段时间里,我们有两种选择,站在旁边等水烧开或者先去做别的事(比如刷个剧),如果我们选择等水烧开,就是阻塞,如果我们先去刷个剧,等会儿再来看一眼水有没有烧开,就是非阻塞。但是你会觉得,看会儿剧又要回来瞅一眼水开没开是一件很烦的事情,要是水开了能够告诉我水开了就好了,比如现在的智能家居,你尽管去刷剧,等水开了通过app给弹个消息告诉你水开了,这就是异步非阻塞。
    放到我们网络编程的场景下,现在有AB两个系统,B调A系统一个接口,如果该系统是一个耗时操作(像烧水那样), BIO 模式下,B系统的当前线程就会等在那里(等水烧开),而NIO模式下,B系统的这个线程就会先去做别的事情,等会儿回来看接口有没有返回,如果返回了继续处理,没返回再去做别的事情(就跟你看会儿剧去看一眼水开没有一样)。而在AIO模式下,B系统就直接去做别的事情了(放宽心的看剧),等接口完成后,会通过一个回调接口通知B系统完成了(水烧开后通过APP给你弹一个水烧开的消息)。
    这就是IO,NIO,AIO的表象区别,并且他们向前兼容,也就是NIO是有BIO的功能的,AIO也有IO,NIO的功能。
    下面我们来讲Buffer和Channel,这两个东西是什么呢?为什么会有呢?我们知道BIO是基于流的,流相当于程序和文件的一个通道,如果是网络传输就是网络间的一个通道,而NIO则是基于通道和缓冲区的。BIO中通过流,将文件中的内容源源不断的流入程序,直到全部传输结束,关闭流,当程序还没有等到数据时,就停在那里等,由于从一边流入到另一边,所以流也是单向的,我们熟知的InputStream和OutputStream。而BIO中则是通过Channel和Buffer,Channel相当于一个通道,buffer在其间传输数据(所以你可以开辟很多个buffer一起运数据,也可以让一个buffer来回跑重复运数据)。
    Buffer是一个抽象类,有许多子类比如ByteBuffer,IntBuffer,LongBuffer等等,我们常用的一般是ByteBuffer(毕竟数据传输的时候,我们不用去管我们运的是什么,反正都是二进制),所以这里以ByteBuffer为例:

 /**
    * position:缓冲区正在操作的位置,默认为0
    * limit:缓冲区可用大小,写模式时,limit等于capacity,写模式切换成读模式时,limit的值会变成position的值,然后position变成0
    * capacity:缓冲区的容量,最大能存放的单位
    *
    * flip():写模式切换到读模式limit=position,position=0。
    * rewind();重新读取,与flip的区别是,limit不变,而是把position改回成0。
    * clear();清空缓存,实际上并没有清楚数据,仅仅是把limit变成capacity,position变为0。
    */
   @Test
   public void test(){
       ByteBuffer byteBuffer = ByteBuffer.allocate(300);
       System.out.println("position="+byteBuffer.position());
       System.out.println("limit="+byteBuffer.limit());
       System.out.println("capacity="+byteBuffer.capacity());
       byteBuffer.put("哈哈".getBytes());
       System.out.println("position="+byteBuffer.position());
       System.out.println("limit="+byteBuffer.limit());
       System.out.println("capacity="+byteBuffer.capacity());
       byteBuffer.flip();
       byte[] bytes = new byte[byteBuffer.limit()];
       byteBuffer.rewind();
       byteBuffer.get(bytes);
       System.out.println(new String(bytes));
       System.out.println(byteBuffer.limit());
       byteBuffer.limit(200);
       System.out.println(byteBuffer.position());
       byteBuffer.put("吸".getBytes());
       System.out.println(byteBuffer.position());
       byteBuffer.flip();
       bytes = new byte[byteBuffer.limit()];
       byteBuffer.get(bytes);
       System.out.println(new String(bytes));
   }

从上面的案例中我们可以看到buffer通过put和get方法进行写入和读取数据。另外还有三个比较重要的属性:position,limit,capacity。代码中的具体不再赘述。
缓冲区一般分为直接缓冲区和非直接缓冲区,我们代码中通过ByteBuffer.allocate(300)创建的缓冲区是非直接缓冲区,如果要创建直接缓冲区可以通过ByteBuffer.allocateDirect(300)来创建。
    那直接缓冲区和非直接缓冲区有什么区别呢,直接缓冲区是直接将缓冲区建立在物理内存上的,而非直接缓冲区则是建立在jvm内存中然后再copy到物理内存中的。我们可以看到非直接缓冲区这样以来多了一步copy的动作,也就意味着其性能会下降,那直接缓冲区是不是就完美了呢?也不是,由于将缓存区直接放到物理内存中,当我们需要传输大文件时可能直接就内存溢出了,另一方面由于不是在我们的jvm中,当我们清理内存的时候,很可能就直接把缓存区给清掉了。下面是一组直接缓冲区,非直接缓冲区,以及BIO模式下文件读取写入的案例,以及时间分析:

    /**
     * 非直接缓冲区 20 21 33 27 31
     * @throws IOException
     */
    @Test
    public void test2() throws IOException {
        long startTime = System.currentTimeMillis();
        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片1.jpg");

        FileChannel inChannel = fileInputStream.getChannel();
        FileChannel outChannel = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        while((inChannel.read(byteBuffer))!=-1){
            byteBuffer.flip();
            outChannel.write(byteBuffer);
            byteBuffer.clear();
        }
        inChannel.close();
        outChannel.close();
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime-startTime));
    }


    /**
     * 普通IO 22 35 36 29 33
     * @throws IOException
     */
    @Test
    public void test3() throws IOException {
        long startTime = System.currentTimeMillis();
        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片2.jpg");
        byte[] bytes = new byte[1024];
        int length = 0;
        while ((length=fileInputStream.read(bytes))!=-1){
            fileOutputStream.write(bytes,0,length);
            fileOutputStream.flush();

        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime-startTime));
    }

    /**
     * 直接缓冲区 13 14 18 15  17
     * @throws IOException
     */
    @Test
    public void test4() throws IOException {
        long startTime = System.currentTimeMillis();
        FileChannel inChannel = FileChannel.open(Paths.get("C:\\Users\\xiaosong.yang\\Desktop\\测试图片.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("C:\\Users\\xiaosong.yang\\Desktop\\测试图片               3.jpg"),StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.READ);

        MappedByteBuffer inMappedByte = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
        MappedByteBuffer outMappedByte = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());

        byte[] dsf = new byte[inMappedByte.limit()];
        inMappedByte.get(dsf);
        outMappedByte.put(dsf);
        inChannel.close();
        outChannel.close();
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime-startTime));
    }

经过测试下来,直接缓冲区的效率最高的,而非直接缓冲区和BIO模式的速度相近,直接缓冲区的速度相当于比非直接缓冲区快了一倍。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!