1. ByteBuffer 介紹及 C++ 實現(xiàn)

        共 17461字,需瀏覽 35分鐘

         ·

        2021-05-25 19:14

        • 1. ByteBuffer 介紹

        • 2. ByteBuffer 的成員變量

          • 2.1 幾個位置變量

          • 2.2 緩存區(qū)

          • 2.3 ByteBuffer 名稱

        • 3. 創(chuàng)建 ByteBuffer

          • 3.1 創(chuàng)建指定大小的空的 ByteBuffer

          • 3.2 從一個數(shù)組創(chuàng)建指定大小的 ByteBuffer

          • 3.3 析構(gòu)方法

        • 4. 狀態(tài)相關(guān)

          • 4.1 初始狀態(tài)

          • 4.2 寫入狀態(tài)

          • 4.3 讀取狀態(tài)

          • 4.4 mark() && discardMark()

          • 4.5 reset()

          • 4.6 rewind()

          • 4.7 compact()

          • 4.8 狀態(tài)相關(guān)方法總結(jié)

        • 5. put 數(shù)據(jù)

          • 5.1 擴容機制

          • 5.2 模板方法

          • 5.3 put 方法

        • 6. get 數(shù)據(jù)

          • 6.1 模板方法

          • 6.2 get 方法

        • 7. 其他方法

          • 7.1 equals

          • 7.2 duplicate

          • 7.3 hasRemaining

          • 7.4 remaining

          • 7.5 printInfo

        • 8. ByteBuffer 的缺點

        ByteBuffer 介紹及 C++ 實現(xiàn)

        之前的工作中遇到過需要打包數(shù)據(jù)然后通過 USB 發(fā)送的功能,當(dāng)時寫了一個簡單的類用來存入各種類型的數(shù)據(jù),然后將其 Buffer 內(nèi)的數(shù)據(jù)發(fā)送,接收到數(shù)據(jù)后通過它的方法再取出各種類型的數(shù)據(jù)。后來接觸到了 Java 的 ByteBuffer,發(fā)現(xiàn)兩者功能大致相同。本文會用 C++ 實現(xiàn) ByteBuffer 的大部分功能。

        1. ByteBuffer 介紹

        ByteBuffer類位于java.nio包下,它是一個字節(jié)緩存區(qū),提供了一些 put 和 get 方法,可以方便的將一些數(shù)據(jù)放到緩存區(qū)或者從緩存區(qū)里讀取某種類型的數(shù)據(jù)。ByteBuffer 的底層存儲結(jié)構(gòu)是數(shù)組,所有的操作都是基于該數(shù)組的操作。

        以下內(nèi)容結(jié)合 Java 版本 ByteBuffer 的原理以及 C++ 實現(xiàn)進行講解。

        2. ByteBuffer 的成員變量

        2.1 幾個位置變量

        變量名稱含義
        position表示從寫入或者讀取的位置。
        limit處于寫入狀態(tài)時,limit 和 capacity 相等;處于讀取狀態(tài)時,表示數(shù)據(jù)索引的上限,也就是實際存放了多少數(shù)據(jù)。
        mark標(biāo)記讀取數(shù)據(jù)的起始位置,便于后續(xù)回退到該位置。
        capacity表示 ByteBuffer 的容量,也就是可以存放的最大字節(jié)數(shù)。

        這四個變量之間的關(guān)系可以表示為:mark <= position <= limit <= capacity。

        數(shù)據(jù)的存入和讀取只會影響 position,不會影響 limit。

        在 C++ 實現(xiàn)中,設(shè)置如下成員變量:

        int32_t  mark_;
        uint32_t limit_;
        uint32_t position_;
        uint32_t capacity_;

        提供如下三個方法分別獲取 capacityposition、limit

        uint32_t capacity() const;
        uint32_t position() const;
        uint32_t limit() const;

        提供如下兩個方法可以重新設(shè)置 limitposition

        ByteBuffer& limit(uint32_t newLimit);
        ByteBuffer& position(uint32_t newPosition);

        2.2 緩存區(qū)

        前面已經(jīng)提到,ByteBuffer 提供一個緩存區(qū)來存儲數(shù)據(jù),在 C++ 實現(xiàn)中,使用一個 uint8_t 類型的數(shù)組進行數(shù)據(jù)的存儲。在 ByteBuffer 類創(chuàng)建時申請空間,在 ByteBuffer 類銷毀時釋放空間。

        uint8_t*         p_buffer_;

        2.3 ByteBuffer 名稱

        為了打印時的美觀,為每一個 ByteBuffer 設(shè)置一個名稱,該名稱為 ByteBuffer 類的一個成員變量,在類創(chuàng)建時設(shè)置,默認為空:

        std::string      name_;

        3. 創(chuàng)建 ByteBuffer

        java.nio.Buffer 類是一個抽象類,不能被實例化。Buffer類的直接子類,如ByteBuffer等也是抽象類,所以也不能被實例化。但是 ByteBuffer 類提供了4個靜態(tài)工廠方法來獲得 ByteBuffer 的實例:

        • allocate(int capacity)
        • allocateDirect(int capacity)
        • wrap(byte[] array)
        • wrap(byte[] array, int offset, int length)

        C++ 版本做了一下簡化,提供兩個構(gòu)造方法進行創(chuàng)建。

        3.1 創(chuàng)建指定大小的空的 ByteBuffer

        // Default size of the buffer
        #define DEFAULT_BUFFER_SIZE 2048

        ByteBuffer(uint32_t capacity = DEFAULT_BUFFER_SIZE, const char* name = ""
            : mark_(-1), 
            limit_(capacity), 
            position_(0),
            capacity_(capacity), 
            name_(name)
        {
            p_buffer_ = NULL;
            p_buffer_ = (uint8_t*)calloc(capacity_, sizeof(uint8_t));
        }

        如果創(chuàng)建時未指定 capacity ,默認大小為 2048 字節(jié)。

        3.2 從一個數(shù)組創(chuàng)建指定大小的 ByteBuffer

        ByteBuffer(uint8_t* arr, uint32_t length, const char* name = "")
            : mark_(-1), 
            limit_(length), 
            position_(0),
            capacity_(length), 
            name_(name)
        {
            p_buffer_ = NULL;
            p_buffer_ = (uint8_t*)calloc(capacity_, sizeof(uint8_t));

            putBytes(arr, capacity_);
            clear();
        }

        putBytes() 方法負責(zé)將一個現(xiàn)有數(shù)組的指定長度存到 ByteBuffer 中,后面會對該方法做介紹。

        3.3 析構(gòu)方法

        析構(gòu)方法主要作用是釋放已經(jīng)申請的內(nèi)存:

        ~ByteBuffer()
        {
            if (p_buffer_)
            {
                free(p_buffer_);
                p_buffer_ = NULL;
            }
        }

        4. 狀態(tài)相關(guān)

        申請一個容量為 10 的 ByteBuffer bf,以下演示都基于 bf

        4.1 初始狀態(tài)

        image-20210323235205921

        初始狀態(tài)下,四個變量的值分別為:

        • mark:-1
        • position:0
        • limit:10
        • capacity:10

        將 ByteBuffer 置為初始狀態(tài)的方法:

        • ByteBuffer 創(chuàng)建之后調(diào)用其它方法之前就是初始狀態(tài)
        • 調(diào)用 clear() 方法可以重置到初始狀態(tài)。

        clear() 方法的 C++ 實現(xiàn)如下:

        ByteBuffer& clear() 
        {
            position_ = 0;
            mark_     = -1;
            limit_    = capacity_;
            return *this;
        }

        4.2 寫入狀態(tài)

        假設(shè)向 bf 寫入 hello 幾個字符,此時四個變量的狀態(tài)如圖所示:

        image-20210323235228538

        position 會隨著數(shù)據(jù)的寫入而后移。

        將 ByteBuffer 置為寫入狀態(tài)的方法:

        • ByteBuffer 創(chuàng)建之后就是寫入狀態(tài),可以調(diào)用一系列 put 方法寫入數(shù)據(jù);

        4.3 讀取狀態(tài)

        bf 進入讀取狀態(tài)時四個變量的狀態(tài)如圖所示:

        image-20210324000012817

        調(diào)用一系列 get 方法從 bf 中讀取數(shù)據(jù),position 隨著數(shù)據(jù)的讀取會向后移動,但不會超過 limit。

        bf 從寫入狀態(tài)進入讀取狀態(tài)需要調(diào)用 flip() 方法,調(diào)用 flip() 方法后,limit 被設(shè)置為原 position 的值,表示已經(jīng)存儲數(shù)據(jù)的位置;position 被置為 0。

        flip() 方法的 C++ 實現(xiàn)如下:

        ByteBuffer& flip() 
        {
            limit_    = position_;
            position_ = 0;
            mark_     = -1;
            return *this;
        }

        4.4 mark() && discardMark()

        這兩個方法比較簡單,mark() 方法將 mark 值設(shè)置為當(dāng)前的 position ;discardMark() 方法將 mark 重置為 -1。調(diào)用 mark()discardMark() 方法后 mark 位置的變化如圖所示:

        這兩個方法的 C++ 代碼實現(xiàn)如下:

        ByteBuffer& mark()
        {
            mark_ = position_;
            return *this;
        }

        ByteBuffer& discardMark() 
        {
            mark_ = -1;
            return *this;
        }

        4.5 reset()

        reset() 方法將 position 恢復(fù)到 mark 的位置。調(diào)用 reset() 方法后的 position 變化如圖所示:

        image-20210324140402132

        reset() 方法的 C++ 實現(xiàn)如下:

        ByteBuffer& reset()
        {
            if (mark_ >= 0)
                position_ = mark_;

            return *this;
        }

        4.6 rewind()

        rewind() 方法負責(zé)將 position 置為 0,將 mark 置為 -1,數(shù)據(jù)的內(nèi)容不會改變,一般在把數(shù)據(jù)重寫入Buffer前調(diào)用。調(diào)用 rewind() 方法后 markposition 的變化如圖所示:

        image-20210324140832110

        rewind() 方法的 C++ 實現(xiàn)如下:

        ByteBuffer& rewind()
        {
            mark_ = -1;
            position_ = 0;

            return *this;
        }

        4.7 compact()

        壓縮緩存區(qū)。把緩存區(qū)當(dāng)前 positionlimit 之間的數(shù)據(jù)移動到緩存區(qū)的開頭。也就是說,將索引 p=position() 處的字節(jié)復(fù)制到索引 0 處,將索引 p+1 處的字節(jié)復(fù)制到索引 1 處。以此類推,直到 limit - 1 處的字節(jié)復(fù)制到索引 n=limit-1-p 處。然后將緩存區(qū)的 position 設(shè)置為 n+1(也就是不能再讀取數(shù)據(jù)了,但是可以寫入數(shù)據(jù)),并將 limit 的值設(shè)置為  capacity。

        調(diào)用 compact() 方法后,幾個變量的位置以及數(shù)據(jù)的變化如圖所示:

        image-20210324192748457

        compact() 方法的 C++ 實現(xiàn)如下:

        ByteBuffer& compact()
        {
            do 
            {
                if (position_ >= limit_)
                {
                    position_ = 0;
                    break;
                }

                for (uint32_t i = 0; i < limit_ - position_; i++)
                {
                    p_buffer_[i] = p_buffer_[position_ + i];
                }
                position_ = limit_ - position_;
            } while (0);        

            limit_ = capacity_;
            return *this;
        }

        4.8 狀態(tài)相關(guān)方法總結(jié)

        函數(shù)名描述
        flip()把 limit 設(shè)置為當(dāng)前 position,把 position 置為 0
        clear()重置ByteBuffer的 position = 0; limit = capacity; mark = -1,數(shù)據(jù)內(nèi)容無變化
        reset()將position恢復(fù)到mark處的位置
        rewind()執(zhí)行后position = 0, mark = -1,數(shù)據(jù)內(nèi)容不變
        mark()將mark值設(shè)置為當(dāng)前的position
        discardMark()將mark的位置重置為-1
        compact()刪除已讀過的數(shù)據(jù),將position到limit之間的數(shù)據(jù)移動到0和limit-position處,并將mark重置為-1,position放到數(shù)據(jù)結(jié)尾,總結(jié)一下,就是可以繼續(xù)寫數(shù)據(jù)了,但是不能讀數(shù)據(jù)

        5. put 數(shù)據(jù)

        ByteBuffer 提供一系列的 put 方法將各種類型的數(shù)據(jù)放到 buffer 中,具體的類型有 char、short、int、long、float、double、char 數(shù)組以及 Bytebuffer。

        5.1 擴容機制

        Java 的 ByteBuffer 在創(chuàng)建時容量就固定了,如果存放的數(shù)據(jù)超出容量,會拋出異常。C++ 版本的 ByteBuffer 增加了擴容機制。理論上,每次向 buffer 中寫入數(shù)據(jù)都要檢查空間是否足夠,如果空間不夠,則擴大容量。

        ByteBuffer 定義成員變量 BUFFER_SIZE_INCREASE 表示擴容的步長,即每次擴大的容量都為 BUFFER_SIZE_INCREASE  的整數(shù)倍,其值為 2048

        const uint32_t BUFFER_SIZE_INCREASE = 2048;

        定義 checkSize() 方法檢查容量是否足夠,如果足夠,不做處理;如果不夠,則計算需要多少容量并擴容:

        void ByteBuffer::checkSize(uint32_t index, uint32_t increase)
        {
            if (index + increase <= capacity_)
                    return;

            uint32_t newSize = capacity_ + (increase + BUFFER_SIZE_INCREASE - 1) /
                BUFFER_SIZE_INCREASE * BUFFER_SIZE_INCREASE;
            uint8_t* pBuf = (uint8_t*)realloc(p_buffer_, newSize);
            if (!pBuf)
            {
                std::cout << "relloc failed!" << std::endl;
                exit(1);
            }

            p_buffer_ = pBuf;
            capacity_ = newSize;
        }

        void ByteBuffer::checkSize(uint32_t increase)
        {
            checkSize(position_, increase);
        }

        在每個 put() 方法里首先調(diào)用 checkSize() 檢查容量,然后再放入數(shù)據(jù)。

        5.2 模板方法

        為了簡化存放數(shù)據(jù)的過程,用一個模板方法去適配各種類型:

        template<typename T>
        void append(T data)
        {
            if (!p_buffer_)
                return;

            uint32_t s = sizeof(data);
            checkSize(s);

            memcpy(&p_buffer_[position_], (uint8_t*)&data, s);
            position_ += s;
        }

        template<typename T>
        void insert(T data, uint32_t index)
        {
            uint32_t s = sizeof(data);
            checkSize(index, s);

            position_ = index;
            append<T>(data);
        }

        append() 方法將數(shù)據(jù)寫入到當(dāng)前的 position_ 處,并相應(yīng)增加 position_

        insert() 方法將數(shù)據(jù)寫入到指定的位置,首先要將 position_ 設(shè)置為 index 然后調(diào)用 append() 方法寫入數(shù)據(jù)。

        5.3 put 方法

        ByteBuffer 提供的所有 put 方法返回值類型都為 ByteBuffer& 便于鏈?zhǔn)讲僮鳎热?bf.put(1).put("hello", 5).put(3.1415926),所有方法如下所示:

        ByteBuffer& put(ByteBuffer* bb);
        ByteBuffer& put(uint8_t value);
        ByteBuffer& put(uint8_t value, uint32_t index);
        ByteBuffer& putBytes(const uint8_t* buf, uint32_t len);
        ByteBuffer& putBytes(const uint8_t* buf, uint32_t len, uint32_t index);
        ByteBuffer& putChar(char value);
        ByteBuffer& putChar(char value, uint32_t index);
        ByteBuffer& putShort(uint16_t value);
        ByteBuffer& putShort(uint16_t value, uint32_t index);
        ByteBuffer& putInt(uint32_t value);
        ByteBuffer& putInt(uint32_t value, uint32_t index);
        ByteBuffer& putLong(uint64_t value);
        ByteBuffer& putLong(uint64_t value, uint32_t index);
        ByteBuffer& putFloat(float value);
        ByteBuffer& putFloat(float value, uint32_t index);
        ByteBuffer& putDouble(double value);
        ByteBuffer& putDouble(double value, uint32_t index);

        注意:由于 Java 采用 Unicode 編碼,一個 Char 類型占兩個字節(jié),但是在 C++ 中 char 類型占一個字節(jié),所以兩個版本的 putChar() 方法有些差異。

        著重講解一下 ByteBuffer& put(ByteBuffer* bb) 方法,該方法將另一個 ByteBuffer 的內(nèi)容(從 0limit() 之間的數(shù)據(jù))拷貝到當(dāng)前 ByteBuffer,其實現(xiàn)為:

        ByteBuffer& put(ByteBuffer* bb)
        {
            for (uint32_t i = 0; i < bb->limit(); i++)
                append<uint8_t>(bb->get(i));

            return *this;
        }

        6. get 數(shù)據(jù)

        ByteBuffer 提供一系列的 get 方法將各種類型的數(shù)據(jù)放到 buffer 中。

        6.1 模板方法

        為了簡化數(shù)據(jù)的獲取,實現(xiàn)模板方法獲取各種類型的數(shù)據(jù)。注意:帶有 index 參數(shù)的 read() 方法不會改變 position 的值。

        template <typename T>
        read(uint32_t index) const
        {
            if (!p_buffer_ || index + sizeof(T) > limit_)
                return 0;

            return *((T*)&p_buffer_[index]);
        }

        template <typename T>
        read()
        {
            T data = read<T>(position_);
            position_ += sizeof(T);
            return data;
        }

        6.2 get 方法

        所有 get() 方法如下:

        uint8_t  get();
        uint8_t  get(uint32_t index) const;
        void     getBytes(uint8_t* buf, uint32_t len);
        void     getBytes(uint32_t index, uint8_t* buf, uint32_t len) const;
        char     getChar();
        char     getChar(uint32_t index) const;
        uint16_t getShort();
        uint16_t getShort(uint32_t index) const;
        uint32_t getInt();
        uint32_t getInt(uint32_t index) const;
        uint64_t getLong();
        uint64_t getLong(uint32_t index) const;
        float    getFloat();
        float    getFloat(uint32_t index) const;
        double   getDouble();
        double   getDouble(uint32_t index) const;

        注意:帶有 index 參數(shù)的方法不會改變 position_ 值。

        看一下 void getBytes(uint32_t index, uint8_t* buf, uint32_t len) const 方法的實現(xiàn):

        void ByteBuffer::getBytes(uint32_t index, uint8_t* buf, uint32_t len) const
        {
            // 合法性檢測
            if (!p_buffer_ || index + len > limit_)
                return;

            uint32_t pos = index;
            for (uint32_t i = 0; i < len; i++)
            {
                buf[i] = p_buffer_[pos++];
            }
        }

        為了實現(xiàn)只做一次合法性檢測,并沒有調(diào)用 read() 模板方法。

        7. 其他方法

        7.1 equals

        函數(shù)原型:bool equals(ByteBuffer* other);

        描述:比較兩個 ByteBuffer 是否相等;

        實現(xiàn):

        bool equals(ByteBuffer* other)
        {
            uint32_t len = limit();
            if (len != other->limit())
                return false;

            for (uint32_t i = 0; i < len; i++)
            {
                if (get(i) != other->get(i))
                    return false;
            }
            return true;
        }

        7.2 duplicate

        函數(shù)原型:ByteBuffer* duplicate();

        描述:復(fù)制一個 ByteBuffer;

        實現(xiàn):新的 ByteBuffer 的 mark-1,不一樣和原 ByteBuffer 相同。

        ByteBuffer* ByteBuffer::duplicate()
        {
            ByteBuffer* newBuffer = new ByteBuffer(capacity_);

            // copy data
            newBuffer->put(this);

            newBuffer->limit(limit_);
            newBuffer->position(position_);

            return newBuffer;
        }

        7.3 hasRemaining

        函數(shù)原型:bool hasRemaining()

        描述:表示 positionlimit 之間是否還有數(shù)據(jù);

        實現(xiàn):

        bool hasRemaining()
        {
            return limit_ > position_;
        }

        7.4 remaining

        函數(shù)原型:uint32_t remaining() const

        描述:返回 positionlimit 之間字節(jié)數(shù);

        實現(xiàn):

        uint32_t remaining() const
        {
            return position_ < limit_ ? limit_ - position_ : 0;
        }

        7.5 printInfo

        函數(shù)原型:void printInfo() const

        描述:打印 markposition、limit、capacity 的值

        實現(xiàn):

        void printInfo() const
        {
            std::cout << "ByteBuffer " << name_ << ":\n"
                << "\tmark(" << mark_ << "), "
                << "position(" << position_ << "), "
                << "limit(" << limit_ << "), "
                << "capacity(" << capacity_ << ")." << std::endl;
        }

        8. ByteBuffer 的缺點

        ByteBuffer 缺點如下:

        • ByteBuffer 并不是線程安全的,如果想要在并發(fā)情況下使用,需要自己為緩存區(qū)做同步控制;

        • ByteBuffer 長度固定,一旦分配完成,它的容量不能動態(tài)擴展和收縮,當(dāng)需要編碼的對象大于 ByteBuffer 的容量時,會發(fā)生索引越界異常;

        • ByteBuffer 只有一個標(biāo)識位的指針 position,讀寫的時候需要手工調(diào)用 flip()rewind()等,使用者必須小心謹慎地處理這些 API,否則很容易導(dǎo)致程序處理失??;

        • ByteBuffer 的 API 功能有限,一些高級和實用的特性它不支持,需要使用者自己編程實現(xiàn)。

        本文的 C++ 實現(xiàn)只對第二點做了調(diào)整,支持了主動擴容;同樣存在其它幾個缺點。

        ByteBuf 是 Netty 里的封裝的數(shù)據(jù)緩存區(qū),區(qū)別于 ByteBuffer 里需要 position、limit、capacity 等屬性來操作 ByteBuffer 數(shù)據(jù)讀寫,而 ByteBuf 是通過兩個指針協(xié)助完成緩存區(qū)的讀寫操作,后續(xù)可能實現(xiàn) C++ 版本的 ByteBuf 或者對當(dāng)前 C++ ByteBuffer 進行修改。

        參考鏈接

        [1]

        Class ByteBuffer: https://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html

        [2]

        java.nio.Buffer 中的 flip()方法: https://blog.csdn.net/hbtj_1216/article/details/53129588

        [3]

        ByteBuffer常用方法詳解: https://blog.csdn.net/moakun/article/details/80630477

        [4]

        ByteBuffer詳解: http://bcoder.com/java/explaination-of-bytebuffer

        [5]

        圖解ByteBuffer和ByteBuf: https://www.gameboys.cn/article/193

        點分享

        點收藏

        點點贊

        點在看

        瀏覽 120
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 亚洲AV无码乱码精品 | 天天av天天 | freehdxxxxmoviejapan | 一级艳片加勒比女海盗1 | 成人三级片视频 |