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

1. ByteBuffer 介紹
2. ByteBuffer 的成員變量
2.1 幾個(gè)位置變量
2.2 緩存區(qū)
2.3 ByteBuffer 名稱
3. 創(chuàng)建 ByteBuffer
3.1 創(chuàng)建指定大小的空的 ByteBuffer
3.2 從一個(gè)數(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 擴(kuò)容機(jī)制
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 的缺點(diǎn)

ByteBuffer 介紹及 C++ 實(shí)現(xiàn)
之前的工作中遇到過需要打包數(shù)據(jù)然后通過 USB 發(fā)送的功能,當(dāng)時(shí)寫了一個(gè)簡(jiǎn)單的類用來存入各種類型的數(shù)據(jù),然后將其 Buffer 內(nèi)的數(shù)據(jù)發(fā)送,接收到數(shù)據(jù)后通過它的方法再取出各種類型的數(shù)據(jù)。后來接觸到了 Java 的 ByteBuffer,發(fā)現(xiàn)兩者功能大致相同。本文會(huì)用 C++ 實(shí)現(xiàn) ByteBuffer 的大部分功能。
1. ByteBuffer 介紹
ByteBuffer類位于java.nio包下,它是一個(gè)字節(jié)緩存區(qū),提供了一些 put 和 get 方法,可以方便的將一些數(shù)據(jù)放到緩存區(qū)或者從緩存區(qū)里讀取某種類型的數(shù)據(jù)。ByteBuffer 的底層存儲(chǔ)結(jié)構(gòu)是數(shù)組,所有的操作都是基于該數(shù)組的操作。
以下內(nèi)容結(jié)合 Java 版本 ByteBuffer 的原理以及 C++ 實(shí)現(xiàn)進(jìn)行講解。
2. ByteBuffer 的成員變量
2.1 幾個(gè)位置變量
| 變量名稱 | 含義 |
|---|---|
| position | 表示從寫入或者讀取的位置。 |
| limit | 處于寫入狀態(tài)時(shí),limit 和 capacity 相等;處于讀取狀態(tài)時(shí),表示數(shù)據(jù)索引的上限,也就是實(shí)際存放了多少數(shù)據(jù)。 |
| mark | 標(biāo)記讀取數(shù)據(jù)的起始位置,便于后續(xù)回退到該位置。 |
| capacity | 表示 ByteBuffer 的容量,也就是可以存放的最大字節(jié)數(shù)。 |
這四個(gè)變量之間的關(guān)系可以表示為:mark <= position <= limit <= capacity。
數(shù)據(jù)的存入和讀取只會(huì)影響 position,不會(huì)影響 limit。
在 C++ 實(shí)現(xiàn)中,設(shè)置如下成員變量:
int32_t mark_;
uint32_t limit_;
uint32_t position_;
uint32_t capacity_;
提供如下三個(gè)方法分別獲取 capacity、position、limit:
uint32_t capacity() const;
uint32_t position() const;
uint32_t limit() const;
提供如下兩個(gè)方法可以重新設(shè)置 limit 和 position:
ByteBuffer& limit(uint32_t newLimit);
ByteBuffer& position(uint32_t newPosition);
2.2 緩存區(qū)
前面已經(jīng)提到,ByteBuffer 提供一個(gè)緩存區(qū)來存儲(chǔ)數(shù)據(jù),在 C++ 實(shí)現(xiàn)中,使用一個(gè) uint8_t 類型的數(shù)組進(jìn)行數(shù)據(jù)的存儲(chǔ)。在 ByteBuffer 類創(chuàng)建時(shí)申請(qǐng)空間,在 ByteBuffer 類銷毀時(shí)釋放空間。
uint8_t* p_buffer_;
2.3 ByteBuffer 名稱
為了打印時(shí)的美觀,為每一個(gè) ByteBuffer 設(shè)置一個(gè)名稱,該名稱為 ByteBuffer 類的一個(gè)成員變量,在類創(chuàng)建時(shí)設(shè)置,默認(rèn)為空:
std::string name_;
3. 創(chuàng)建 ByteBuffer
java.nio.Buffer 類是一個(gè)抽象類,不能被實(shí)例化。Buffer類的直接子類,如ByteBuffer等也是抽象類,所以也不能被實(shí)例化。但是 ByteBuffer 類提供了4個(gè)靜態(tài)工廠方法來獲得 ByteBuffer 的實(shí)例:
allocate(int capacity)allocateDirect(int capacity)wrap(byte[] array)wrap(byte[] array, int offset, int length)
C++ 版本做了一下簡(jiǎn)化,提供兩個(gè)構(gòu)造方法進(jìn)行創(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)建時(shí)未指定 capacity ,默認(rèn)大小為 2048 字節(jié)。
3.2 從一個(gè)數(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() 方法負(fù)責(zé)將一個(gè)現(xiàn)有數(shù)組的指定長(zhǎng)度存到 ByteBuffer 中,后面會(huì)對(duì)該方法做介紹。
3.3 析構(gòu)方法
析構(gòu)方法主要作用是釋放已經(jīng)申請(qǐng)的內(nèi)存:
~ByteBuffer()
{
if (p_buffer_)
{
free(p_buffer_);
p_buffer_ = NULL;
}
}
4. 狀態(tài)相關(guān)
申請(qǐng)一個(gè)容量為 10 的 ByteBuffer bf,以下演示都基于 bf。
4.1 初始狀態(tài)

初始狀態(tài)下,四個(gè)變量的值分別為:
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++ 實(shí)現(xiàn)如下:
ByteBuffer& clear()
{
position_ = 0;
mark_ = -1;
limit_ = capacity_;
return *this;
}
4.2 寫入狀態(tài)
假設(shè)向 bf 寫入 hello 幾個(gè)字符,此時(shí)四個(gè)變量的狀態(tài)如圖所示:

position 會(huì)隨著數(shù)據(jù)的寫入而后移。
將 ByteBuffer 置為寫入狀態(tài)的方法:
ByteBuffer 創(chuàng)建之后就是寫入狀態(tài),可以調(diào)用一系列 put方法寫入數(shù)據(jù);
4.3 讀取狀態(tài)
bf 進(jìn)入讀取狀態(tài)時(shí)四個(gè)變量的狀態(tài)如圖所示:

調(diào)用一系列 get 方法從 bf 中讀取數(shù)據(jù),position 隨著數(shù)據(jù)的讀取會(huì)向后移動(dòng),但不會(huì)超過 limit。
bf 從寫入狀態(tài)進(jìn)入讀取狀態(tài)需要調(diào)用 flip() 方法,調(diào)用 flip() 方法后,limit 被設(shè)置為原 position 的值,表示已經(jīng)存儲(chǔ)數(shù)據(jù)的位置;position 被置為 0。
flip() 方法的 C++ 實(shí)現(xiàn)如下:
ByteBuffer& flip()
{
limit_ = position_;
position_ = 0;
mark_ = -1;
return *this;
}
4.4 mark() && discardMark()
這兩個(gè)方法比較簡(jiǎn)單,mark() 方法將 mark 值設(shè)置為當(dāng)前的 position ;discardMark() 方法將 mark 重置為 -1。調(diào)用 mark() 和 discardMark() 方法后 mark 位置的變化如圖所示:

這兩個(gè)方法的 C++ 代碼實(shí)現(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 變化如圖所示:

reset() 方法的 C++ 實(shí)現(xiàn)如下:
ByteBuffer& reset()
{
if (mark_ >= 0)
position_ = mark_;
return *this;
}
4.6 rewind()
rewind() 方法負(fù)責(zé)將 position 置為 0,將 mark 置為 -1,數(shù)據(jù)的內(nèi)容不會(huì)改變,一般在把數(shù)據(jù)重寫入Buffer前調(diào)用。調(diào)用 rewind() 方法后 mark 和 position 的變化如圖所示:

rewind() 方法的 C++ 實(shí)現(xiàn)如下:
ByteBuffer& rewind()
{
mark_ = -1;
position_ = 0;
return *this;
}
4.7 compact()
壓縮緩存區(qū)。把緩存區(qū)當(dāng)前 position 到 limit 之間的數(shù)據(jù)移動(dòng)到緩存區(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() 方法后,幾個(gè)變量的位置以及數(shù)據(jù)的變化如圖所示:

compact() 方法的 C++ 實(shí)現(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ù)移動(dòng)到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 擴(kuò)容機(jī)制
Java 的 ByteBuffer 在創(chuàng)建時(shí)容量就固定了,如果存放的數(shù)據(jù)超出容量,會(huì)拋出異常。C++ 版本的 ByteBuffer 增加了擴(kuò)容機(jī)制。理論上,每次向 buffer 中寫入數(shù)據(jù)都要檢查空間是否足夠,如果空間不夠,則擴(kuò)大容量。
ByteBuffer 定義成員變量 BUFFER_SIZE_INCREASE 表示擴(kuò)容的步長(zhǎng),即每次擴(kuò)大的容量都為 BUFFER_SIZE_INCREASE 的整數(shù)倍,其值為 2048 :
const uint32_t BUFFER_SIZE_INCREASE = 2048;
定義 checkSize() 方法檢查容量是否足夠,如果足夠,不做處理;如果不夠,則計(jì)算需要多少容量并擴(kuò)容:
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);
}
在每個(gè) put() 方法里首先調(diào)用 checkSize() 檢查容量,然后再放入數(shù)據(jù)。
5.2 模板方法
為了簡(jiǎn)化存放數(shù)據(jù)的過程,用一個(gè)模板方法去適配各種類型:
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 編碼,一個(gè) Char 類型占兩個(gè)字節(jié),但是在 C++ 中 char 類型占一個(gè)字節(jié),所以兩個(gè)版本的 putChar() 方法有些差異。
著重講解一下 ByteBuffer& put(ByteBuffer* bb) 方法,該方法將另一個(gè) ByteBuffer 的內(nèi)容(從 0 到 limit() 之間的數(shù)據(jù))拷貝到當(dāng)前 ByteBuffer,其實(shí)現(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 模板方法
為了簡(jiǎn)化數(shù)據(jù)的獲取,實(shí)現(xiàn)模板方法獲取各種類型的數(shù)據(jù)。注意:帶有 index 參數(shù)的 read() 方法不會(huì)改變 position 的值。
template <typename T>
T read(uint32_t index) const
{
if (!p_buffer_ || index + sizeof(T) > limit_)
return 0;
return *((T*)&p_buffer_[index]);
}
template <typename T>
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ù)的方法不會(huì)改變 position_ 值。
看一下 void getBytes(uint32_t index, uint8_t* buf, uint32_t len) const 方法的實(shí)現(xiàn):
void ByteBuffer::getBytes(uint32_t index, uint8_t* buf, uint32_t len) const
{
// 合法性檢測(cè)
if (!p_buffer_ || index + len > limit_)
return;
uint32_t pos = index;
for (uint32_t i = 0; i < len; i++)
{
buf[i] = p_buffer_[pos++];
}
}
為了實(shí)現(xiàn)只做一次合法性檢測(cè),并沒有調(diào)用 read() 模板方法。
7. 其他方法
7.1 equals
函數(shù)原型:bool equals(ByteBuffer* other);
描述:比較兩個(gè) ByteBuffer 是否相等;
實(shí)現(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ù)制一個(gè) ByteBuffer;
實(shí)現(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()
描述:表示 position 和 limit 之間是否還有數(shù)據(jù);
實(shí)現(xiàn):
bool hasRemaining()
{
return limit_ > position_;
}
7.4 remaining
函數(shù)原型:uint32_t remaining() const
描述:返回 position 和 limit 之間字節(jié)數(shù);
實(shí)現(xiàn):
uint32_t remaining() const
{
return position_ < limit_ ? limit_ - position_ : 0;
}
7.5 printInfo
函數(shù)原型:void printInfo() const
描述:打印 mark、position、limit、capacity 的值
實(shí)現(xiàn):
void printInfo() const
{
std::cout << "ByteBuffer " << name_ << ":\n"
<< "\tmark(" << mark_ << "), "
<< "position(" << position_ << "), "
<< "limit(" << limit_ << "), "
<< "capacity(" << capacity_ << ")." << std::endl;
}
8. ByteBuffer 的缺點(diǎn)
ByteBuffer 缺點(diǎn)如下:
ByteBuffer 并不是線程安全的,如果想要在并發(fā)情況下使用,需要自己為緩存區(qū)做同步控制;
ByteBuffer 長(zhǎng)度固定,一旦分配完成,它的容量不能動(dòng)態(tài)擴(kuò)展和收縮,當(dāng)需要編碼的對(duì)象大于 ByteBuffer 的容量時(shí),會(huì)發(fā)生索引越界異常;
ByteBuffer 只有一個(gè)標(biāo)識(shí)位的指針 position,讀寫的時(shí)候需要手工調(diào)用
flip()和rewind()等,使用者必須小心謹(jǐn)慎地處理這些 API,否則很容易導(dǎo)致程序處理失敗;ByteBuffer 的 API 功能有限,一些高級(jí)和實(shí)用的特性它不支持,需要使用者自己編程實(shí)現(xiàn)。
本文的 C++ 實(shí)現(xiàn)只對(duì)第二點(diǎn)做了調(diào)整,支持了主動(dòng)擴(kuò)容;同樣存在其它幾個(gè)缺點(diǎn)。
ByteBuf 是 Netty 里的封裝的數(shù)據(jù)緩存區(qū),區(qū)別于 ByteBuffer 里需要 position、limit、capacity 等屬性來操作 ByteBuffer 數(shù)據(jù)讀寫,而 ByteBuf 是通過兩個(gè)指針協(xié)助完成緩存區(qū)的讀寫操作,后續(xù)可能實(shí)現(xiàn) C++ 版本的 ByteBuf 或者對(duì)當(dāng)前 C++ ByteBuffer 進(jìn)行修改。
參考鏈接
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

點(diǎn)分享

點(diǎn)收藏

點(diǎn)點(diǎn)贊

點(diǎn)在看
