因为GO中的string类型是只读不可改变的类型,实际中我们经常会遇到改变一个字符串中的某几个字符,如果通过go中不可变的string类型来做比较麻烦,可以让原数据是[]byte类型,在改变其中几个字符的时候直接通过下标修改[]byte里面的内容,在需要string的时候通过string([]byte)生成,这有可能提高效率,还不会产生太多的子字符串浪费内存,同时导致GC任务加重。这个操作需要注意的是,string([]byte)新生成的string是重新开辟了一块内存来保存(s1 := string(byte1)),所以在强制转换之后,如果修改原来[]byte的某几个字符(byte1[1] = 'x'),这强制转换之后的string不会跟随改变(s1[1]还是原来的字符,而不是后来改变的'x')。看如下分析过程。
package main
//仿造string的底层结构
type FackeString struct {
ptr *byte
len int
}
//仿造[]byte的底层结构
type FackeByteSlice struct {
ptr *byte
len int
cap int
}
func main() {
var tmp FackeString
var byte1 = []byte("hai")
s1 := string(byte1)
//s1是通过强制转换[]byte而来,s1底层的dataPtr与[]byte底层的ptr指向不同的内存地址,
//下面是证明:
//利用unsafe包,强制吧byte1的起始地址看成FackeByteSlice的起始地址,并赋值给tmpByte
tmpByte := *((*FackeByteSlice)(unsafe.Pointer(&byte1)))
//打印byte1的一些信息:
fmt.Printf("size of bytes :%d, len(byte1):%d, cap(byte1):%d,&byte1[0]:%p\n",
unsafe.Sizeof(byte1), len(byte1), cap(byte1), &byte1[0])
//通过tmpByte查看byte1里面的东西:
fmt.Printf("tmpByte-> len:%d, cap:%d, ptr:%p\n",
tmpByte.len, tmpByte.cap, tmpByte.ptr )
/*这里输出:
size of bytes :24, len(byte1):3, cap(byte1):8, &byte1[0]:0xc04205a208
tmpByte-> len:3, cap:8, ptr:0xc04205a208
*/
//强制把s1的起始地址看成FackeString 的起始地址,并赋值给tmp 变量
tmp = *((*FackeString)(unsafe.Pointer(&s1)))
//通过tmp查看s1里面的东西
fmt.Printf("len of string :%d, addr of data:%p, len of s1:%d\n",
len(s1), tmp.ptr, tmp.len)
/*这里输出:
len of string :3, addr of data:0xc04205a220, len of s1:3
这里tmp.ptr != tmpByte.ptr
*/
//进一步的证明
byte1[1] = 'o' //修改byte[1]
fmt.Printf("s1:%s, data addr: %x \n",
s1, *((*uintptr)(unsafe.Pointer(&s1))) )
//输出:s1:hai, data addr: c04205a220
//修改了byte[1]之后,s1还是原来的hai。
}
浪费时间在这里做这些,而不去泡妞,就为了得到开头的那个结论,可以回头再看看,也不知道值不值得呢~~