先来直观的感受一下他们的执行结果:
const Prefix = "abc"
func main() {
str := "abccbsfjs"
result1 := strings.TrimLeft(str, Prefix)
fmt.Println(result1)
fmt.Println("----------------")
result2 := strings.TrimPrefix(str, Prefix)
fmt.Println(result2)
}
result:
sfjs
----------------
cbsfjs
从结果来看,TrimPrefix确实是从左端去除了prefix,保留其余字符串,但是TrimLeft的结果,就有点令人迷惑了,不是从左端去掉abc吗,为什么结果只有后四个字符,带着这个疑惑我翻看了一下go的源码
// TrimPrefix returns s without the provided leading prefix string.
// If s doesn't start with prefix, s is returned unchanged.
func TrimPrefix(s, prefix string) string {
if HasPrefix(s, prefix) {
return s[len(prefix):]
}
return s
}
// HasPrefix tests whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}
TrimPrefix很好理解,判断开头的几个字符是否跟prefix匹配(HasPrefix),匹配则删除掉,不匹配则返回原来的字符串。
我们再来看一下TrimLeft的代码
// TrimLeft returns a slice of the string s with all leading
// Unicode code points contained in cutset removed.
//
// To remove a prefix, use TrimPrefix instead.
func TrimLeft(s, cutset string) string {
if s == "" || cutset == "" { //两个任何一个是空串,则返回原字符串s
return s
}
return TrimLeftFunc(s, makeCutsetFunc(cutset)) //返回TrimLeftFunc的执行结果
}
// TrimLeftFunc returns a slice of the string s with all leading
// Unicode code points c satisfying f(c) removed.
func TrimLeftFunc(s string, f func(rune) bool) string {
i := indexFunc(s, f, false) //重点:找到需要截取的位置
if i == -1 { //如果i为-1,返回空串
return ""
}
return s[i:] //返回从i位置开始后面所有字符,包括i位置
}
// indexFunc is the same as IndexFunc except that if
// truth==false, the sense of the predicate function is
// inverted.
func indexFunc(s string, f func(rune) bool, truth bool) int {
for i, r := range s {
if f(r) == truth { //遍历字符串s,每一个字符执行入参f func(rune) bool方法,结果为false的话返回位置i,如果是true,就继续遍历
return i
}
}
return -1
}
到这里可以看到这里最重要的处理是在TrimLeftFunc方法的入参f,即TrimLeft方法中的makeCutsetFunc方法
func makeCutsetFunc(cutset string) func(rune) bool {
//分三种情况处理
//1. cutset长度为1,且它的字符值小于128,ASCII码的前128位
if len(cutset) == 1 && cutset[0] < utf8.RuneSelf {
return func(r rune) bool {
return r == rune(cutset[0])
}
}
//2.cutset的每个字符都是ASCII的前128位
if as, isASCII := makeASCIISet(cutset); isASCII {
return func(r rune) bool {
return r < utf8.RuneSelf && as.contains(byte(r))
}
}
//3.非2的情况,即有任何一个不是ASCII前128位
return func(r rune) bool { return IndexRune(cutset, r) >= 0 }
}
第一种比较简单,这里就不说了
第三种在编码中比较少遇到,这里也不讲了,有兴趣可以自己研究一下
这里重点讲第二种
makeASCIISet返回一个数组,和bool值。我们在理一下上面的调用关系,如下图:
由此可见参数f很重要,而参数f就是最上面的makeCutsetFunc(),这个方法里面又分三种情况,第二种情况如下
//2.cutset的每个字符都是ASCII的前128位
if as, isASCII := makeASCIISet(cutset); isASCII {
return func(r rune) bool {
return r < utf8.RuneSelf && as.contains(byte(r))
}
}
那也就是说这个字符在ASCII的前128位,且包含在as数组里面,就继续判断下一个,直到不符合的时候截取。那么as数组怎么来的,又是怎么做包含判断的就是我们接下来就分析的内容
// makeASCIISet creates a set of ASCII characters and reports whether all
// characters in chars are ASCII.
func makeASCIISet(chars string) (as asciiSet, ok bool) {
for i := 0; i < len(chars); i++ {
c := chars[i]
if c >= utf8.RuneSelf {
return as, false
}
as[c>>5] |= 1 << uint(c&31)
}
return as, true
}
当要判断的所有字符都属于utf8.RuneSelf, 则进行计算赋值as[c>>5] |= 1 << uint(c&31),这个意思是这样,c>>5, 即取这个字符的高3位作为as的下标,1 << uint(c&31)这个意思是把字符的低5位值获取到,然后把1左移这么多位,最终在把这个值与下标位置的值做一个或运算。contains代码如下:
// contains reports whether c is inside the set.
func (as *asciiSet) contains(c byte) bool {
return (as[c>>5] & (1 << uint(c&31))) != 0
}
这里我讲一下这个判断思路,下面是asciiSet的定义
type asciiSet [8]uint32
这个做法是这样,把128范围内的数分布到一个8*32的数列里面(uint32可以用一个32位的二进制来表示),每一个下标位置代表一个数值,举个例子,一个字符a对应ascii为97,二进制:01100001 ,向右移动5位,结果为011,(即取前3位,值为3,而前三位最大数字为7,刚好对应asciiSet 下标(8位数组))。接下来01100001 & 31 = 00000001,(31的二进制位00011111,结果为97的低5位值,5位的最大值是00011111=31,也就是说1左移的可能性为0~31位,刚好对应一个uint32值)然后把1左移1位得到00000010。那么a对应到这个数组里面的位置就是这里
接下来是或运算,假如之前a没有出现过,那么这里的位置应该是0,执行或之后,变为1,加入之前a出现过那么这里的位置就已经是1了,执行完或运算还是1.就是用这种方式把128个字符构造成8个uint32的值。接下来contains就很好理解了,来一个字符,取前3位判断下标,然后在取后5位值,用1左移,确定位置上如果是1就表示存在,继续匹配下一个字符,如果是位置上的值是0,表示没有在匹配表里面出现过,要从这里截取字符串,这种位计算的表示方式效率非常的高。