Golang 进阶:strings -- 字符串操作

判断是否存在某个字符或者子串

可以使用的函数有:

func Contain(s, substr string) bool
func ContainAny(s, chars string) bool
func ContainsRune(s string, r rune) bool

这三个函数中需要注意的是第二个

func ContainAny(s, chars string) bool

这个函数需要注意的是第二个参数chars中任意一个字符(Unicode Code Point)如果在第一个参数s中存在就返回true
而当我们深究其源码时,发现其实只有简短的几行实现:

// Contains reports whether substr is within s.
func Contains(s, substr string) bool {
	return Index(s, substr) >= 0
}

// ContainsAny reports whether any Unicode code points in chars are within s.
func ContainsAny(s, chars string) bool {
	return IndexAny(s, chars) >= 0
}

// ContainsRune reports whether the Unicode code point r is within s.
func ContainsRune(s string, r rune) bool {
	return IndexRune(s, r) >= 0
}

子串出现次数(字符串匹配)

在数据结构与算法中,可能会讲解以下字符串匹配算法:

  • 朴素匹配算法
  • KMP算法
  • Rabin-Karp算法
  • Boyer-Moore算法
  • 还有其他的算法,这里不一一列举,感兴趣的可以网上搜一下。
    在Go中,查找子串出现次数即字符串模式匹配,实现的是Rabin-Karp算法。Count 函数的签名如下:
func Count(s, sep string) int

但如果当我们输入的参数sep为空时,返回的结果则是utf8.RuneCountInString(s)+1
另外,Count 是计算子串在字符串中出现的无重叠的次数。比如vevev中的vev出现的次数是1.
我们可以一起来看一下 Rabin-Karp 的实现:

// Count counts the number of non-overlapping instances of substr in s.
// If substr is an empty string, Count returns 1 + the number of Unicode code points in s.
func Count(s, substr string) int {
	// special case
	if len(substr) == 0 {
		return utf8.RuneCountInString(s) + 1
	}
	if len(substr) == 1 {
		return bytealg.CountString(s, substr[0])
	}
	n := 0
	for {
		i := Index(s, substr)
		if i == -1 {
			return n
		}
		n++
		s = s[i+len(substr):]
	}
}

我们可以发现,在该函数中仍然使用了 Index 函数。

字符串分割为[]string

该包提供了六个三组分割函数:Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。

Fields 和 FieldsFunc

我们先来看一下第一组函数

func Fields(s string) []String
func FieldsFunc(s string, f func(rune) bool) []string

Fields 用一个或多个连续的空格分隔字符串 s,返回子字符串的数组(slice)。如果字符串 s 只包含空格,则返回空列表([]string的长度为0)。其中,空格的定义是 unicode.IsSpace,之前已经介绍过。
由于是用空格分隔,因此结果中不会含有空格或空子字符串。
FieldsFunc 用这样的Unicode代码点 c 进行分隔:满足 f© 返回 true。该函数返回[]string。如果字符串 s 中所有的代码点(unicode code points)都满足f©或者 s 是空,则 FieldsFunc 返回空slice。
也就是说,我们可以通过实现一个回调函数来指定分隔字符串 s 的字符。
之所以将两个函数放在一起来说,主要是因为前者就是调用后者来实现的:

func Fields(s string) []string {
  ...
  // Some runes in the input string are not ASCII.
	return FieldsFunc(s, unicode.IsSpace)
}

而 FieldsFunc 函数如下:

func FieldsFunc(s string, f func(rune) bool) []string {
	// A span is used to record a slice of s of the form s[start:end].
	// The start index is inclusive and the end index is exclusive.
	type span struct {
		start int
		end   int
	}
	spans := make([]span, 0, 32)

	// Find the field start and end indices.
	wasField := false
	fromIndex := 0
	for i, rune := range s {
		if f(rune) {
			if wasField {
				spans = append(spans, span{start: fromIndex, end: i})
				wasField = false
			}
		} else {
			if !wasField {
				fromIndex = i
				wasField = true
			}
		}
	}

	// Last field might end at EOF.
	if wasField {
		spans = append(spans, span{fromIndex, len(s)})
	}

	// Create strings from recorded field indices.
	a := make([]string, len(spans))
	for i, span := range spans {
		a[i] = s[span.start:span.end]
	}

	return a
}
Split 和 SplitAfter、 SplitN 和 SplitAfterN

之所以将这四个函数放在一起讲,是因为它们都是通过一个同一个内部函数来实现的。
我们可以看一下具体的实现:

func Split(s, sep string) []string {
    return genSplit(s, sep, 0, -1)
}
func SplitAfter(s, sep string) []string {
    return genSplit(s, sep, len(sep), -1)
}
func SplitN(s, sep string, n int) []string {
    return genSplit(s, sep, 0, n)
}
func SplitAfterN(s, sep string, n int) []string {
    return genSplit(s, sep, len(sep), n)
}

我们可以看到这四个函数均调用了 genSplit 函数,这个函数我们稍后再详细的讲。
再来说一下这四个函数,这四个函数都是通过 sep 进行分割,返回[]string。如果 sep 为空,相当于分成一个个的 UTF-8 字符,如 Split(“abc”,""),得到的是[a b c]。
Split(s, sep) 和 SplitN(s, sep, -1) 等价;SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等价。
那么, Split 和 SplitAfter 到底有什么区别呢?
我们开看一个具体的例子就能很清楚的明白:

fmt.Printf("%q\n", strings.Split("foo,bar,baz", ","))
fmt.Printf("%q\n", strings.SplitAfter("foo,bar,baz", ","))

而他们的输出分别是:

["foo" "bar" "baz"]
["foo," "bar," "baz"]

也就是说,Split 会将 s 中的 sep 去掉,而 SplitAfter 会保留 sep。
带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数,当 n < 0 时,返回所有的子字符串;当 n == 0 时,返回的结果是 nil;当 n > 0 时,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割。

字符串是否有某个前缀或后缀

这两个函数比较简单,源码如下:

// s 中是否以 prefix 开始
func HasPrefix(s, prefix string) bool {
    return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}
// s 中是否以 suffix 结束
func HasSuffix(s, suffix string) bool {
    return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}

字符或子串在字符串中出现的位置

实现这一功能有一系列的函数:

// 在 s 中查找 sep 的第一次出现,返回第一次出现的索引
func Index(s, sep string) int
// chars中任何一个Unicode代码点在s中首次出现的位置
func IndexAny(s, chars string) int
// 查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int
// Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int

// 有三个对应的查找最后一次出现的位置
func LastIndex(s, sep string) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int

我们在前面也提到,Contain 相关的函数内部调用的就是这个里面所提到的 Index 函数。

字符串 JOIN 操作

将字符串数组(或slice)连接起来可以通过 Join 实现。

func Join(a []string, sep string) string

我们来看一下标准库的实现:

func Join(a []string, sep string) string {
    if len(a) == 0 {
        return ""
    }
    if len(a) == 1 {
        return a[0]
    }
    n := len(sep) * (len(a) - 1)
    for i := 0; i < len(a); i++ {
        n += len(a[i])
    }

    b := make([]byte, n)
    bp := copy(b, a[0])
    for _, s := range a[1:] {
        bp += copy(b[bp:], sep)
        bp += copy(b[bp:], s)
    }
    return string(b)
}

具体实现案例:

fmt.Println(Join([]string{"name=xxx", "age=xxx"}, "&"))
// 输出的结果是 name=xxx&age=xxx

字符串重复几次

函数签名如下:

func Repeat(s string, count int) string

字符串子串替换

进行字符串替换时,考虑到性能问题,能不用正则尽量别用,应该用这里的函数。

// 用 new 替换 s 中的 old,一共替换 n 个。
// 如果 n < 0,则不限制替换次数,即全部替换
func Replace(s, old, new string, n int) string

Replacer 类型

这是一个结构,没有导出任何字段,实例化通过 func NewReplacer(oldnew ...string) *Replacer 函数进行,其中不定参数 oldnew 是 old-new 对,即进行多个替换。

// Replacer replaces a list of strings with replacements.
// It is safe for concurrent use by multiple goroutines.
type Replacer struct {
	r replacer
}

// replacer is the interface that a replacement algorithm needs to implement.
type replacer interface {
	Replace(s string) string
	WriteString(w io.Writer, s string) (n int, err error)
}

WriteString 是提供的另外一个方法,该方法在执行替换后将结果写入 io.Writer 中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值