首先说下需求。
最近一个朋友,遇到一个大数据处理,需要大量节约字符串空间,给我提了一个需求。大概是如此:
给定类似如下字符串,是一个由浮点数组成的字符串数字
"499.00 499.00 490.00 490.00 47345"
要求的结果是什么呢?能生成一个压缩后的结果,尽可能减少存储空间,也就是字符长度。
也就是传说中的:
怎么实现我不管,我只要它能变短
解决思路
emmm,一开始,根本没理解需求,和他撕了半天之后,才理解。
于是我死来想去,想出3种解决方案。
- zlib glib库
- map存储字符出现次数和位置
- 不告诉你,看下文吧。
zlib glib库
这个实现方案很简单,go提供了相应的包。 只需要调用api就行了
b := []byte(`499.00 499.00 490.00 490.00 47345`)
w := zlib.NewWriter(&in)
w.Write(b)
w.Close()
fmt.Println(len(b))
fmt.Println(len(in.Bytes()))
var out bytes.Buffer
r, _ := zlib.NewReader(&in)
io.Copy(&out, r)
fmt.Println(out.String())
但是运行结果,不是很明显,33个字符长度只压缩到了27。
再仔细看,NewWriter可以设置压缩比例(NewWriterLevel)。我设置为9(最好压缩flate.BestCompression),对acsii中的字符并没有更多的优化。
无奈,只好另寻方案。
map存储字符出现次数和位置
此方案就是遍历字符串,然后存储响应位置。 方案代码就不贴了,实际效果很差。
位运算合并存储
看需求分析,我们得到的是一个字符串为"float float float float int",所有字符由'0~9','.',' '组成。
嗯? 有漏洞。
一个字节8bit(11111111),那么,1111 => 最多可以表示16种字符,完全可以使用位替代。高位一个字符,低位一个字符。
说做咱就做啊。
比如:
49=> 00000101 00001010
合并为一个
01011010
特殊字符怎么办?我们不是只用了0~9嘛,用剩下的, 比如10 替换成空格, 11替换成.。
高低位置换?这个比较简单:
x:= value << 4 //高位使用value
x = value | (x) //低位使用value
还有一个细节,当我们整个字符串为奇数位时,要进行特殊操作。高位为value,低位要补充一个字符标识结束了。我这里用的12,比较随意
if i == l-1 {
if supply {
value = x << 4
value = value | 12
res[i/2] = value
break
}
}
具体实现如下:
func CompressFloatString(str string) []byte {
l := len(str)
supply := false
var res []byte
if l%2 != 0 {
supply = true
res = make([]byte, l/2+1)
} else {
res = make([]byte, l/2)
}
var value uint8
for i, v := range str {
v8 := uint8(v)
x := NumberConvertNormal(v8)
if i == l-1 {
if supply {
value = x << 4
value = value | 12
res[i/2] = value
break
}
}
if i%2 == 0 {
value = x << 4
} else {
value = value | (x)
res[i/2] = value
}
}
return res
}
func NumberConvertNormal(v8 uint8) uint8 {
var x uint8
switch {
// 0-9
case v8 > 47 && v8 < 58:
return v8 - 48
case v8 == 46: //为小数点
return 11
case v8 == 32: //为空格
return 10
}
return x
}
解码的过程一样,反过来就行。
func NumberConvertSmall(n uint8) uint8 {
switch {
case n < 10:
return n + 48
case n == 11:
return 46
case n == 10:
return 32
case n == 12:
return 0
}
return 0
}
func UnCompressFloatString(bt []byte) []byte {
var res []byte
for _, v := range bt {
res = append(res, NumberConvertSmall(v>>4))
vNum := NumberConvertSmall(v & 15)
if vNum == 0 {
break
}
res = append(res, vNum)
}
return res
}
实现结果为:
原始串字符 499.00 499.00 490.00 490.00 47345
原始串bytes [52 57 57 46 48 48 32 52 57 57 46 48 48 32 52 57 48 46 48 48 32 52 57 48 46 48 48 32 52 55 51 52 53]
原始串bt len 33
压缩串bytes [73 155 0 164 153 176 10 73 11 0 164 144 176 10 71 52 92]
压缩串字符 I� ���
I ���
G4\
压缩串bt len 17
还原 499.00 499.00 490.00 490.00 47345
总结
在适当的时候使用位运算,能起到极好的作用。
很多通信协议,都是使用位来控制协议内容以及编解码。