yas源码解析 之 数据压缩的底层原理

背景

GitHub源码地址:https://github.com/niXman/yas
个人注释gitee地址:https://gitee.com/AriesSun/yas_comment
yas介绍一节的示例代码中,大家应该还记得yas::compacted,这个标识的目的就是在进行序列化时,将数据进行压缩。然而,着并不意味着yas支持对所有的数据进行压缩,实际上,yas只支持对整型数据的压缩,比如,int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t等。下面就对其原理进行详细介绍。

计算stroage_size

针对整型数据,根据数值大小计算合适的storage size。

template<typename T>
static constexpr std::uint8_t storage_size(const T &v, __YAS_ENABLE_IF_IS_16BIT(T)) {
    // 对于2字节整数
    // 如果v < 256, 那么,使用1字节存储,
    // 否则,使用2字节存储
    return __YAS_SCAST(std::uint8_t, (v < (1u<<8 )) ? 1u : 2u);
}
template<typename T>
static constexpr std::uint8_t storage_size(const T &v, __YAS_ENABLE_IF_IS_32BIT(T)) {
    return __YAS_SCAST(std::uint8_t,
        // 二分法
        (v < (1u<<16) )
            ? ((v < (1u<<8 )) ? 1u : 2u)
            : ((v < (1u<<24)) ? 3u : 4u)
    );
}
template<typename T>
static constexpr std::uint8_t storage_size(const T &v, __YAS_ENABLE_IF_IS_64BIT(T)) {
    return __YAS_SCAST(std::uint8_t,
        // 二分法
        (v < (1ull<<32))
            ? (v < (1u<<16) )
                ? ((v < (1u<<8 )) ? 1u : 2u)
                : ((v < (1u<<24)) ? 3u : 4u)
            : (v < (1ull<<48) )
                ? ((v < (1ull<<40)) ? 5u : 6u)
                : ((v < (1ull<<56)) ? 7u : 8u)
    );
}

上面的三个函数分别针对16bit、32bit、64bit的整数进行处理,试图得到最小保存整数的字节数。

  • 16bit的整数,可以有1字节和2字节两种选择
  • 32bit的整数,可以有1字节、2字节、3字节和4字节4种选择
  • 64bit的整数,可以有1字节、2字节、3字节、4字节、5字节、6字节、7字节、8字节等8种选择
    如果你字节阅读代码,会发现作者使用了二分法,便于快速得到最小的保存字节数。

有符号整数的压缩存储

// for signed
template<typename T>
void write(const T &v, __YAS_ENABLE_IF_IS_SIGNED_INTEGER(T)) {
    __YAS_CONSTEXPR_IF ( F & yas::compacted ) {
        // 如果v为正数
        if ( __YAS_LIKELY(v >= 0) ) {// 正整数处理
            // 转为对应的unsigned类型
            typename std::make_unsigned<T>::type uv = v;
            // v >= 64,为什么要这么处理,因为第7、8bit有特殊用途
            // 第8bit用来表示符号,第7bit表示是否1字节存储
            if ( uv >= (1u<<6) ) {
                // 计算存储size
                const std::uint8_t ns = storage_size(uv);
                // 写入压缩后整数字节数,字节数使用1字节进行存储,因为为正数,所以,最高bit为0
                write(__YAS_SCAST(std::uint8_t, ns | __YAS_SCAST(std::uint8_t, 0u<<7)));
                // 写入压缩后的数据
                __YAS_THROW_WRITE_ERROR(ns != os.write(&uv, ns));
            } else {
                // one byte,第8bit为0, 第7bit为1
                uv |= __YAS_SCAST(std::uint8_t, (1u<<6|0u<<7));
                __YAS_THROW_WRITE_ERROR(1 != os.write(&uv, 1));
            }
        } else {	// 负整数处理
            // 转为对应的unsigned类型
            typename std::make_unsigned<T>::type uv = std::abs(v);
            // v >= 64
            if ( uv >= (1u<<6) ) {
                // 计算存储size
                const std::uint8_t ns = storage_size(uv);
                // 写入保存的字节数,负数,第8bit为1
                write(__YAS_SCAST(std::uint8_t, ns | __YAS_SCAST(std::uint8_t, 1u<<7)));
                // 写入压缩后数据
                __YAS_THROW_WRITE_ERROR(ns != os.write(&uv, ns));
            } else {
                // one byte,第8bit和第7bit都为1,表述数据为负数,且为1字节存储
                uv |= __YAS_SCAST(std::uint8_t, (1u<<6|1u<<7));
                // 写入数据
                __YAS_THROW_WRITE_ERROR(1 != os.write(&uv, 1));
            }
        }
    } else {
        __YAS_CONSTEXPR_IF ( __YAS_BSWAP_NEEDED(F) ) {
            T t = endian_converter::bswap(v);
            __YAS_THROW_WRITE_ERROR(sizeof(t) != os.write(&t, sizeof(t)));
        } else {
            __YAS_THROW_WRITE_ERROR(sizeof(v) != os.write(&v, sizeof(v)));
        }
    }
}

代码中的注视,已经非常详尽,就不再进行过多的赘述了。核心就是:

  • 在数据长度的字节中,保存符号和是否1字节存储
  • 负数先转为整数
  • 将数据保存为压缩后的数据长度

压缩的有符号整数的读取

	// for signed
    template<typename T>
    void read(T &v, __YAS_ENABLE_IF_IS_SIGNED_INTEGER(T)) {
        __YAS_CONSTEXPR_IF ( F & yas::compacted ) {
            // 读取第1个字节
            std::uint8_t ns = __YAS_SCAST(std::uint8_t, is.getch());
            // 是否为负数
            const bool neg = __YAS_SCAST(bool, (ns >> 7) & 1u);
            // 是否数1字节存储
            const bool onebyte = __YAS_SCAST(bool, (ns >> 6) & 1u);
            // 计算字节数
            ns &= ~((1u << 7) | (1u << 6));
            // 如果不是1字节存储
            if ( __YAS_LIKELY(!onebyte) ) {
                typename std::make_unsigned<T>::type av = 0;
                __YAS_THROW_READ_STORAGE_SIZE_ERROR(sizeof(av) < ns);
                // 读取压缩后的数据
                __YAS_THROW_READ_ERROR(ns != is.read(&av, ns));
                // 将数据转为实际的类型,并赋予正负号
                v = (__YAS_UNLIKELY(neg) ? -__YAS_SCAST(T, av) : __YAS_SCAST(T, av));
            } else {    // 如果数1字节存储
                v = (__YAS_UNLIKELY(neg) ? -__YAS_SCAST(T, ns) : __YAS_SCAST(T, ns));
            }
        } else {
            __YAS_THROW_READ_ERROR(sizeof(v) != is.read(&v, sizeof(v)));
            __YAS_CONSTEXPR_IF ( __YAS_BSWAP_NEEDED(F) ) {
                v = endian_converter::bswap(v);
            }
        }
    }

无符号整数的压缩存储

// for unsigned
template<typename T>
void write(const T &v, __YAS_ENABLE_IF_IS_UNSIGNED_INTEGER(T)) {
    __YAS_CONSTEXPR_IF ( F & yas::compacted ) {
        // 如果 v>=128
        if ( v >= (1u<<7) ) {
            // 计算存储size
            const std::uint8_t ns = storage_size(v);
            // 写存储size
            write(ns);
            // 写数据
            __YAS_THROW_WRITE_ERROR(ns != os.write(&v, ns));
        } else {    // 如果 v<128, 则使用1字节存储
            // one byte
            T t{v};
            // 第8bit置1
            t |= __YAS_SCAST(std::uint8_t, 1u<<7);
            // 写入数据
            __YAS_THROW_WRITE_ERROR(1 != os.write(&t, 1));
        }
    } else {
        __YAS_CONSTEXPR_IF ( __YAS_BSWAP_NEEDED(F) ) {
            T t = endian_converter::bswap(v);
            __YAS_THROW_WRITE_ERROR(sizeof(t) != os.write(&t, sizeof(t)));
        } else {
            __YAS_THROW_WRITE_ERROR(sizeof(v) != os.write(&v, sizeof(v)));
        }
    }
}

压缩无符号整数的读取

    // for unsigned
    template<typename T>
    void read(T &v, __YAS_ENABLE_IF_IS_UNSIGNED_INTEGER(T)) {
        __YAS_CONSTEXPR_IF ( F & yas::compacted ) {
            // 读取第1个字节
            std::uint8_t ns = __YAS_SCAST(std::uint8_t, is.getch());
            // 是否是1字节存储
            const bool onebyte = __YAS_SCAST(bool, (ns >> 7) & 1u);
            // 计算数据长度
            ns &= ~(1u << 7);
            if ( __YAS_LIKELY(!onebyte) ) { // 如果不是1字节
                __YAS_THROW_READ_STORAGE_SIZE_ERROR(sizeof(v) < ns);
                // 读取压缩后的数据
                __YAS_THROW_READ_ERROR(ns != is.read(&v, ns));
            } else {    // 如果是1字节
                // 当前的ns就是要读取的数据
                v = __YAS_SCAST(T, ns);
            }
        } else {
            __YAS_THROW_READ_ERROR(sizeof(v) != is.read(&v, sizeof(v)));
            __YAS_CONSTEXPR_IF ( __YAS_BSWAP_NEEDED(F) ) {
                v = endian_converter::bswap(v);
            }
        }
    }

总结

纵观yas数据压缩的原理,我认为最大的亮点是:

  • 假设整数的长度不会超过64位10进制,实际也应该是这个样子的
  • 使用最小可保存数据的字节数进行数据的压缩
  • 针对1字节数据的存储,直接将符号、是否为1字节、1字节数据本身都存储在这个1字节数据中

整数的压缩存储,就是上面这个样子了,是不是还挺简单。浮点数因为没有进行压缩,所以这里没有提,大家可以直接阅读源码 yas_comment

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值