javaNIO 學習筆記(三)

javaNIO 學習筆記(三)

Java NIO Buffer

緩衝區,可以通過channel將數據寫入緩衝區,也可以從通道中讀取數據到緩衝區

緩衝區本質上是一個內存塊,您可以將數據寫入其中,然後再讀取數據。這個內存塊包裝在一個NIO緩衝區對象中,該對象提供了一組方法,使使用內存塊變得更容易。

使用緩衝區讀取和寫數據一般分爲下面四個步驟:

  • 將數據寫入緩衝區
  • 調用 flip()方法
  • 從緩衝區讀取數據
  • 調用clear()或者compact()

當數據寫入數據到緩衝區時,緩衝區會記錄對應數據量的信息(這個需要看緩衝區大小和數據量大小)。

數據讀取後需要使用flip()方法切換緩衝區的讀寫模式(換個角度可以理解爲存取模式)。在讀取模式下,緩可以通過相應的方法將數據從緩衝區中讀取出來。讀取完數據後需要將緩衝區清空。這樣緩衝區就會再次進入寫入模式。(這裏調用的方式就是clearcompact, clear是清空, compact則是清理掉已讀取的數據,未讀取的數據則放入緩衝區頭)

使用的例子上一篇學習筆記已經寫了。

我們來看下buffer的工作模式。buffer有三個主要屬性

  • capacity(buffer容量):這是buffer的大小即容量。在調用buffer.allocate()指定大小即是這個屬性

  • position(讀取模式的位置,在讀寫模式中表現不一樣)

  • limit(讀寫模式中表現也不一樣)

在寫入模式下,position初始值爲0.當一個字節,沒寫入一個字節數字都會將position+1.limit初始值爲capacity。在讀取模式下,會將limit位置設置在```position位置放置爲0,此時讀取數據是從position到limit。看下clearflip的源碼大概就可以理解了

public final Buffer clear() {        
    position = 0; //設置當前下標爲0        
    limit = capacity; //設置寫越界位置與和Buffer容量相同         
    mark = -1; //取消標記         
    return this; 
} 


public final Buffer flip() { 
        limit = position; 
        position = 0; 
        mark = -1; 
        return this; 
} 

buffer的主要類型如下:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
  • MappedByteBuffer

可以看到,這些緩衝區類型表示不同的數據類型。換句話說,它們允許您將緩衝區中的字節改爲char、short、int、long、float或double。

學習下buffer的幾個常見方法:

// 可以通過調用Buffer.mark()方法標記緩衝區中的給定位置。然後,您可以通過調用Buffer.reset()方法將位置重置回標記的位置。
public final Buffer mark() {
    mark = position;
    return this;
}

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}
  • equals()和compareTo ()

    可以使用equals()和compareTo()比較兩個緩衝區。

equals ()

兩個緩衝區是相等的,如果:

它們是相同類型的(字節、char、int等)。

它們在緩衝區中有相同數量的剩餘字節、字符等。

所有剩餘的字節、字符等都是相等的。

可以看到,equals只比較緩衝區的一部分,而不是其中的每個元素。實際上,它只是比較緩衝區中剩餘的元素。

compareTo ()

方法比較兩個緩衝區的剩餘元素(字節,字符等),用於排序例程。一個緩衝區被認爲比另一個緩衝區“小”,如果:

第一個元素等於另一個緩衝區中對應的元素,小於另一個緩衝區中的元素。

所有的元素都是相等的,但是第一個緩衝區比第二個緩衝區早耗盡元素(它的元素更少)。

Java NIO Scatter / Gather

Java NIO 提供了內置功能 Scatter/Gather。可以將數據從一個通道寫入多個緩衝區。通道也可以從多個緩衝區收集數據。在需要分別處理傳輸數據的各個部分的情況下,Scatter/Gather非常有用。例如,如果消息由消息頭和消息體組成,則可以將消息頭和消息體保存在單獨的緩衝區中。這樣做可以更容易分別使用標題和主體。

package jniolearn;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Author: jimmy
 * @Date: 2020/6/14 14:50
 * @Description:
 */
public class NioScatter {

    public static void main(String[] args) throws IOException {

        // 創建一個rw模式的隨機文件
        RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
        // 獲取fileChinnel
        FileChannel fileChannel = randomAccessFile.getChannel();

        // 1、分配緩衝區
        ByteBuffer header = ByteBuffer.allocate(10);
        ByteBuffer body  = ByteBuffer.allocate(128);

        // 緩衝數組
        ByteBuffer[] bufferArray = {header, body};
        // 2、將數組讀入到數組
        fileChannel.read(bufferArray);

        // Buffer切換模式之前,即處於寫模式下,打印Buffer,查看position, limit, capacity屬性
        System.out.println(header.toString());
        System.out.println(body.toString());

        // 3、切換模式
        header.flip();
        body.flip();

        //  4、獲取數組
        System.out.println("header:");
        while (header.hasRemaining()) {
            System.out.print((char) header.get());
        }

        System.out.println("\nbody:");
        while (body.hasRemaining()) {
            System.out.print((char) body.get());
        }

        header.clear();
        body.clear();

        fileChannel.close();

    }

}
// 返回結果
java.nio.HeapByteBuffer[pos=10 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=24 lim=128 cap=128]
header:
Hi, I am j
body:
immy;
be happy everyday

read按照buffer數組中的順序將從channel中讀取的數據寫入到buffer,當一個buffer被寫滿後,channel緊接着向另一個buffer中寫。Scattering Reads在移動下一個buffer前,必須填滿當前的buffer,這也意味着它不適用於動態消息。這樣若要是想在消息中使用那麼就必須規定好每一段的字節長度,並且按照這個規定嚴格執行,不能存在偏差。不然就會出現部分信息少讀或者多讀的情況。

package jniolearn;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Author: jimmy
 * @Date: 2020/6/14 15:07
 * @Description:
 */
public class NioGather {

    public static void main(String[] args) throws IOException {

        RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\gaterFile.txt", "rw");
        // 獲取fileChinnel
        FileChannel fileChannel = randomAccessFile.getChannel();

        // 1、分配緩衝區
        ByteBuffer header = ByteBuffer.allocate(32);
        ByteBuffer body  = ByteBuffer.allocate(128);

        // 2、放入數據
        header.put("hi, weather ?".getBytes());
        header.put("body:raining".getBytes());

        // 3、存入buffer數組
        ByteBuffer[] bufferArray = {header, body};

        System.out.println(header.toString());
        System.out.println(body.toString());

        header.flip();
        body.flip();

        fileChannel.write(bufferArray);

        fileChannel.close();
    }
}

查看文件信息:hi, weather ?body:raining

write方法同樣是按照buffer數組的順序,將數據寫入到channel,注意只有position和limit之間的數據纔會被寫入。因此,如果一個buffer的容量爲128byte,但是僅僅包含14byte的數據,那麼這14byte的數據將被寫入到channel中。和Scattering Reads相反,Gathering Writes能較好的處理動態消息。

Java NIO Channel to Channel Transfers

Java NIO還提供了一種通道間傳輸數據的方法,FileChannel類有一個transferTo()和一個transferFrom()方法

----  transferFrom   ----
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();

long position = 0;
long count    = fromChannel.size();

toChannel.transferFrom(fromChannel, position, count);

----  transferTo   ----
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();

long position = 0;
long count = fromChannel.size();

fromChannel.transferTo(position, count, toChannel);

小結下前面幾篇學習的內容:

  • 如何使用buffer

    • 需要先分配一個緩衝區(我這裏理解爲初始化一個指定大小的緩衝區)使用ByteBuffer.allocate()
    • 讀則是從channel read,寫則是先要將數據放入緩衝區,然偶使用channel write把數據寫入通道
    • 注意buffer的模式切換使用flip方法。
    • 另外還有 clear compact rewind等方法
  • buffer的幾個主要屬性

    • capacity 緩衝區大小(比如緩衝區長度48,則capacity也是48.但是這裏要注意capacity下表是從0開始,所以這個可以理解爲內存溢出位)
    • position 在讀模式的時候初始值爲0,讀數據的時候會逐步增加最大能讀到capacity -1。切換到寫模式的時候position爲0
    • limit 讀模式的時候默認爲capacity。寫模式的時候,會將limit設置爲讀模式的position的位置。
  • scattergather

    • 可以理解爲分散收集,一個是讀,一個是寫。這個是內置的。就是一個channel和多個buffer的讀寫。
  • channel如何獲取

    • 可以直接使用open方法,fileChannel則還可以通過類RandomAccessFile的實例getChannel

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章