GO 语言中的字符串

332 阅读9分钟

Go 语言中的字符串是由单个字节连接起来的字符序列;字符串的字节使用 UTF-8 编码来表示 Unicode 文本。在 Go 中,字符串类型为 string;无论是字符串常量、字符串变量或是代码中出现的字符串字面值,它们的类型都被统一设置为 string字符串是一种值类型;底层是字节的定长数组

概念

字符串的声明和初始化

使用双引号将多个字符包起来;将单个 Unicode 字符字面值一个接一个地连在一起,并用双引号包裹起来就构成了字符串字面值。

str := "this is a string"

字符串的组成

Go 语言中的字符串值也是一个可空的字节序列,字节序列中的字节个数称为该字符串的长度。string 类型其实是一个“描述符”,它本身并不真正存储字符串数据,而是由一个指向底层存储的指针字符串的长度字段组成的。

字符串的转义

在 Go 语言中字符串字面量使用英文双引号(")或者反引号(`)来创建。

  • 双引号用来创建可解析的字符串,支持转义,但不能用来引用多行。

    package main
    
    import "fmt"
    
    func main() {
    	str := "这是个神马\"玩意\""
    	fmt.Println(str) // 这是个神马"玩意"
    }
    
  • 反引号可创建原生的字符串字面量,可以由多行组成,但不支持转义,并且可以包含除反引号外的其他所有字符。

    package main
    
    import "fmt"
    
    func main() {
    	str := `人生得以须尽欢,
    莫使金樽空对月。`
    	fmt.Println(str) // 打印后是两行
    }
    
    

特性

  • string 类型的数据是不可变的,提高了字符串的并发安全性和存储利用率。

    var s string = "hello"
    s[0] = 'k'   // 错误:字符串的内容是不可改变的
    s = "gopher" // ok
    
  • 没有C语言定义字符串的结尾’\0’,而且获取长度的时间复杂度是常数时间,消除了获取字符串长度的开销。

  • 原生支持“所见即所得”的原始字符串,大大降低构造多行字符串时的心智负担,定义方式双引号或反引号。

  • 对非 ASCII 字符提供原生支持,消除了源码在不同环境下显示乱码的可能。

rune 类型与字符字面值

rune 类型

  • rune 类型表示一个 Unicode 码点,rune 本质上是 int32 类型的别名类型,它与 int32 类型是完全等价的

    // $GOROOT/src/builtin.go
    type rune = int32
    
  • 一个 rune 实例就是一个 Unicode 字符。

  • 可以通过字符串字面量来初始化一个 rune 变量。

字符串的表示法

  • 单引号括起的字符串字面值。

    package main
    
    func main() {
    	ascii := 'a'        // ASCII字符
    	unicodeChar := '中'  // Unicode字符集中的中文字符
    	specialChar := '\n' // 换行字符
    	singleChar := '\''  // 单引号字符
    }
    
    
  • Unicode 专用的转义字符 \u\U 作为前缀。

    \u 后面接四个十六进制数。如果是用四个十六进制数无法表示的 Unicode 字符,我们可以使用 \U\U 后面可以接八个十六进制数来表示一个 Unicode 字符。

    package main
    
    import "fmt"
    
    func main() {
    	str := "\u4e2d"
    	fmt.Println(str) // 中
    
    	str = "\U00004e2d"
    	fmt.Println(str) // 中
    
    	str = "\u0027"
    	fmt.Println(str) // 单引号字符 '
    }
    
    
  • 由于表示码点的 rune 本质上就是一个整型数,所以我们还可用整型值来直接作为字符字面值给 rune 变量赋值。

    package main
    
    import "fmt"
    
    func main() {
    	char := '\x27'    // 使用十六进制表示的单引号字符
    	fmt.Println(char) // 39
    
    	char = '\047'     // 使用八进制表示的单引号字符
    	fmt.Println(char) // 39
    }
    

UTF-8字符编码

  • UTF-8 编码解决的是 Unicode 码点值在计算机中如何存储和表示(位模式)的问题。
  • 与UTF-32相比,UTF-32 编码方案固定使用 4 个字节表示每个 Unicode 字符码点;这种编码方案使用 4 个字节存储和传输一个整型数的时候,需要考虑不同平台的字节序问题;由于采用 4 字节的固定长度编码,与采用 1 字节编码的 ASCII 字符集无法兼容;所有 Unicode 字符码点都用 4 字节编码,显然空间利用率很差。
  • UTF-32编码方案,将所有 Unicode字符的码点都按照 4 字节编码。 UTF-8编码方案:根据 Unicode 字符的码点序号不同,所编码的字节数不同。1~4 个字节。

字符串的常见操作

下标操作

在字符串的实现中,真正存储数据的是底层的数组。字符串的下标操作本质上等价于底层数组的下标操作。

package main

import "fmt"

func main() {
	var s = "中国人"
	fmt.Printf("0x%x\n", s[0]) // 0xe4:字符“中” utf-8编码的第一个字节
}

拼接操作

Go 原生支持通过 ++= 操作符进行字符串连接,还提供了 strings.Builderstrings.Joinfmt.Sprintf 等函数来进行字符串连接操作。

package main

import "fmt"

func main() {
	s := "Rob Pike, "
	s = s + "Robert Griesemer, "
	s += " Ken Thompson"

	fmt.Printf("founder: %s\n", s) // result: Rob Pike, Robert Griesemer,  Ken Thompson
}

比较操作

Go 字符串类型支持各种比较关系操作符,包括 ==!=>=<=><。在字符串的比较上,Go 采用字典序的比较策略,分别从每个字符串的起始处,开始逐个字节地对两个字符串类型变量进行比较。当两个字符串之间出现了第一个不相同的元素,比较就结束了,这两个元素的比较结果就会做为字符串最终的比较结果。如果出现两个字符串长度不同的情况,长度比较小的字符串会用空元素补齐,空元素比其他非空元素都小

package main

import "fmt"

func main() {
	// ==
	s1 := "世界和平"
	s2 := "世界" + "和平"
	fmt.Println(s1 == s2) // true

	// !=
	s1 = "Go"
	s2 = "JavaScript"
	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 切片的双向转换,并且这种转换无需调用任何函数,只需使用显式类型转换。

package main

import "fmt"

func main() {
	var s string = "中国人"

	// string -> []rune
	rs := []rune(s)
	fmt.Printf("%x\n", rs) // [4e2d 56fd 4eba]

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

	// []rune -> string
	s1 := string(rs)
	fmt.Println(s1) // 中国人

	// []byte -> string
	s2 := string(bs)
	fmt.Println(s2) // 中国人
}
  • 字符串转数字

    使用strconv.ParseInt()函数将字符串转换为整数类型。

    使用strconv.ParseFloat()函数将字符串转换为浮点数类型。

    package main
    
    import (
    	"fmt"
    	"reflect"
    	"strconv"
    )
    
    func main() {
    	str := "12345"
    	num, err := strconv.ParseInt(str, 10, 64)
    	if err != nil {
    		fmt.Println("Error:", err)
    		return
    	}
    	fmt.Printf("num: %d, typeof: %s\n", num, reflect.TypeOf(num)) // num: 12345, typeof: int64
    
    	floatStr := "3.14"
    	floatNum, err := strconv.ParseFloat(floatStr, 64)
    	if err != nil {
    		fmt.Println("Error:", err)
    		return
    	}
    	fmt.Printf("float num: %f, typeof: %s \n", floatNum, reflect.TypeOf(floatNum)) // float num: 3.140000, typeof: float64
    }
    
    
  • 字符串转布尔

    使用strconv.ParseBool()函数将字符串转换为布尔类型。

    package main
    
    import (
    	"fmt"
    	"reflect"
    	"strconv"
    )
    
    func main() {
    	strTrue := "true"
    	boolValue, err := strconv.ParseBool(strTrue)
    	if err != nil {
    		fmt.Println("Error:", err)
    		return
    	}
    	fmt.Printf("bool value: %t, typeof: %s\n", boolValue, reflect.TypeOf(boolValue)) // bool value: true, typeof: bool
    
    	strFalse := "false"
    	boolValue, err = strconv.ParseBool(strFalse)
    	if err != nil {
    		fmt.Println("Error:", err)
    		return
    	}
    	fmt.Printf("bool value: %t, typeof: %s\n", boolValue, reflect.TypeOf(boolValue)) // bool value: false, typeof: bool
    }
    

字符串迭代

Go 有两种迭代形式:常规 for 迭代与 for range 迭代。

package main

import "fmt"

func main() {
	str := "programming"
	chars := []rune(str)
	for _, char := range chars {
		fmt.Println(string(char))
	}
    
    str = "I am a gopher"
	for index, char := range str {
		fmt.Printf("%d %U %c\n", index, char, char)
	}
}

字符串修改

Go 语言中不能直接修改字符串的内容;如果要修改字符串的内容。则需要先将字符串的内容复制到一个可写的变量中(一般是 []byte 或者 []rune 类型的变量),然后再进行修改

  • 修改字节([]byte

    package main
    
    import "fmt"
    
    func main() {
    	str := "programming"
    	by := []byte(str)
    	by[0] = 'P'
    	fmt.Printf("%s\n", str) // programming
    	fmt.Printf("%s", by)    // Programmming
    }
    
  • 修改字符([]rune

    package main
    
    import "fmt"
    
    func main() {
    	str := "programming"
    	ru := []rune(str)
    	ru[0] = 'P'
    	fmt.Printf("%s\n", str)      // programming
    	fmt.Printf("%s", string(ru)) // Programmming
    }
    

字符串反转

package main

import "fmt"

func main() {
	str := "programming"
	fmt.Printf("%s", Reversal(str)) // gnimmargorp
}

func Reversal(str string) string {
	b := []byte(str)
	length := len(b)

	for i, j := 0, length-1; i < j; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}

	return string(b)
}

生成随机字符串

  • 使用 math/rand 包生成随机字符串

    package main
    
    import (
    	"fmt"
    	"math/rand"
    	"time"
    )
    
    func main() {
    	fmt.Printf(generateRandomString(10))
    }
    
    // 生成指定长度的随机字符串
    func generateRandomString(length int) string {
    	str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    	bytes := []byte(str)
    	result := []byte{}
    	r := rand.New(rand.NewSource(time.Now().UnixNano()))
    	for i := 0; i < length; i++ {
    		result = append(result, bytes[r.Intn(len(bytes))])
    	}
    	return string(result)
    }
    
  • crypto/rand 包生成随机字符串

    package main
    
    import (
    	"fmt"
    	"math/rand"
    )
    
    func main() {
    	fmt.Printf("generate random string: %s\n", generateRandomString(10, []byte("0123456789")))
    }
    
    // 生成指定长度的随机字符串
    func generateRandomString(length int, chars []byte) string {
    	if length == 0 {
    		return ""
    	}
    	clen := len(chars)
    	if clen < 2 || clen > 256 {
    		panic("chars out of range")
    	}
    	maxrb := 255 - (256 % clen)
    	b := make([]byte, length)
    	r := make([]byte, length+(length/4)) // 存储随机字节
    	i := 0
    
    	for {
    		if _, err := rand.Read(r); err != nil {
    			panic("rand.Read error" + err.Error())
    		}
    		for _, rb := range r {
    			c := int(rb)
    			if c > maxrb {
    				continue
    			}
    			b[i] = chars[c%clen]
    			i++
    			if i == length {
    				return string(b)
    			}
    		}
    	}
    }
    
  • 用哈希值来表示随机字符串

    package main
    
    import (
    	"crypto/md5"
    	"fmt"
    	"io"
    	"time"
    )
    
    func main() {
    	fmt.Printf("create password string: %s\n", createPassword())
    }
    
    func createPassword() string {
    	t := time.Now()
    	h := md5.New()
    	io.WriteString(h, "123456")
    	io.WriteString(h, t.String())
    	password := fmt.Sprintf("%x", h.Sum(nil))
    	return password
    }
    

控制大小写

Go 语言转换所有字符串为大写或小写,strings 包提供了 ToLower()ToUpper() 用于将字符串转换成小写和大写

package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "Gopher"
	fmt.Printf("to lower: %s\n", strings.ToLower(str)) // to lower: gopher
	fmt.Printf("to upper: %s\n", strings.ToUpper(str)) // to upper: GOPHER
}

去除首尾空格

Go 语言 string 包提供了 TrimSpace()Trim() 来去除字符串的空格

package main

import (
	"fmt"
	"strings"
)

func main() {
	str := " learn go "
	fmt.Printf("trim space: %s\n", strings.TrimSpace(str)) // trim space: learn go
	fmt.Printf("trim: %s\n", strings.Trim(str, " "))       // trim: learn go
}

合并与分割字符串

  • 合并字符串

    Go 语言 strings 包提供了一个名为 Join() 的函数;第一个参数是一个字符串数组,第二个参数是分隔符

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	str := []string{"l", "e", "a", "r", "n", " ", "Go"}
    	fmt.Printf("join: %s\n", strings.Join(str, "")) // join: learn Go
    }
    
  • 分割字符串

    strings 包提供了 Split()SplitN()SplistAfter()SplitAfterN();4个函数来处理正则分割字符串

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	str := "Learn Go Programming"
    	// 第一个参数为被分割字符串,第二个参数为分隔符
    	res := strings.Split(str, " ")
    	for i := range res {
    		fmt.Printf("Split: %s\n", res[i])
    	}
    
    	// 第一个参数为被分割字符串,第二个参数为分隔符,第三个参数为控制分割的片数,如果为-1则不限制片数
    	res = strings.SplitN(str, " ", 2)
    	for i := range res {
    		fmt.Printf("SplitN: %s\n", res[i])
    	}
    
    	// 与 Split 方法一样
    	res = strings.SplitAfter(str, " ")
    	for i := range res {
    		fmt.Printf("SplitAfter: %s\n", res[i])
    	}
    
    	// 与 SplitN 一样
    	res = strings.SplitAfterN(str, " ", 2)
    	for i := range res {
    		fmt.Printf("SplitAfterN: %s\n", res[i])
    	}
    }
    
    // 打印结果
    // Split: Learn
    // Split: Go
    // Split: Programming
    // SplitN: Learn
    // SplitN: Go Programming
    // SplitAfter: Learn 
    // SplitAfter: Go 
    // SplitAfter: Programming
    // SplitAfterN: Learn 
    // SplitAfterN: Go Programming