strings.TrimPrefix 和 strings.TrimLeft 的区别

本文详细分析了Go语言中`TrimPrefix`和`TrimLeft`函数的工作原理,通过源码解读揭示了它们在字符串处理中的不同行为。`TrimPrefix`简单直接地移除指定前缀,而`TrimLeft`则根据指定字符集去除左侧的字符。文章深入探讨了`TrimLeft`的实现细节,包括位操作在构建字符查找表中的高效应用,对于理解Go语言字符串处理有重要参考价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先来直观的感受一下他们的执行结果:

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,表示没有在匹配表里面出现过,要从这里截取字符串,这种位计算的表示方式效率非常的高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值