子串相关操作
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?
实际上,是 27
。strings.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