zoukankan      html  css  js  c++  java
  • Java基础——NIO(一)通道与缓冲区

    一、概述

      1.什么是NIO

      NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。

      在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO

      更多介绍与全面概述,请参见http://www.iteye.com/magazines/132-Java-NIO#579

      2.NIO与IO的主要区别

      

        // 原先的IO是面向流的,类似水流,是单向的;现在的NIO是面向缓冲的双向的,类似铁路,提供一个运输的通道,实际运输的是火车(缓冲区)

        简而言之,通道(channel)负责传输(打开通往文件的通道),缓冲区(Buffer)负责存储

        其它特性将会在后续陆续介绍

      3.NIO的主要内容

    二、通道(channel)与缓冲区(buffer)

      1.缓冲区的数据存取 

      初始化分配缓冲区:——通过两个静态方法

      

      数据存取:——主要通过put和get方法

      

       要操作缓冲区,必须理解其中四个重要属性——位于父类Buffer中:

      

      capacity:容量——缓冲区最大存储数据容量,一旦声明不能改变(底层数组的限制)

      limit:界限——缓冲区中可操作数据的大小(limit后的数据不能操作)

      position:位置——缓冲区中正在操作数据的位置

      mark:标记——记住当前position的位置,可以通过reset()恢复到position位置 

        // 注意这三个属性都是 Int 类型,代表的是位置(可以理解为类似数组下标)

        图解如下:

        

      测试以上几个属性如下:

      初始状态:

     @Test
        public void test1() {
            // 分配一个指定大小的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
        }
    View Code

      结果:

      

      使用put存数据:

      @Test
        public void test1() {
            // 分配一个指定大小的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            String str = "i love the world!";
            buf.put(str.getBytes());
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
        }
    View Code

      

      读数据模式:

     @Test
        public void test1() {
            // 分配一个指定大小的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            String str = "i love the world!";
            // 存数据模式
            buf.put(str.getBytes());
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 读数据模式——flip()
            buf.flip();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
    
        }
    View Code

      结果:

       

       使用get读取数据:

     @Test
        public void test1() {
            // 分配一个指定大小的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            String str = "i love the world!";
            // 存数据模式put()
            buf.put(str.getBytes());
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 读数据模式——flip()
            buf.flip();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 利用get()读取缓冲区数据
            byte[] dst = new byte[buf.limit()];
            buf.get(dst);
            System.out.println(new String(dst, 0, dst.length));
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
        }
    View Code

      结果:

      

      //读数据的时候游标position也是会移动过去的,就像读模式游标自动到首位

      使用rewind()重读数据(将get读取时导致的position的位置偏移改回来,使position回到初始位置0)

     @Test
        public void test1() {
            // 分配一个指定大小的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            String str = "i love the world!";
            // 存数据模式put()
            buf.put(str.getBytes());
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 读数据模式——flip()
            buf.flip();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 利用get()读取缓冲区数据
            byte[] dst = new byte[buf.limit()];
            buf.get(dst);
            System.out.println(new String(dst, 0, dst.length));
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // rewind()进行重读
            buf.rewind();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
        }
    View Code

      结果:

      

       使用clear()清空数据缓冲区

      @Test
        public void test1() {
            // 分配一个指定大小的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            String str = "i love the world!";
            // 存数据模式put()
            buf.put(str.getBytes());
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 读数据模式——flip()
            buf.flip();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 利用get()读取缓冲区数据
            byte[] dst = new byte[buf.limit()];
            buf.get(dst);
            System.out.println(new String(dst, 0, dst.length));
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // rewind()进行重读
            buf.rewind();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // clear()进行缓冲区清空
            buf.clear();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
    
        }
    View Code

      结果:(位置清零,限制置为容量,回到最初状态,但并不是真正清空缓冲区,只是相应属性被重置,缓冲区数据处于“被遗忘”状态)

      

      使用reset()重置到mark()标记的位置:

       @Test
        public void test1() {
            // 分配一个指定大小的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            String str = "i love the world!";
            // 存数据模式put()
            buf.put(str.getBytes());
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 读数据模式——flip()
            buf.flip();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 利用get()读取缓冲区数据
            byte[] dst = new byte[buf.limit()];
            buf.get(dst, 0, 6);
            System.out.println(new String(dst, 0, 6));
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            // 通过mark()标记position位置
            buf.mark();
            // 继续读取,改变position位置
            buf.get(dst, 6,4);
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
            System.out.println(new String(dst, 6, 4));
            // 使用reset()恢复position到mark位置
            buf.reset();
            System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
    
        }
    View Code

      结果:

      

      //其它例如查看缓冲区可操作数据的方法 hasRemaining()等参见API

       

      2.直接缓冲区与非直接缓冲区

      概念:

      非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
      直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率

      关于这方面的详细介绍,请参见:http://www.cnblogs.com/androidsuperman/p/7083049.html

        API中也有相关的判断直接缓冲区的方法:isDirect()

      3.通道(channel)

      概念:

      通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

      通道是访问IO服务的导管,负责缓冲区数据的传输,配合缓冲区传输数据。通过通道,我们可以以最小的开销来访问操作系统的I/O服务

      通道相关的介绍,请参见 风一样的码农 的随笔:http://www.cnblogs.com/chenpi/p/6481271.html

      分类:主要是红箭头所指处的实现类

      

      获取通道:——打开通道,打开一条向哪个文件的通道

         getChannel()

        静态方法 open()——1.7后的NIO.2

        Files工具类的 newByteChannel()——1.7后的NIO.2

      使用通道:

        使用方式一进行文件复制:  

     @Test
        public void test1() {
            FileInputStream fis = null;
            FileOutputStream fos = null;
            FileChannel fisChannel = null;
            FileChannel fosChannel = null;
            try {
                fis = new FileInputStream(new File("D:\test\1.jpg"));
                fos = new FileOutputStream(new File("D:\test\2.jpg"));
    
                // 获取通道
                fisChannel = fis.getChannel();
                fosChannel = fos.getChannel();
    
                // 分配非直接缓冲区进行数据存取
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                // 与IO流类似的读取思路
                while ((fisChannel.read(byteBuffer)) != -1) {
                    // 将读到的数据进行存储,一定要切换为读模式!
                    byteBuffer.flip();// 切换读模式
                    fosChannel.write(byteBuffer);
                    byteBuffer.clear();// 清空缓冲区
                }
                System.out.println("复制完成!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4个平行的if即使某个出现异常,后续的也会关闭。而且后续1.7后获取通道将会简化
                if (fosChannel != null) {
                    try {
                        // 显示关闭流
                        fosChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fisChannel != null) {
                    try {
                        // 显示关闭流
                        fisChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fos != null) {
                    try {
                        // 显示关闭流
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fis != null) {
                    try {
                        // 显示关闭流
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    View Code

      // 也不需要缓冲流包装之类了

        使用方式二通过内存映射进行文件复制:

     @Test
        public void test2() {
            FileChannel inChnnel = null;
            FileChannel outChnnel = null;
            try {
                // NIO.2的方式获取读通道
                inChnnel = FileChannel.open(Paths.get("D:\test\2.jpg"), StandardOpenOption.READ);
                // 由于outMappedBuf为读写模式,故这里给outChannel加读写两个模式(CREATE_NEW为创建新文件)
                outChnnel = FileChannel.open(Paths.get("D:\test\3.jpg"), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE,StandardOpenOption.READ);
                // 使用内存映射创建直接缓冲区(只有byteBuffer支持),与allocateDirect()原理一致
                MappedByteBuffer inMappedBuf = inChnnel.map(FileChannel.MapMode.READ_ONLY, 0, inChnnel.size());
                MappedByteBuffer outMappedBuf = outChnnel.map(FileChannel.MapMode.READ_WRITE, 0, inChnnel.size());
                // 现在往缓冲区中放数据就直接放到文件中了,无需通过通道读写
                byte[] dst = new byte[inMappedBuf.limit()];
                inMappedBuf.get(dst);
                outMappedBuf.put(dst);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outChnnel != null) {
                    try {
                        outChnnel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (inChnnel != null) {
                    try {
                        inChnnel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    View Code

      关于其中得MappedByteBuffer,请参见http://blog.csdn.net/z69183787/article/details/53695590

        事实上,NIO提供了通道之间传输数据得方式:

        

       @Test
        public void test3() {
            FileChannel inChannel = null;
            FileChannel outChannel = null;
            try {
                // NIO.2的方式获取读通道
                inChannel = FileChannel.open(Paths.get("D:\test\3.jpg"), StandardOpenOption.READ);
                // 由于outMappedBuf为读写模式,故这里给outChannel加读写两个模式
                outChannel = FileChannel.open(Paths.get("D:\test\4.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE,StandardOpenOption.READ);
                // 通道之间传送数据
                inChannel.transferTo(0, inChannel.size(), outChannel);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outChannel != null) {
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (inChannel != null) {
                    try {
                        inChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    View Code

       FileChannel常用方法:

      

       4.分散和聚集

       分散读取(Scattering Reads):

        将通道中的数据分散到各个缓冲区中

      @Test
        public void test4() {
            RandomAccessFile raf = null;
            FileChannel rafChannel = null;
            try {
                raf = new RandomAccessFile("D:\test\hello.txt", "rw");
                rafChannel = raf.getChannel();
                // 分配缓冲区
                ByteBuffer byteBuffer1 = ByteBuffer.allocate(400);
                ByteBuffer byteBuffer2 = ByteBuffer.allocate(201);
                ByteBuffer byteBuffer3 = ByteBuffer.allocate(800);
                ByteBuffer[] bufs = {byteBuffer1, byteBuffer2, byteBuffer3};
                // 分散读取
                rafChannel.read(bufs);
                // 切换读模式,读取缓冲区数据
                for (ByteBuffer buf : bufs) {
                    buf.flip();
                    System.out.println(new String(buf.array(), 0, buf.limit()));
                    System.out.println("==================================");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (raf != null) {
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (rafChannel != null) {
                    try {
                        rafChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    View Code

      聚集写入(Gathering Writes):

        将多个缓冲区中的数据聚集到通道中

      示例与分散类似,也是操作缓冲区数组

      5.字符集(Charset)

      编码:编成字节码(数组)——将字符串转换成字节数组

      解码:解释成看的懂的字符串——将字节数组解成字符串

      查看所有可用的字符集:

    @Test
        public void test5() {
            // 所有可用的字符集
            Map<String, Charset> map = Charset.availableCharsets();
            for (Map.Entry<String, Charset> entry : map.entrySet()) {
                System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
            }
        }
    View Code

      编码:

      @Test
        public void test6() {
            // 获取字符集的字符集对象
            Charset cs1 = Charset.forName("GBK");
            // 获取编码器与解码器
            CharsetEncoder encoder = cs1.newEncoder();
            CharsetDecoder decoder = cs1.newDecoder();
            // 进行编码与解码(其实也就是CharBuffer与ByteBuffer之间的转换)
            CharBuffer charBuffer = CharBuffer.allocate(1024);
            charBuffer.put("金陵岂是池中物,一遇风云便化龙。");
            // 切换读模式,开始编码
            charBuffer.flip();
            ByteBuffer byteBuffer = null;
            try {
                byteBuffer = encoder.encode(charBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 32; i++){
                System.out.println(byteBuffer.get());
            }
        }
    View Code

      结果是字节数组的形式(数值):

      

      解码查看:

        @Test
        public void test6() {
            // 获取字符集的字符集对象
            Charset cs1 = Charset.forName("GBK");
            // 获取编码器与解码器
            CharsetEncoder encoder = cs1.newEncoder();
            CharsetDecoder decoder = cs1.newDecoder();
            // 进行编码与解码(其实也就是CharBuffer与ByteBuffer之间的转换)
            CharBuffer charBuffer = CharBuffer.allocate(1024);
            charBuffer.put("金陵岂是池中物,一遇风云便化龙。");
            // 切换读模式,开始编码
            charBuffer.flip();
            ByteBuffer byteBuffer = null;
            try {
                byteBuffer = encoder.encode(charBuffer);
                CharBuffer charBuffer1 = decoder.decode(byteBuffer);
                System.out.println(charBuffer1.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
    //        for (int i = 0; i < 32; i++){
    //            System.out.println(byteBuffer.get());
    //        }
        }
    View Code

      

  • 相关阅读:
    神经网络编程入门
    RBF神经网络通用函数 newrb, newrbe
    机器学习-RBF高斯核函数处理
    单元测试的基本概念
    File.Copy的时候Could not find a part of the path
    xunit inlinedata classdata memberdata
    xunit输出output到控制台
    Getting Started with xUnit.net (desktop)
    confluence的使用
    Why is an 'Any CPU' application running as x86 on a x64 machine?
  • 原文地址:https://www.cnblogs.com/jiangbei/p/7499444.html
Copyright © 2011-2022 走看看