序列化模块概述
序列化模块提供序列化与反序列化的功能,支持大端序和小端序,底层使用链表的形式存储数据,这样可以节省内存,管理内存碎片。该模块支持所有基本类型数据的写入和读取,可以选择固定字节长度或可变字节长度写入,使用可变字节长度时,使用Zigzag
将有符号整型压缩后再进行Varint
编码,这样能够节省大量内存空间。再写入数据时,将数据写入链表的最后一个节点中,若写不下时,创建新的节点继续写入数据。
Varint & Zigzag
Varint
是一种使用一个或多个字节序列化整数的方法,会把整数编码为变长字节。对于32位整型数据经过Varint
编码后需要15个字节,小的数字使用1个10个字节。在实际场景中小数字的使用率远远多于大数字,因此通过byte
,大的数字使用5个bytes
。64位整型数据编码后占用1Varint
编码对于大部分场景都可以起到很好的压缩效果。
Zigzag
算法将有符号负整数转为正数,这样能够节省字节,因为负数的二进制位几乎全为1。
编码
原理:使用小端序:低位字节存入数组低地址,高位字节存入数组高地址。
1. 判断`value`是否大于`127`(127为7位的最大值),若值大于`127`则还需要新的字节来保存`value`
2. `(value & 0x7f) | 0x80`; 低七位保持不变,最高位置为`1`,表示这个字节之后还有数据要读
3. `value >>= 7`; 右移七位,判断是否需要新的字节保存数据;
void EncodeVarint(uint32_t value) {
uint8_t temp[5];
uint8_t i = 0;
while(value > 127) {
temp[i++] = (value & 0x7f) | 0x80;
value >>= 7;
}
temp[i] = value;
write(tmp, i);
}
压缩
原理:将-1
转为1
,1
转为2
,-2
转为3
,2
转为4
。
//将-1变为1,1变为2
void EncodeZigzag32(const int32_t value) {
if(v < 0) {
return ((uint32_t)(-v)) * 2 - 1;
} else {
return value * 2;
}
}
解码
原理:数组低地址保存到数据的低地址,数组高地址保存到数据的高地址。
b
每次读8
个字节,判断b
是否小于128
,若小于则说明最高位为1
,该字节是最后一个字节,读完就返回。- 若大于则说明还有数据没有取完,将数据的最高位置为0然后左移
i
位,和result
做与操作将该字节放到数据的高位。
void DecoderVarint() {
uint32_t result;
for(uint i = 0; i < 32; i += 7) {
uint8_t b = readFuint8();
if(b < 0x80) {
result |= (uint32_t)b << i;
break;
} else {
result |= ((uint32_t)(b & 0x7f) << i);
}
}
}
解压缩
原理:
- 对输入整数
v
进行右移一位操作(即v >> 1
),将其向右移动一位,相当于除以2。 (v & 1)
,获取其二进制形式的最低位,若为奇数则最低位为1
,取反为-1
,若为偶数则最低位为0
。- 步骤1和步骤2结果做异或运算。
void DecodeZigzag(const uint32_t& v) {
return (v >> 1) ^ -(v & 1);
}
详解
读写操作覆盖了所有的类型,这里就不一一举例了,只举几个例子。
class ByteArray
Node(链表结构)
所有的数据都存在这个链表结构中,在堆区开辟内存存储在ptr
中;size
为一个节点大小,一般设置为一个页面大小4KB;next
指向下一个节点。
struct Node {
Node(size_t s);
Node();
~Node();
char* ptr;
Node* next;
size_t size;
};
mumber(成员变量)
// 内存块的大小
size_t m_baseSize;
// 当前操作位置
size_t m_position;
// 当前的总容量
size_t m_capacity;
// 当前数据的大小
size_t m_size;
// 字节序,默认大端
int8_t m_endian;
// 第一个内存块指针
Node* m_root;
// 当前操作的内存块指针
Node* m_cur;
ByteArray(构造函数)
ByteArray::ByteArray(size_t base_size)
:m_baseSize(base_size)
,m_position(0)
,m_capacity(base_size)
,m_size(0)
,m_endian(SYLAR_BIG_ENDIAN)
,m_root(new Node(base_size))
,m_cur(m_root) {
}
~ByteArray(析构函数)
ByteArray::~ByteArray() {
Node* temp = m_root;
while (temp) {
m_cur = temp;
temp = temp->next;
delete m_cur;
}
}
writeFint8(写int8:固定长度)
void ByteArray::writeFint8(int8_t value) {
write(&value, sizeof(value));
}
writeFint16(写int16:固定长度)
void ByteArray::writeFint16(int16_t value) {
// 两个字节需要判断字节序
if (m_endian != SYLAR_BYTE_ORDER) {
value = byteswap(value);
}
write(&value, sizeof(value));
}
EncodeZigzag(压缩)
static uint32_t EncodeZigzag(const int32_t& v) {
if (v < 0) {
return ((uint32_t)(-v)) * 2 - 1;
} else {
return v * 2;
}
}
DecodeZigzag(解压缩)
static int32_t DecodeZigzag(const uint32_t& v) {
return (v >> 1) ^ -(v & 1);
}
writeUint32(写Uint32:不固定长度)
void ByteArray::writeUint32(uint32_t value) {
uint8_t tmp[5];
uint8_t i = 0;
while (value >= 0x80) {
tmp[i++] = (value & 0x7F) | 0x80;
value >>= 7;
}
tmp[i++] = value;
write(tmp, i);
}
writeInt32(写Int32:不固定长度)
void ByteArray::writeInt32(int32_t value) {
writeUint32(EncodeZigzag(value));
}
writeFloat(写Float:固定长度)
void ByteArray::writeFloat(float value) {
uint32_t v;
memcpy(&v, &value, sizeof(value));
writeFuint32(v);
}
writeStringF16(写StringF16:固定长度)
void ByteArray::writeStringF16(const std::string& value) {
writeFuint16(value.size());
write(value.c_str(), value.size());
}
readFint8(读int8:固定长度)
int8_t ByteArray::readFint8() {
int8_t v;
read(&v, sizeof(v));
return v;
}
readFint16(读int16:固定长度)
int16_t ByteArray::readFint16() {
int16_t v;
read(&v, sizeof(v));
if(m_endian == SYLAR_BYTE_ORDER) {
return v;
} else {
return byteswap(v);
}
}
readUint32(读Uint32:不固定长度)
uint32_t ByteArray::readUint32() {
uint32_t result = 0;
for (uint i = 0; i < 32; i += 7) {
uint8_t b = readFuint8();
if (b < 0x80) {
result |= ((uint32_t)b) << i;
break;
} else {
result |= ((uint32_t)(b & 0x7f) << i);
}
}
return result;
}
readInt32(读int32:不固定长度)
int32_t ByteArray::readInt32() {
return DecodeZigzag(readUint32());
}
readFloat(读Float:固定长度)
float ByteArray::readFloat() {
uint32_t v = readFuint32();
float value;
memcpy(&value, &v, sizeof(v));
return value;
}
readStringF16(读StringF16:固定长度)
std::string ByteArray::readStringF16() {
uint16_t len = readFuint16();
std::string buff(len, 0);
read(&buff[0], len);
return buff;
}
clear(清空ByteArray)
void ByteArray::clear() {
m_position = m_size = 0;
m_capacity = m_baseSize;
Node* tmp = m_root->next;
while (tmp) {
m_cur = tmp;
tmp = tmp->next;
delete m_cur;
}
m_cur = m_root;
m_root->next = nullptr;
}
write(写操作,改变position)
void ByteArray::write(const void* buf, size_t size) {
if (size == 0) {
return;
}
// 增加容量
addCapacity(size);
// 在当前Node的位置
size_t npos = m_position % m_baseSize;
// 当前Node剩余容量
size_t ncap = m_cur->size - npos;
// buf写到哪了
size_t bpos = 0;
while (size > 0) {
// 该节点剩余的位置比写入的数据多
if (ncap >= size) {
// 在当前位置将所有数据都写入
memcpy(m_cur->ptr + npos, static_cast<const char*>(buf) + bpos, size);
// 若正好写满了这个节点,则跳到下一个节点
if (m_cur->size == (npos + size)) {
m_cur = m_cur->next;
}
// 更新当前操作的位置
m_position += size;
// 更新数据写到哪个位置
bpos += size;
// 数据写完了
size = 0;
// 该节点不够写的
} else {
// 将数据写入这个节点的剩余位置
memcpy(m_cur->ptr + npos, static_cast<const char*>(buf) + bpos, ncap);
// 更新当前操作位置
m_position += ncap;
// 更新buf写到哪了
bpos += ncap;
// 更新剩余数据
size -= ncap;
// 指向下一个节点
m_cur = m_cur->next;
// 剩余大小为base大小
ncap = m_cur->size;
// 更新在当前节点的位置
npos = 0;
}
}
// 更新size
if (m_position > m_size) {
m_size = m_position;
}
}
read(读操作,改变position)
void ByteArray::read(void* buf, size_t size) {
// size比当前操作位置后的数据还多
if (size > getReadSize()) {
throw std::out_of_range("not enougth len");
}
// 当前节点的位置
size_t npos = m_position % m_baseSize;
// 当前节点剩余容量
size_t ncap = m_cur->size - npos;
// buf读到哪了
size_t bpos = 0;
while (size > 0) {
// 该节点剩余的位置比要读的数据多
if (ncap >= size) {
// 将剩余的数据都读到buf中
memcpy(static_cast<char*>(buf) + bpos, m_cur->ptr + npos, size);
// 若正好读完这个节点,则跳到下一个节点
if (m_cur->size == (npos + size)) {
m_cur = m_cur->next;
}
// 更新当前操作位置
m_position += size;
// 更新当前读到哪了
bpos += size;
size = 0;
// 该节点不够读的
} else {
// 将该节点剩余的容量都读到buf中
memcpy(static_cast<char*>(buf) + bpos, m_cur->ptr + npos, ncap);
// 更新当前操作位置
m_position += ncap;
// 更新读到哪了
bpos += ncap;
// 更新剩余多少数据没读
size -= ncap;
// 指向下一个节点
m_cur = m_cur->next;
// 剩余大小为base大小
ncap = m_cur->size;
// 更新在当前节点的位置
npos = 0;
}
}
}
read(读操作,不改变position)
void ByteArray::read(void* buf, size_t size, size_t position) const {
if(size > (m_size - position)) {
throw std::out_of_range("not enough len");
}
size_t npos = position % m_baseSize;
size_t ncap = m_cur->size - npos;
size_t bpos = 0;
Node* cur = m_cur;
while(size > 0) {
if(ncap >= size) {
memcpy((char*)buf + bpos, cur->ptr + npos, size);
if(cur->size == (npos + size)) {
cur = cur->next;
}
position += size;
bpos += size;
size = 0;
} else {
memcpy((char*)buf + bpos, cur->ptr + npos, ncap);
position += ncap;
bpos += ncap;
size -= ncap;
cur = cur->next;
ncap = cur->size;
npos = 0;
}
}
}
setPosition(设置当前位置)
void ByteArray::setPosition(size_t v) {
// 比容量还大,抛出异常
if (v > m_capacity) {
throw std::out_of_range("setPosition out of range");
}
// 设置当前位置
m_position = v;
// 若当前位置比数据大小还大
if (m_position > m_size) {
// 更新大小
m_size = m_position;
}
// 链表遍历到当前位置
m_cur = m_root;
while (v > m_cur->size) {
v -= m_cur->size;
m_cur = m_cur->next;
}
if (v == m_cur->size) {
m_cur = m_cur->next;
}
}
addCapacity(增加容量)
void ByteArray::addCapacity(size_t size) {
if (size == 0) {
return;
}
// 剩余容量
size_t old_cap = getCapacity();
if (old_cap >= size) {
return;
}
// 需要扩充多少字节数据
size = size - old_cap;
// 需要扩充多少节点
size_t count = ceil(1.0 * size / m_baseSize);
// 遍历到链表的末尾
Node* tmp = m_root;
while (tmp->next) {
tmp = tmp->next;
}
Node* first = nullptr;
// 创建新的节点
for (size_t i = 0; i < count; ++i) {
tmp->next = new Node(m_baseSize);
if (first == nullptr) {
first = tmp->next;
}
tmp = tmp->next;
m_capacity += m_baseSize;
}
// 若剩余容量为0,则跳到下一个节点
if (old_cap == 0) {
m_cur = first;
}
}
toString(转为string)
[m_position, m_size)
std::string ByteArray::toString() const {
std::string str;
str.resize(getReadSize());
if (str.empty()) {
return str;
}
read(&str[0], str.size(), m_position);
return str;
}
toHexString(转为十六进制输出)
std::string ByteArray::toHexString() const {
std::string str = toString();
std::stringstream ss;
for (size_t i = 0; i < str.size(); ++i) {
// 32字节换行
if (i > 0 && i % 32 == 0) {
ss << std::endl;
}
ss << std::setw(2) << std::setfill('0') << std::hex
<< (int)(uint8_t)str[i] << " ";
}
return ss.str();
}
getReadBuffers(获取可读缓存,iovec数组,不改变position)
uint64_t ByteArray::getReadBuffers(std::vector<iovec>& buffers, uint64_t len) const {
len = len > getReadSize() ? getReadSize() : len;
if (len == 0) {
return 0;
}
uint64_t size = len;
size_t npos = m_position % m_baseSize;
size_t ncap = m_cur->size - npos;
struct iovec iov;
Node* cur = m_cur;
while (len > 0) {
if (ncap >= len) {
iov.iov_base = cur->ptr + npos;
iov.iov_len = len;
len = 0;
} else {
iov.iov_base = cur->ptr + npos;
iov.iov_len = ncap;
len -= ncap;
cur = cur->next;
ncap = cur->size;
npos = 0;
}
buffers.push_back(iov);
}
return size;
}
getWriteBuffers(获取可写缓存,iovec数组,不改变position)
uint64_t ByteArray::getWriteBuffers(std::vector<iovec>& buffers, uint64_t len) {
if (len == 0) {
return 0;
}
addCapacity(len);
uint64_t size = len;
size_t npos = m_position % m_baseSize;
size_t ncap = m_cur->size - npos;
struct iovec iov;
Node* cur = m_cur;
while (len > 0) {
if (ncap >= len) {
iov.iov_base = cur->ptr + npos;
iov.iov_len = len;
len = 0;
} else {
iov.iov_base = cur->ptr + npos;
iov.iov_len = ncap;
len -= ncap;
cur = cur->next;
ncap = cur->size;
npos = 0;
}
buffers.push_back(iov);
}
return size;
}
总结
使用序列化模块时要注意使用的方法是否会导致position
发生变化,避免写入和读取数据时出core。