Java基礎——NIO(一)通道與緩衝區

1、概述

  1.什麼是NIOhtml

  NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的做用和目的,但實現方式不一樣,NIO主要用到的是塊,因此NIO的效率要比IO高不少。java

  在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另外一套就是網絡編程NIOandroid

  更多介紹與全面概述,請參見http://www.iteye.com/magazines/132-Java-NIO#579編程

  2.NIO與IO的主要區別數組

  

    // 原先的IO是面向流的,相似水流,是單向的;如今的NIO是面向緩衝的雙向的,相似鐵路,提供一個運輸的通道,實際運輸的是火車(緩衝區)網絡

    簡而言之,通道(channel)負責傳輸(打開通往文件的通道),緩衝區(Buffer)負責存儲app

    其它特性將會在後續陸續介紹dom

  3.NIO的主要內容ide

2、通道(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

  

相關文章
相關標籤/搜索