十六进制保存数据,减少包体积
随着前端项目的复杂度越来越高,打包后的文件体积也越来越大,这直接影响到页面的加载速度。为了优化加载速度,我们需要采取各种方式来减小包的体积。使用 16 进制存储就是一种非常有效的方法。
以 @tenado/lunarjs 库为例,它提供了阴阳历转换以及获取节日信息等功能。如果我们查看它的源码,可以看到使用了 16 进制存储。例如src/lunar.js
js
# src/lunar.js
export default [ 0x4bd8, 0x4ae0, 0xa570, 0x54d5, 0xd260, ... ];
这些信息如果用字符串存储,会占用很多字节。但使用 16 进制后,每个阴历信息只需要 6 个字符串。这极大地减少了库的大小。在实际生产环境中,使用 16 进制存储甚至可以节省几十 KB 的大小。
此外,16 进制还可以用于压缩其他数据,比如图片等资源。使用 16 进制编码,可以达到无损压缩的效果,相比传统压缩算法可以减小体积而不影响质量。
总之,16 进制编码是一种非常高效的存储方式,可以大幅减小项目的打包体积,提升页面加载速度。在前端优化中,合理使用 16 进制编码是一个非常重要和有效的手段。
16 进制的基本概念
16 进制在数学中是一种逢16进1的进位制。一般用数字0到9和字母A到F表示,其中:A~F
相当于十进制的10~15,这些称作十六进制数字,在 js 中,16 进制使用 0x
前缀表示。
它的优点是可以使用更少的位数来表示一个数值,每个 16 进制位数代表 4 个二进制位,例如0x10
,表示16进制的10,十进制的16,二进制表示为00010000
,占用 4 个二进制位。
16 进制在代码中的表示
在库@tenado/lunarjs 中,保存了 1900-2100 年的阴历信息,数据格式如下:
js
{
1900: {
year: 1900,
firstMonth: 1,
firstDay: 31,
isRun: false,
runMonth: 8,
runMonthDays: 29,
monthsDays: [29, 30, 29, 29, 30, 29, 30, 30, 30, 30, 29, 30],
},
1901: {
year: 1901,
firstMonth: 2,
firstDay: 19,
isRun: false,
runMonth: 0,
runMonthDays: 0,
monthsDays: [29, 30, 29, 29, 30, 29, 30, 29, 30, 30, 30, 29],
},
}
将 1900-2100 年的数据存起来,占用的体积大概是 41k,作为包来说这个体积很大,这里存为十六进制数据,将数据压缩到 4k。
如何压缩数据呢?查看数据规律,我们发现:
闰月天数:只有三种值,即 0、29、30,在计算的时候先判断是否为闰月,再计算天数,0 代表 29, 1 代表 30
1-12 月天数,天数可以为 29 和 30,分别用 0 和 1 表示
闰月月份,闰月可能为 1-12,因此我们使用4个二进制数表示,最大可以表示 16
用 17 个二进制数表示阴历数据信息,从右到左:
17 | 16-5 | 4-1 |
---|---|---|
闰月天数 | 1-12 月天数 | 闰月月份 |
这里transform/index.js实现了一个简单的转换处理,将数据转换为 1900-2100 年依次按 index 排序的数组,数组的每一项里面存储了该年的阴历信息。
使用位运算从 16 进制中还原数据
位运算是将参与运算的数字转换为二进制,然后逐位对应进行运算。
按位与& 按位与运算为:两位全为1,结果为1,即1&1=1,1&0=0,0&1=0,0&0=0
按位或| 按位或运算为:两位只要有一位为1,结果则为1,即1|1=1,1|0=1,0|1=1,0|0=0
异或运算^ 两位为异,即一位为1一位为0,则结果为1,否则为0。即1 ^ 1=0,1 ^ 0=1,0 ^ 1=1,0 ^ 0=0
取反~ 将一个数按位取反,即~ 0 = 1,~ 1 = 0
右移>> 将一个数右移若干位,右边舍弃,正数左边补0,负数左边补1。每右移一位,相当于除以一次2 例如8 >> 2
表示将8的二进制数1000右移两位变成0010 例如i >>= 2
表示将变量i的二进制右移两位,并将结果赋值给i
设置二进制指定位置的值为1value | (1 << position)
,例如设置十进制数8(1000)的第2位二进制数为1,注意这里index从0开始,且是从右向左计算,可以这样做8 | (1 << 2)
,结果为1100
设置二进制指定位置的值为0value & ~(1 << position)
,例如设置十进制数8(1000)的第3位二进制数为0,注意这里index从0开始,可以这样做8 & ~(1 << 3)
,结果为0000
1、获取 1-4 位存储的闰月月份信息
取出16进制数据中存储的,从右边数1-4位数据,使用二进制数1111
和十六进制数据按位与运算,可以获取到月份信息。通过转换1111
可以得到对应的十六进制为0xf
例如,从src/lunar.js的数据里面获取1900年,即index为0的数据,进行位运算,0x4bd8 & 0xf
可以得到结果为8,即1900年的8月为闰月。
2、获取 5-16 位存储的月天数信息
取出16进制数据中存储的,从右边数5-16位数据,仍旧可以使用按位与运算,获取到月天数信息。
从16开始的二进制数为1000000000000000
,对应的十六进制为0x8000
,到4结束的二进制数为1000
,对应的十六进制为0x8
,每次向右移一位进行按位与
计算,可以获取到1-12月的天数数据,可以这样计算:
js
let sum = 0;
const lunar = 0x4bd8;
for (let i = 0x8000; i > 0x8; i >>= 1) {
sum += lunar & i ? 1 : 0;
}
3、获取 17 位存储的闰月天数信息
取出16进制数据中存储的,从右边数17位数据,使用二进制数10000000000000000
和十六进制数据按位与运算,可以获取到闰月天数信息,10000000000000000
对应的十六进制为0x10000
,0x4bd8 & 0x10000
可以得到结果为0,即1900年的闰月天数为29。
总结
总结起来,使用16进制保存数据可以有效减小包体积,提高前端项目的加载速度。在具体实现上,可以利用位运算来从16进制中还原数据。以下是一些关键点的总结:
1、数据格式
数据以16进制形式存储,可以有效减小体积。在JavaScript中,16进制使用0x前缀表示,例如0x4bd8。
2、数据规律
观察数据规律,了解存储的信息是如何组织的,包括每部分数据的含义和位数
3、使用位运算还原数据
使用位运算可以从16进制中还原具体的数据。以下是一些常用的位运算操作:
text
按位与&: 用于提取指定位的信息。
右移>>: 用于将二进制数向右移动,类似于除以2的操作。
设置二进制指定位置的值为1: 使用value | (1 << position)操作。
设置二进制指定位置的值为0: 使用value & ~(1 << position)操作。
4、注意事项
使用16进制存储需要在代码中添加注释,以便他人理解和维护。
数据存储格式的选择要根据具体场景和需求,权衡可读性和体积优化。
综合以上总结,合理使用16进制存储数据是一种有效的前端优化手段,特别适用于需要大量静态数据的情况。