
1.2 字符串实战技巧
1.2.1 字符串基础
字符串是一串固定长度的字符连接起来的字符序列。Go语言中字符串是由单个字节连接起来的。Go语言中字符串的字节使用UTF-8编码来表示Unicode文本。UTF-8是一种广泛使用的编码格式,是文本文件的标准编码,包括XML和JSON在内都使用该编码。
由于该编码占用字节长度的不定性,所以在Go语言中,字符串也可能根据需要占用1~4个字节,这与其他编程语言如C++、Java或者Python不同(Java始终使用2个字节)。Go语言这样做,不仅减少了对内存和硬盘空间占用,而且也不用像其他语言那样需要对使用UTF-8字符集的文本进行编码和解码。
字符串是一种值类型,且值不可变,即在创建某个文本后将无法再次修改这个文本的内容。更深入地讲,字符串是字节的定长数组。
1.2.2 中文字符串的截取
在Go语言中,可以使用len()函数获取字符串的字节长度,其中英文占1个字节长度,中文占3个字节长度。可以使用“变量名[n]”获取字符串第n+1个字节,返回这个字节对应的Unicode码值(uint8类型)。注意n的取值范围是[0,len(n)-1]。
在Go语言中可以通过切片截取一个数组或字符串,但是当截取的字符串是中文时,可能会出现的问题是:由于中文一个字不只是由一个字节组成,所以直接通过切片获取可能会把一个中文字的编码截成两半,结果导致最后一个字符是乱码。解决办法可以先将其转为[]rune类型,再在截取后,转回字符串类型。示例如下:
代码路径:chapter1/string/1.2.2-str5.go。


1.2.3 按单词或字节反转字符串
在Go语言中,如果要反转字符串,可以先将字符串转成rune数组类型,利用平行赋值的方式反转,再将rune数组转回字符串类型,示例如下。
代码路径:chapter1/string/1.2.3-strRev.go。


1.2.4 生成随机字符串
1 用math/rand包生成随机字符串
math/rand包实现了伪随机数生成器,可以生成整型和浮点型伪随机数。该包中根据生成伪随机数是否有种子(可以理解为初始化伪随机数),可以分为以下两类。
1)有种子。通常以时钟、输入输出等特殊节点作为参数进行初始化。该类型生成的随机数相比无种子时重复概率较低。
2)无种子。可以理解为此时种子为1。
用“math/rand”包生成随机字符串示例如下。
代码路径:chapter1/string/1.2.4-rand1.go。

2 用crypto/rand包生成随机字符串
上面的例子用的是math/rand包生成的(伪)随机数,如果对随机性有高要求的话,则可以用crypto/rand包实现(速度相对来说会慢些)。crypto/rand包实现了用于加解密的更安全的随机数生成器。该包中常用的是Read()函数,其定义如下。

该方法将随机的byte参数值填充到数组b中,以供b使用,示例如下。

3 用哈希值来表示随机字符串
除了上面两个包之外,还有一个不是很常用的方法,就是用哈希值来表示随机字符串。可以通过crypto/md5包的New()函数来实现,示例如下。
代码路径:chapter1/string/1.2.4-password.go。

1.2.5 控制大小写
(1)Go语言转换所有字符串为大写或者小写。
Go语言的strings包提供了ToLower()和ToUpper()函数,用于将字符串转换成小写和大写,其定义如下。

转换为大写的示例如下。
代码路径:chapter1/string/1.2.5-str1.go。

(2)Go语言区分大小写地替换字符串。
可以通过regexp包的MustCompile()和ReplaceAllString()两个函数组合使用来处理实现区分大小写地替换字符串,示例如下。
代码路径:chapter1/string/1.2.5-str2.go。


1.2.6 去除字符串首尾的空格
Go语言strings包提供了TrimSpace()函数来去除字符串的空格,其定义如下。

也可以用strings包提供的Trim()函数去除字符串的空格,其定义如下。

在以上定义中,Trim()函数的第1个参数是字符串,第2个参数是要去除空格的字符串,Trim()和TrimSpace()函数的使用示例如下:
代码路径:chapter1/string/1.2.6-str.go。

1.2.7 生成CSV数据
逗号分隔值(Comma-Separated Values,CSV)是指文件以纯文本形式存储表格数据(数字和文本)。Go语言提供encoding/csv包来处理CSV数据。生成CSV数据的处理流程如下:
首先,需要使用os.Create()函数创建“.csv”文件,os.Create()函数的定义如下:

os.Create()函数如果创建文件成功,则会返回文件指针对象∗File。
其次,调用文件对象∗File的WriteString()方法来设置写入文件的内容为字符串类型。WriteString()方法的定义如下。


最后,调用∗Writer对象提供Write()方法来写入数据到CSV文件中,其定义如下。

其使用示例如下。
代码路径:chapter1/string/1.2.7-csv.go。

运行以上代码,会在代码所在目录生成一个名为“test.csv”的文件,用WPS打开的效果如图1-1所示。

● 图1-1
1.2.8 解析CSV数据
Go语言可以通过简单的逐行扫描输入并使用strings.Split()等函数解析CSV格式。但其实Go语言提供了更好的方法,那就是用encoding/csv包来处理。encoding/csv包中的NewReader()函数返回Reader结构体,该结构体提供了读取CSV文件的API接口。其解析CSV数据的处理流程如下。
首先,调用os.Open()函数打开将要解析的文件,代码如下。

然后调用encoding/csv包中的NewReader()函数返回Reader结构体,并设置Reader结构体的Comma字段来设置分隔符,代码如下。

Reader结构体根据需要保留变量来配置读取参数。Reader的FieldsPerRecord参数是一个重要的设置。如果设置了FieldsPerRecord参数,则会校验每一行的字段计数量是否匹配该参数的值。默认情况下,当设置为0时,则Read结构体将其设置为第1条记录中的字段数,因此将来的记录必须具有相同的字段计数。如果设置为正值,则Read要求每条记录具有给定数量的字段。如果设置了负值,则不进行校验。另一个有趣的配置是注释参数,它允许开发者在已解析的数据中定义注释字符。在本例中,以这种方式忽略整行,代码如下。

Go语言禁止使用无意义的逗号和注释设置。这意味着null、回车、换行符、无效的符文和Unicode替换字符等都不能使用。此外,Go语言还禁止将逗号和注释设置为相等。完整示例如下。代码路径:chapter1/string/1.2.8-csv.go。

1.2.9 获取中文字符串
获取中文字符串可以通过regexp包的MustCompile()函数来实现,regexp包实现了正则表达式搜索。Go语言的正则表达式语法和Perl、Python等语言的基本一致。获取中文字符串的示例如下。
代码chapter1/string/1.2.9-str.go。


1.2.10 按指定函数分割字符串
Go语言strings包中提供了一个名为FieldsFunc()的函数来分割字符串,该方法定义如下。

该方法第1个参数为字符串,第2个参数是1个闭包,返回的结果是切片,示例如下。
代码路径:chapter1/string/1.2.10-str.go。


1.2.11 合并与分割字符串
(1)使用Go语言合并字符串
Go语言strings包中提供了一个名为Join()的函数,该函数定义如下。

第1个参数elems是字符串数组,第2个参数sep是分隔符,示例如下。
代码路径:chapter1/string/1.2.11-str.go。

(2)使用Go语言分割字符串
strings包提供了Split()、SplitN()、SplitAfter()、SplitAfterN() 4个函数来处理正则分割字符串。
1)Split()函数的定义如下。


其中,s为正则分割字符串,sep为分隔符,n为控制分割的片数,n如果为-1则不限制片数。如果匹配,则函数会返回一个字符串切片。
3)SplitAfter()函数的定义如下。

4)SplitAfterN()函数的定义如下。

以上4个函数都是通过sep参数对传入字符串参数s进行分割的,返回类型为[]string。如果sep为空,则相当于分成一个UTF-8字符。在以上4个函数中,Split(s, sep)和SplitN(s, sep,-1)等价;SplitAfter(s, sep)和SplitAfterN(s, sep,-1)等价。
代码路径:chapter1/string/1.2.11-str2.go。

代码运行结果如下。


1.2.12 按照指定函数截取字符串
Go语言的strings包中提供了TrimFunc()函数来截取字符串,其定义如下。

以上函数表示截取字符串s两端满足函数f的字符。按照指定函数截取字符串的示例如下。
代码路径:chapter1/string/1.2.12-str.go。

1.2.13 【实战】生成可下载的CSV文件
生成可下载的CSV文件的思路如下:
1)绑定一个路由和建立一个对应的处理器函数,示例如下。

2)生成CSV文件,示例如下。
代码路径:chapter1/string/1.2.13-downloadCsv.go。

3)编写处理器函数,示例如下。

运行服务器,打开浏览器,输入http://127.0.0.1:8088/down,即可下载对应的CSV文件。
1.2.14 【实战】用Go运行Shell脚本程序
Go语言提供了os/exec包来运行Shell命令,os/exec包的Command()函数可以用于输入Shell命令,用给定的参数执行指定的程序。该函数需要传入要执行的程序命令行和参数,第1个参数是要执行的程序命令行,其他参数是程序命令行的参数。它会返回一个名为∗Cmd的结构体,该结构体代表一个执行的外部命令,当调用它的Run()、Output()、CombinedOutput()等方法后这个对象就不能重用了。一般也不会重用这个对象,而是在需要的时候重新生成一个。例如,执行ls命令,并传给它参数-a的示例如下。

假如执行这个命令,会发现控制台中并没有任何输出,但其实这个命令已经执行了,只不过Go程序并没有捕获和处理输出,所以控制台中没有任何输出。
可以设置如下这几个字段,实现定制化的输入和输出。

如果Stdin为空,则进程会从null device(os.DevNull)中读取数据;如果Stdin是∗os.File对象,则会从这个文件中读取数据;如果Stdin是os.Stdin,则会从标准输入(如命令行)中读取数据。
Stdout和Stderr代表外部程序进程的“标准输出”和“错误输出”。如果为空,则输出到null device中;如果Stdout和Stderr是 ∗os.File对象,则会往文件中输出数据;如果Stdout和Stderr分别设置为os.Stdout、os.Stderr,则会输出到命令行中。带输出命令的示例如下。

在示例chapter1/string/1.2.14-shell.go中,可以通过tr命令转换输入,示例如下。

tr命令用于Linux命令转换、压缩或删除标准输入中的字符,写入标准输出。在本例中,将小写字母转换为大写字母,示例如下。

通过cmd.Stdout字段,接收命令传递的一个字符串并写入标准输出,示例如下。

完整示例如下:
代码路径:chapter1/string/1.2.14-shell.go。

程序的输出将写入字节缓冲区,运行结果如下。
