>

在对 Go 语言中的数据进行加密解密的时候会需要把一些常用的数据类型转换成 []byte,因为 Go 提供的加解密函数接受的是 []byte 类型的输入。
本文探讨如何把常用的基本数据类型转换成 []byte,由于 string 类型的变量天然可以通过 []byte 强制转换,剩下的需要额外步骤来转换的基本数据类型主要就是数字类型了,比如 int, int32, uint64 等等。

把一个 int 类型的数据转换成 []byte 主要有下面几种:

1. Use binary.Write() to convert a numeric value to []byte

通过 Go 标准库提供的 binary 包,我们可以把指定大小的数字类型数据转换成 []byte,可参考如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// var num uint16 = 1234
// binary.Write() does not accept int, it only accepts a numeric type with specific size, e.g. int32, float64, etc.
func testBinaryWrite(x interface{}) []byte {
buf := new(bytes.Buffer)
// for int32, the resulting size of buf will be 4 bytes
// for int64, the resulting size of buf will be 8 bytes
err := binary.Write(buf, binary.BigEndian, x)
if err != nil {
fmt.Println("binary.Write failed:", err)
}
fmt.Printf("% x\n", buf.Bytes())
return buf.Bytes()
}

2. Convert to string first, then convert the resulting string to []byte

主要思路是:先把 int 转换成 string,然后再把 string 转换成 []byte。
从数字类型转换成 string 类型相关的函数有:

1
2
3
4
func FormatBool(b bool) string
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
func FormatInt(i int64, base int) string
func FormatUint(i uint64, base int) string

还可以使用下面的函数,它们可以把任意类型转换成 string。

1
2
func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string

提示:推荐是用 FormatXXX 函数而不是 Sprintf 函数来进行转换,因为后者的效率比较低。

测试例子:

1
2
3
4
5
6
7
8
9
func testStrconv(x interface{}) []byte {
v, ok := x.(int)
if !ok {
fmt.Println("x is not int")
return nil
}
return []byte(strconv.Itoa(v))

}

3. Use fmt.Fprint() to convert anything to []byte

函数原型

1
2
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

测试例子:

1
2
3
4
5
6
7
8
9
// fmt.PrintF 的默认写入格式是按照 ASCII 字符来写入。
// 比如,如果一个 int 类型的变量 a = 12345,fmt.PrintF 会把 12345 看做是 5 个 ASCII 字符(每个 ASCII 字符是一个 byte 大小)
// 因此,转换的结果就是对应的 ASCII 编码所表示的一串 byte [49 50 51 52 53]
func testPrintFWrite(x interface{}) []byte {
buf := new(bytes.Buffer)
cnt, _ := fmt.Fprint(buf, x)
fmt.Println("byte counts: ", cnt)
return buf.Bytes()
}

hex to string

加解密程序中,如获取 md5 值等可能会经常需要用到把十六进制数([]byte)转换成 string。
通常有两种方法:fmt.Sprintfhex.EncodeToString。 下面通过一个程序来比较一下两者的性能差别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"time"
)

const LOOP = 10000

func makeMd5(data string) []byte {
h := md5.New()
io.WriteString(h, data)
return h.Sum(nil)
}

func main() {
s := "123456"
hexBytes := makeMd5(s)
s1 := fmt.Sprintf("%x", hexBytes)
s2 := hex.EncodeToString(hexBytes)
fmt.Println("result of fmt.Sprintf == hex.EncodeToString:", s1 == s2) // 确保结果一致

start := time.Now()
for i := 0; i < LOOP; i++ {
fmt.Sprintf("%x", hexBytes)
}
fmt.Printf("fmt.Sprintf taken: %v\n", time.Since(start))

start = time.Now()
for i := 0; i < LOOP; i++ {
hex.EncodeToString(hexBytes)
}
fmt.Printf("hex.EncodeToString taken: %v\n", time.Since(start))
}

运行结果:

result of fmt.Sprintf == hex.EncodeToString: true
fmt.Sprintf taken: 10.285488ms
hex.EncodeToString taken: 2.080457ms

可见,fmt.Sprintf 所需要的时间大约是 hex.EncodeToString 的 5 倍。

结论:最好避免使用 fmt.Sprintf 来转换数据类型(除非必要或者对性能要求不高)。