如果你是从其他语言(像 Ruby 或 Python )转的,那么会有一些语言上的差异需要学习,其中很多是围绕处理 string
类型的。下面是一些字符串技巧,这些技巧解决了我最初使用 Golang 时遇到的问题。
多行字符串
在 Go 中创建多行字符串很简单。只需要在声明或赋值时,使用 ( ` ) 。
str := `This is a
multiline
string.`
跑一下吧: play.golang.org/p/mpIQuNhC1…
注意 - 在字符串中的缩进的都会保留到最终结果。
str := `This string
will have
tabs in it`
跑一下吧: play.golang.org/p/mpIQuNhC1…
高效的字符串连接方式
Go 1.10 有一个新的字符串生成器 strings.Builder
!
虽然下面的代码仍然有效,但 Go 1.10 在 strings
包中引入了 Builder 类型,该类型更易于使用,通常效率更高。我在这篇文章中提到了如何使用它,在 Go 1.10+ 中连接和构建字符串。
虽然 Go 允许使用 +
运算符连接字符串,但在处理大量字符串连接时,这可能会变得非常低效。使用 bytes.Buffer
连接字符更高效,它会一次性将所有的内容连接起来转化成字符串。
import (
"bytes"
"fmt"
)
func main() {
var b bytes.Buffer
for i := 0; i < 1000; i++ {
b.WriteString(randString())
}
fmt.Println(b.String())
}
func randString() string {
// Pretend to return a random string
return "abc-123-"
}
跑一下吧: play.golang.org/p/mpIQuNhC1…
如果你提前准备好所有字符串,你也可以使用 strings.Join
方法。
import (
"fmt"
"strings"
)
func main() {
var strs []string
for i := 0; i < 1000; i++ {
strs = append(strs, randString())
}
fmt.Println(strings.Join(strs, ""))
}
func randString() string {
// Pretend to return a random string
return "abc-123-"
}
跑一下吧: play.golang.org/p/mpIQuNhC1…
将整型(或其他类型)转成字符串类型
在很多语言中,任何数据类型只要和字符串拼接,或者插入字符串,就可以将其转换为字符串(举例在 Ruby 中 "ID=#{id}")不幸的是,如果你想在 Go 中这样操作,比如强制把一个整型转换成字符串类型,你就不可能得到你想要的结果。
i := 123
s := string(i)
你期望 s 的输出是什么?如果你像大多数人一样猜 "123"
,那你就大错特错了。相反,你会得到类似 "E"
的值作为 s
的值。这根本不是我们想要的!
相反,你应该使用 strconv 之类的包或 fmt.Sprintf
这样的函数。例如,下面是一个使用 strconv.Itoa
将整数转换成字符串。
package main
import (
"fmt"
"strconv"
)
func main() {
i := 123
t := strconv.Itoa(i)
fmt.Println(t)
}
跑一下吧: play.golang.org/p/mpIQuNhC1…
你也可以使用 fmt.Sprintf
函数将几乎任何数据类型转换为字符串,但通常应保留在这样的实例上,如正在创建的字符串包含嵌入数据,而不是在将单个整数转换为字符串时用。
package main
import "fmt"
func main() {
i := 123
t := fmt.Sprintf("We are currently processing ticket number %d.", i)
fmt.Println(t)
}
跑一下吧: play.golang.org/p/mpIQuNhC1…
fmt.Sprintf
的操作与 fmt.Printf
几乎相同,只是它没有将结果字符串输出到标准输出,而是将其作为字符串返回。
限制 Sprintf
的使用
正如我之前提到的, fmt.Sprintf
通常应保留用于创建具有嵌入值的字符串。有几个原因,但最突出的一个是 fmt.Sprintf
不做任何类型检查,所以在实际运行代码之前不太可能发现任何错误。
fmt.Sprintf
也比 strconv
包中通常使用的大多数函数都慢,不过老实说,速度差非常小,通常不值得考虑。
创建随机字符串
这不是一个真正的 「快速技巧」,但我发现这是一个经常被问到的问题。
“ 如何在Go中创建随机字符串?
听起来很简单。许多语言如 Ruby 和 Python 都提供了一些帮助程序,使得生成随机字符串非常容易,所以 Go 肯定有一个,对吧?嘿嘿,错了。
Go 选择只提供创建随机字符串的工具,并将细节留给开发人员。虽然一开始这可能会让人觉得不舒服,但好处是你可以完全控制如何生成字符串。这意味着你可以指定字符集、如何播种随机生成以及任何其他相关细节。简而言之,你拥有更多的控制权,但代价是需要编写一些额外的代码。
下面是一个使用 math/rand 包和一组字母数字字符作为字符集的快速示例。
import (
"fmt"
"math/rand"
"time"
)
func main() {
fmt.Println(RandString(10))
}
var source = rand.NewSource(time.Now().UnixNano())
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func RandString(length int) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[source.Int63()%int64(len(charset))]
}
return string(b)
}
跑一下吧: play.golang.org/p/mpIQuNhC1…
Go Playground 总是输出相同的字符串
如果在 Go Playground 上运行了多次此代码,您可能会注意到它总是输出相同的字符串 - aJFLa7XPH5
。
这是因为 Go Playground 总是使用相同的时间,所以当我们用 rand.NewSource
函数并且传递在当前时间的值总是相同的,所以我们生成的字符串将始终相同。
对于您的特殊需求,可能会有比这个更好的解决方案,但这是一个很好的起点。如果您正在寻找改进/更改代码的方法,您可以考虑使用 crypto/rand
包来生成随机数据 - 这通常更安全,但最终可能需要更多的工作。
不管你最终使用什么,这个例子应该可以帮助你开始。对于大多数不涉及敏感数据(如密码和身份验证系统)的实际用例,它都能很好地工作。一定要记住给你的随机数发生器播种!这可以在 math/rand
包中通过 rand.Seed
函数实现,或通过创建源代码。在上面的例子中是创建源代码的方式。
strings
包、HasPrefix
和自定义代码
当处理字符串时,想要知道字符串是以特定字符串开头还是以特定字符串结尾是很常见的。例如,如果API密钥都以 sk_
开头,你可能希望验证 API 请求中 提供的所有 API 密钥都以此前缀开头,否则查数据太浪费时间。
对于听起来非常常见的用例的函数,你最好直接访问 String 包并检查是否有助于你解决问题。在这种情况下,你可能会使用到 strings.HasPrefix(str,prefix) 和 strings.HasSuffix(str,prefix) 。看下下边这个例子:
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.HasPrefix("something", "some"))
fmt.Println(strings.HasSuffix("something", "thing"))
}
跑一下吧: play.golang.org/p/mpIQuNhC1…
虽然 strings 包中有很多有用的通用函数,但值得注意的是,并不总是值得去找一个满足你需要的包。如果你用过其他语言又开始用 Go,一个常见错误是开发人员花费了太多时间去找能够提供他们所需功能的包,而他们本可以自己轻松地编写代码实现。
使用标准库肯定有好处;例如,它们经过了彻底的测试,并且有很好的文档记录。尽管有这些好处,但如果你发现自己花了超过几分钟的时间寻找一个函数,那么自己编写它也同样有益。在这种情况下,根据自己的需求编码,将很快完成,你将完全了解正在发生的事情,不会被奇怪的边缘案件搞的猝不及防。你也不必担心其他人维护代码。
字符串可以转换为 byte 节片(反之亦然)
reddit 上的评论指出,字符串和字节片之间的相关性并不总是显而易见的,因此虽然本文最初是5个技巧,但现在已经扩展到6个技巧。
在 Go 中,可以将字符串转换为字节片 ([]byte
) ,将字节片转换为字符串。这样做非常简单,看起来像任何其他类型转换。这种转换通常是为了将字符串传递给接受字节片的函数,或者将字节片传递给需要字符串的函数。
以下是转换示例:
package main
import "fmt"
func main() {
var s string = "this is a string"
fmt.Println(s)
var b []byte
b = []byte(s)
fmt.Println(b)
for i := range b {
fmt.Println(string(b[i]))
}
s = string(b)
fmt.Println(s)
}
跑一下吧: play.golang.org/p/mpIQuNhC1…
这就是 Go 语言字符串使用过程中的一些小技巧,希望能帮到你。如果你需要更多 Go 相关的实践,可以查阅我发表的其他相关教程。