开发go程序过程中遇到的问题以及解决方法

hash

使用 crypto.sha256 可以获得文件哈希:

1
2
3
4
5
6
7
8
9
10
11
12
13
fileName := "main.go"
file, err := os.Open(fileName)
if err != nil {
log.Fatal(err)
}
defer file.Close()

hash := sha256.New()
if _, err = io.Copy(hash, file); err != nil {
log.Fatal(err)
}
sum := hash.Sum(nil)
fmt.Printf("Sum: %x\n", sum)

最终得到的哈希值 sum 的类型是 []byte ,若想要得到 string 类型的哈希值不能通过 string(sum) 的方式强制转换,而需要使用以下方法转换:

1
hash := hex.EncodeToString(sum)

json

使用 json.Marshal 方法可以将 struct 转换为字符串,但其中的特殊html字符会被转义成unicode,例如 & 会被转义为 \u0026 ,若不想被转义,则需要通过以下方法转换:

1
2
3
4
5
6
bf := bytes.NewBuffer([]byte{})
jsonEncoder := json.NewEncoder(bf)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.Encode(fdata.Data)
fileData := bf.String()
fmt.Println(f)

slice

拷贝

切片是底层数组的视图,实际指向一段地址

eg. 在将arr放入res,底层是将指向arr切片地址的指针放入res中。若不改变arr大小,仅改变arr中元素的值,会导致之前放入res中的arr产生变化(在递归中会碰到这样的问题)

1
2
3
4
5
6
7
8
9
10
11
12
arr := []int{1,2,3}
res := [][]int{}
res = append(res, arr)
// 改变arr中的值,不做扩容
arr[2] = 4
res = append(res, arr)
// arr扩容
arr = append(arr, 5)
res = append(res, arr)
fmt.Println(res)

// 输出:[[1 2 4] [1 2 4] [1 2 4 5]]

正确的做法是进行深拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
arr := []int{1,2,3}
res := [][]int{}
tmp := make([]int, len(arr))
copy(tmp, arr)
res = append(res, tmp)
// 改变arr中的值,不做扩容
arr[2] = 4
tmp = make([]int, len(arr))
copy(tmp, arr)
res = append(res, tmp)
// arr扩容
arr = append(arr, 5)
tmp = make([]int, len(arr))
copy(tmp, arr)
res = append(res, tmp)
fmt.Println(res)

// 输出:[[1 2 3] [1 2 4] [1 2 4 5]]

垃圾回收

底层的数组会被保存在内存中,直到它不再被引用。但是有时候可能会因为一个slice的小的内存引用而导致底层整个数组处于被使用的状态,这会延迟自动内存回收器对底层数组的回收

例如假设切片里存放的是指针对象,那么下面删除末尾的元素后,被删除的元素依然被切片底层数组引用,从而导致不能及时被自动垃圾回收器回收(这要依赖回收器的实现方式):

1
2
var a []*int{ ... }
a = a[:len(a)-1] // 被删除的最后一个元素依然被引用, 可能导致GC操作被阻碍

修复如下:

1
2
3
var a []*int{ ... }
a[len(a)-1] = nil // GC回收最后一个元素内存
a = a[:len(a)-1] // 从切片删除最后一个元素

另外,若对于一个很大的临时数组,只需要读取其中的一小部分,读取后整个数组不再需要放在var内存中,则应该传值到一个新的数组,而不是直接使用slice引用:

1
2
3
var bigArr []int{ ... }
a := append([]int{}, bigArr[1:3]...)

map

拷贝

与slice类似,map也是一个指针,若要进行深拷贝,需要对逐个键值对进行拷贝:

1
2
3
4
5
6
7
8
9
10
11
m := map[int]string{1: "a", 2: "b", 3: "c"}
mCopy := map[int]string{}
for k, v := range m {
mCopy[k] = v
}
m[4] = "d"
fmt.Println(m)
fmt.Println(mCopy)

// 输出:map[1:a 2:b 3:c 4:d]
// map[1:a 2:b 3:c]

package

同一目录下的同级文件属于一个包,main.go可以直接调用其他文件中的函数,但是在运行的时候需要使用 go run .

SQL

动态条件

SQL查询时存在不定长的 WHERE 子句,例如定义这样一个查询条件的结构体:

1
2
3
4
5
6
7
8
type Query struct {
Id *int64
FirstName *string
MiddleName *string
LastName *string
AreaId *int64
Birthday *time.Time
}

其中的每一个字段都不是必须存在于 WHERE 子句中的,这样就需要动态构造SQL语句,通过类似这种方式:

1
2
3
4
5
6
7
8
9
10
type Query map[string]interface{}

var model Query{}
var values []interface{}
var where []string
for k, v := range model {
values = append(values, v)
where = append(where, fmt.Sprintf("%s = ?", k))
}
string := ("SELECT name FROM users WHERE " + strings.Join(where, " AND "))