Go string

作者: JunChow520 | 来源:发表于2020-12-28 03:28 被阅读0次
    • 字符串表示不可改变的字节序列
    • 字符串虽然可以包含任意数据,但通常指用来包含人类可读的文本。
    • 字符串是一种值类型且值不可变,即字节的定长数组,因此创建文本后将无法再次修改内容。

    概念

    • 字节

    字节即byte由8个比特位组成,即1byte = 8bit,字节是计算机中基本的计量单位。Golang中存在byte类型,其本质是uint8的别名。

    • 字符

    字符的概念比较模糊,在Unicode中通常使用code point码元来表示,简单来说字符是一种信息单元,比如符号、字母等。

    • rune

    rune实际上是int32的别名,为了区分将字符和整数值分开,而增加了rune类型代表一个字符。

    • UTF-8

    Golang中string是Unicode兼容的,是UTF-8编码的。

    Golang中string的内部实现使用UTF-8编码。由于采用UTF-8编码,string可以包含文本,而文本是世界上任何语言的混合,因而不会造成页面的混乱和限制。

    UTF-8编码格式是文本的标准编码,对占用的字节长度具有不定性。string是UTF-8字符的一个序列,当字符为ASCII码表上的字符时则会占用1个字节,其它字符会根据需要占用2到4个字节。Golang为减少内存和磁盘占用空间,string可能会根据需要占用1到4个字节。

    Golang中string不同于其他语言,它是一个变宽字符序列,每个字符使用UTF-8编码的一个或多个字节表示。string是任意字节(包括值为零的字节)的不可变链。

    Golang中的string的字节采用UTF-8编码来标识Unicode文本,统一使用UTF-8编码以解决中文乱码问题。

    string

    Golang标准库builtin给出了所有内置类型的定义

    type string string
    
    • string是一系列8位字节的集合,但并不一定代表UTF-8编码的文本。
    • string可以为空(长度为0),但不能为nil
    • string对象不可修改

    例如:使用var关键字声明字符串变量

    var str string
    

    stringStruct

    Golang的string是一种数据类型,占16字节空间。stringruntime包中就是stringStruct对外呈现才叫做string

    • 前8字节是一个指针指向字符串值(字节切片)的地址
    • 后8个字节是一个整数,用于标识字符串的长度。
    type stringStruct struct{
      str unsafe.Pointer //字符串的首地址
      len int //字符串的长度
    }
    

    string数据结构跟切片类似,只不过切片还有一个表示容量的成员。事实上,string和切片准确来说是byte切片,经常会发生转移。因此string本质上可看作是一个只读的字节切片。

    string的结构上看实际上是一个指针str,指向某个字节切片的首地址,字节切片中存放着字符串的真正内容。

    例如:字符串“你好”在内存中的表示

    var str string
    str = "你好"
    
    内存表示

    由于Golang源码默认采用UTF-8编码,因此string字面量也是UTF-8编码的。中文汉字的“你”会被编码为\xe4\xbd\xa0,中文汉字的“好”会被编码为\xe5\xa5\xbd

    例如:将UTF-8编码转换为字符串

    bs := []byte{0xe4, 0xbd, 0xa0}
    fmt.Printf("%s", string(bs))//你
    

    虽然string并非切片,但支持切片操作。

    对于同一个string字面量,不同的string变量指向相同的底层数组,因为string是只读的,为了节省内存,相同字面量的string通常对应同一个string产量。

    str1 := "hello"
    str2 := "hello"
    str3 := "hello "
    
    data1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)).Data
    fmt.Printf("str1 data = %d\n", data1) //str1 data = 7858269
    
    data2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)).Data
    fmt.Printf("str2 data = %d\n", data2) //str2 data = 7858269
    
    data3 := (*reflect.StringHeader)(unsafe.Pointer(&str3)).Data
    fmt.Printf("str3 data = %d\n", data3) //str3 data = 7858734
    

    Golang中string内部并非以\0作为结尾,而是通过一个长度域来表示字符串的长度。

    字面量

    Golang中string的值使用双引号包裹内容(使用单引号包裹的是字符),因此可在Golang的源码中直接添加非ASCII码字符。string以原生数据类型出现,使用string就像使用其他原生数据类型一样。

    str := "hello world"
    

    Golang中string字面量可以通过两种方式创建

    • 使用双引号包裹

    使用双引号创建的字符串支持转义字符,但不能跨行。

    • 使用反引号包裹

    使用反引号创建的字符串又称为原始文本(raw literals),原始文本不支持转义字符,但可以跨越多行。两个反引号之间包裹的字符串会被原样输出,因此在反引号中使用转义字符将会是无效的。在反引号之间的所有代码均不会被编译器识别,只会作为字符串的一部分。

    • 不可变性

    Golang中string一旦创建则不可变,即无法更改string的值,也就是说string是只读的。若尝试更改则编译器会引发错误。这是由于字符串字面量的存储位置并不在堆和栈上,通常编译器会分配到只读段(.rodata)上,因此对应的地址是不可修改的。

    Golang的实现中string不包含内存空间,只有一个内存的指针,这样做的好处是string变得轻量,可以方便地进行传递且无需担心内存拷贝。由于string通常指向字符串字面量,而字符串字面量存储位置是只读段,而不是堆或栈上,所有string才不可修改。

    string是一个只读字节片,string的字节可以使用UTF-8编码在Unicode文本中表示。

    转义字符

    使用双引号包裹的字符串支持转义字符

    转义字符 含义
    \r 回车符,返回行首。
    \n 换行符,直接跳到下一行的同列位置。
    \t 制表符
    \' 单引号
    \" 双引号
    \\ 反斜杠

    长度

    • len()获取字符串占用字节的长度(字节数)
    • utf8.RuneCountInString()获取字符串的长度(符文数)
    var str string
    str = "hell?我"
    fmt.Printf("len = %d, RuneCount = %d\n", len(str), utf8.RuneCountInString(str)) //len = 8, RuneCount = 6
    

    拼接

    • 多个字符串可使用+字符串拼接符来进行追加形成新的字符串
    • 由于编译器会自动在行尾补全分号,因此字符串拼接符必须放在行尾。连接跨行字符串时+必须在上一行末尾否则会导致编译错误。
    var str string
    str = "hello " +
        "world"
    fmt.Println(str)
    
    • 可使用+=来对字符串进行拼接
    • 可使用fmt.Sprintf进行拼接
    var str string
    str = fmt.Sprintf("id = %d, name = %s\n", 1, "admin")
    fmt.Println(str)
    

    字节

    var str string
    str = "h1你"
    
    i := 0
    fmt.Printf("str[%d] = %q, val = %v, hex = %x, unicode = %U\n", i, str[i], str[i], str[i], str[i])
    
    str[0] = 'h', val = 104, hex = 68, unicode = U+0068
    
    占位符 描述
    %q 字符字面量,字节对应的字符字面量。
    %v 默认格式,字节对应的编码值。
    %x 小写十六进制
    %U Unicode格式

    字节切片

    Golang中string是一个字节切片,而非字符切片。通过标准索引的方式获取的是字节,即在方括号[]中指定索引值,索引值从0开始计数。

    var str string
    str = "hello"
    fmt.Printf("char = %c, byte = %d\n", str[0], str[0]) //char = h, byte = 104
    

    通过rune类型可方便地对每个UTF-8字符进行访问

    var str string
    str = "hello"
    
    for i:=0; i<len(str); i++{
        char := str[i]
        fmt.Printf("char = %c, byte = %d\n", char, char)
    }
    
    char = h, byte = 104
    char = e, byte = 101
    char = l, byte = 108
    char = l, byte = 108
    char = o, byte = 111
    
    • 获取string中某个字节的地址属于非法行为
    var str string
    str = "hello"
    
    for i, v := range str {
        fmt.Printf("index = %d, value = %c, byte = %d\n", i, v, v)
    }
    
    index = 0, value = h, byte = 104
    index = 1, value = e, byte = 101
    index = 2, value = l, byte = 108
    index = 3, value = l, byte = 108
    index = 4, value = o, byte = 111
    

    比较

    • 比较运算符在内存中会按字节比较来实现字符串的比较,因此比较的结果是字符串自然编码的顺序。

    遍历

    • 使用forbyte形式遍历字符串
    var str string
    str = "h1你"
    for i := 0; i < len(str); i++ {
        fmt.Printf("i = %d, v = %v, char = %c\n", i, str[i], str[i])
    }
    
    i = 0, v = 104, char = h
    i = 1, v = 49, char = 1
    i = 2, v = 228, char = ä
    i = 3, v = 189, char = ½
    i = 4, v = 160, char =  
    
    • 使用for...rangerune方式遍历字符串

    当使用for...range迭代字符串时,每次迭代Golang都会使用UTF-8解码出一个rune类型的字符,且索引为当前rune的起始位置(以字节为最小单位)。

    var str string
    str = "h1你"
    
    fmt.Println(reflect.TypeOf(str))    //string
    fmt.Println(reflect.TypeOf(str[0])) //uint8 = byte
    for _, v := range str {
        fmt.Println(reflect.TypeOf(v)) // int32 = rune
    }
    

    实际上使用for...range迭代string时,会将string中的byte重新转换为UTF-8字符,对于错误的编码则会使用一个字节的占位符来替代。

    for i,v := range []rune(str){
    
    }
    

    例如:

    var str string
    str = "h1你"
    for i, v := range str {
        fmt.Printf("i = %d, v = %v, char = %c\n", i, v, v)
    }
    
    i = 0, v = 104, char = h
    i = 1, v = 49, char = 1
    i = 2, v = 20320, char = 你
    

    转换

    要修改字符串,可先将其转换为[]byte[]rune,然后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

    相关文章

      网友评论

          本文标题:Go string

          本文链接:https://www.haomeiwen.com/subject/xdxznktx.html