Java-NIO 之 Buffer 與 Channel

NIO:一種同步非阻塞的 I/O 模型,也是 I/O 多路複用的基礎。

同步與異步

  • 同步:發起一個調用後,被調用者未處理完請求以前,調用不返回。
  • 異步:發起一個調用後,馬上獲得被調用者的迴應表示已接收到請求,可是被調用者並無返回結果,此時咱們能夠處理其餘的請求,被調用者一般依靠事件,回調等機制來通知調用者其返回結果。

同步和異步的區別最大在於異步的話調用者不須要等待處理結果,被調用者會經過回調等機制來通知調用者其返回結果。html

阻塞和非阻塞

  • 阻塞:發起一個請求,調用者一直等待請求結果返回,也就是當前線程會被掛起,沒法從事其餘任務,只有當條件就緒才能繼續。
  • 非阻塞:發起一個請求,調用者不用一直等着結果返回,能夠先去幹其餘事情。

舉個生活中簡單的例子,你媽媽讓你燒水,小時候你比較笨啊,在那裏傻等着水開(同步阻塞)。java

等你稍微再長大一點,你知道每次燒水的空隙能夠去幹點其餘事,而後只須要時不時來看看水開了沒有(同步非阻塞)。git

後來,大家家用上了水開了會發出聲音的壺,這樣你就只須要聽到響聲後就知道水開了,在這期間你能夠隨便幹本身的事情,你須要去倒水了(異步非阻塞)。github

 

1、Buffer(緩衝區)

在 Java NIO 中負責數據的存取。緩衝區就是數組。用於存儲不一樣數據類型的數據。數組

/*
 * 根據數據類型不一樣(boolean 除外),提供了相應類型的緩衝區:
 * ByteBuffer
 * CharBuffer
 * ShortBuffer
 * IntBuffer
 * LongBuffer
 * FloatBuffer
 * DoubleBuffer
 *
 * 上述緩衝區的管理方式幾乎一致,經過 allocate() 獲取緩衝區
 */

1.基本屬性

  • 容量(capacity) :表示 Buffer 最大數據容量,緩衝區容量不能爲負,而且建立後不能更改。
  • 界限(limit):第一個不該該讀取或寫入的數據的索引,即位於 limit 後的數據不可讀寫。緩衝區的限制不能爲負,而且不能大於其容量。
  • 位置(position):下一個要讀取或寫入的數據的索引。緩衝區的位置不能爲負,而且不能大於 limit。
  • 標記(mark)與重置(reset):標記是一個索引,經過 Buffer 中的 mark() 方法指定 Buffer 中一個特定的 position,以後能夠經過調用 reset() 方法恢復到這個 position。
  • 標記、位置、限制、容量遵照如下不變式:0 <= mark <= position <= limit <= capacity

2.經常使用方法

Buffer 全部子類提供了兩個用於數據操做的方法:get() 與 put() 
獲取 Buffer 中的數據網絡

  • get() :讀取單個字節
  • get(byte[] dst):批量讀取多個字節到 dst 中
  • get(int index):讀取指定索引位置的字節(不會移動 position)

放入數據到 Buffer 中app

  • put(byte b):將給定單個字節寫入緩衝區的當前位置
  • put(byte[] src):將 src 中的字節寫入緩衝區的當前位置
  • put(int index, byte b):將指定字節寫入緩衝區的索引位置(不會移動 position)

其它方法dom

  • Buffer clear():清空緩衝區並返回對緩衝區的引用,不會真正的刪除掉 buffer 中的數據,只是把 position 移動到 0,同時把 limit 調整爲 capacity,marks 置爲 -1。
  • Buffer flip():將緩衝區的 limit 設置爲 position,並將 position 置爲 0,marks 置爲 -1。

3.直接緩衝區與非直接緩衝區

// 分配緩衝區:JVM 內存中
ByteBuffer buf = ByteBuffer.allocate(1024);
// 分配直接緩衝區:本地內存中
ByteBuffer bufDirect = ByteBuffer.allocateDirect(1024);
// 是否爲直接緩衝區
System.out.println(buf.isDirect());

// 直接字節緩衝區還能夠經過 FileChannel 的 map() 方法將文件區域直接映射到內存中來建立。該方法返回 MappedByteBuffer。

非直接緩衝區異步

直接緩衝區ide

4.簡單使用

import org.junit.Test;

import java.nio.ByteBuffer;

public class TestBuffer {
    @Test
    public void markAndReset() {
        String str = "abcde14693090";
        // 分配直接緩衝區
        ByteBuffer buf = ByteBuffer.allocateDirect(1024);

        // 存入數據
        buf.put(str.getBytes());
        // 切換到讀取模式
        buf.flip();

        byte[] dst = new byte[buf.limit()];
        buf.get(dst, 0, 2);
        System.out.println(new String(dst, 0, 2));
        System.out.println(buf.position());

        // mark() : 標記
        buf.mark();

        buf.get(dst, 2, 2);
        System.out.println(new String(dst, 2, 2));
        System.out.println(buf.position());

        // reset() : 恢復到 mark 的位置
        buf.reset();
        System.out.println(buf.position());

        // 判斷緩衝區中是否還有剩餘數據
        if (buf.hasRemaining()) {
            // 獲取緩衝區中能夠操做的數量
            System.out.println(buf.remaining());
        }
    }

    @Test
    public void getAndPut() {
        String str = "abcde";

        //1. 分配一個指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);

        System.out.println("allocate():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //2. 利用 put() 存入數據到緩衝區中
        buf.put(str.getBytes());

        System.out.println("put():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //3. 切換讀取數據模式
        buf.flip();

        System.out.println("flip():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //4. 利用 get() 讀取緩衝區中的數據
        byte[] dst = new byte[buf.limit()];
        buf.get(dst);
        System.out.println(new String(dst, 0, dst.length));

        System.out.println("get():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //5. rewind() : 可重複讀
        buf.rewind();

        System.out.println("rewind():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //6. clear() : 清空緩衝區. 可是緩衝區中的數據依然存在,只是處於「被遺忘」狀態
        buf.clear();

        System.out.println("clear():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        // 獲取單個字符
        System.out.println((char) buf.get());
    }
}
View Code

 

2、通道(Channel)

表示 IO 源與目標節點打開的鏈接,在 Java NIO 中負責緩衝區中數據的傳輸,相似於傳統的「流」。只不過 Channel 自己不能直接訪問數據,只能與 Buffer 進行交互。

/*
 * java.nio.channels.Channel 接口的主要實現類:
 *         |--FileChannel:用於讀取、寫入、映射和操做文件的通道。
 *         |--SocketChannel:經過 TCP 讀寫網絡中的數據。
 *         |--DatagramChannel:經過 UDP 讀寫網絡中的數據通道。
 *         |--ServerSocketChannel:能夠監聽新進來的 TCP 鏈接,對每個新進來的鏈接都會建立一個 SocketChannel。
 */

1.獲取通道

/*
 * 獲取通道的一種方式是對支持通道的對象調用 getChannel() 方法。
 * 支持通道的類以下:
 * 本地 IO:
 * |--FileInputStream
 * |--FileOutputStream
 * |--RandomAccessFile
 * 網絡 IO:
 * |--Socket
 * |--ServerSocket
 * |--DatagramSocket
 *
 * 在 JDK 1.7 中, NIO.2 針對各個通道的實現類提供了靜態方法 open() 來獲取通道。
 * 在 JDK 1.7 中, NIO.2 的 Files 工具類的靜態方法 newByteChannel() 也能夠獲取通道。
 */

2.通道數據傳輸

/*
 * 將 Buffer 中數據寫入 Channel
 * int bytesWritten = inChannel,write(buf)
 * 從 Channel 讀取數據到 Buffer
 * int bytesRead = inChannel.read(buf)
 * 
 * Channel 之間的數據傳輸(將數據從源通道傳輸到其餘 Channel 中)
 * transferFrom()
 * transferTo()
 */

複製文件的幾種方式

// 通道之間的數據傳輸(直接緩衝區)
@Test
public void channelCopy() throws IOException {
    FileChannel inChannel = FileChannel.open(Paths.get("D:/123.txt"), StandardOpenOption.READ);
    FileChannel outChannel = FileChannel.open(Paths.get("D:/456.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

    // inChannel.transferTo(0, inChannel.size(), outChannel);
    outChannel.transferFrom(inChannel, 0, inChannel.size());

    inChannel.close();
    outChannel.close();
}

// 使用直接緩衝區完成文件的複製(內存映射文件)
@Test
public void byteBuffCopy() {
    long start = System.currentTimeMillis();

    try (FileChannel inChannel = FileChannel.open(Paths.get("D:/123.txt"), StandardOpenOption.READ);
         FileChannel outChannel = FileChannel.open(Paths.get("D:/456.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE)) {

        // 內存映射文件
        MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());

        // 直接對緩衝區進行數據的讀寫操做
        byte[] dst = new byte[inMappedBuf.limit()];
        inMappedBuf.get(dst);
        outMappedBuf.put(dst);
    } catch (IOException e) {
        e.printStackTrace();
    }

    long end = System.currentTimeMillis();
    System.out.println("耗費時間爲:" + (end - start));
}

// 利用通道完成文件的複製(非直接緩衝區)
@Test
public void fileCopy() {
    long start = System.currentTimeMillis();

    try (FileInputStream fis = new FileInputStream("D:/123.txt");
         FileOutputStream fos = new FileOutputStream("D:/456.txt");
         // 獲取通道
         FileChannel inChannel = fis.getChannel();
         FileChannel outChannel = fos.getChannel()) {

        // 分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);

        // 將通道中的數據存入緩衝區中
        while (inChannel.read(buf) != -1) {
            // 切換讀取數據的模式
            buf.flip();
            // 將緩衝區中的數據寫入通道中
            outChannel.write(buf);
            // 清空緩衝區
            buf.clear();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    long end = System.currentTimeMillis();
    System.out.println("Time:" + (end - start));
}
View Code

3.分散(Scatter)與彙集(Gather)

分散讀取(Scattering Reads):從 Channel 中讀取的數據「分散」到多個 Buffer 中。(按照緩衝區的順序,從 Channel 中讀取的數據依次將 Buffer 填滿。)

彙集寫入(Gathering Writes):將多個 Buffer 中的數據「彙集」到 Channel。(按照緩衝區的順序,寫入 position 和 limit 之間的數據到 Channel 。)

使用分散和彙集來複制文件部份內容

// 分散和彙集
@Test
public void scatterAndGather() throws IOException {
    RandomAccessFile raf1 = new RandomAccessFile("D:/123.txt", "rw");

    // 獲取通道
    FileChannel channel1 = raf1.getChannel();
    // 分配指定大小的緩衝區
    ByteBuffer buf1 = ByteBuffer.allocate(100);
    ByteBuffer buf2 = ByteBuffer.allocate(1024);
    // 分散讀取
    ByteBuffer[] bufs = {buf1, buf2};
    channel1.read(bufs);

    // 轉換模式
    for (ByteBuffer byteBuffer : bufs) {
        byteBuffer.flip();
    }

    System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
    System.out.println("-----------------");
    System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));

    // 彙集寫入
    RandomAccessFile raf2 = new RandomAccessFile("D:/456.txt", "rw");
    FileChannel channel2 = raf2.getChannel();
    channel2.write(bufs);
}
View Code

4.字符集

/*
 * 字符集:Charset
 * 編碼:字符串 -> 字節數組
 * 解碼:字節數組  -> 字符串
 */
@Test
public void testCharset() throws IOException {
    // 指定字符集
    Charset cs1 = Charset.forName("GBK");

    CharBuffer cBuf = CharBuffer.allocate(1024);
    cBuf.put("字符集");

    cBuf.flip();
    //獲取編碼器
    CharsetEncoder ce = cs1.newEncoder();
    //編碼
    ByteBuffer bBuf = ce.encode(cBuf);
    System.out.println(Arrays.toString(bBuf.array()));


    bBuf.flip();
    //獲取解碼器
    CharsetDecoder cd = cs1.newDecoder();
    //解碼
    cBuf = cd.decode(bBuf);
    System.out.println(cBuf.toString());
}

// Java 支持的字符集
@Test
public void getCharset() {
    Map<String, Charset> map = Charset.availableCharsets();
    for (Entry<String, Charset> entry : map.entrySet()) {
        System.out.println(entry.getKey() + "=" + entry.getValue());
    }
}

 


https://snailclimb.gitee.io/javaguide/#/java/BIO-NIO-AIO

https://snailclimb.gitee.io/javaguide/#/java/Java%20IO%E4%B8%8ENIO

https://cyc2018.github.io/CS-Notes/#/notes/Java%20IO?id=%e4%b8%83%e3%80%81nio

http://www.javashuo.com/article/p-gdlhnnih-mk.html

https://ifeve.com/java-nio-all/

相關文章
相關標籤/搜索