几乎任何程序都离不开字符串,字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。
Go语言字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组。
Go 代码使用 UTF-8 编码(且不能带 BOM),同时标识符支持 Unicode 字符。在标准库 unicode 包及其子包 utf8、utf16中,提供了对 Unicode 相关编码、解码的支持,同时提供了测试 Unicode 码点(Unicode code points)属性的功能。
Go语言中的字符串也可能根据需要占用 1至 4 个字节,这与其它语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。
Go语言支持以下2种形式的字符串:
1. 解释性字符串:带引号的字节序列。该类字符串使用双引号括起来,其中的相关的转义字符将被替换。例如:
str := "laoYu"
2. 原生字符串: 该类字符串使用反引号(注意:不是单引号)括起来,支持换行。例如:
`This is a raw string \n`
上面原生字符串中的 \n
会被原样输出。
获取字符串长度可以用内置的函数len
。
strings 包提供了很多操作字符串的简单函数,通常一般的字符串操作需求都可以在这个包中找到。
下面简单举几个例子:
判断是否以某字符串打头/结尾
strings.HasPrefix(s, prefix string) bool
strings.HasSuffix(s string, suffix string) bool
字符串分割
strings.Split(s string, sep string) []string
返回子串索引
strings.Index(s string, sub string) int
strings.LastIndex 最后一个匹配索引
字符串连接
strings.Join(a []string, sep string) string
另外可以直接使用“+”来连接两个字符串
字符串替换
strings.Replace(s, old, new string, n int) string
字符串转化为大小写
strings.ToUpper(s string) string
strings.ToLower(s string) string
统计某个字符在字符串出现的次数
strings.Count(s string, sep string) int
判断字符串的包含关系
strings.Contains(s, substr string) bool
strconv 包提供了基本数据类型和字符串之间的转换。在 Go 中,没有隐式类型转换,一般的类型转换可以这么做:int32(i),将 i (比如为 int 类型)转换为 int32,然而,字符串类型和 int、float、bool 等类型之间的转换却没有这么简单。
与字符串相关的类型转换都是通过 strconv 包实现的。
针对从数字类型转换到字符串,Go 提供了以下函数:
- strconv.Itoa(i int) string
返回数字 i 所表示的字符串类型的十进制数。
下面我们示范下上述函数的简单使用:
package main
import(
"fmt"
"strings"
"strconv"
)
func main() {
str01 :=`This is a raw string \n` //原生字符串
str02 :="This is a raw string \n"//引用字符串
fmt.Println("原生字符串和引用字符串的区别")
fmt.Printf(str01)
fmt.Println("")
fmt.Printf(str02)
fmt.Println("")
fmt.Println("+连接字符串")
var str03 string = str01 +str02
fmt.Printf(str03)
fmt.Println("")
var str string = "This is an example of a string"
fmt.Println("HasPrefix 函数的用法")
fmt.Printf("T/F? Does the string \"%s\"have prefix %s? ", str, "Th") //前缀
fmt.Printf("%t\n", strings.HasPrefix(str, "Th\n"))
fmt.Println("")
fmt.Println("Contains 函数的用法")
fmt.Println(strings.Contains("seafood", "foo")) //true
fmt.Println(strings.Contains("seafood", "bar")) //false
fmt.Println("Count 函数的用法")
fmt.Println(strings.Count("cheese", "e")) // 3
fmt.Println(strings.Count("five", ""))
fmt.Println("")
fmt.Println("Index 函数的用法")
fmt.Println(strings.IndexRune("NLT_abc", 'b')) // 返回第一个匹配字符的位置,这里是4
fmt.Println(strings.IndexRune("NLT_abc", 's')) // 在存在返回 -1
fmt.Println(strings.IndexRune("我是中国人", '中')) // 在存在返回 6
fmt.Println("")
fmt.Println("Join 函数的用法")
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", ")) // 返回字符串:foo, bar, baz
fmt.Println("")
fmt.Println("LastIndex 函数的用法")
fmt.Println(strings.LastIndex("go gopher", "go")) // 3
fmt.Println("")
fmt.Println("Replace 函数的用法")
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
fmt.Println("")
fmt.Println("Split 函数的用法")
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
fmt.Println("")
fmt.Println("ToLower 函数的用法")
fmt.Println(strings.ToLower("Gopher")) //gopher
fmt.Println("")
fmt.Println("strconv.Itoa()函数用法")
var an int = 6
newS := strconv.Itoa(an)
fmt.Printf("The new string is: %s\n", newS)
}
字符串的本质就是一个字节数组。在Go 语言里面字符串和字节数组可以相互进行显式转换,这一性质通常被用来“修改”字符串的内容。
因为Go 语言中的字符串是不可变的,也就是说 str[index]
这样的表达式是不可以被放在等号左侧的。如果尝试运行 str[i] = 'D'
会得到错误:cannot assign to str[i]
。
因此必须先将字符串转换成字节数组,然后再通过修改数组中的元素值来达到修改字符串的目的,最后将字节数组转换回字符串格式。示例代码如下:
package main
import (
"fmt"
)
var name string //声明一个字符串
var emptyname string = "" //声明一个空字符串
func main() {
//申明多个字符串并且赋值
a, b, v := "hello", "word", "widuu"
fmt.Println(a, b, v)
c := []byte(a) //转换字符串的内容,先转换a的类型为[]byte
c[0] = 'n' //赋值
//在转换成字符串类型,其实我们发现我们的a并没有改变
//而是一个新的字符串的改变
d := string(c) //转换为字符串
fmt.Println(a) //hello
fmt.Println(c) //[110 101 108 108 111]
fmt.Println(d) //nello
}
UTF-8
是被广泛使用的编码格式,是文本文件的标准编码。在 Go语言字符串是UTF-8
字符的一个序列。由于该编码对占用字节长度的不定性,Go 中的字符串也可能根据需要占用 1 至 4 个字节。Go 语言这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用UTF-8
字符集的文本进行编码和解码。
接下来我们创建一个用于统计字节和字符(rune)的程序,并对字符串 asSASA ddd dsjkdsjs dk 进行分析,然后再分析 asSASA ddd dsjkdsjsこん dk。如下:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// count number of characters:
str1 := "asSASA ddd dsjkdsjs dk"
fmt.Printf("The number of bytes in string str1 is %d\n",len(str1))
fmt.Printf("The number of characters in string str1 is %d\n",utf8.RuneCountInString(str1))
str2 := "asSASA ddd dsjkdsjsこん dk"
fmt.Printf("The number of bytes in string str2 is %d\n",len(str2))
fmt.Printf("The number of characters in string str2 is %d",utf8.RuneCountInString(str2))
}
/* Output:
The number of bytes in string str1 is 22
The number of characters in string str1 is 22
The number of bytes in string str2 is 28
The number of characters in string str2 is 24
*/
我们在之前的关卡讲过,Go语言中字符串的拼接用+
。例如:
package main
import (
"fmt"
)
func main() {
//申明多个字符串并且赋值
a, b:= "hello", "world"
var c string = a+b
fmt.Println(c) //helloworld
}
不过用+
这种合并方式效率非常低,每合并一次,都是创建一个新的字符串,就必须遍历复制一次字符串。
Java中提供StringBuilder类(最高效,线程不安全)来解决这个问题。Go中也有类似的机制,那就是Buffer(线程不安全)。代码如下:
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer
for i := 0; i < 100; i++ {
buffer.WriteString("a")
}
fmt.Println(buffer.String())
}
使用bytes.Buffer来组装字符串,不需要复制,只需要将添加的字符串放在缓存末尾即可。不过需要强调,Golang源码对于Buffer的定义中并没有任何关于锁的字段,所以Buffer是线程不安全的。