Golang:strings包和字符串操作

strings包

  • string类型值是不可变的,如果想要获得一个不一样的字符串,就只能基于原字符串进行剪裁,拼接等操作,然后将得到的新字符串存放到一块连续的内存中。
  • string值包含了指向底层字节数组头部的指针值,以及该字节数组的长度。在string值上做切片,就相当于在其底层字节数组做切片。
  • 而字符串拼接时(使用+),会把所有被拼接的字符串依次拷贝到一个崭新且足够大的连续内存空间中,并把持有新的底层字节数组头部指针的string返回。所以当程序中存在过多的字符串拼接操作,会对内存分配产生很大压力
  • 虽然string值在内部持有一个指针值,但其仍然属于值类型,由于string值不可变,其中的指针值也为内存空间的节省做出了贡献。一个string值在底层与它的所有副本共用一个字节数组,由于字节数组不可变,所以这样绝对安全
  • 通过函数传参时,复制的是string的指针和长度,这两个指向原先的string,但是只要尝试对这个参数进行剪裁或拼接,就会生成新string,对原先的string没影响

strings.Builder

  • Builder类型开箱即用,与string类型相比,优势在于字符串拼接方面。
// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
	addr *Builder // of receiver, to detect copies by value
	buf  []byte
}
  • Builder是一个结构体,数据放在buf字段中,addr用来检查是否发生了复制。buf是一个字节切片([]byte),[]byte和string都是通过一个unsafe.Pointer类型的字段来持有指向底层字节数组的指针。
  • Builder没有修改buf的方法,只有拼接方法,都是指针方法,比如Write、WriteByte、WriteRune和WriteString。通过这些方法将新内容放到buf中,只有buf容量不足了,才触发扩容。
  • 除了自动扩容,还可以使用Grow手动扩容,接收一个正数,当剩余容量(容量与长度差)小于给定数字n,进行扩容。分配的新切片长度不变,容量变为原容量的二倍加上n,然后拷贝数据。当剩余容量大于等于n,什么都不做。
// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *Builder) grow(n int) {
	buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
	copy(buf, b.buf)
	b.buf = buf
}

// Grow grows b's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to b
// without another allocation. If n is negative, Grow panics.
func (b *Builder) Grow(n int) {
	b.copyCheck()
	if n < 0 {
		panic("strings.Builder.Grow: negative count")
	}
	if cap(b.buf)-len(b.buf) < n {
		b.grow(n)
	}
}
  • Reset方法可以让Builder值回到零值状态,用来重用。一旦被重用Builder值中原有的内容容器会被直接丢弃,之后被垃圾回收。
func (b *Builder) Reset() {
	b.addr = nil
	b.buf = nil
}
  • strings.Builder类型在使用后不可再复制;由于其内容不是完全不可变,所以需要自行解决操作冲突和并发安全问题。
  • 只要调用了拼接方法或者扩容方法,就表示开始真正使用了,如果之后再进行复制(包括函数间传递,通道传递,把值赋予变量等),那么在副本调用上述方法时就会panic。这样做是为了避免多个同源Builder共用一个底层字节数组的情况,这种情况可能产生冲突。
  • Builder的值不能复制,但是Builder指针可以,复制的指针值指向的还是原先的Builder。正因为如此,同一个Builder可能会被多方同时操作,需要自己保证并发安全,最彻底的解决方案是,绝不共享Builder值以及它的指针值。
  • 零值状态的Builder是可以复制的,比如刚声明或者刚Reset

strings.Reader

  • Reader类型是为了高效读取字符串存在的,封装了很多用于string值上读取内容的最佳实践。
  • Reader包含三个字段,s是要读取的字符串,i是当前读到哪里了,prevRune是前一个Unicode字符的索引位置。
// A Reader implements the io.Reader, io.ReaderAt, io.Seeker, io.WriterTo,
// io.ByteScanner, and io.RuneScanner interfaces by reading
// from a string.
type Reader struct {
	s        string
	i        int64 // current reading index
	prevRune int   // index of previous rune; or < 0
}
  • 在读取过程中,Reader值会保存已读取的字节数(就是结构体中的i),依靠这个计数,还有针对字符串的切片表达式来实现快速读取。
  • Reader的Size方法返回字符串长度,Len方法返回还有多少字节未读,所以Size()-Len()就得到了读取了多少,即i的值
// Len returns the number of bytes of the unread portion of the
// string.
func (r *Reader) Len() int {
	if r.i >= int64(len(r.s)) {
		return 0
	}
	return int(int64(len(r.s)) - r.i)
}

// Size returns the original length of the underlying string.
// Size is the number of bytes available for reading via ReadAt.
// The returned value is always the same and is not affected by calls
// to any other method.
func (r *Reader) Size() int64 { return int64(len(r.s)) }
  • Reader的大部分用于读取的方法都会及时更新已读计数。比如ReadByte和ReadRune,还拥有两者的回退方法。
func (r *Reader) ReadByte() (byte, error) {
	r.prevRune = -1
	if r.i >= int64(len(r.s)) {
		return 0, io.EOF
	}
	b := r.s[r.i]
	r.i++
	return b, nil
}

func (r *Reader) UnreadByte() error {
	r.prevRune = -1
	if r.i <= 0 {
		return errors.New("strings.Reader.UnreadByte: at beginning of string")
	}
	r.i--
	return nil
}

func (r *Reader) ReadRune() (ch rune, size int, err error) {
	if r.i >= int64(len(r.s)) {
		r.prevRune = -1
		return 0, 0, io.EOF
	}
	r.prevRune = int(r.i)
	if c := r.s[r.i]; c < utf8.RuneSelf {
		r.i++
		return rune(c), 1, nil
	}
	ch, size = utf8.DecodeRuneInString(r.s[r.i:])
	r.i += int64(size)
	return
}

func (r *Reader) UnreadRune() error {
	if r.prevRune < 0 {
		return errors.New("strings.Reader.UnreadRune: previous operation was not ReadRune")
	}
	r.i = int64(r.prevRune)
	r.prevRune = -1
	return nil
}
  • 但是ReadAt是个例外,它不会根据i来读取,也不会更新i。它将给定位置后面的数据拷贝到切片中(如果切片长度不足返回错误,copy方法返回两个切片中较小的长度)
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) {
	// cannot modify state - see io.ReaderAt
	if off < 0 {
		return 0, errors.New("strings.Reader.ReadAt: negative offset")
	}
	if off >= int64(len(r.s)) {
		return 0, io.EOF
	}
	n = copy(b, r.s[off:])
	if n < len(b) {
		err = io.EOF
	}
	return
}
  • Seek方法更新计数,用来设置下次读取的起始索引位置。当whence值是io.SeekCurrent时,Seek方法将i与offset相加作为新的i。最终返回新的i。
// Seek implements the io.Seeker interface.
func (r *Reader) Seek(offset int64, whence int) (int64, error) {
	r.prevRune = -1
	var abs int64
	switch whence {
	case io.SeekStart:
		abs = offset
	case io.SeekCurrent:
		abs = r.i + offset
	case io.SeekEnd:
		abs = int64(len(r.s)) + offset
	default:
		return 0, errors.New("strings.Reader.Seek: invalid whence")
	}
	if abs < 0 {
		return 0, errors.New("strings.Reader.Seek: negative position")
	}
	r.i = abs
	return abs, nil
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值