// slicebytetostringtmp returns a "string" referring to the actual []byte bytes. // // Callers need to ensure that the returned string will not be used after // the calling goroutine modifies the original slice or synchronizes with // another goroutine. // 调用者得确保不会在另外一个goroutine里修改源切片 // The function is only called when instrumenting // and otherwise intrinsified by the compiler. // 这个函数仅在检测时调用,或者是编译器优化的时候用。 // Some internal compiler optimizations use this function. // - Used for m[T1{... Tn{..., string(k), ...} ...}] and m[string(k)] // where k is []byte, T1 to Tn is a nesting of struct and array literals. // - Used for "<"+string(b)+">" concatenation where b is []byte. // - Used for string(b)=="foo" comparison where b is []byte. //byte切片转string,也是指针强转,指向的还是原来的地址,所以会相互影响 funcslicebytetostringtmp(b []byte)string { if raceenabled && len(b) > 0 { //竞争检测,忽略 racereadrangepc(unsafe.Pointer(&b[0]), uintptr(len(b)), getcallerpc(), funcPC(slicebytetostringtmp)) } if msanenabled && len(b) > 0 { //Memory Sanitizer 用于检测危险指针等内存问题,忽略 msanread(unsafe.Pointer(&b[0]), uintptr(len(b))) } return *(*string)(unsafe.Pointer(&b)) }
//string转byte切片,这个就不是指针强转了,是内存拷贝 funcstringtoslicebyte(buf *tmpBuf, s string) []byte { var b []byte if buf != nil && len(s) <= len(buf) { // 判断一下空间够不够 *buf = tmpBuf{} b = buf[:len(s)] } else { // 不够就去申请 b = rawbyteslice(len(s)) } // 然后拷贝 copy(b, s) return b }
//string转rune切片,一般情况是多字节编码的时候用(我猜的) funcstringtoslicerune(buf *[tmpStringBufSize]rune, s string) []rune { // two passes. // 双扫描算法。。也就是先for一次计数,然后判断空间,然后再for一次做正事 // unlike slicerunetostring, no race because strings are immutable. n := 0 forrange s { //注意这里是用range来遍历s来计算长度而不是用len,因为len算的是字节数 //对于多字节编码,比如中文来说,字数!=字节数 n++ }
var a []rune if buf != nil && n <= len(buf) { *buf = [tmpStringBufSize]rune{} a = buf[:n] } else { a = rawruneslice(n) }
n = 0 for _, r := range s { a[n] = r n++ } return a }
//rune切片转string,一般是多字节编码用(还是我猜的) funcslicerunetostring(buf *tmpBuf, a []rune)string { if raceenabled && len(a) > 0 { //竞争检测,忽略 racereadrangepc(unsafe.Pointer(&a[0]), uintptr(len(a))*unsafe.Sizeof(a[0]), getcallerpc(), funcPC(slicerunetostring)) } if msanenabled && len(a) > 0 { //Memory Sanitizer 用于检测危险指针等内存问题,忽略 msanread(unsafe.Pointer(&a[0]), uintptr(len(a))*unsafe.Sizeof(a[0])) } //这个变量没用,单纯为了调用encoderune这个函数,因为这个函数会把r转到第一个参数里面,下面再详细看这个函数 var dum [4]byte size1 := 0 for _, r := range a { // 算大小 size1 += encoderune(dum[:], r) } //申请空间 s, b := rawstringtmp(buf, size1+3) size2 := 0 for _, r := range a { // check for race if size2 >= size1 { break } //干正事 size2 += encoderune(b[size2:], r) } return s[:size2] }
// int转string,注意是 int ,不是int64 // 虽然我也不知道为啥int转string非要传一个int64,只能理解是编译器会干点什么 funcintstring(buf *[4]byte, v int64) (s string) { //runeSelf是一个常量 0x80,也就是127 //staticbytes是一个常量数组,里面是0-127的的字节码 if v >= 0 && v < runeSelf { // 所以这个其实是直接扫表加速处理 stringStructOf(&s).str = unsafe.Pointer(&staticbytes[v]) stringStructOf(&s).len = 1 return }
var b []byte if buf != nil { //有空间,直接用 b = buf[:] s = slicebytetostringtmp(b) } else { //没空间,申请 s, b = rawstring(4) } //rune其实就是int32,所以把int64转int32再转int64看是不是自己来判断是否是一个int大小的 ifint64(rune(v)) != v { v = runeError } //再算一遍大小,并且填到b里面去 n := encoderune(b, rune(v)) return s[:n] }
// 这个函数不是string.go里的,是在src/runtime/utf8.go:104 // encoderune writes into p (which must be large enough) the UTF-8 encoding of the rune. // It returns the number of bytes written. // 注释的意思是,会把rune进行utf8编码写到p(必须保证空间足够大)里, // 返回值是写入的字节数 funcencoderune(p []byte, r rune)int { // Negative values are erroneous. Making it unsigned addresses the problem. switch i := uint32(r); { case i <= rune1Max: //1<<7 -1 127 ,一个字节的情况 p[0] = byte(r) return1 case i <= rune2Max: //1<<11 -1 2047,两个字节 _ = p[1] // eliminate bounds checks p[0] = t2 | byte(r>>6) p[1] = tx | byte(r)&maskx return2 case i > maxRune, surrogateMin <= i && i <= surrogateMax: //看是不是不再unicode处理范围 // maxRune '\U0010FFFF' // Maximum valid Unicode code point. // Code points in the surrogate range are not valid for UTF-8. //const ( // surrogateMin = 0xD800 // surrogateMax = 0xDFFF //) r = runeError fallthrough case i <= rune3Max:// 1<<16 -1 65535,三个字节 _ = p[2] // eliminate bounds checks p[0] = t3 | byte(r>>12) p[1] = tx | byte(r>>6)&maskx p[2] = tx | byte(r)&maskx return3 default:// 自然就是四个字节啦 _ = p[3] // eliminate bounds checks p[0] = t4 | byte(r>>18) p[1] = tx | byte(r>>12)&maskx p[2] = tx | byte(r>>6)&maskx p[3] = tx | byte(r)&maskx return4 } }
// rawstring allocates storage for a new string. The returned // string and byte slice both refer to the same storage. // The storage is not zeroed. Callers should use // b to set the string contents and then drop b. // 申请size大小的一片内存(不为空,就是纯内存空间) // 返回的s和b其实指向的一个地方,不过类型不一样 // 调用者应该用b来进行赋值,完成后就释放b funcrawstring(size int) (s string, b []byte) { // 这个函数是申请空间,第一个参数是大小,第二个参数是类型,第三个参数是是否初始化为0 // 小对象直接在goroutine的栈上申请 // 大对象(>32kb)会直接在堆上申请 p := mallocgc(uintptr(size), nil, false)
stringStructOf(&s).str = p stringStructOf(&s).len = size
// rawruneslice allocates a new rune slice. The rune slice is not zeroed. // 申请size大小的rune切片,rune是4字节的,所以函数里出现了很多4 funcrawruneslice(size int) (b []rune) { ifuintptr(size) > maxAlloc/4 { //看看内存够不够 throw("out of memory") } //下面和上面一样的处理方式 mem := roundupsize(uintptr(size) * 4) p := mallocgc(mem, nil, false) if mem != uintptr(size)*4 { memclrNoHeapPointers(add(p, uintptr(size)*4), mem-uintptr(size)*4) }
funcconcatstrings(buf *tmpBuf, a []string)string { //首先声明一些用的上的变量 idx := 0 l := 0 count := 0 for i, x := range a { n := len(x) if n == 0 { //如果当前的这个string长度为0直接跳过 continue } if l+n < l { //判断是不是超过大小,为什么这么写呢,举个例子 //比如 l是一个int64,那么它的最大值就是2^63-1 //如果我的l现在是2^63,然后n现在是2 //那么我的l就会越界变成最小值 //所以判断是不是加完小于自己就行了 throw("string concatenation too long") } l += n count++ idx = i } if count == 0 { return"" }
// If there is just one string and either it is not on the stack // or our result does not escape the calling frame (buf != nil), // then we can return that string directly. // 想明白这个位置的话,首先得理解go里面对变量的内存处理 // 我们都知道go里面的函数是可以返回一个地址的,这点跟c不一样, // c里面如果返回一个局部变量的地址,这个地址在函数执行完之后会被释放掉 // 但是go里面返回的地址则不会,是别的地方可用的,go是怎么实现的呢? // 主要是go会对代码进行逃逸分析,如果说这个变量的适用范围不仅仅在自己的调用栈 // 那么会直接把它分配到堆上而不是自己的栈上。 if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) { //stringDataOnStack见下方工具函数 return a[idx] } //判断内存空间够不够,不够就申请 //这里返回的s是实际的string,b其实是一个游标指针,方便后面拷贝 //同见下方工具函数 s, b := rawstringtmp(buf, l) for _, x := range a { //开始实际的拷贝,每次都是把x拷贝到b指向到空间,然后把b往后移动len(x)长度继续考呗 copy(b, x) b = b[len(x):] } return s }
funcconcatstring2(buf *tmpBuf, a [2]string)string { return concatstrings(buf, a[:]) }
funcconcatstring3(buf *tmpBuf, a [3]string)string { return concatstrings(buf, a[:]) }
funcconcatstring4(buf *tmpBuf, a [4]string)string { return concatstrings(buf, a[:]) }
funcconcatstring5(buf *tmpBuf, a [5]string)string { return concatstrings(buf, a[:]) }
// s中找t开始位置,正常朴素算法 funcindex(s, t string)int { iflen(t) == 0 { return0 } for i := 0; i < len(s); i++ { if s[i] == t[0] && hasPrefix(s[i:], t) { return i } } return-1 } // 判断t在不在s中 funccontains(s, t string)bool { return index(s, t) >= 0 } // 判断s是否以prefix开头 funchasPrefix(s, prefix string)bool { returnlen(s) >= len(prefix) && s[:len(prefix)] == prefix }
// atoi parses an int from a string s. // The bool result reports whether s is a number // representable by a value of type int. // atoi,也是朴素算法 funcatoi(s string) (int, bool) { if s == "" { return0, false }
neg := false if s[0] == '-' { neg = true s = s[1:] }
un := uint(0) for i := 0; i < len(s); i++ { c := s[i] if c < '0' || c > '9' { return0, false } if un > maxUint/10 { // overflow return0, false } un *= 10 un1 := un + uint(c) - '0' if un1 < un { // overflow return0, false } un = un1 }
if !neg && un > uint(maxInt) { return0, false } if neg && un > uint(maxInt)+1 { return0, false }
n := int(un) if neg { n = -n }
return n, true }
// atoi32 is like atoi but for integers // that fit into an int32. // 就是调一下上方的函数 funcatoi32(s string) (int32, bool) { if n, ok := atoi(s); n == int(int32(n)) { returnint32(n), ok } return0, false }
// stringDataOnStack reports whether the string's data is // stored on the current goroutine's stack. // 判断string是否在当前栈空间内 funcstringDataOnStack(s string)bool { ptr := uintptr(stringStructOf(&s).str) stk := getg().stack //对于go来说,每个goroutine的栈空间记录都是记录栈顶和栈底的地址 //所以只用判断这个指针是不是在这两之间就可以判断在不在当前栈空间上 return stk.lo <= ptr && ptr < stk.hi }
// 封装了一下,如果buf空间够就直接用buf不够才申请 funcrawstringtmp(buf *tmpBuf, l int) (s string, b []byte) { if buf != nil && l <= len(buf) { b = buf[:l] //把byte的切片转成string s = slicebytetostringtmp(b) } else { //申请空间 s, b = rawstring(l) } return }
//go:nosplit //无拷贝的byte转string,就是指针类型转换 funcgostringnocopy(str *byte)string { ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} s := *(*string)(unsafe.Pointer(&ss)) return s }