go语言strings包中的工具与操作

子串相关操作

func Contains(s, substr string) bool

判断substr是否存在于字符串s中,如果存在就返回true

func ContainsAny(s, chars string) bool

如果字符串chars中有字符在s中则返回true

func ContainsRune(s string, r rune) bool

代码r在s中则返回true

查找字串位置相关操作

//返回子串sep在字符串s中第一次出现的索引值,不在的话返回-1.
func Index(s, sep string) int
//chars中任何一个Unicode代码点在s中首次出现的位置,不存在返回-1
func IndexAny(s, chars string) int
//查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int   //rune类型是int32别名,UTF-8字符格式编码。
//返回字符c在s中第一次出现的位置
func IndexByte(s string, c byte) int   //byte是字节类型
// Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int
//查找最后一次出现的位置
func LastIndex(s, sep string) int
func LastIndexByte(s string, c byte) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int

更多方法详见:(38条消息) Go语言---strings包(字符串操作)_每天进步一点点。。。的博客-CSDN博客_go 字符串操作

strings.Builder类型

与string相比,strings.Builder的主要优势有

1:减少内存分配和内容拷贝次数,提升程序性能

2:已存在内容不可变,并且可以追加更多内容

3:内容可重置,可重用

string类型的值操作

1:通过切片进行裁剪

2:拼接用操作符+来实现

string值的内容在底层储存是一个连续的数组,同时包含了指向底层数组头部的指针值,所以我们可以通过切片表达式来对他的底层数组做切片操作,以完成对字符串的裁剪,但是拼接时底层会把所有被拼接的字符串依次拷贝到一个更长的连续内存空间,所以当程序进行到足够多的字符串拼接操作就会对内存产生巨大压力

Builder值优势就在于字符串拼接方面,Builder值中有一个用于承载内容的容器,他是一个字节切片,Builder包含四个拼接方法

func (b *Builder) Write(p []byte) (int, error)
func (b *Builder) WriteByte(c byte) error
func (b *Builder) WriteRune(r rune) (int, error)
func (b *Builder) WriteString(s string) (int, error)

当我们需要扩容时,Builder会自动对容器进行扩容,我们也可以手动扩容,通过调用值的Grow方法

func (b *Builder) Grow(n int)

他接受一个int类型的参数,它代表了需要扩容的字节数

当调用 Grow() 时,我们必须定义要扩容的字节数(n)。 Grow() 方法保证了其内部的 slice 一定能够写入 n 个字节。只有当 slice 空余空间不足以写入 n 个字节时,扩容才有可能发生。

  • builder 内部 slice 容量为 10。
  • builder 内部 slice 长度为 5。
  • 当我们调用 Grow(3) => 扩容操作并不会发生。因为当前的空余空间为 5,足以提供 3 个字节的写入。
  • 当我们调用 Grow(7) => 扩容操作发生。因为当前的空余空间为 5,已不足以提供 7 个字节的写入。

关于上面的情形,如果这时我们调用 Grow(7),则扩容之后的实际容量是多少?

17 还是 12?

实际上,是 27strings.Builder 的 Grow() 方法是通过 current_capacity * 2 + n (n 就是你想要扩充的容量)的方式来对内部的 slice 进行扩容的。所以说最后的容量是 10*2+7 = 27

最后我们可以通过Reset方法重用这个值,他的内容容器和内容将被垃圾回收,这时就是一个未被使用的值

strings.Builder 值已经使用后就不可以拷贝。当你试图拷贝 strings.Builder 并写入的时候,你的程序就会崩溃

var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
b2.WriteString("DEF") 
// illegal use of non-zero Builder copied by value

你已经知道,strings.Builder 内部通过 slice 来保存和管理内容。slice 内部则是通过一个指针指向实际保存内容的数组。

当我们拷贝了 builder 以后,同样也拷贝了其 slice 的指针。但是它仍然指向同一个旧的数组。当你对源 builder 或者拷贝后的 builder 写入的时候,问题就产生了。另一个 builder 指向的数组内容也被改变了。这就是为什么 strings.Builder 不允许拷贝的原因。

正因为如此,我们在通过传递指针来共享Builder值时,要保证对他的使用是有序的,并发安全的,最好的方法是我们尽量不去共享它

strings.Reader类型

方法代码引用:(38条消息) golang strings包NewReader方法_u014270740的专栏-CSDN博客

Reader值可以让我们很方便地读取一个字符串中的内容。它的高效主要体现在它对字符串的读取机制上。在读取的过程中,Reader值会保存已读取的字节的计数,也称已读计数。
这个计数代表着下一次读取的起始索引位置,同时也是高效读取的关键所在。我们可以利用这类值的Len方法和Size方法,计算出其中的已读计数的值。有了它,我们就可以更加灵活地进行字符串读取了。

底层结构:

type Reader struct {
	s        string
	i        int64 // 当前读取的下标
	prevRune int   // 记录读取中文时候的下标,除了ReadRune会赋值,其他赋值-1
}

Len,Size,Read方法

Len作用: 返回未读的字符串长度

Size的作用:返回字符串的长度

Read的作用: 读取字符串信息

	r := strings.NewReader("abcdefghijklmn")
	fmt.Println(r.Len())   // 输出14  初始时,未读长度等于字符串长度
	var buf []byte
	buf = make([]byte, 5)
	readLen, err := r.Read(buf)
	fmt.Println("读取到的长度:", readLen) //读取到的长度5
	if err != nil {
		fmt.Println("错误:", err)
	}
	fmt.Println(buf)            //adcde
	fmt.Println(r.Len())        //9   读取到了5个 剩余未读是14-5
	fmt.Println(r.Size())       //14   字符串的长度

ReadAt方法

偏移位数读取

func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)

读取偏移off字节后的剩余信息到b中
 

	r := strings.NewReader("abcdefghijklmn")
 
	var bufAt, buf []byte
	buf = make([]byte, 5)
	r.Read(buf)
	fmt.Println("剩余未读的长度", r.Len())   //剩余未读的长度 9
	fmt.Println("已读取的内容", string(buf)) //已读取的内容 abcde
	bufAt = make([]byte, 256)
	r.ReadAt(bufAt, 5)
	fmt.Println(string(bufAt))              //fghijklmn
 
	//测试下是否影响Len和Read方法
	fmt.Println("剩余未读的长度", r.Len())    //剩余未读的长度 9
	fmt.Println("已读取的内容", string(buf))  //已读取的内容 abcde

ReadByte,UnreadByte方法

func (r *Reader) ReadByte() (b byte, err error)
 
func (r *Reader) UnreadRune() error

ReadByte从当前已读取位置继续读取一个字节

UnreadByte将当前已读取位置回退一位,当前位置的字节标记成未读取字节

	r := strings.NewReader("abcdefghijklmn")
	
	//读取一个字节
	b, _ := r.ReadByte()
	fmt.Println(string(b))					// a
	//int(r.Size()) - r.Len() 已读取字节数
	fmt.Println(int(r.Size()) - r.Len())	// 1
 
	//读取一个字节
	b, _ = r.ReadByte()
	fmt.Println(string(b))					// b
	fmt.Println(int(r.Size()) - r.Len())	// 2
 
	//回退一个字节
	r.UnreadByte()
	fmt.Println(int(r.Size()) - r.Len())	// 1
 
	//读取一个字节
	b, _ = r.ReadByte()
	fmt.Println(string(b))					// b

Seek方法

func (r *Reader) Seek(offset int64, whence int) (int64, error)

前面有ReadAt方法可以将字符串偏移多少位读取剩下的字符串内容,但是该方法不会影响正在用Read方法读取的内容,如果相对Read方法读取的内容做偏移就可以使用seek方法, offset是偏移的位置,whence是偏移起始位置,支持三种位置(io.SeekStart起始位,io.SeekCurrent当前位,io.SeekEnd末位)。需要注意的是offset可以未负数,当时偏移起始位 与offset相加得到的值不能小于0或者大于size()的长度

	r := strings.NewReader("abcdefghijklmn")
 
	var buf []byte
	buf = make([]byte, 5)
	r.Read(buf)
	fmt.Println(string(buf)) //adcde
 
	buf = make([]byte, 5)
	r.Seek(-2, io.SeekCurrent) //从当前位置向前偏移两位 (5-2)
	r.Read(buf)
	fmt.Println(string(buf)) //defgh
	
	buf = make([]byte, 5)
	r.Seek(-3, io.SeekEnd) //设置当前位置是末尾前移三位
	r.Read(buf)
	fmt.Println(string(buf)) //lmn
 
	buf = make([]byte, 5)
	r.Seek(3, io.SeekStart) //设置当前位置是起始位后移三位
	r.Read(buf)
	fmt.Println(string(buf)) //defgh

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值