3.10 字符串操作
字符串操作
对于任何一门语言来说,字符串的操作都是最常见的操作。
字符串长度
len(yourStr) 有的时候判断字符串是否为空,可以使用if len(yourStr){},这个方法和if yourStr == "" { }效果相同。
字符串截取
yourStr[开始:结束]
yourStr := "Mr. Watson, Come Here, I Want You!"
fmt.Println(yourStr[4:16]) //out [Watson, Come]
字符串切割方法
func Fields(s string) []string,这个函数的作用是按照1:n个空格来分割字符串最后返回的是 []string的切片
yourStr = "Mr. Watson, Come Here, I Want You!"
fmt.Println(strings.Fields(yourStr)) //out [Mr. Watson, Come Here, I Want You!]
func FieldsFunc(s string, f func(rune) bool) []string一看就了解了,这就是根据自定义函数分割了
func Sunnysplit(s rune) bool {
if s == '$' {
return true
}
return false
}
yourStr = "Mr.$Watson,$Come$Here,$I$Want$You!"
fmt.Println(strings.FieldsFunc(yourStr, Sunnysplit)) //out [Mr. Watson, Come Here, I Want You!]
func Split(s, sep string) []string,把字符串按照指定的分隔符切割成slice
yourStr = "Mr.$Watson,$Come$Here,$I$Want$You!"
fmt.Println(strings.Split(yourStr, "$")) //out [Mr. Watson, Come Here, I Want You!]
func SplitAfter(s, sep string) []string,这个函数是在前边的切割完成之后再后边在加上sep分割符
yourStr = "Mr.$Watson,$Come$Here,$I$Want$You!"
fmt.Println(strings.SplitAfter(yourStr, "$")) //out [Mr.$ Watson,$ Come$ Here,$ I$ Want$ You!]
字符串查找
strings.Index("Mr. Watson, Come Here, I Want You!", "Come") 结果为12
strings.Index("Mr. Watson, Come Here, I Want You!", "ff") 结果为-1
字符串和数字的相互转换
字符串与数字的相互转换,主要在strconv这个包里面.
与整数的相互转换
字符串变数字strconv.Atoi()
func JudgeType(e interface{}) (result string) {
switch e.(type) {
case int:
result = "整型"
break
case string:
result = "字符串"
break
}
return
}
yourStr = "123456789"
yourNumber, error := strconv.Atoi(yourStr)
if error != nil {
fmt.Println("字符串转换成整数失败")
} else {
fmt.Println(JudgeType(yourNumber),yourNumber)
}
//out 整型 123456789
也可以使用fmt.Sprintf来把数值转换为字符串
yourNumberFloat := "1234.56789"
yourStr = fmt.Sprintf("%f", yourNumberFloat)
fmt.Println(JudgeType(yourStr), yourStr)
//out 字符串 %!f(string=1234.56789)
数字变字符串strconv.Itoa()
yournumber = 123456789
yourStr = strconv.Itoa(yourNumber)
fmt.Println(JudgeType(yourStr), yourStr)
//字符串 123456789
字符串替换
strings.Replace()
yourStr = "Mr.$Watson,$Come$Here,$I$Want$You!"
fmt.Println(strings.Replace(yourStr, "$", " ", -1)) //out Mr. Watson, Come Here, I Want You!
字符串与[]byte的相互转换
import (
"reflect"
"unsafe"
)
func JudgeType(e interface{}) (result string) {
switch e.(type) {
case int:
result = "整型"
break
case string:
result = "字符串"
break
case []byte:
result = "[]bytes"
break
}
return
}
func Byte2Str(buf []byte) string {
return *(*string)(unsafe.Pointer(&buf))
}
func Str2Byte(s *string) []byte {
return *(*[]byte)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(s))))
}
yourStr = "Mr. Watson, Come Here, I Want You!"
yourByte := Str2Byte(&yourStr)
fmt.Println(JudgeType(yourByte), yourByte)
//out []bytes [77 114 46 36 87 97 116 115 111 110 44 36 67 111 109 101 36 72 101 114 101 44 36 73 36 87 97 110 116 36 89 111 117 33]
yourStr = Byte2Str(yourByte)
fmt.Println(JudgeType(yourStr), yourStr)
//out 字符串 Mr. Watson, Come Here, I Want You!
字符串去除空格
strings.Trim()
yourStr = " Mr. Watson, Come Here, I Want You! "
fmt.Println(strings.Trim(yourStr, " "))
//第二个参数是指trim掉什么字符,这个和很多语言不相同
strings.TrimSpace()
yourStr = "\r\n\t Mr. Watson, Come Here, I Want You! \r\n\t"
fmt.Println(strings.TrimSpace(yourStr))
//这个是把所有的空格类的字符全部trim掉
查找子串是否在指定的字符串中
strings.Contains("Mr. Watson, Come Here, I Want You!", "Come") 结果为true
strings.Contains("Mr. Watson, Come Here, I Want You!", "ff") 结果为false
strings.Contains("Mr. Watson, Come Here, I Want You!", "") 结果为true
strings.Contains("", "") 特别注意,这个结果为true
查找子串替换大小写
strings.ToLower()全部替换为小写
strings.ToUpper()全部替换为大写
Go语言中字符串的拼装方法很多,那么问题来了,到底哪家性能好?
下面代码,分别比较了 fmt.Sprintf,string +,strings.Join,bytes.Buffer,方法是循环若干次比较总时间。
在VMWare下的Ubuntu 14.04下运行的结果表明:
fmt.Sprintf 和 strings.Join 速度相当 string + 比上述二者快一倍 bytes.Buffer又比上者快约400-500倍 如果循环内每次都临时声明一个bytes.Buffer来使用,会比持续存在慢50%,但是仍然很快 测试代码如下:
package main
import (
"bytes"
"fmt"
"strings"
"time"
)
var way map[int]string
func benchmarkStringFunction(n int, index int) (d time.Duration) {
v := "ni shuo wo shi bu shi tai wu liao le a?"
var s string
var buf bytes.Buffer
t0 := time.Now()
for i := 0; i < n; i++ {
switch index {
case 0: // fmt.Sprintf
s = fmt.Sprintf("%s[%s]", s, v)
case 1: // string +
s = s + "[" + v + "]"
case 2: // strings.Join
s = strings.Join([]string{s, "[", v, "]"}, "")
case 3: // stable bytes.Buffer
buf.WriteString("[")
buf.WriteString(v)
buf.WriteString("]")
}
}
d = time.Since(t0)
if index == 3 {
s = buf.String()
}
fmt.Printf("string len: %d\t", len(s))
fmt.Printf("time of [%s]=\t %v\n", way[index], d)
return d
}
func main() {
way = make(map[int]string, 5)
way[0] = "fmt.Sprintf"
way[1] = "+"
way[2] = "strings.Join"
way[3] = "bytes.Buffer"
k := 4
d := [5]time.Duration{}
for i := 0; i < k; i++ {
d[i] = benchmarkStringFunction(10000, i)
}
}
测试的结果很惊人
string len: 410000 time of [fmt.Sprintf]= 359.41771ms
string len: 410000 time of [+]= 245.564328ms
string len: 410000 time of [strings.Join]= 581.570649ms
string len: 410000 time of [bytes.Buffer]= 484.275µs
- trings.Join 最慢
- fmt.Sprintf 和 string + 差不多
bytes.Buffer又比上者快约500倍
+操作符 通过汇编可知实现在runtime/string.go中, 主要是concatstrings函数 短字符串优化,没有借助[]byte造成转换string的消耗,故单次调用+操作符是最快的。灵活性最差
- bytes.Buffer 源码实现在bytes/buffer.go中 小内存优化,能提前预分配内存,内存不足时*2倍增长,但是最后获取string结果有[]byte转string的消耗,故bytes.Buffer在一次初始化(提前计算总长度,一次性预分配好内存更好),多次字符串连接操作,最后一次性获取string结果的场景中是最快的。灵活性是最强的
- strings.Join 源码实现在strings/strings.go中 少量字符串连接优化,一次性分配内存,有[]byte转换string的消耗,故单次调用能达到bytes.Buffer的最好效果,但是它不够灵活
- fmt.Sprintf 源码实现在fmt/print.go中 因为a...interface{}有参数转换的消耗, 借助[]byte每次添加调用append,逻辑相对复杂,最后获取结果有[]byte转string的消耗,故fmt.Sprintf一般要慢于bytes.Buffer和strings.Join,灵活性和strings.Join差不多
- 结论
- 单次调用性能:操作符+>strings.Join>=bytes.Buffer>fmt.Sprintf
- 灵活性:bytes.Buffer>fmt.Sprintf>=strings.Join>操作符+
- 正确使用,多次连接字符串操作的情况下,bytes.Buffer应该是最快的。
在go 1.10中有了新的strings.Builder