介绍

在 go 语言中的字符串有以下几个特点:

  1. 字符串是不可变的序列,可以包含任意数据,不过主要是文本。
  2. 字符串被解读成 utf-8 的序列。

Unicode 编码

计算机一开始,用 ascii 编码即可满足需求,使用 7 位表示 128 个字符,包括大小写字母,英文标点符号、控制符等。

但是,随着互联网的发展,各个国家都要用计算机,ascii 编码已经不够用了,出现了 unicode 编码,而我们熟悉的 UTF-8 UTF-16 UTF-32 是 Unicode 的具体实现 (解决的是怎么存储在计算机)。

Unicode 编码是怎么实现的呢?

基本思路是:把世界上所有能出现的字符,都为其分配一个数字来表示。在 go 语言中,这个标准数字叫做文字符号 rune,计算机中用 int32 类型比较适合保存这个文字符号,所以 rune 就是 int32 的别名,对应的就是 utf-32 的编码。

早期的 unicode 编码为了扩充字符,直接在 ascii 前面加一个字节就好,用两个字节来表示,所以最多容纳 65535 个字符。但是文字非常多,后面把 unicode 编码改成是一个可以变成的字符集,目前规划了U+0000 至 U+10FFFF 为 unicode 编码,占用 21 个 bit,前面增加了 10 ,也就十进制的 17,所以是原来的 17 倍,足够用了。

这个 17 个把 unicode 编码分为 17 个平面,每个平面 65535 个字符。由于早期两个字节表示的是第 0 个平面,已经把大量常用的字符给包含在里面了,所以称作基本平面。不在基本平面里的字符都是罕见字符。

对于大于 256 的 unicode 的编码,使用转义字符表示的时候,有两种方式,\uhhhh\Uhhhhhhhh ,小于 256 的可以使用 \x 来表示

  1. \u 开头的表示是用 16 位的码点值
  2. \U 开头的表示 32 位的码点值,这几乎用不到

以下几个表示是一样的

"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"

fmt.Println('世' == '\u4e16') // true
fmt.Println("世" == "\u4e16") // true

Utf-8

Utf-8 是如果实现 unicode 转换呢?

Utf-8 的最大的特点就是变长,节省空间。用 1-4 个字节表示一个字符,和 unicode 的转换是:

  1. 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 Unicode 码。兼容 ascii
  2. 对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为 0,剩余的 N - 1 个字节的前两位都设位 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充

基于以上规则,就可以实现 utf-8 编码到 unicode 编码之间的转换。

Utf-8 编码的汉字确要变成了 3 字节存储,会不会导致空间浪费?

相对于直接用 unicode 编码的 2 字节表示,确实会,不过美国人认为,大部分都是因为,所以英文用 ascii 的话更加节省。

Utf-8 的缺点?

  1. 无法按下标获取第 n 个字符。
  2. 需要和 unicode 编码转换,会比较耗费时间

如何获取utf-8编码的字符串长度?

可以使用utf8包来获取

fmt.Println(utf8.RuneCountInString("你好")) // 2

如何变量一个utf8的字符串?

可以使用DecodeRuneInString

s := "你好世界"
for i := 0; i < len(s); {
   // 返回utf8的第一个码值,并且返回占用的字节数
  r, size := utf8.DecodeRuneInString(s[i:])
   fmt.Printf("%d\t%c\n", i, r)
    i += size
}

不过还可以直接使用 range 循环,会按 Utf-8 隐式解码

// i 是按字节的索引位置
for i, r := range "你好世界" {
    fmt.Printf("%d\t%c\n", i, r)
}

如不字符串的编码出现问题,导致无法解码,会产生一个unicode编码\uFFFD替换它,输出的形状是六边形,里面有一个问号。

当对utf8编码的字符串进行[]rune转换的时候,会发生什么?

会返回该字符串的 unicode 的码点序列,注意这个是 unicode 码值,不是 utf8

s := "你好世界"
r := []rune(s)
fmt.Printf("%x", r) // [4f60 597d 554a]

// 文字符号类型的切片,可以转为字符串
fmt.Println(string(r)) // 你好世界

如果将一个整数转为字符串,会把这个整数作为utf-8的码值

fmt.Println(string(65))     //"A",而不是"65"
fmt.Println(string(0x4eac)) //"京"

如果是非法的编码,则会使用编码\uFFFD替换它

fmt.Println(string(1234567)) //"�"

Utf-16

它和 unicode 的转换比较简单:

  1. 码位范围是 0x0000~0xFFFF 的字符,只占用 2 个字节,utf-16 用 2 字节表示
  2. 码位范围是 0x10000~0x10FFFF 的字符,需要占用 4 个字节来表示。Utf-16 用 4 字节表示

Utf -32

UTF-32 是 UNICODE 编码的 4 字节传输格式,每一个传输单元是 4 个字节,前面补零就好。

相关操作

如何获取字符串的字节数量呢?

可以通过 len 函数获取,需要注意的是,这个返回的是字节数量,不是字符数量。

s := "Hello World"
fmt.Println(len(s)) // 11

如何访问因为字符串的中的某个元素呢?

可以通过下标访问,可以获取第 i 个字符,i 大小范围是 0 <= i <= len(s),不能超过这个范围,不然会触发宕机异常。需要注意的是,这个是第几个字节,而不是字符,因为 utf-8 编码,一个汉字可能有 3 个字节。

s := "Hello World"
fmt.Printf("%c", s[0]) // H

如何截取字符串的某一部分呢?

s := "Hello World"
fmt.Println(s[:5]) // Hello
fmt.Println(s[6:]) // World
fmt.Println(s[:])  // Hello World

字符串如何拼接呢?

可以使用 + 来拼接字符串。

s := "张三的性别是:"
str := s + "男"
fmt.Println(str) // 张三的性别是:男

拼接的时候有一点要注意,如果字符串太长,需要将+号写在行末,不能写在另外一行的开头,因为 go 会在行末加上分号,如果发现是+号,就不加分号

// 错误的写法
var str = "a"+ "b"
+ "c"
// 正确的写法
var str = "a"+ "b"+
"c"

字符串是如何比较的呢?

字符是通过 == 或 > 来比较的,比较是按字节进行的。

s := "张三的性别是:"
sex := "男"
fmt.Println(sex == s) // false
fmt.Println(sex > s)  // true

可以改变字符串里面的内容吗?

不可以,如果去改变的话,会触发编译错误。

s := "Hello World"
s[0] = "1" // cannot assign to s[0] (strings are immutable)

这样的好处是可以安全的共用同一段底层内存:

  1. 复制长字符串开销很低廉
  2. 子字符串的生成,开销很低廉

字符串的字面量形式是怎么样的?

通常是用双引号括起来的字符序列,因为 go 源码是 utf-8 的编码,所以字符串一般也是按 utf-8 的编码进行解读的。

s := "Hello 世界"

对于双引号,是会解析特殊字符的,例如以反斜杠开头的转义字符。

s := "Hello 世界 \t"

如果想要字符串的内容原生输出,不被转义,可以使用反引号,需要注意的是:回车符号会被删除,换行符号保留

s := `Hello 世界
 1. 西部世界
`

整数是如何转换成字符串呢?

可以使用 sprintf 来转换,这个比较简单:

x := 123
y := fmt.Sprintf("%d", x) // 双引号括起来
fmt.Println(y)

还可以使用 strconv.Itoa 方法来转换,这个等效 strconv.FormatInt(int64(i), 10),就是把数字格式化成 10 进制的一个简写,本身也是返回字符串。

x := 123
fmt.Println(y, strconv.Itoa(x))

还可以使用 strconv.FormatInt 方法来转成字符串,这个方法本身是用于转换成制定进制的数字,只是本身是返回字符串就是了

x := 123
y := strconv.FormatInt(int64(x), 10)

fmt.Printf("%T, %v", y, y) // string, 123

如何将字符串转为整数呢?

可以使用 ParseInt 方法来解析成 int 类型

v32 := "-354634382"
if s, err := strconv.ParseInt(v32, 10, 32); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}

// 等效 strconv.ParseInt(v32, 10, 0) 0代表是int类型
if s, err := strconv.Atoi(v32); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}

对于浮点类型,也可以参考上述方法,strconv 包也有相应的方法进行转换

string 包的一些操作

// 不区分大小比较字符串是否相当
strings.EqualFold("Go", "go")// true

// 是否包含
strings.Contains("seafood", "foo") // true

// 分割
res := strings.Split("http://www.pdfqupu.com", "://")
res := strings.SplitN("http://www.pdfqupu.com", "://", 2) // 指定分割两个
return res[0], res[1]

// 结合,将切片结合在一起
t = strings.Join(numStrList, ":")

// 字符串出现的位置,不存在返回-1,应该?
splitCharIndex := strings.Index(sheetName, "-")  

// 去除头尾空格
strings.TrimSpace(c.rawData)

// 是否包含前缀
strings.HasPrefix(sheetName, "指定区服排期")

// 替换所有字符
strings.ReplaceAll(timeStr, ":", ":")

// 不存在返回-1,有的话,返回第一个
strings.Index(sheetName, "-")
最后修改:2023 年 12 月 30 日
如果觉得我的文章对你有用,请随意赞赏