关于go:Go-String-解析

7次阅读

共计 3434 个字符,预计需要花费 9 分钟才能阅读完成。

一、String 构造定义

// src/runtime/string.go:stringStruct

type stringStruct struct {
    str unsafe.Pointer
    len int
}

String 类型在 Go 语言内存模型中其实是一个“描述符”,用一个 2 字节的数据结构示意,它自身并不真正存储字符串数据,而仅是由一个指向底层存储的指针和字符串的长度字段组成的。

str:指向字符串底层存储首地址的指针,1 字节。
len:字符串的长度,1 字节。

因而,咱们即使间接将 String 类型变量作为函数参数,其传递的开销也是恒定的,不会随着字符串大小的变动而变动。

二、String 个性

1. String 类型的值在它的生命周期内不可扭转

type stringStruct struct {
    str unsafe.Pointer
    len int
}

func main() {
    var s string = "hello"

    s[0] = 'a' // 谬误:无奈给 s[0]赋值,因为字符串内容是不可扭转的

    fmt.Printf("%#v\n", (*stringStruct)(unsafe.Pointer(&s))) 
    // 输入:&main.stringStruct{str:(unsafe.Pointer)(0x2b599c), len:5}

    s = "world" // 批改字符串,字符串底层构造体中 str 指针曾经发生变化
    
    fmt.Printf("%#v\n", (*stringStruct)(unsafe.Pointer(&s))) 
    // 输入:&main.stringStruct{str:(unsafe.Pointer)(0x2b5a00), len:5}
    
    fmt.Printf("%#v\n", (*[5]byte)((*stringStruct)(unsafe.Pointer(&s)).str)) 
    // 输入:&[5]uint8{0x77, 0x6f, 0x72, 0x6c, 0x64} 别离对应 w o r l d 的 ASCII 码
}

因为 runtime.stringStruct 构造是非导出的,不能间接应用,所以手动定义了一个 stringStruct 构造体。

String 类型的数据不可扭转的个性,进步了字符串的 并发安全性 存储利用率

  • 字符串能够被多个协程共享,开发者不必再放心字符串的并发平安问题。
  • 针对同一个字符串值,无论它在程序的几个地位被应用,编译器只须要为它调配一块存储,大大提高了存储利用率。

2. 没有结尾’\0’,存储了字符串长度

Go 字符串中没有结尾’\0’,并且存储了字符串长度,获取字符串长度的工夫复杂度是常数,无论字符串中字符个数有多少,咱们都能够疾速失去字符串的长度值。

3. String 能够是空的"",但不能是 nil

var s string = ""
s = nil // 谬误

4. 对非 ASCII 字符提供原生反对,打消了源码在不同环境下显示乱码的可能

Go 语言源文件默认采纳的是 Unicode 字符集,Unicode 字符集是目前市面上最风行的字符集,它囊括了简直所有支流非 ASCII 字符(包含中文字符)。
Go 字符串中的每个字符都是一个 Unicode 字符,并且这些 Unicode 字符是以 UTF-8 编码格局存储在内存当中的。

5. 原生反对“所见即所得”的原始字符串,大大降低结构多行字符串时的心智累赘。

var s string = `         ,_---~~~~~----._
    _,,_,*^____      _____*g*\"*,--,
   / __/ /'     ^.  /      \ ^@q   f
  [@f | @))    |  | @))   l  0 _/
   \/   \~____ / __ \_____/     \
    |           _l__l_           I
    }          [______]           I
    ]            | | |            |
    ]             ~ ~             |
    |                            |
     |                           |`
fmt.Println(s)

三、String 惯例操作

1. 下标操作

在字符串的实现中,真正存储数据的是底层的数组。字符串的下标操作实质上等价于底层数组的下标操作。咱们在后面的代码中理论碰到过针对字符串的下标操作,模式是这样的:

var s = "乘风破浪"
fmt.Printf("0x%x\n", s[0]) // 0xe4:字符“乘”utf- 8 编码的第一个字节

咱们能够看到,通过下标操作,咱们获取的是字符串中特定下标上的字节,而不是字符。

2. 字符迭代

Go 有两种迭代模式:惯例 for 迭代 for range 迭代
通过这两种模式的迭代对字符串进行操作失去的后果是不同的。

通过惯例 for 迭代对字符串进行的操作是一种字节视角的迭代,每轮迭代失去的的后果都是组成字符串内容的一个字节,以及该字节所在的下标值,这也等价于对字符串底层数组的迭代:

var s = "乘风破浪"
for i := 0; i < len(s); i++ {fmt.Printf("index: %d, value: 0x%x\n", i, s[i])
}

输入:

index: 0, value: 0xe4
index: 1, value: 0xb9
index: 2, value: 0x98 // "\xe4\xb9\x98" 乘
index: 3, value: 0xe9
index: 4, value: 0xa3
index: 5, value: 0x8e // "\xe9\xa3\x8e" 风
index: 6, value: 0xe7
index: 7, value: 0xa0
index: 8, value: 0xb4 // "\xe7\xa0\xb4" 破
index: 9, value: 0xe6
index: 10, value: 0xb5
index: 11, value: 0xaa // "\xe6\xb5\xaa" 浪

通过 for range 迭代,咱们每轮迭代失去的是字符串中 Unicode 字符的码点值,以及该字符在字符串中的偏移值

var s = "乘风破浪"
for i, v := range s {fmt.Printf("index: %d, value: 0x%x\n", i, v)
}

输入:

index: 0, value: 0x4e58
index: 3, value: 0x98ce
index: 6, value: 0x7834
index: 9, value: 0x6d6a

3. 字符串连贯

尽管通过 +/+= 进行字符串连贯的开发体验是最好的,但连贯性能就未必是最快的了。
Go 还提供了 strings.Builder、strings.Join、fmt.Sprintf 等函数来进行字符串连贯操作。

4. 字符串比拟

Go 字符串类型反对各种比拟关系操作符,包含 ==!=>=<=><。在字符串的比拟上,Go 采纳字典序的比拟策略,别离从每个字符串的起始处,开始一一字节地对两个字符串类型变量进行比拟。
当两个字符串之间呈现了第一个不雷同的元素,比拟就完结了,这两个元素的比拟后果就会做为串最终的比拟后果。如果呈现两个字符串长度不同的状况,长度比拟小的字符串会用空元素补齐,空元素比其余非空元素都小。

如果两个字符串的长度不雷同,那么咱们不须要比拟具体字符串数据,也能够判定两个字符串是不同的。然而如果两个字符串长度雷同,就要进一步判断,数据指针是否指向同一块底层存储数据。如果还雷同,那么咱们能够说两个字符串是等价的,如果不同,那就还须要进一步去比对理论的数据内容。

func main() {
    // ==
    s1 := "乘风破浪"
    s2 := "乘风" + "破浪"
    fmt.Println(s1 == s2) // true

    // !=
    s1 = "Go"
    s2 = "PHP"
    fmt.Println(s1 != s2) // true

    // < and <=
    s1 = "12345"
    s2 = "23456"
    fmt.Println(s1 < s2)  // true
    fmt.Println(s1 <= s2) // true

    // > and >=
    s1 = "12345"
    s2 = "123"
    fmt.Println(s1 > s2)  // true
    fmt.Println(s1 >= s2) // true
}

第五个操作:字符串转换。

Go 反对字符串与字节切片、字符串与 rune 切片的双向转换,并且这种转换无需调用任何函数,只需应用显式类型转换就能够了

var s = "乘风破浪"

// string -> []rune
rs := []rune(s)
fmt.Printf("%x\n", rs) // [4e58 98ce 7834 6d6a]

// string -> []byte
bs := []byte(s)
fmt.Printf("%x\n", bs) // e4b998e9a38ee7a0b4e6b5aa

// []rune -> string
s1 := string(rs)
fmt.Println(s1) // 乘风破浪

// []byte -> string
s2 := string(bs)
fmt.Println(s2) // 乘风破浪
正文完
 0