奇淫技巧之golang 數字字串壓縮儲存

語言: CN / TW / HK

需求

首先說下需求。

最近一個朋友,遇到一個大資料處理,需要大量節約字串空間,給我提了一個需求。大概是如此:

給定類似如下字串,是一個由浮點陣列成的字串數字

"499.00 499.00 490.00 490.00 47345"

要求的結果是什麼呢?能生成一個壓縮後的結果,儘可能減少儲存空間,也就是字元長度。

也就是傳說中的:

怎麼實現我不管,我只要它能變短

解決思路

emmm,一開始,根本沒理解需求,和他撕了半天之後,才理解。

於是我死來想去,想出3種解決方案。

  1. zlib glib庫
  2. map儲存字元出現次數和位置
  3. 不告訴你,看下文吧。

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

總結

在適當的時候使用位運算,能起到極好的作用。

很多通訊協議,都是使用位來控制協議內容以及編解碼。

分享到: