3.6.1. strings包
在字符串处理中一个常见的需求是能够将字符串切分为字符串切片,然后再做进一步的处理,例如,将字符串转换为数字或去除字符串前后的空格。
为了学习如何使用strings包中的函数,我们将通过一些简单的例子来使用这些函数。表3.6和表3.7列出了strings包中所有的函数。首先让我们以切分字符串开始。
1 2 3 4 5 | names := "Niccolò•Noël•Geoffrey•Amélie••Turlough•José" fmt.Print("|") for _, name := range strings.Split(names, "•") { fmt.Printf("%s|", name) } |
1 | |Niccolò|Noël|Geoffrey|Amélie||Turlough|José| |
我们使用了strings.Split()函数将以项目符号分隔的字符串names(包括一个空的名字)进行切分。该函数接受一个要切分的字符串和一个分隔符参数并尽可能的将字符串全部切分开。(如果我们想要限制切分的数量,我们可以使用strings.SplitN()函数。)如果使用strings.SplitAfter()函数的话输出结果如下:
1 | |Niccolò•|Noël•|Geoffrey•|Amélie•|•|Turlough•|José| |
strings.SplitAfter()函数和strings. Split()函数执行相同的切分操作,但前者会保留分隔符。也是字符串。当我们希望切分特定次数时,我们可以使用strings.SplitAfterN()函数。
我们可以使用strings.FieldsFunc()函数来按照多个不同的字符进行切分。
1 2 3 4 5 6 7 8 9 10 | for _, record := range []string{"László Lajtha*1892*1963", "Édouard Lalo\t1823\t1892", "José Ángel Lamas|1775|1814"} { fmt.Println(strings.FieldsFunc(record, func(char rune) bool { switch char { case '\t', '*', '|': return true } return false })) } |
1 2 3 | [László·Lajtha·1892·1963] [Édouard·Lalo·1823·1892] [José·Ángel·Lamas·1775·1814] |
表3.6 strings包中的函数#1
变量s和是string类型,xs是[]string类型,i是int类型,f是一个具有func(rune)bool签名的函数。索引位置是匹配Unicode码点(字符)或字符串的第一个UTF-8字节的位置,当没有匹配时,索引位置为-1。
strings.Contains(s, t) | 如果s中含有t则返回true |
strings.Count(s, t) | t在s中出现的次数 |
strings.EqualFold(s, t) | 大小写敏感,如果s与t相等则返回true |
strings.Fields(s) | 将s以空格切分,返回[]string |
strings .FieldsFunc(s, f) | 在函数位置切分s,返回[]string |
strings.HasPrefix(s, t) | 如果s以t开头则返回true |
strings.HasSuffix(s, t) | 如果s以t结尾则返回true |
strings.Index(s, t) | t在s中第一次出现的位置 |
strings.IndexAny(s, t) | t中的任意字符在s中第一次出现的位置 |
strings.IndexFunc(s, f) | s中当f返回true时的第一个索引位置 |
strings.InexRune(s, char) | rune类型的字符char在s中第一次出现的位置 |
strings.Join(xs, t) | 将xs中的所有字符串以分隔符t(可以是””)进行连接 |
strings.LastIndex(s, t) | t在s中最后一次出现的位置 |
strings.LastIndexAny(s, t) | t中的任意字符在s中最后一次出现的位置 |
strings.LastIndexFunc(s,f) | s中当f返回true时的最后一个索引位置 |
strings.Map(mf, t) | 根据具有func(rune) rune签名的函数mf,替换或删除t中的所以对应的字符 |
strings.NewReader(s) | 将值s转化为指针并具有Read()、ReadByte()和ReadRune()方法 |
strings.NewReplacer(…) | 将值转化为指针并具有将给定的每一个旧字符串替换为新的字符串的方法 |
strings.Repeat(s, i) | 将s重复i次 |
表3.7 strings包中的函数#2
变量r是unicode类型。SpecialCase用于指定Unicode规则(高级特性)。
strings.Replace(s, old, new, i) | 如果i=-1,则将s中所有非重复出现的字符串old替换为字符串new,否则执行i次替换 |
strings.Split(s, t) | 将s以t进行切分,返回[]string |
strings.SplitAfter(s, t) | 同上,但保留分隔符 |
strings.SplitAfterN(s, t, i) | 与strings.SplitN()函数类似,但保留分隔符 |
strings.SplitN(s, t, i) | 将s以t进行i-1次切分,返回[]string |
strings.Title(s) | 将s中每一个单词的首字母大写 |
strings.ToLower(s) | 将s中的所有字符转换为小写 |
strings.ToLowerSpecial(r, s) | 按照r中的优先级规则,将s转换为小写 |
strings.ToTitle(s) | 将s中所有字符大写 |
strings.ToTitleSpecial(r, s) | 按照r中的优先级规则,将s中所有字符大写 |
strings.ToUpper(s) | 将s中的所有字符转换为大写 |
strings.ToUpperSpecial(r, s) | 按照r中的优先级规则,将s中的所有字符转换为大写 |
strings.Trim(s, t) | 将t从s的前后两端去掉 |
strings.TrimFunc(s, f) | 当函数f返回true时,将相应的字符从s的前后两端去掉 |
strings.TrimLeft(s, t) | 将t从s的开始处去掉 |
strings.TrimLeftFunc(s, f) | 当函数f返回true时,将相应的字符从s的开始处去掉 |
strings.TrimRight(s, t) | 将t从s的结尾处去掉 |
strings.TrimRightFunc(s, f) | 当函数f返回true时,将相应的字符从s的结尾处去掉 |
strings.TrimSpace(s) | 将空格从s的前后两端去掉 |
strings.FieldsFunc()函数接受一个字符串(本例中是record)和一个签名为func(rune)bool的函数引用作为参数。因为这个函数很小且只能用在这种地方,所以我们在需要的地方创建了一个匿名函数。(用这种方式创建的函数被称为闭包函数,不过在这种特殊情况下,我们并没有将其封闭)。strings.FieldsFunc()函数会逐字符的遍历给定的字苻串并将每一个字符作为参数传递给函数引用,如果该函数引用返回true则执行切分操作。这里我们可以看到上例中字符串是以制表符、星号或竖线进行切分的。
我们可以使用strings. Replace()函数将字符串中出现的所有特定字苻替换掉。例如︰
1 2 3 | names = " Antônio\tAndré\tFriedrich\t\t\tJean\t\tÉlisabeth\tIsabella \t" names = strings.Replace(names, "\t", " ", -1) fmt.Printf("|%s|\n", names) |
1 | |·Antônio·André··Friedrich···Jean··Élisabeth·Isabella··| |
strings.Replace()函数接受四个参数:要处理的字符串、被替换的子字符串、要替换的字符串和要替换的次数(-1表示替换所有,小于0的任意整数都表示替换所有)并返回一个替换完成的字符串。
当从用户输入或程序外部读取数据时,我们希望规范其空白符,例如,去除开头和结尾处的空白符并将内部连续的多个空白符替换为单个空白符。
1 | fmt.Printf("|%s|\n", SimpleSimplifyWhitespace(names)) |
1 | |Antônio·André·Friedrich·Jean·Élisabeth·Isabella| |
下面是只有一行的SimpleSimplifyWhitespace()函数:
1 2 3 | func SimpleSimplifyWhitespace(s string) string { return strings.Join(strings.Fields(strings.TrimSpace(s)), " ") } |
strings.TrimSpace()函数返回一个去除首尾空白符的字符串。strings.Fields()函数将字符串以空白符切分并返回[]string切片。strings.Join()函数将[]string切片中的所有字符串用给定的分隔符(可以为空,但这里我们用了一个空格)连接在一起并返回连接后的字符串。通过组合使用这三个函数,我们就可以得到规范化的空白符。
当然,我们还可以使用bytes.Buffer来更加高效的简化空白符的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | func SimplifyWhitespace(s string) string { var buffer bytes.Buffer skip := true for _, char := range s { if unicode.IsSpace(char) { if !skip { buffer.WriteRune(' ') skip = true } } else { buffer.WriteRune(char) skip = false } } s = buffer.String() if skip && len(s) > 0 { s = s[:len(s)-1] } return s } |
SimplifyWhitespace()函数会逐字符的遍历给定的字符串并使用unicode.IsSpace()函数(参见表3.11)跳过字符串开头处的空白符,然后通过将字符写入到bytes.Buffer里来累计,对于字符串中出现的任何连续的多个空白符都用单个空白符替换掉,最后去除(算法允许最多保留一个)结尾处的空白符并返回处理过的字符串。稍后我们会介绍一种更简单的处理方法,那就是正则表达式。
strings.Map()函数可以用于替换或去除字符串中的字符。它接受两个参数:具有func(rune) rune签名的映射函数和一个字符串。对于字符串中的每个字符,该映射函数都会被调用并将字符串中的每个字符以该映射函数返回的字符替换掉,当该映射函数返回的是负数时,字符串中相应的字符会被删除。
1 2 3 4 5 6 7 | asciiOnly := func(char rune) rune { if char > 127 { return '?' } return char } fmt.Println(strings.Map(asciiOnly, "Jérôme Österreich")) |
1 | J?r?me·?sterreich |
这里我们并没有如之前在strings.FieldsFunc()例子中所做的那样在调用的地方创建一个映射函数,而是创建了一个匿名映射函数并将其赋值(函数引用)给一个变量(这里是asciiOnly)。这里我们使用了strings.Map()函数并将指向匿名映射函数引用的变量和要处理的字符串传递给它,最后打印结果——将字符串中所有的非ASCII字符替换为“?”。当然,我们也可以在调用的地方直接创建该映射函数,但是这里我们这样做可能更加便捷,因为该函数可能很长,或我们需要在多个地方使用到它。
使用这个方法删除所有非ASCII字符并产生如下结果也是非常容易的:
1 | Jrme·sterreich |
通过修改映射函数让其返回-1而不是将非ASCII字符替换为“?”就可以做到。
之前我们提到过可以使用for … range循环来逐字符(Unicode码点)的遍历字符串。通过从实现了ReadRune()函数的类型中读取数据也可以得到类似的效果,如bufio.Reader。
1 2 3 4 5 6 7 8 9 10 | for { char, size, err := reader.ReadRune() if err != nil { // might occur if the reader is reading a file if err == io.EOF { // finished without incident break } panic(err) // a problem occurred } fmt.Printf("%U '%c' %d: % X\n", char, char, size, []byte(string(char))) } |
1 2 3 4 | U+0043·'C'·1:·43 U+0061·'a'·1:·61 U+0066·'f'·1:·66 U+00E9·'é'·2:·C3·A9 |
这段代码读取一个字符串并输出该字符串中每个字符的码点、字符本身、字符占用的UTF-8 字节数和用来表示字符的字节码。在大多数情况下,reader用于操作文件,所以这里我们假设reader变量是通过os.Open()返回的reader调用bufio.NewReader()而被创建的,在第一章的americanise例子中就是使用的这种方法。但是在本例中我们创建reader用于操作字符串:
1 | reader := strings.NewReader("Café") |
由strings.NewReader()返回的*strings.Reader提供了bufio.Reader功能的一部分;特别是提供了strings.Reader.Read()、strings.Reader.ReadByte()、strings.Reader.ReadRune()、strings.Reader.UnreadByte()和strings.Reader.UnreadRune()方法。这种操作具有一个特定接口的值而不是具有特定类型的值的能力,是Go语言一个非常强大且灵活的特性。