奇淫技巧之golang 數字字串壓縮儲存
需求
首先說下需求。
最近一個朋友,遇到一個大資料處理,需要大量節約字串空間,給我提了一個需求。大概是如此:
給定類似如下字串,是一個由浮點陣列成的字串數字
"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
總結
在適當的時候使用位運算,能起到極好的作用。
很多通訊協議,都是使用位來控制協議內容以及編解碼。
「其他文章」
- golang json 效能分析
- MySQL的鎖機制 - 記錄鎖、間隙鎖、臨鍵鎖 - 咖啡屋小羅的文章 - 知乎 http://zhuanlan.zhihu.com/p/48269420
- 現在面試都這麼直接的嘛?(golang map)
- 現在面試都這麼直接的嘛?(golang 切片)
- 協程究竟比執行緒能省多少開銷?
- 微服務架構的演進過程
- mysql千萬級大表的優化
- b tree演進
- TCP和併發相關知識梳理
- dfs遍歷二叉樹
- 資料結構-棧
- 貪心演算法
- 動態規劃2
- 動態規劃
- mongodb replica set
- 基於binlog的mysql主從同步
- Redis 的落地策略
- 奇淫技巧之golang 數字字串壓縮儲存
- rtmp協議詳解 (二)Chunking 組塊
- rtmp協議詳解 (一) handshake