Netty 源碼分析 —— NIO 基礎(chǔ)(三)之 Buffer


我準(zhǔn)備戰(zhàn)斗到最后敌蜂,不是因?yàn)槲矣赂遥俏蚁胍娮C一切津肛。 --雙雪濤《獵人》

[TOC]
Thinking

  1. 一個(gè)技術(shù)章喉,為什么要用它,解決了那些問題?
  2. 如果不用會(huì)怎么樣秸脱,有沒有其它的解決方法落包?
  3. 對(duì)比其它的解決方案,為什么最終選擇了這種摊唇,都有何利弊咐蝇?
  4. 你覺得項(xiàng)目中還有那些地方可以用到,如果用了會(huì)帶來那些問題巷查?
  5. 這些問題你又如何去解決的呢有序?

本文基于Netty 4.1.45.Final-SNAPSHOT

1、概述(Buffer 緩沖區(qū))

? java.nio.Buffer岛请,一個(gè)Buffer對(duì)象是固定數(shù)量數(shù)據(jù)容器旭寿。實(shí)質(zhì)上是內(nèi)存中的一塊。我們可以向這塊內(nèi)存存取數(shù)據(jù)崇败。

? 緩沖區(qū)的工作與通道精密聯(lián)系的盅称。通道時(shí)I/O傳輸發(fā)生時(shí)通過的入口。而緩沖區(qū)是這些數(shù)據(jù)傳輸?shù)膩碓椿蚰繕?biāo)后室。(可寫可讀的雙向性缩膝。)

? 緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存岸霹。這塊內(nèi)存被包裝成NIO Buffer對(duì)象逞盆。并提供了一組方法。用于方便當(dāng)問該區(qū)域松申。

類圖:
Buffer提供的包裝對(duì)象.png
  • 其實(shí)可以將Buffer理解為一個(gè)數(shù)組的封裝云芦。例如:IntBuffer —>int[]
  • MappedByteBuffer 用于實(shí)現(xiàn)內(nèi)存映射文件。

2贸桶、Buffer的基礎(chǔ)使用

  1. 寫入數(shù)據(jù)到Buffer
  2. 調(diào)用flip()方法
  3. 從Buffer中讀取數(shù)據(jù)
  4. 調(diào)用clear()方法或者compact()方法

當(dāng)向buffer寫入數(shù)據(jù)時(shí)舅逸,buffer會(huì)記錄下寫了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù)皇筛,需要通過flip()方法將Buffer從寫模式切換到讀模式琉历。在讀模式下,可以讀取之前寫入到buffer的所有數(shù)據(jù)水醋。

一旦讀完了所有的數(shù)據(jù)旗笔,就需要清空緩沖區(qū),讓它可以再次被寫入拄踪。有兩種方式能清空緩沖區(qū):調(diào)用clear()或compact()方法蝇恶。clear()方法會(huì)清空整個(gè)緩沖區(qū)。compact()方法只會(huì)清除已經(jīng)讀過的數(shù)據(jù)惶桐。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處撮弧,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面潘懊。(這種定義可以理解為,緩沖區(qū)本身維護(hù)著一個(gè)類似于隊(duì)列的數(shù)據(jù)結(jié)構(gòu)贿衍,先進(jìn)先出原則)

/**
 * Buffer 基本使用
 *
 * @author by Mr. Li
 * @date 2020/2/11 12:23
 */
@Slf4j
public class BufferExample {

    public static void main(String[] args) throws Exception {
        RandomAccessFile accessFile = new RandomAccessFile("E:/idea_workspace/springcloud2.0/netty/netty-mytest/src/main/resources/data/nio-data.txt", "rw");
        // 獲取 NIO 文件通道
        FileChannel channel = accessFile.getChannel();
        // 獲取緩沖區(qū)Buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(48);
        int count = channel.read(byteBuffer);// read into buffer.
        while (count != -1) {
            // make buffer ready for read
            byteBuffer.flip();

            while (byteBuffer.hasRemaining()) {
                log.info("read 1 byte at a time : {}", byteBuffer.get());
            }
            byteBuffer.clear(); // make buffer ready for writing .即清空緩沖區(qū)授舟,是Buffer又可以讀寫數(shù)據(jù)
            count = channel.read(byteBuffer);
        }
        // 關(guān)閉文件
        accessFile.close();
    }
}

3、Buffer 屬性

? Buffer緩沖區(qū)中提供了四個(gè)屬性贸辈。用于來提供其包含的數(shù)據(jù)元素的信息释树。

容量(Capacity)

? 緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量。這一容量在緩沖區(qū)被初始化時(shí)擎淤,就會(huì)被創(chuàng)建躏哩,并且永遠(yuǎn)不會(huì)改變。

上界(Limit)

? 緩沖區(qū)的第一個(gè)不能被讀或?qū)懙脑厝嗳肌#梢岳斫鉃榫彌_區(qū)中現(xiàn)有可讀的數(shù)據(jù))

位置(Position)

? 下一個(gè)要被讀或?qū)懙脑氐乃饕ǔ摺#ㄔ搶傩员?code>#get()``#put()自動(dòng)維護(hù))

標(biāo)記(Mark)

? 一個(gè)備忘位置。調(diào)用#mark()來設(shè)定mark = position炊汤。調(diào)用#reset()設(shè)定position = mark正驻。標(biāo)記在設(shè)定前是未定義的(undefined)

源碼

public abstract class Buffer {

    /**
     * The characteristics of Spliterators that traverse and split elements
     * maintained in Buffers.
     */
    static final int SPLITERATOR_CHARACTERISTICS =
        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

    // Used only by direct buffers
    // 僅由直接緩沖區(qū)使用
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;

    // Creates a new buffer with the given mark, position, limit, and capacity,
    // after checking invariants.
    //
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
//....doSomething

從源碼可以看出四種屬性的關(guān)系:

mark <= position <= limit <= capacity

0 <= mark <= position <= limit <= capacity

Buffer四種屬性圖解

? Buffer緩沖區(qū)是分讀寫模式的抢腐。(使用filp() 切換讀寫模式)

porsition和limit的含義取決于Buffer處在讀模式還是寫模式姑曙。不管Buffer處在什么模式,capacity的含義總是一樣的迈倍。

寫模式

寫模式.png
  • 假設(shè)定義的ByteBuffer對(duì)象伤靠。(長(zhǎng)度為10 的緩沖區(qū)長(zhǎng)度 ByteBuffer.allocate(10);
  • 此時(shí)創(chuàng)建出來的Buffer對(duì)象 Limit 和capacity為指向內(nèi)存末端,數(shù)值為9啼染。

代碼示例如下:

    /**
     *  Buffer 讀寫模式切換即 屬性賦值變化宴合。
     * @throws Exception
     */
    @Test
    public void bufferTest() throws Exception {
        // 初始化 長(zhǎng)度為10 的Buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        RandomAccessFile accessFile = new RandomAccessFile("E:/idea_workspace/springcloud2.0/netty/netty-mytest/src/main/resources/data/buf-data.txt", "rw");

        // 獲取 文件通道
        FileChannel channel = accessFile.getChannel();

        // 將通道的中的數(shù)據(jù)讀取到緩沖區(qū)中
        int i = channel.read(byteBuffer);
        while (i != -1) {
            // 切換 成讀模式
            byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                log.info("write -> read {}", byteBuffer.get());
            }
            byteBuffer.clear();
            // 確保 數(shù)據(jù)讀取完畢。返回-1
            i = channel.read(byteBuffer);
        }
        accessFile.close();
    }

Debug查看屬性變化:

屬性賦值過程.gif

讀模式

讀模式.png

總結(jié)一下

  • 從上述讀寫切換可以看出
    • 此內(nèi)存區(qū)域?qū)崉t可以看作是一塊數(shù)組內(nèi)存區(qū)域迹鹅。在初始化時(shí)卦洽,capacity是被固定的,用來標(biāo)識(shí)內(nèi)存的容量斜棚,可存放的最大元素?cái)?shù)阀蒂。并且不管讀寫如何切換,capacity是不會(huì)改變的弟蚀。
  • Limit 在讀寫模式下的含義(上限)
    • 寫模式:即在緩存被初始化時(shí)(ByteBuffer.allocate(10);)Limit就指向了內(nèi)存的最大容量與capacity相等蚤霞。
    • 讀模式:當(dāng)調(diào)用#filp()切換為讀模式后,limit就會(huì)指向內(nèi)存中的實(shí)際容量义钉,此時(shí)position就會(huì)指向下一個(gè)內(nèi)存中即將被讀的索引位置昧绣。
  • position 永遠(yuǎn)指向下一個(gè)即將被寫/讀的索引位置。初始值為0断医。
    • 模式下滞乙,每往 Buffer 中寫入一個(gè)值奏纪,position 就自動(dòng)加 1 鉴嗤,代表下一次的寫入位置斩启。
    • 模式下,每從 Buffer 中讀取一個(gè)值醉锅,position 就自動(dòng)加 1 兔簇,代表下一次的讀取位置。( 和寫模式類似 )
  • mark 標(biāo)記硬耍,通過#mark()方法垄琐,記錄當(dāng)前position;通過#reset()恢復(fù)position為標(biāo)記经柴。
    • 模式下狸窘,標(biāo)記上一次寫位置。
    • 模式下坯认,標(biāo)記上一次讀位置翻擒。
  • 所以從代碼層次上充分的體現(xiàn)了四種屬性的關(guān)系
    • limit最大只能與 capacity相等。
      • 最小則是緩沖區(qū)為空牛哺。
    • position則是四大屬性中陋气,最善變的一個(gè),一直隨將要讀寫的操作變化著引润。
    • mark 則永遠(yuǎn)都不會(huì)超過position巩趁。并且初始化下的值為-1.

mark <= positioon <= limit <= capacity

思考

? 在這種反轉(zhuǎn)切換讀寫的模式下,真的非常的繁瑣淳附。操作復(fù)雜议慰。完全可以使用獨(dú)立的標(biāo)記,用于標(biāo)記讀寫不同的操作奴曙。類似于Netty使用的模式褒脯,就是摒棄了這種反轉(zhuǎn)的操作。

? Netty的ByteBuf下優(yōu)雅的設(shè)計(jì)

0 <= readerIndex <= writerIndex <= capacity

4缆毁、創(chuàng)建Buffer

? 1. 每個(gè)Buffer實(shí)現(xiàn)類番川,都相應(yīng)的提供了#allocate(int capacity)靜態(tài)方法。

    /**
     * Allocates a new byte buffer. Allocates a new direct byte buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  It will have a {@link #array backing array},
     * and its {@link #arrayOffset array offset} will be zero.
     *
     * @param  capacity
     *         The new buffer's capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

由于ByteBuffer 為抽象類脊框,返回的是它基于堆內(nèi)(Non-Direct)內(nèi)存的實(shí)現(xiàn)類HeapByteBuffer的對(duì)象颁督。

    HeapByteBuffer(int cap, int lim) {            // package-private

        super(-1, 0, lim, cap, new byte[cap], 0);
        /*
        hb = new byte[cap];
        offset = 0;
        */
    }
// 這里可以清晰的看出,Buffer 其實(shí)質(zhì)就是一個(gè)內(nèi)存數(shù)組浇雹。用于存儲(chǔ)數(shù)據(jù)
  1. 每個(gè) Buffer 實(shí)現(xiàn)類沉御,都提供了 #wrap(array) 靜態(tài)方法,幫助我們將其對(duì)應(yīng)的數(shù)組包裝成一個(gè) Buffer 對(duì)象昭灵。還是以 ByteBuffer 舉例子吠裆,代碼如下:
// ByteBuffer.java   Allocates a new direct byte buffer.
public static ByteBuffer wrap(byte[] array,int offset, int length){
       try {
           return new HeapByteBuffer(array, offset, length);
       } catch (IllegalArgumentException x) {
           throw new IndexOutOfBoundsException();
       }
   }

   public static ByteBuffer wrap(byte[] array) {
       return wrap(array, 0, array.length);
   }
  • #allocate(int capacity) 靜態(tài)方法一樣伐谈,返回的也是 HeapByteBuffer 的對(duì)象。

為什么Buffer要?jiǎng)?chuàng)建為 Direct Buffer

Direct Buffer 與Non-Direct Buffer 的區(qū)別

FROM 《Java NIO 的前生今世 之三 NIO Buffer 詳解》

Direct Buffer:

  • 所分配的內(nèi)存不在 JVM 堆上, 不受 GC 的管理.(但是 Direct Buffer 的 Java 對(duì)象是由 GC 管理的, 因此當(dāng)發(fā)生 GC, 對(duì)象被回收時(shí), Direct Buffer 也會(huì)被釋放)
  • 因?yàn)?Direct Buffer 不在 JVM 堆上分配, 因此 Direct Buffer 對(duì)應(yīng)用程序的內(nèi)存占用的影響就不那么明顯(實(shí)際上還是占用了這么多內(nèi)存, 但是 JVM 不好統(tǒng)計(jì)到非 JVM 管理的內(nèi)存.)
  • 申請(qǐng)和釋放 Direct Buffer 的開銷比較大. 因此正確的使用 Direct Buffer 的方式是在初始化時(shí)申請(qǐng)一個(gè) Buffer, 然后不斷復(fù)用此 buffer, 在程序結(jié)束后才釋放此 buffer.
  • 使用 Direct Buffer 時(shí), 當(dāng)進(jìn)行一些底層的系統(tǒng) IO 操作時(shí), 效率會(huì)比較高, 因?yàn)榇藭r(shí) JVM 不需要拷貝 buffer 中的內(nèi)存到中間臨時(shí)緩沖區(qū)中.

Non-Direct Buffer:

  • 直接在 JVM 堆上進(jìn)行內(nèi)存的分配, 本質(zhì)上是 byte[] 數(shù)組的封裝.
  • 因?yàn)?Non-Direct Buffer 在 JVM 堆中, 因此當(dāng)進(jìn)行操作系統(tǒng)底層 IO 操作中時(shí), 會(huì)將此 buffer 的內(nèi)存復(fù)制到中間臨時(shí)緩沖區(qū)中. 因此 Non-Direct Buffer 的效率就較低.

總結(jié)對(duì)比:

  • 之所以使用堆外內(nèi)存试疙,是為了避免每次使用buffe如對(duì)象時(shí)诵棵,都會(huì)將此對(duì)象復(fù)制到中間林是緩沖區(qū)中,因此Non-Direct Buffer效率會(huì)非常低下祝旷。
  • 堆外內(nèi)存(直接內(nèi)存--direct byte buffer)則可以直接使用履澳,避免了對(duì)象的復(fù)制,提高了效率怀跛。

5距贷、寫入數(shù)據(jù)

? 每個(gè)Buffer的實(shí)現(xiàn)類,都可以基于Channel來寫入數(shù)據(jù)吻谋,但每個(gè)實(shí)現(xiàn)類也單獨(dú)的提供了寫入數(shù)據(jù)的方法#put(...)忠蝗。向Buffer寫入數(shù)據(jù),以ByteBuffer舉例子漓拾。

    /**
     * buffer 的 讀寫 操作
     */
    @Test
    public void bufferTest02() throws Exception {
        log.info("Write is coming.this is two method");
        // Buffer 的寫 有兩種阁最,一種基于 Channel 一種是自己提供的#put()方法
        ByteBuffer byteBuffer = ByteBuffer.allocate(48);
        byteBuffer.put((byte) 1); // position = 1

        // 獲取 文件Channel
        RandomAccessFile accessFile = new RandomAccessFile("E:/idea_workspace/springcloud2.0/netty/netty-mytest/src/main/resources/data/buf-data.txt", "rw");
        // 將Channel 中的數(shù)據(jù)寫入Buffer 中
        FileChannel channel = accessFile.getChannel();
        int readResult = channel.read(byteBuffer);// position = 4

        log.info("Read is coming。晦攒。闽撤。。");
        while (readResult != -1) {
            // Write -> Read
            byteBuffer.flip();
            /**
             *     public final boolean hasRemaining() {
             *         return position < limit;
             *     }
             *     用來判斷 隊(duì)首是否還有未讀數(shù)據(jù)脯颜。
             */
            while (byteBuffer.hasRemaining()) {
                log.info("byteBuffer read ready -> {}", byteBuffer.get());
            }

            byteBuffer.clear(); // 清除/并且重置到初始化狀態(tài)哟旗。寫狀態(tài)
            readResult = channel.read(byteBuffer);
        }
        accessFile.close();
    }
}

上述代碼,提供了兩種向Buffer 寫入數(shù)據(jù)的方式栋操。

兩種方式闸餐,分別的豐富了Buffer 的實(shí)用性。Channle則是滿足了來自各個(gè)網(wǎng)絡(luò)或文件等外部資源的輸入矾芙。

int num = channel.read(buffer);
// 返回的是從Channel中寫入到Buffer的數(shù)據(jù)大小

這里需要注意的是:在NIO 中讀Buffer 的讀寫操作舍沙。

? 當(dāng)Channel調(diào)用#read()方法時(shí),實(shí)則是Buffer示例的寫入操作剔宪。(需要重點(diǎn)明確拂铡。)

? 因?yàn)樵谶@里所謂的讀寫操作都是先對(duì)于Buffer示例而言的。所以不要看到調(diào)用的方法是read()葱绒。實(shí)則是寫入操作感帅。與Buffer而言。

6地淀、讀取數(shù)據(jù)

? 每個(gè)Buffer實(shí)現(xiàn)類失球,都提供了#get(...)方法,從Buffer讀取數(shù)據(jù)帮毁。

// 讀取 byte
public abstract byte get();
public abstract byte get(int index);
// 讀取 byte 數(shù)組
public ByteBuffer get(byte[] dst, int offset, int length) {...}
public ByteBuffer get(byte[] dst) {...}
// ... 省略实苞,還有其他 get 方法

對(duì)于 Buffer 來說豺撑,還有一個(gè)非常重要的操作就是,我們要講來向 Channel 的寫入 Buffer 中的數(shù)據(jù)黔牵。在系統(tǒng)層面上聪轿,這個(gè)操作我們稱為寫操作,因?yàn)閿?shù)據(jù)是從內(nèi)存中寫入到外部( 文件或者網(wǎng)絡(luò)等 )荧止。示例如下:

int num = channel.write(buffer);
  • 上述方法會(huì)返回向 Channel 中寫入 Buffer 的數(shù)據(jù)大小屹电。對(duì)應(yīng)方法的代碼如下:

    public interface WritableByteChannel extends Channel {
    
        public int write(ByteBuffer src) throws IOException;
        
    }
    

7阶剑、rewind() / flip()/clear()

7.1跃巡、filp

? #filp()將Buffer寫狀態(tài)切換成度狀態(tài)。

    /**
     * Flips this buffer.  The limit is set to the current position and then
     * the position is set to zero.  If the mark is defined then it is
     * discarded.
     反轉(zhuǎn)整個(gè)緩沖區(qū)牧愁,將limit設(shè)置為當(dāng)前的position(即為當(dāng)前緩沖區(qū)的實(shí)際數(shù)據(jù)容量)素邪,然后將position設(shè)置為0,如果使用了標(biāo)記mark猪半,將其拋棄兔朦,設(shè)置為初始值-1。
     *
     * <p> After a sequence of channel-read or <i>put</i> operations, invoke
     * this method to prepare for a sequence of channel-write or relative
     * <i>get</i> operations.  For example:
     *
     * <blockquote><pre>
     * buf.put(magic);    // Prepend header
     * in.read(buf);      // Read data into rest of buffer
     * buf.flip();        // Flip buffer
     * out.write(buf);    // Write header + data to channel</pre></blockquote>
     *
     * <p> This method is often used in conjunction with the {@link
     * java.nio.ByteBuffer#compact compact} method when transferring data from
     * one place to another.  </p>
     *
     * @return  This buffer
     */
    public final Buffer flip() {
        limit = position; // 設(shè)置讀取上限
        position = 0; // 重置position
        mark = -1; // 清空mark
        return this;
    }

示例代碼磨确,如下:

    @Test
    public void flipTest() throws Exception {
        RandomAccessFile accessFile = new RandomAccessFile("E:/idea_workspace/springcloud2.0/netty/netty-mytest/src/main/resources/data/buf-data.txt", "rw");
        FileChannel channel = accessFile.getChannel();
        ByteBuffer allocate = ByteBuffer.allocate(48);
        allocate.put("magic".getBytes());
        allocate.flip();
        channel.write(allocate);
        log.info("channel 當(dāng)前容量為{}",channel.position());

    }

7.2沽甥、rewind

? #rewind() 方法,可以重置 position 的值為 0 乏奥。因此摆舟,我們可以重新讀取和寫入 Buffer 了。

大多數(shù)情況下邓了,該方法主要針對(duì)于讀模式恨诱,所以可以翻譯為“倒帶”。也就是說骗炉,和我們當(dāng)年的磁帶倒回去是一個(gè)意思照宝。(意思就是璃哟,重置緩沖區(qū)中的數(shù)據(jù)斑匪。)代碼如下:

public final Buffer rewind() {
    position = 0; // 重置 position
    mark = -1; // 清空 mark
    return this;
}

從代碼上,和 #flip() 相比嬉愧,非常類似乍丈,除了少了第一行的 limit = position 的代碼塊剂碴。

使用示例,代碼如下:

channel.write(buf);    // Write remaining data
buf.rewind();      // Rewind buffer
buf.get(array);    // Copy data into array

7.3诗赌、clear

? #clear() 方法汗茄,可以“重置” Buffer 的數(shù)據(jù)。因此铭若,我們可以重新讀取和寫入 Buffer 了洪碳。

大多數(shù)情況下递览,該方法主要針對(duì)于寫模式。代碼如下:

public final Buffer clear() {
    position = 0; // 重置 position
    limit = capacity; // 恢復(fù) limit 為 capacity
    mark = -1; // 清空 mark
    return this;
}
  • 從源碼上瞳腌,我們可以看出绞铃,Buffer 的數(shù)據(jù)實(shí)際并未清理掉,所以使用時(shí)需要注意嫂侍。
  • 讀模式下儿捧,盡量不要調(diào)用 #clear() 方法,因?yàn)?limit 可能會(huì)被錯(cuò)誤的賦值為 capacity 挑宠。相比來說菲盾,調(diào)用 #rewind() 更合理,如果有重讀的需求各淀。

使用示例懒鉴,代碼如下:

buf.clear();     // Prepare buffer for reading
in.read(buf);    // Read data

8、 mark() 搭配 reset()

8.1 mark

#mark() 方法碎浇,保存當(dāng)前的 positionmark 中临谱。代碼如下:

public final Buffer mark() {
    mark = position;
    return this;
}
  • 唯一的設(shè)置 標(biāo)記位的方法。

8.2 reset

#reset() 方法奴璃,恢復(fù)當(dāng)前的 postionmark 悉默。代碼如下:

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}
  • 恢復(fù)到標(biāo)記位,有類似于秒表的功能苟穆,撤回到標(biāo)記位抄课。并且可重新設(shè)置標(biāo)記位后面的值。

9 鞭缭、hasRemaining

? 多半使用在讀操作中剖膳,用作判斷緩沖區(qū)中是否還有可讀數(shù)據(jù)。(可想而知:是直接判斷 下個(gè)即將要讀數(shù)據(jù)的索引位置與上限做比較岭辣。如果相等則沒有可讀數(shù)據(jù) return position < limit

    /**
     * Tells whether there are any elements between the current position and
     * the limit.
     *告訴當(dāng)前位置和上限之間是否有任何元素
     * @return  <tt>true</tt> if, and only if, there is at least one element
     *          remaining in this buffer
     */
    public final boolean hasRemaining() {
        return position < limit;
    }

10吱晒、equals()與compareTo()方法

10.1、equals()

? 當(dāng)滿足下列條件時(shí)沦童,表示兩個(gè)Buffer相等:

  1. 有相同的類型(byte仑濒、char、int 等)
  2. Buffer中剩余的Byte偷遗、char等的個(gè)數(shù)相同墩瞳。
  3. Buffer中所有剩余的byte、char等都相同氏豌。

如你所見喉酌,equals只是比較Buffer的一部分,不是每一個(gè)在它里面的元素都比較。實(shí)際上泪电,它只比較Buffer中的剩余元素般妙。

10.2、compareTo()

compareTo()方法比較兩個(gè)Buffer的剩余元素(byte相速、char等)碟渺, 如果滿足下列條件,則認(rèn)為一個(gè)Buffer“小于”另一個(gè)Buffer:

  1. 第一個(gè)不相等的元素小于另一個(gè)Buffer中對(duì)應(yīng)的元素 突诬。
  2. 所有元素都相等苫拍,但第一個(gè)Buffer比另一個(gè)先耗盡(第一個(gè)Buffer的元素個(gè)數(shù)比另一個(gè)少)。

(譯注:剩余元素是從 position到limit之間的元素)


其它源碼相對(duì)簡(jiǎn)單旺隙。不一一介紹了绒极。

本文僅供筆者本人學(xué)習(xí),有錯(cuò)誤的地方還望指出催束,一起進(jìn)步集峦!望海涵伏社!

Java NIO系列教程(三) Buffer

歡迎關(guān)注我的公共號(hào)抠刺,無廣告,不打擾摘昌。不定時(shí)更新Java后端知識(shí)速妖,我們一起超神。


qrcode.jpg

——努力努力再努力xLg

加油聪黎!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末罕容,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子稿饰,更是在濱河造成了極大的恐慌锦秒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喉镰,死亡現(xiàn)場(chǎng)離奇詭異旅择,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)侣姆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門生真,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捺宗,你說我怎么就攤上這事柱蟀。” “怎么了蚜厉?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵长已,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)术瓮,這世上最難降的妖魔是什么胶果? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮斤斧,結(jié)果婚禮上早抠,老公的妹妹穿的比我還像新娘。我一直安慰自己撬讽,他們只是感情好蕊连,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著游昼,像睡著了一般甘苍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烘豌,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天载庭,我揣著相機(jī)與錄音,去河邊找鬼廊佩。 笑死囚聚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的标锄。 我是一名探鬼主播顽铸,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼料皇!你這毒婦竟也來了谓松?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤践剂,失蹤者是張志新(化名)和其女友劉穎鬼譬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逊脯,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡优质,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了男窟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盆赤。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歉眷,靈堂內(nèi)的尸體忽然破棺而出牺六,到底是詐尸還是另有隱情,我是刑警寧澤汗捡,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布淑际,位于F島的核電站畏纲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏春缕。R本人自食惡果不足惜盗胀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锄贼。 院中可真熱鬧票灰,春花似錦、人聲如沸宅荤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯键。三九已至惹盼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惫确,已是汗流浹背手报。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留改化,地道東北人掩蛤。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像所袁,于是被迫代替她去往敵國(guó)和親盏档。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容