1. 字符串简介

  • 双引号:字符串使用双引号括起来,其中的相关的转义字符将被替换
str := "Hello World! \n Hello Gopher! \n"

输出:

Hello World! 
Hello Gopher!
  • 反引号:字符串使用反引号括起来,其中的相关的转义字符不会被替换
str :=  `Hello World! \n Hello Gopher! \n`

输出:

Hello World! \nHello Gopher! \n

双引号中的转义字符被替换,而反引号中原生字符串中的 \n 会被原样输出。

string类型的零值是为长度为零的字符串,即空字符串 ""

Go 语言中的string类型是一种值类型,存储的字符串是不可变的,如果要修改string内容需要将string转换为[]byte[]rune,并且修改后的string内容是重新分配的

可以通过函数len()来获取字符串所占的字节长度

str := "asd"
len(str)

如果需要获得字符,应该这么做:

package main

import "fmt"

func main() {
    str := "我与春风皆过客,你携秋水揽星河。"
	for _, char := range str {
		fmt.Printf("%c", char)
	}
}

输出:

我与春风皆过客,你携秋水揽星河。

获取字符串中某个字节的地址的行为是非法的,例如:&str[i]

2. 字符串的拼接

  • 直接使用运算符
str := "Beginning of the string " + 
	"second part of the string"

由于编译器行尾自动补全分号的缘故,加号 + 必须放在第一行。

拼接的简写形式 += 也可以用于字符串:

s := "hel" + "lo, "
s += "world!"
fmt.Println(s) // 输出 “hello, world!”

里面的字符串都是不可变的,每次运算都会产生一个新的字符串,所以会产生很多临时的无用的字符串,不仅没有用,还会给 GC 带来额外的负担,所以性能比较差。

  • fmt.Sprintf()
str := fmt.Sprintf("%d:%s", 2018, "年")
fmt.Println(str) // 2018:年

内部使用 []byte 实现,不像直接运算符这种会产生很多临时的字符串,但是内部的逻辑比较复杂,有很多额外的判断,还用到了 interface,所以性能一般。

  • strings.Join()
str = strings.Join([]string{"hello", "world"}, ", ")
fmt.Println(str) // hello, world

Join会先根据字符串数组的内容,计算出一个拼接之后的长度,然后申请对应大小的内存,一个一个字符串填入,在已有一个数组的情况下,这种效率会很高,但是本来没有,去构造这个数据的代价也不小。

  • bytes.Buffer
var buffer bytes.Buffer
buffer.WriteString("hello")
buffer.WriteString(", ")
buffer.WriteString("world")

fmt.Print(buffer.String()) // hello, world

这个比较理想,可以当成可变字符使用,对内存的增长也有优化,如果能预估字符串的长度,还可以用 buffer.Grow() 接口来设置 capacity

  • strings.Builder
var b1 strings.Builder
b1.WriteString("ABC")
b1.WriteString("DEF")

fmt.Print(b1.String()) // ABCDEF

strings.Builder 内部通过 slice 来保存和管理内容。slice 内部则是通过一个指针指向实际保存内容的数组。strings.Builder 同样也提供了 Grow() 来支持预定义容量。当我们可以预定义我们需要使用的容量时,strings.Builder 就能避免扩容而创建新的 slice 了。strings.Builder是非线程安全,性能上和 bytes.Buffer 相差无几。

3. 有关 string 的常用处理

标准库中有四个包对字符串处理尤为重要:bytesstringsstrconvunicode 包。

  • strings 包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能
  • bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的 []byte 类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer 类型将会更有效
  • strconv 包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换
  • unicode 包提供了 IsDigitIsLetterIsUpperIsLower 等类似功能,它们用于给字符分类

3.1 strings 包

3.1.1 判断两个 utf-8 编码字符串是否相同

  • func EqualFold(s, t string) bool

unicode 大写、小写、标题三种格式字符视为相同

func main() {
    str1 := "Golang"
    str2 := "golang"
    fmt.Println(strings.EqualFold(str1, str2)) // true
}

3.1.2 判断字符串 str 是否是以 prefix 开头

  • strings.HasPrefix(s string,prefix string) bool
func main() {
    str := "哈利·波特"
    prefix := "哈利"
    res := strings.HasPrefix(str, prefix)
    fmt.Println(res) // true
}

3.1.3 判断字符串 str 是否是以 suffix 结尾

  • strings.HasSuffix(str string,suffix string) bool
func main() {
    str := "哈利·波特"
    prefix := "波特"
    res := strings.HasSuffix(str, prefix)
    fmt.Println(res) // true
}

3.1.4 判断 s 在 str 中首次出现的位置,如果没有出现返回 -1

  • strings.Index(str string,s string) int
func main() {
    str := "哈利·波特"
    s := "波特"
    res := strings.Index(str, s)
    fmt.Println(res) // 8,这是字节的index,不是Unicode字符数组的index
}

3.1.5 判断 s 在 str 中最后一次出现的位置,如果没有出现返回 -1

  • strings.LastIndex(str tring, s string) int
func main() {
    str := "哈利·波特"
    s := "·"
    res := strings.LastIndex(str, s)
    fmt.Println(res) // 6,这是字节的index,不是Unicode字符数组的index
}

3.1.6 查询非 ASCII 编码的字符在父字符串中的位置

  • func IndexRune(s string, r rune) int
func main() {
    str := "哈利·波特"
    s := '波'
    res := strings.IndexRune(str, s)
    fmt.Println(res) // 8,这是字节的index,不是Unicode字符数组的index
}

虽然搜索的时rune字符,但是返回的还是字节的索引。

3.1.7 字符串替换

  • strings.Replace(str string, old string, newStr string, n int) string

str中的 old 字符串替换为 newStr 字符串,返回替换后的结果,n 参数为替换次数,n<0 表示无限次。

func main() {
    str := "I love her, her name is red"
    old := "her"
    newStr := "him"
    res := strings.Replace(str, old, newStr,1)
    fmt.Println(res) // I love him, her name is red
    res = strings.Replace(str, old, newStr,2)
    fmt.Println(res) // I love him, him name is red
    res = strings.Replace(str, old, newStr,-2)
    fmt.Println(res) // I love him, him name is red
}

3.1.8 返回 str 中 substr 出现的次数

  • strings.Count(str string, substr string) int
func main() {
    str := "I love her, her name is red"
    substr := "e"
    count := strings.Count(str, substr)
    fmt.Println(count) // 5
}

3.1.9 重复 count 次的 str

  • strings.Repeat(str string, count int) string
func main() {
    str := "love !"
    count := 3
    res := strings.Repeat(str, count)
    fmt.Println(res) //love!love!love!
}

3.1.10 转换大小写

  • strings.ToLower(str string) string
  • strings.ToUpper(str string) string
func main() {
    str := "I Love You !"
    lower := strings.ToLower(str)
    upper := strings.ToUpper(str)
    fmt.Println(lower) // i love you !
    fmt.Println(upper) // I LOVE YOU !
}

3.1.11 去掉收尾空格符

  • strings.TrimSpace(str string) string
func main() {
    str := "I Love You !      "
    res := strings.TrimSpace(str)
    fmt.Println(res) // I Love You !
}

3.1.12 去掉 str 两边的 cut 字符串

  • strings.Trim(str string, cut string) string
func main() {
    str := "ooI Love You !oo"
    res := strings.Trim(str, "oo")
    fmt.Println(res) // I Love You !
}

3.1.13 去掉 str 一边的cut字符串

  • strings.TrimLeft(str string, cut string) string
  • strings.TrimRight(str string, cut string) string

3.1.14 以空格作为分隔符,将 str 分隔成切片

  • strings.Fields(str string) []string
func main() {
    str := "liu hai zhang"
    res := strings.Fields(str)
    fmt.Println(res)
    for _, x := range res {
        fmt.Println(x)
    }
}

输出结果:

[liu hai zhang]
liu
hai
zhang

3.1.15 以 split 作为分隔符,将 str 分隔成切片

  • strings.Split(str string,split string) []string
func main() {
    str := "liu-hai-zhuang"
    res := strings.Split(str, "-")
    fmt.Println(res) // [liu hai zhuang]
}

3.1.16 用 sep 把 slice 中的所有元素连接成字符串

  • strings.Join(slice []string,sep string) string
func main() {
	slice := []string{"liu", "hai", "zhuang"}
	res := strings.Join(slice, "-")
	fmt.Println(res) // liu-hai-zhuang
}

3.1.17 判断字符串 s 是否包含子串 substr

  • func Contains(s, substr string) bool
func main() {
    var str = "中国,台湾"
    fmt.Println(strings.Contains(str, "台湾")) //true
    fmt.Println(strings.Contains(str, "日本")) //false
}

3.1.18 判断字符串 s 是否包含 utf-8 码值 r

  • func ContainsRune(s string, r rune) bool
func main() {
    var r rune = '中'
    var str = "中国"
    fmt.Println(strings.ContainsRune(str, r)) //true
    fmt.Println(strings.ContainsRune(str, '日')) //false
}

3.1.19 判断字符串 s 是否包含字符串 chars 中的任一字符

  • func ContainsAny(s, chars string) bool
func main() {
    var s = "我爱你,中国"
    var chars = "我与春风皆过客"
    var test  = "日"

    fmt.Println(strings.ContainsAny(s, chars)) //true
    fmt.Println(strings.ContainsAny(s, test))  //false
}

3.2 bytes 包

方法类似strings包,不给过是针对[]byte 类型,这里省略,可以参考博客

3.3 strconv 包

3.3.1 string 转 int

  • func Atoi(s string) (i int, err error)
func main() {
    numStr := "999"
    num, err := strconv.Atoi(numStr)
    if err != nil {
        fmt.Println("can't convert to int")
    } else {
        fmt.Printf("type:%T value:%#v\n", num, num) // type:int value:999
    }
}

另外还可以用 strconv 包下的:

func ParseInt(s string, base int, bitSize int) (i int64, err error)

func ParseUint(s string, base int, bitSize int) (n uint64, err error)

base 指定进制(2到36),如果 base0,则会从字符串前置判断,”0x”16 进制,”0”8 进制,否则是 10 进制;

bitSize 指定结果必须能无溢出赋值的整数类型,08163264 分别代表 intint8int16int32int64

3.3.2 int 转 string

  • func Itoa(i int) string
func main() {
    num := 200
    numStr := strconv.Itoa(num)
    fmt.Printf("type:%T value:%#v\n", numStr, numStr) // type:string value:"200"
}

3.3.3 string 转 bool

  • func ParseBool(str string) (bool, error)

str 为:1tTTRUEtrueTrue 中的一种时为真值
str 为:0fFFALSEfalseFalse 中的一种时为假值

func main() {
    fmt.Println(strconv.ParseBool("t"))    // true
    fmt.Println(strconv.ParseBool("TRUE")) // true
    fmt.Println(strconv.ParseBool("true")) // true
    fmt.Println(strconv.ParseBool("True")) // true
    fmt.Println(strconv.ParseBool("0"))    //false
    fmt.Println(strconv.ParseBool("f"))    //false
}

3.3.4 string 转 float

  • func ParseFloat(s string, bitSize int) (f float64, err error)

bitSize3264, 对应系统的位数

func main() {
    strF := "250.56"
    str, err := strconv.ParseFloat(strF, 64)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("type:%T value:%#v\n", str, str) // type:float64 value:250.56
}

3.3.5 float 转 string

  • func FormatFloat(f float64, fmt byte, prec, bitSize int) string

bitSize 表示f的来源类型(32float3264float64),会据此进行舍入。

fmt 表示格式:'f'-ddd.dddd)、'b'-ddddp±ddd,指数为二进制)、'e'-d.dddde±dd,十进制指数)、'E'-d.ddddE±dd,十进制指数)、'g'(指数很大时用 'e' 格式,否则 'f' 格式)、'G'(指数很大时用 'E' 格式,否则 'f' 格式)。

prec 控制精度(排除指数部分):对 'f''e''E',它表示小数点后的数字个数;对 'g''G',它控制总的数字个数。如果 prec-1,则代表使用最少数量的、但又必需的数字来表示 f

func main() {
    num := 250.56
    str := strconv.FormatFloat(num, 'f', 4, 64)
    fmt.Printf("type:%T value:%#v\n", str, str) // type:string value:"250.5600"
}

当然,以上类型转string的话,可以直接用fmt.Sprintf实现。

3.4 unicode 包

3.4.1 判断字符大小写

  • func IsUpper(r rune) bool
  • func IsLower(r rune) bool

3.4.2 转换字符大小写

  • func ToUpper(r rune) rune
  • func ToLower(r rune) rune

3.4.3 判断字符 Title 格式

// 判断字符 r 是否为 Unicode 规定的 Title 字符
// 大部分字符的 Title 格式就是其大写格式
// 只有少数字符的 Title 格式是特殊字符
// 这里判断的就是特殊字符
func IsTitle(r rune) bool

3.4.4 字符转换 Title 格式

  • func ToTitle(r rune) rune

3.4.5 将字符转换为指定格式

  • func To(_case int, r rune) rune

_case 取值:UpperCaseLowerCaseTitleCase

3.4.6 判断字符是否是汉字

func main() {
    for _, r := range "Hello 世界!" {
        // 判断字符是否为汉字
        if unicode.Is(unicode.Scripts["Han"], r) {
            fmt.Printf("%c", r) // 世界
        }
    }
}

更多 unicode.Scripts 取值请参考:

3.4.7 字符判断

  • func IsDigit(r rune) bool

IsDigit 判断 r 是否为一个十进制的数字字符

  • func IsNumber(r rune) bool

IsNumber 判断 r 是否为一个数字字符 (类别 N)

  • func IsLetter(r rune) bool

IsLetter 判断 r 是否为一个字母字符 (类别 L),汉字也是一个字母字符

  • func IsSpace(r rune) bool

IsSpace 判断 r 是否为一个空白字符,包括\t, \n, \v, \f, \r

  • func IsControl(r rune) bool

IsControl 判断 r 是否为一个控制字符

  • func IsGraphic(r rune) bool

IsGraphic 判断字符 r 是否为一个“图形字符”,包括字母标记数字标点符号空格

  • func IsPrint(r rune) bool

IsPrint 判断字符r是否为 Go 所定义的“可打印字符”,包括字母标记数字标点符号ASCII 空格

  • func IsPunct(r rune) bool

IsPunct 判断 r 是否为一个标点字符

  • func IsSymbol(r rune) bool

IsSymbol 判断 r 是否为一个符号字符