GO 中 string 的实现原理
上次咱们分享的内容咱回顾一下
- 分享了 ETCD 的简略单点部署,ETCD 应用到的包装置,以及会遇到的问题
- ETCD 的设置 和 获取 KEY
- ETCD 的 WATCH 监控 KEY 的简化
- ETCD 的租约 和保活机制
- ETCD 的分布式锁的简略实现
要是对 GO 对 ETCD 的编码还有点趣味的话,欢送查看文章 GO 中 ETCD 的编码案例分享
字符串是什么?
他是一种根本类型(string 类型
),并且是一个 不可扭转的 UTF-8字符序列
在泛滥编程语言外面,置信都少不了字符串类型
字符串,顾名思义就是一串字符,咱们要明确,字符也是分为中文字符和英文字符的
例如咱们在 C/C++
中,一个英文字符占 1 个字节,一个中文字符有的占 2 个字节,有的占 3 个字节
用到 mysql
的中文字符,有的占 4 个字节
回过来看 GO 外面的字符串,字符也是依据英文和中文不一样,一个字符所占用的字节数也是不一样的,大体分为如下 2 种
- 英文的字符,依照 ASCII 码来算,占用 1 个字节
- 其余的字符,包含中文字符在内的,依据不同字符,占用字节数是 2 — 4个字节
字符串的数据结构是啥样的?
说到字符串的数据结构,咱们先来看看 GO 外面的字符串,是在哪个包外面
不难发现,咱们轻易在 GOLANG 外面 定义个string 变量
,就可能晓得 string 类型是在哪个包外面,例如
var name string
GO 外面的字符串对应的包是 builtin
// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string
- 字符串这个类型,是所有
8-bits
字符串的汇合,通常但不肯定示意utf -8
编码的文本 - 字符串能够为空,但不能为
nil
,此处的字符串为空是""
- 字符串类型的值是不可变的
另外,找到 string
在 GO 外面对应的源码文件中 src/runtime/string.go
,有这么一个构造体,只提供给包内应用,咱们能够看到string
的数据结构 stringStruct
是这个样子的
type stringStruct struct {
str unsafe.Pointer
len int
}
整个构造体,就 2 个成员,string 类型是不是很简略呢
- str
是对应到字符串的首地址
- len
这个就是不难理解,是字符串的长度
那么,在创立一个字符串变量的时候,stringStruct
是在哪里应用到的呢?
咱们看看 GO string.go 文件中的源码
//go:nosplit
func gostringnocopy(str *byte) string {ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} // 构建成 stringStruct
s := *(*string)(unsafe.Pointer(&ss)) // 强转成 string
return s
}
//go:nosplit
func findnull(s *byte) int {
if s == nil {return 0}
// Avoid IndexByteString on Plan 9 because it uses SSE instructions
// on x86 machines, and those are classified as floating point instructions,
// which are illegal in a note handler.
if GOOS == "plan9" {p := (*[maxAlloc/2 - 1]byte)(unsafe.Pointer(s))
l := 0
for p[l] != 0 {l++}
return l
}
// pageSize is the unit we scan at a time looking for NULL.
// It must be the minimum page size for any architecture Go
// runs on. It's okay (just a minor performance loss) if the
// actual system page size is larger than this value.
const pageSize = 4096
offset := 0
ptr := unsafe.Pointer(s)
// IndexByteString uses wide reads, so we need to be careful
// with page boundaries. Call IndexByteString on
// [ptr, endOfPage) interval.
safeLen := int(pageSize - uintptr(ptr)%pageSize)
for {t := *(*string)(unsafe.Pointer(&stringStruct{ptr, safeLen}))
// Check one page at a time.
if i := bytealg.IndexByteString(t, 0); i != -1 {return offset + i}
// Move to next page
ptr = unsafe.Pointer(uintptr(ptr) + uintptr(safeLen))
offset += safeLen
safeLen = pageSize
}
}
简略分为 2 步:
- 先将字符数据构建程 stringStruct
- 再通过 gostringnocopy 函数 转换成 string
字符串中的数据为什么不能被批改呢?
从上述官网阐明中,咱们能够看到,字符串类型的值是不可变的
可是这是为啥呢?
咱们以前在写 C/C++
的时候,为啥能够开拓空间寄存多个字符,并且还能够批改其中的某些字符呢?
可是在 C/C++
外面的字面量也是不能够扭转的
GO 外面的 string 类型,是不是也和 字面量一样的呢?咱们来看看吧
字符串类型,自身也是领有对应的内存空间的,那么批改 string
类型的值应该是要反对的。
可是,XDM
在 Go 的实现中,string
类型是不蕴含内存空间,只有一个内存的指针,这里就有点想 C /C++ 外面的案例:
char * str = "XMTONG"
上述的 str
是相对不能做批改的,str
只是作为可读,不能写的
在 GO 外面的字符串,就与上述相似
这样做的益处是 string
变得十分轻量,能够很不便的进行传递而不必放心内存拷贝(这也防止了内存带来的诸多问题)
GO 中的 string
类型个别是指向字符串字面量
字符串字面量存储地位是在虚拟内存分区的 只读段 下面,而不是堆或栈上
因而,GO 的 string
类型不可批改的
可是咱们想一想,要是在 GO 外面字符串全都是只读的,那么咱们如何动静批改一些咱们须要扭转的字符呢,这岂不是缺点了
别慌
GO 外面还有 byte 数组,[]byte
这里顺带说一下
上述 char * str = "XMTONG"
- 字符串长度,就是字符的个数,为 6
- 计算
str
所占字节数(C/C++ 中是通过sizeof()
来计算的)的话,那就是 7,因为尾巴前面还有一个 ’\0′
计算机中有这样的对应关系,简略提一下:
1 Bytes = 8 bit
1 K = 1024 Bytes
1 M = 1024 K
1 G = 1024 M
为什么有了字符串 还要 []byte?
起因正如上述咱们说到的,如果全是一些只读的字面量,那么咱们编码的时候就没得玩了
另外,也是依据应用字符串的场景起因,单是 string 无奈满足所有的场景,因而得有一个咱们能够批改外面值的 []byte 来补救一下
说到这里,咱们应该就晓得了,string
和 []byte
都是能够示意字符串,没故障,
不过,他们毕竟对应不同的数据结构,应用形式也有肯定的区别,GO 提供的对应办法也是不尽相同
咱们来看看什么场景用 string 类型,啥场景 应用 []byte 类型
应用到 string 类型 的 中央:
- 须要对字符串进行比拟的时候,应用 string 类型 十分不便,间接应用操作符进行比拟即可
- string 类型 类型,为空的时候是 “”,他不能和
nil
做比拟,因而,不必到nil
的时候,也能够应用 string 类型
应用到 []byte 类型 的 中央:
- 须要批改字符串中字符的利用场景,应用 []byte 类型 就相当灵便了,用起来很香
- []byte 类型 为空的话,会是返回 nil,须要应用到 nil 的时候,就能够应用他
- []byte 类型 自身就能够依照切片的形式来玩,因而须要操作切片的时候,也能够用他
就上述场景来看,如同应用 []byte 更加切实和灵便,为啥还要用 string?
起因如下:
- string 类型 看起来直观,用起来简略
- []byte,byte 数组,咱们能够晓得,外面都是一个字节一个字节的,这个会比拟多的用在底层,对操作字节比拟关注的时候
字符串 和 []byte 如何相互转换?
看到这里,别离理解了 string
类型,和 []byte
类型的利用场景
毋庸置疑,咱们编码过程中,必定少不了对他们做互相转换,咱们来看看在 GO,外面如何应用
字符串转 []byte
package main
import ("fmt")
func main(){
var str string
str = "XMTONG"
strByte := []byte(str)
for _,v :=range strByte{fmt.Printf("%x",v)
}
}
代码输入为:
58 4d 54 4f 4e 47
上述代码转成 []byte
之后是一个字节,一个字节的
将每一个字节的值用十六进制打印进去,咱们能够看到,XMTONG
对应 584d544f4e47
[]byte 转字符串
[]byte
转字符串在 GO 外面那就更简略了
func main(){name := []byte("XMTONG")
fmt.Println(string(name))
}
GO 中 字符串都会波及到哪些函数?
无论什么语言,对于字符串大略波及如下几种操作,若有偏差,还请斧正:
- 计算字符串长度
- 拼接
- 切割
- 找到字串进行替换,找到字符串的具体位置和呈现的次数
- 统计字符串
- 字符串进制转换
具体的函数应用办法也比较简单,举荐大家感兴趣的能够间接看 go 的开发文档,须要的时候去查一下即可。
GO 的规范开发文档,在搜索引擎外面还是比拟容易搜寻到的
总结
- 分享了字符串具体是啥
- GO 中字符串的个性,为什么不能被批改
- 字符串 GO 源码是如何构建的
- 字符串 和
[]byte
的由来和利用场景 - 字符串与
[]byte
互相转换 - 顺带提了 GO 的规范开发文档,大家能够用起来哦
欢送点赞,关注,珍藏
敌人们,你的反对和激励,是我保持分享,提高质量的能源
好了,本次就到这里,下一次 GO 中 slice 的实现原理分享
技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。
我是 小魔童哪吒,欢送点赞关注珍藏,下次见~