Golang 的string, byte和rune常见问题

Golangstring, byterune往往让有其他语言使用习惯的人不适应,下文总结其注意事项

1. string

标准库builtin解释:

type string

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.

源码:src/runtime/string.go:209
type stringStruct struct {
    str unsafe.Pointer
    len int
}
官方博客释义:

In Go, a string is in effect a read-only slice of bytes.………… As far as
the content of a string is concerned, it is exactly equivalent to a
slice of bytes.
https://blog.golang.org/strings

String 总结:
  • string是结构体, 包含指向[]byte 的指针str
  • str 指向的内容不可变,但可以被替换(这一点与Java等类似)
  • 由于string内包含pointer, 因此在一些情况下会引起gc的压力, 如体量大的map[string]int结构,会在每个gc周期内查找map内的string
  • 这里插一段关于golang的参数传递总结
    1. string\map\slice\channel中, 其make函数分别有不同的实现,这由编译系统通过判断make的参数来决定。
    2. stringslicemake返回结构对象, mapchannel返回的是对象指针。
    3. slice作为参数在函数间(假设为外函数和内函数)传递时,由于slice结构体中均包含指向底层数据的指针,因此即便是值传递,内函数在接收到从外函数复制过来的slice时,其结构体的指针仍相同。因此内函数的大部分改动会改变外函数中定义的slice的真实值。
    4. slice在内函数作append操作时,虽然底层数组首地址不变,但其内函数的结构体的len增加(拷贝的版本的slicelen增加,外函数的slice的真实值len不变,二者指向相同的底层数组首地址),因此外函数的slice的真实值不变。
    5. string,其传递场景与slice类似,因为string结构体中也有其底层数据的指针,但其值传递的拷贝行为与slice不同(这一点取决于编译器)。
    6. 关于golang slice的资料, 看这篇官方博客已足够。

2. string和[]byte的互换

string -> []byte:

[]byte(string)

[]byte -> string:

string([]byte)
string和[]byte的互换总结:
  • string ->[]byte时,系统会重新分配内存(mallocgc),构造一个新的[]byte,
    而不是直接返回被转换的string结构中的指针str所指向的[]byte。
  • 同理[]byte ->string时,系统会构造一个新的[]byte
    而不是直接将被转换的[]byte的地址赋值到string结构中的str
  • 要修改字符串,可先将其转换成[]rune[]byte,完成后再转换为 string。⽆无论哪种转换,都会重新分配内存,并复制字节数组。—《Go语言学习笔记》
  • golang编译器针对string[]byte的转换作了优化, 参见 stringtoslicebytetmp 和slicebytetostringtmp:
    即针对特定情况,string[]byte的转换不会涉及到复制和内存分配 :

stringtoslicebytetmp调用的前提是保证返回的[]byte之后不会被修改
slicebytetostringtmp调用的前提是保证返回的string在生命周期结束之前,[]byte不会被修改
引用自:https://www.pengrl.com/p/31544/

3. 遍历string

方法一,range遍历:官方示例一
for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}

输出:

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

注意:

range on strings iterates over Unicode code points. The first value is
the starting byte index of the rune and the second the rune itself.

以上例中的为例:

  1. golang中的range可以用来遍历slice\map\string等类型,详见
  2. 在遍历string的过程,返回的分别是int类型的indexrune类型的Unicode code points
  3. Unicode PointU+65E5,代表值是
  4. golangfmt实现中,%#U会写出该字符的Unicode 编码形式(即其代表值)
  5. fmt.Printf(“%c”)亦可直接输出其代表值。
    4.fmt.Printlnfmt.Printf(“%v”)则会用golang的默认格式来输出rune的内存值,由于rune的实际类型是Int32, 会输出26085

golang的默认格式%v :

bool:                    %t
int, int8 etc.:          %d
uint, uint8 etc.:        %d, %#x if printed with %#v
float32, complex64, etc: %g
string:                  %s
chan:                    %p
pointer:                 %p
方法二,Index遍历: 官方实例二
 const sample = "abcd"
 for i := 0; i < len(sample); i++ {
        fmt.Printf("%x ", sample[i])
		fmt.Println(sample[i])
    }

输出:

61 97
62 98
63 99
64 100
注意:
  • 直接用Index遍历string结构
  • 返回的是对应的byte类型(结合上文对string结构的分析),实质上是uint8类型
  • 上例中分别以16进制和10进制(默认格式)输出各字母对应的内存内容。


总结golang 中的string遍历:

  • range遍历返回rune类型,index遍历返回byte类型

  • rune实际上是int32byte类型实际上是uint8

  • 不论runebyte类型,其内存值均是整形数值,配合格式输出或类型转换才能输出其对应的有语义的值。

  • 在实际编程中可以灵活比较runebyte类型:

     a := 'a'//rune, int32
     b := "a"//[]byte, b[0] with type of uint8
     fmt.Printf("%T, %v, %c \n", a,a,a)
     fmt.Printf("%T, %v, %v \n", b[0],b[0], string(b[0]))
     //invalid operation: b[0] == a (mismatched types byte and rune)
     /*if b[0] == a {
     	fmt.Println("b[0] == a")
     }*/
     if b[0] == 'a'{
     	fmt.Println("b[0] == a")
     }
    

输出

int32, 97, a
uint8, 97, a
a == b[0]

你可能感兴趣的:(Golang辅助)