背景
今年在公司内部主导了两个的行情数据系统的构建,两者均使用到了常见的时序数据压缩算法。
这里简单总结一下过程中积累的一些经验。
让我们先来思考一个问题:压缩算法生效的前提是什么?
数据本身至少要符合以下两种特性其一:
- 数据存在冗余
- 数据符合特定的概率分布
在时序数据领域,数据冗余度与相似度较高,因此天生适合进行压缩。
但对于不同类型的数据,其所适用的压缩算法也大相径庭。
下面我们逐一介绍这些数据相应的压缩算法。
整数
整型数据是构建各种应用的基石,时序型应用也不例外。
在行情数据中,存在大量的整型数据,例如:逐笔成交中的时间戳、成交量。
根据压缩算法的不同,可以将整型数据分为以下 3 类:
- 无符号整型 —— Varint
- 有符号整型 —— ZigZag
- 时间戳 —— Delta2 + Simple8b
Varint
一个 32 位的无符号整型能表达 0 - 4294967295 之间的任意数字
但这些数字在日常生活中出现的概率并不是均匀分布的,一个著名的例子是 本福特定律 ,该定律常被用于辨别数据的真伪。
通常情况下,较小的数字出现的概率会高于极大的数据。
以年龄为例,无论人口如何分布,大部分人的年龄都位于 0 ~ 100 之间。
表示 128 仅需要 7bit 足矣,如果使用 32bit 的无符号整型进行存储,意味着至少浪费了 24bit。
幸运的是,我们能通过一种自适应编码方式来减少这种浪费 —— Varint。
public class VarIntCodec { static int encodeInt(int v, byte[] bytes, int offset) { if (v < 0) { throw new IllegalStateException(); } else if (v < 128) { bytes[offset++] = (byte) v; } else if (v < 16384) { bytes[offset++] = (byte) (v | 0x80); bytes[offset++] = (byte) ((v >>> 7) & 0x7F); } else if (v < 2097152) { bytes[offset++] = (byte) (v | 0x80); bytes[offset++] = (byte) ((v >>> 7) | 0x80); bytes[offset++] = (byte) (v >>> 14); } else if (v < 268435456) { bytes[offset++] = (byte) (v | 0x80); bytes[offset++] = (byte) ((v >>> 7) | 0x80); bytes[offset++] = (byte) ((v >>> 14) | 0x80); bytes[offset++] = (byte) (v >>> 21); } else { bytes[offset++] = (byte) (v | 0x80); bytes[offset++] = (byte) ((v >>> 7) | 0x80); bytes[offset++] = (byte) ((v >>> 14) | 0x80); bytes[offset++] = (byte) ((v >>> 21) | 0x80); bytes[offset++] = (byte) (v >>> 28); } return offset; } static int decodeInt(byte[] bytes, int[] offset) { int val; int off = offset[0]; byte b0, b1, b2, b3; if ((b0 = bytes[off++]) >= 0) { val = b0; } else if ((b1 = bytes[off++]) >&