一 slice 作为函数参数
在go 中,slice 结构体如下:
type slice struct {
// uintptr 是一种特殊的无符号整型,实际占一个机器字大小(32位系统占有4个字
//节,64位系统占有8个字节),而指针类型*T同样占一个机器字大小,所以uintptr
// 足以存储一个指针
Data uintptr
Len int
Cap int
}
在下文示例中,go中如果让params作为函数参数,那么相当于拷贝生成了一个slice结构体params2,结构体params2的Data与原来的结构体params中的Data指向内存中相同的位置。但是如果在Test方法中对params 进行扩容,新的结构体params2指向的内存空间位置必然会发生改变,但原来的结构体params指向的内存空间则不会发生改变。
func Test( params []int) {
}
如果希望将切片的变化从方法中传出,则可以传入一个结构体指针,如下所示:
func Test( params *[]int){
}
测试如下:
func main() {
m := make([]int, 0, 2)
m = append(m, 0)
m = append(m, 1)
Test(m)
fmt.Println(m)
Test2(&m)
fmt.Println(m)
}
func Test( params []int){
params = append(params, 2)
}
func Test2(params *[]int){
*params = append(*params, 3)
}
// 输出:
//[0 1]
//[0 1 3]
二 []byte 与 string 的转换
在项目代码中,鉴权过程需要对字符串类型转换为byte数组,常规方式如下所示,在需要频繁进行转换的场景下,会影响程序性能:
var a = []byte("hello boy")
var b = string(a)
后来参考如下的处理方式,
transfer
从类型type上看,string相当于 [2]uintptr , [ ]byte相当于 [3]uintptr ,因此直接进行转换操作。
func str2bytes(s string) []byte {
x := (*[2]uintptr)(unsafe.Pointer(&s))
// 类型转换, *string -> &[2]uintptr
h := [3]uintptr{x[0], x[1], x[1]}
// 生成新的类型描述 , [3]uintptr
return *(*[]byte)(unsafe.Pointer(&h))
// 类型转换,&[3]uintptr -> *[ ]byte ,然后取值。
}
func bytes2str(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
后来发现在go源码库中提供了更清晰的实现方式:
// 源码库中reflect包
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
type StringHeader struct {
Data unsafe.Pointer
Len int
}
// github.com/cespare/xxhash/xxhash_unsafe.go
func Sum64String(s string) uint64 {
var b []byte
bh := (*SliceHeader)(unsafe.Pointer(&b))
bh.Data = (*StringHeader)(unsafe.Pointer(&s)).Data
bh.Len = len(s)
bh.Cap = len(s)
return Sum64(bh)
}
三 结构体的拷贝
以go 源码中 request.go 中WithContext 方法作为示例:
这个方法是一个Request的浅拷贝实现,
考虑到新Request中的URL 是指针类型,因此需要进行深复制.
// net/http/request.go
tyep Request struct{
Method string
URL *url.URL
Header Header
Body io.ReadCloser
ctx context.Context
...
}
func (r *Request) withContext(ctx context.Context) *Request {
if ctx == nil { panic{" nil context") }
r2 := new(Request)
*r2 = *r
r2.Ctx = ctx
// Deep copy the URL because it is not a map, and the url is
// mutable by users WithContext
if r.URL != nil {
r2Url := new(url.URL)
*r2Url = *r.URL
r2.URL = r2Url
}
return r2
}