GoLang之bytes.Builder底层

GoLang之bytes.Builder底层

1.1bytes.Builder结构体

1.1.1bytes.Builder结构体
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
	buf      []byte // contents are the bytes buf[off : len(buf)]
	off      int    // read at &buf[off], write at &buf[len(buf)]
	lastRead readOp // last read operation, so that Unread* can work correctly.
}
1.1.2Bytes方法
// Bytes returns a slice of length b.Len() holding the unread portion of the buffer.
// The slice is valid for use only until the next buffer modification (that is,
// only until the next call to a method like Read, Write, Reset, or Truncate).
// The slice aliases the buffer content at least until the next buffer modification,
// so immediate changes to the slice will affect the result of future reads.
func (b *Buffer) Bytes() []byte { return b.buf[b.off:] }
1.1.3String方法
// String returns the contents of the unread portion of the buffer
// as a string. If the Buffer is a nil pointer, it returns "<nil>".
//
// To build strings more efficiently, see the strings.Builder type.
func (b *Buffer) String() string {
	if b == nil {
		// Special case, useful in debugging.
		return "<nil>"
	}
	return string(b.buf[b.off:])
}
1.1.4empty方法
// empty reports whether the unread portion of the buffer is empty.
func (b *Buffer) empty() bool { return len(b.buf) <= b.off }
1.1.5Len方法
// Len returns the number of bytes of the unread portion of the buffer;
// b.Len() == len(b.Bytes()).
func (b *Buffer) Len() int { return len(b.buf) - b.off }
1.1.6Cap方法
// Cap returns the capacity of the buffer's underlying byte slice, that is, the
// total space allocated for the buffer's data.
func (b *Buffer) Cap() int { return cap(b.buf) }
1.1.7Truncate方法
// Truncate discards all but the first n unread bytes from the buffer
// but continues to use the same allocated storage.
// It panics if n is negative or greater than the length of the buffer.
func (b *Buffer) Truncate(n int) {
	if n == 0 {
		b.Reset()
		return
	}
	b.lastRead = opInvalid
	if n < 0 || n > b.Len() {
		panic("bytes.Buffer: truncation out of range")
	}
	b.buf = b.buf[:b.off+n]
}
1.1.8Reset方法
// Reset resets the buffer to be empty,
// but it retains the underlying storage for use by future writes.
// Reset is the same as Truncate(0).
func (b *Buffer) Reset() {
	b.buf = b.buf[:0]
	b.off = 0
	b.lastRead = opInvalid
}
1.1.9Grow方法
// Grow grows the buffer's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to the
// buffer without another allocation.
// If n is negative, Grow will panic.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) Grow(n int) {
	if n < 0 {
		panic("bytes.Buffer.Grow: negative count")
	}
	m := b.grow(n)
	b.buf = b.buf[:m]
}
1.1.10Write方法
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
	b.lastRead = opInvalid
	m, ok := b.tryGrowByReslice(len(p))
	if !ok {
		m = b.grow(len(p))
	}
	return copy(b.buf[m:], p), nil
}
1.1.11WriteString方法
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
func (b *Buffer) WriteString(s string) (n int, err error) {
	b.lastRead = opInvalid
	m, ok := b.tryGrowByReslice(len(s))
	if !ok {
		m = b.grow(len(s))
	}
	return copy(b.buf[m:], s), nil
}
1.1.12ReadFrom方法
// ReadFrom reads data from r until EOF and appends it to the buffer, growing
// the buffer as needed. The return value n is the number of bytes read. Any
// error except io.EOF encountered during the read is also returned. If the
// buffer becomes too large, ReadFrom will panic with ErrTooLarge.
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
	b.lastRead = opInvalid
	for {
		i := b.grow(MinRead)
		b.buf = b.buf[:i]
		m, e := r.Read(b.buf[i:cap(b.buf)])
		if m < 0 {
			panic(errNegativeRead)
		}

		b.buf = b.buf[:i+m]
		n += int64(m)
		if e == io.EOF {
			return n, nil // e is EOF, so return nil explicitly
		}
		if e != nil {
			return n, e
		}
	}
}
1.1.13WriteTo方法
// WriteTo writes data to w until the buffer is drained or an error occurs.
// The return value n is the number of bytes written; it always fits into an
// int, but it is int64 to match the io.WriterTo interface. Any error
// encountered during the write is also returned.
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error) {
	b.lastRead = opInvalid
	if nBytes := b.Len(); nBytes > 0 {
		m, e := w.Write(b.buf[b.off:])
		if m > nBytes {
			panic("bytes.Buffer.WriteTo: invalid Write count")
		}
		b.off += m
		n = int64(m)
		if e != nil {
			return n, e
		}
		// all bytes should have been written, by definition of
		// Write method in io.Writer
		if m != nBytes {
			return n, io.ErrShortWrite
		}
	}
	// Buffer is now empty; reset.
	b.Reset()
	return n, nil
}
1.1.14WriteByte方法
// WriteByte appends the byte c to the buffer, growing the buffer as needed.
// The returned error is always nil, but is included to match bufio.Writer's
// WriteByte. If the buffer becomes too large, WriteByte will panic with
// ErrTooLarge.
func (b *Buffer) WriteByte(c byte) error {
	b.lastRead = opInvalid
	m, ok := b.tryGrowByReslice(1)
	if !ok {
		m = b.grow(1)
	}
	b.buf[m] = c
	return nil
}
1.1.15WriteRune方法
// WriteRune appends the UTF-8 encoding of Unicode code point r to the
// buffer, returning its length and an error, which is always nil but is
// included to match bufio.Writer's WriteRune. The buffer is grown as needed;
// if it becomes too large, WriteRune will panic with ErrTooLarge.
func (b *Buffer) WriteRune(r rune) (n int, err error) {
	// Compare as uint32 to correctly handle negative runes.
	if uint32(r) < utf8.RuneSelf {
		b.WriteByte(byte(r))
		return 1, nil
	}
	b.lastRead = opInvalid
	m, ok := b.tryGrowByReslice(utf8.UTFMax)
	if !ok {
		m = b.grow(utf8.UTFMax)
	}
	n = utf8.EncodeRune(b.buf[m:m+utf8.UTFMax], r)
	b.buf = b.buf[:m+n]
	return n, nil
}
1.1.16Read方法
// Read reads the next len(p) bytes from the buffer or until the buffer
// is drained. The return value n is the number of bytes read. If the
// buffer has no data to return, err is io.EOF (unless len(p) is zero);
// otherwise it is nil.
func (b *Buffer) Read(p []byte) (n int, err error) {
	b.lastRead = opInvalid
	if b.empty() {
		// Buffer is empty, reset to recover space.
		b.Reset()
		if len(p) == 0 {
			return 0, nil
		}
		return 0, io.EOF
	}
	n = copy(p, b.buf[b.off:])
	b.off += n
	if n > 0 {
		b.lastRead = opRead
	}
	return n, nil
}
1.1.17Next方法
// Next returns a slice containing the next n bytes from the buffer,
// advancing the buffer as if the bytes had been returned by Read.
// If there are fewer than n bytes in the buffer, Next returns the entire buffer.
// The slice is only valid until the next call to a read or write method.
func (b *Buffer) Next(n int) []byte {
	b.lastRead = opInvalid
	m := b.Len()
	if n > m {
		n = m
	}
	data := b.buf[b.off : b.off+n]
	b.off += n
	if n > 0 {
		b.lastRead = opRead
	}
	return data
}
1.1.18ReadByte方法
// ReadByte reads and returns the next byte from the buffer.
// If no byte is available, it returns error io.EOF.
func (b *Buffer) ReadByte() (byte, error) {
	if b.empty() {
		// Buffer is empty, reset to recover space.
		b.Reset()
		return 0, io.EOF
	}
	c := b.buf[b.off]
	b.off++
	b.lastRead = opRead
	return c, nil
}
1.1.19ReadRune方法
// ReadRune reads and returns the next UTF-8-encoded
// Unicode code point from the buffer.
// If no bytes are available, the error returned is io.EOF.
// If the bytes are an erroneous UTF-8 encoding, it
// consumes one byte and returns U+FFFD, 1.
func (b *Buffer) ReadRune() (r rune, size int, err error) {
	if b.empty() {
		// Buffer is empty, reset to recover space.
		b.Reset()
		return 0, 0, io.EOF
	}
	c := b.buf[b.off]
	if c < utf8.RuneSelf {
		b.off++
		b.lastRead = opReadRune1
		return rune(c), 1, nil
	}
	r, n := utf8.DecodeRune(b.buf[b.off:])
	b.off += n
	b.lastRead = readOp(n)
	return r, n, nil
}
1.1.20UnreadRune方法
// UnreadRune unreads the last rune returned by ReadRune.
// If the most recent read or write operation on the buffer was
// not a successful ReadRune, UnreadRune returns an error.  (In this regard
// it is stricter than UnreadByte, which will unread the last byte
// from any read operation.)
func (b *Buffer) UnreadRune() error {
	if b.lastRead <= opInvalid {
		return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune")
	}
	if b.off >= int(b.lastRead) {
		b.off -= int(b.lastRead)
	}
	b.lastRead = opInvalid
	return nil
}
1.1.21ReadBytes方法
// ReadBytes reads until the first occurrence of delim in the input,
// returning a slice containing the data up to and including the delimiter.
// If ReadBytes encounters an error before finding a delimiter,
// it returns the data read before the error and the error itself (often io.EOF).
// ReadBytes returns err != nil if and only if the returned data does not end in
// delim.
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error) {
	slice, err := b.readSlice(delim)
	// return a copy of slice. The buffer's backing array may
	// be overwritten by later calls.
	line = append(line, slice...)
	return line, err
}
1.1.22readSlice方法
// readSlice is like ReadBytes but returns a reference to internal buffer data.
func (b *Buffer) readSlice(delim byte) (line []byte, err error) {
	i := IndexByte(b.buf[b.off:], delim)
	end := b.off + i + 1
	if i < 0 {
		end = len(b.buf)
		err = io.EOF
	}
	line = b.buf[b.off:end]
	b.off = end
	b.lastRead = opRead
	return line, err
}
1.1.23ReadString方法
// ReadString reads until the first occurrence of delim in the input,
// returning a string containing the data up to and including the delimiter.
// If ReadString encounters an error before finding a delimiter,
// it returns the data read before the error and the error itself (often io.EOF).
// ReadString returns err != nil if and only if the returned data does not end
// in delim.
func (b *Buffer) ReadString(delim byte) (line string, err error) {
	slice, err := b.readSlice(delim)
	return string(slice), err
}
1.1.24grow方法
// grow grows the buffer to guarantee space for n more bytes.
// It returns the index where bytes should be written.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) grow(n int) int {
	m := b.Len()
	// If buffer is empty, reset to recover space.
	if m == 0 && b.off != 0 {
		b.Reset()
	}
	// Try to grow by means of a reslice.
	if i, ok := b.tryGrowByReslice(n); ok {
		return i
	}
	if b.buf == nil && n <= smallBufferSize {
		b.buf = make([]byte, n, smallBufferSize)
		return 0
	}
	c := cap(b.buf)
	if n <= c/2-m {
		// We can slide things down instead of allocating a new
		// slice. We only need m+n <= c to slide, but
		// we instead let capacity get twice as large so we
		// don't spend all our time copying.
		copy(b.buf, b.buf[b.off:])
	} else if c > maxInt-c-n {
		panic(ErrTooLarge)
	} else {
		// Not enough space anywhere, we need to allocate.
		buf := makeSlice(2*c + n)
		copy(buf, b.buf[b.off:])
		b.buf = buf
	}
	// Restore b.off and len(b.buf).
	b.off = 0
	b.buf = b.buf[:m+n]
	return m
}

1.2 readOp数据类型

// The readOp constants describe the last action performed on
// the buffer, so that UnreadRune and UnreadByte can check for
// invalid usage. opReadRuneX constants are chosen such that
// converted to int they correspond to the rune size that was read.
type readOp int8

1.3makeSlice函数

// makeSlice allocates a slice of size n. If the allocation fails, it panics
// with ErrTooLarge.
func makeSlice(n int) []byte {
	// If the make fails, give a known error.
	defer func() {
		if recover() != nil {
			panic(ErrTooLarge)
		}
	}()
	return make([]byte, n)
}

1.4NewBuffer函数

// NewBuffer creates and initializes a new Buffer using buf as its
// initial contents. The new Buffer takes ownership of buf, and the
// caller should not use buf after this call. NewBuffer is intended to
// prepare a Buffer to read existing data. It can also be used to set
// the initial size of the internal buffer for writing. To do that,
// buf should have the desired capacity but a length of zero.
//
// In most cases, new(Buffer) (or just declaring a Buffer variable) is
// sufficient to initialize a Buffer.
func NewBuffer(buf []byte) *Buffer { return &Buffer{buf: buf} }

1.5NewBufferString函数

// NewBufferString creates and initializes a new Buffer using string s as its
// initial contents. It is intended to prepare a buffer to read an existing
// string.
//
// In most cases, new(Buffer) (or just declaring a Buffer variable) is
// sufficient to initialize a Buffer.
func NewBufferString(s string) *Buffer {
	return &Buffer{buf: []byte(s)}
}

2.bytes.Builder介绍

bytes.buffer是一个缓冲byte类型的缓冲器
bytes.Buffer 是 Golang 标准库中的缓冲区,具有读写方法和可变大小的字节存储功能。缓冲区的零值是一个待使用的空缓冲区。
注:从 bytes.Buffer 读取数据后,被成功读取的数据仍保留在原缓冲区,只是无法被使用,因为缓冲区的可见数据从偏移 off 开始,即buf[off : len(buf)]。

3.常用函数

声明一个Buffer

var b bytes.Buffer       				//直接定义一个Buffer变量,不用初始化,可以直接使用
b := new(bytes.Buffer)   				//使用New返回Buffer变量
b := bytes.NewBuffer(s []byte)   		//从一个[]byte切片,构造一个Buffer
b := bytes.NewBufferString(s string)	//从一个string变量,构造一个Buffer

4.常用方法

往Buffer中写入数据。

b.Write(d []byte) (n int, err error)   			//将切片d写入Buffer尾部
b.WriteString(s string) (n int, err error) 		//将字符串s写入Buffer尾部
b.WriteByte(c byte) error  						//将字符c写入Buffer尾部
b.WriteRune(r rune) (n int, err error)    		//将一个rune类型的数据放到缓冲区的尾部
b.ReadFrom(r io.Reader) (n int64, err error)	//从实现了io.Reader接口的可读取对象写入Buffer尾部

往Buffer中读取数据。

//读取 n 个字节数据并返回,如果 buffer 不足 n 字节,则读取全部
b.Next(n int) []byte

//一次读取 len(p) 个 byte 到 p 中,每次读取新的内容将覆盖p中原来的内容。成功返回实际读取的字节数,off 向后偏移 n,buffer 没有数据返回错误 io.EOF
b.Read(p []byte) (n int, err error)

//读取第一个byte并返回,off 向后偏移 n
b.ReadByte() (byte, error)

//读取第一个 UTF8 编码的字符并返回该字符和该字符的字节数,b的第1个rune被拿掉。如果buffer为空,返回错误 io.EOF,如果不是UTF8编码的字符,则消费一个字节,返回 (U+FFFD,1,nil)
b.ReadRune() (r rune, size int, err error)

//读取缓冲区第一个分隔符前面的内容以及分隔符并返回,缓冲区会清空读取的内容。如果没有发现分隔符,则返回读取的内容并返回错误io.EOF
b.ReadBytes(delimiter byte) (line []byte, err error)

//读取缓冲区第一个分隔符前面的内容以及分隔符并作为字符串返回,缓冲区会清空读取的内容。如果没有发现分隔符,则返回读取的内容并返回错误 io.EOF
b.ReadString(delimiter byte) (line string, err error)

//将 Buffer 中的内容输出到实现了 io.Writer 接口的可写入对象中,成功返回写入的字节数,失败返回错误
b.WriteTo(w io.Writer) (n int64, err error)

其它操作。

b.Bytes() []byte		//返回字节切片
b.Cap() int				//返回 buffer 内部字节切片的容量
b.Grow(n int)			//为 buffer 内部字节切片的容量增加 n 字节
b.Len() int				//返回缓冲区数据长度,等于 len(b.Bytes())
b.Reset() 				//清空数据
b.String() string		//字符串化
b.Truncate(n int)		//丢弃缓冲区中除前n个未读字节以外的所有字节。如果 n 为负数或大于缓冲区长度,则引发 panic
b.UnreadByte() error	//将最后一次读取操作中被成功读取的字节设为未被读取的状态,即将已读取的偏移 off 减 1
b.UnreadRune() error	//将最后一次 ReadRune() 读取操作返回的 UTF8 字符 rune设为未被读取的状态,即将已读取的偏移 off 减去 字符 rune 的字节数

5.使用示例

从文件 test.txt 中读取全部内容追加到 buffer 尾部。

test.txt的内容为:
My name is dablelv

package main

import (
	"os"
	"fmt"
	"bytes"
)

func main() {
    file, _ := os.Open("./test.txt")    
    buf := bytes.NewBufferString("Hello world ")    
    buf.ReadFrom(file)              //将text.txt内容追加到缓冲器的尾部    
    fmt.Println(buf.String())
}

6.创建缓冲器

使用bytes.NewBuffer创建:参数是[]byte的话,缓冲器里就是这个slice的内容;如果参数是nil的话,就是创建一个空的缓冲器。

func main(){
   buf1 := bytes.NewBufferString("hello")
   buf2 := bytes.NewBuffer([]byte("hello"))
   buf3 := bytes.NewBuffer([]byte{'h','e','l','l','o'})
  //以上三者等效,输出:hello
   buf4 := bytes.NewBufferString("")
   buf5 := bytes.NewBuffer([]byte{})
   //以上两者等效,输出:""
   		fmt.Println(buf1.String(),buf2.String(),buf3.String(),buf4,buf5,1)
}

7.空缓冲器不是nil

func main() {
	buf := bytes.NewBufferString("")
	if buf == nil {
		fmt.Println("yes")
	} else {
		fmt.Println("no")
	}
	//输出no
}

8.bytes.Buffer不能与nil对比

image-20220910091541094

9.写入到缓冲区

1.Write方法,将一个byte类型的slice放到缓冲器的尾部

//func (b *Buffer) Write(p []byte) (n int,err error)

func main(){
   s := []byte(" world")
   buf := bytes.NewBufferString("hello") 
   fmt.Println(buf.String())    //hello
   buf.Write(s)                 //将s这个slice添加到buf的尾部
   fmt.Println(buf.String())   //hello world
}

2.WriteString方法,把一个字符串放到缓冲器的尾部

//func (b *Buffer) WriteString(s string)(n int,err error)

func main(){
   s := " world"
   buf := bytes.NewBufferString("hello")
   fmt.Println(buf.String())    //hello
   buf.WriteString(s)           //将string写入到buf的尾部
   fmt.Println(buf.String())    //hello world
}

3.WriteByte方法,将一个byte类型的数据放到缓冲器的尾部

//func (b *Buffer) WriteByte(c byte) error

func main(){
   var s byte = '?'
   buf := bytes.NewBufferString("hello") 
   fmt.Println(buf.String()) //把buf的内容转换为string,hello
   buf.WriteByte(s)         //将s写到buf的尾部
   fmt.Println(buf.String()) //hello?
}

4.WriteRune方法,将一个rune类型的数据放到缓冲器的尾部

// func (b *Buffer) WriteRune(r Rune) (n int,err error)

func main(){
   var s rune = '好'
   buf := bytes.NewBufferString("hello")
   fmt.Println(buf.String()) //hello
   buf.WriteRune(s)   
   fmt.Println(buf.String()) //hello好
}

10.从缓冲器写出

WriteTo方法,将一个缓冲器的数据写到w里,w是实现io.Writer的,比如os.File

func main(){
   file,_ := os.Create("text.txt")
   buf := bytes.NewBufferString("hello world")
   buf.WriteTo(file)
   //或者使用写入,fmt.Fprintf(file,buf.String())
}

11.读出缓冲器

1.Read方法,给Read方法一个容器,读完后p就满了,缓冲器相应的减少。

// func (b *Buffer) Read(p []byte)(n int,err error)

func main(){
   s1 := []byte("hello")
   buff := bytes.NewBuffer(s1)
   s2 := []byte(" world")
   buff.Write(s2)
   fmt.Println(buff.String()) //hello world
   
   s3 := make([]byte,3)
   buff.Read(s3)     //把buff的内容读入到s3,s3的容量为3,读了3个过来
   fmt.Println(buff.String()) //lo world
   fmt.Println(string(s3))   //hel
   buff.Read(s3) //继续读入3个,原来的被覆盖
   
   fmt.Println(buff.String())     //world
   fmt.Println(string(s3))    //"lo "
}

2.ReadByte方法,返回缓冲器头部的第一个byte,缓冲器头部第一个byte取出

//func (b *Buffer) ReadByte() (c byte,err error)

func main(){
   buf := bytes.NewBufferString("hello")
   fmt.Println(buf.String())
   b,_ := buf.ReadByte()   //取出第一个byte,赋值给b
   fmt.Println(buf.String()) //ello
   fmt.Println(string(b))   //h
}

3.ReadRune方法,返回缓冲器头部的第一个rune

// func (b *Buffer) ReadRune() (r rune,size int,err error)

func main(){
   buf := bytes.NewBufferString("你好smith")
   fmt.Println(buf.String())
   b,n,_ := buf.ReadRune()  //取出第一个rune
   fmt.Println(buf.String()) //好smith
   fmt.Println(string(b))   //你
   fmt.Println(n)   //3,"你“作为utf8存储占3个byte

   b,n,_ = buf.ReadRune()  //再取出一个rune
   fmt.Println(buf.String()) //smith
   fmt.Println(string(b))  //好
   fmt.Println(n)   //3
}

4.ReadBytes方法,需要一个byte作为分隔符,读的时候从缓冲器里找出第一个出现的分隔符,缓冲器头部开始到分隔符之间的byte返回。

//func (b *Buffer) ReadString(delim byte) (line string,err error)

func main(){
   var d byte = 'e'
   buf := bytes.NewBufferString("你好esmieth")
   fmt.Println(buf.String())  //你好esmieth
   b,_ := buf.ReadString(d)   //读取到分隔符,并返回给b
   fmt.Println(buf.String()) //smieth
   fmt.Println(string(b)) //你好e
}

5.ReadString方法,和ReadBytes方法一样

//func (b *Buffer) ReadString(delim byte) (line string,err error)

func main(){
   var d byte = 'e'
   buf := bytes.NewBufferString("你好esmieth")
   fmt.Println(buf.String())  //你好esmieth
   b,_ := buf.ReadString(d)   //读取到分隔符,并返回给b
   fmt.Println(buf.String()) //smieth
   fmt.Println(string(b)) //你好e
}

12.读入缓冲区

ReadFrom方法,从一个实现io.Reader接口的r,把r的内容读到缓冲器里,n返回读的数量

//func (b *Buffer) ReadFrom(r io.Reader) (n int64,err error)

func main(){
   file, _ := os.Open("text.txt")
   buf := bytes.NewBufferString("bob ")
   buf.ReadFrom(file)
   fmt.Println(buf.String()) //bob hello world
}

13.从缓冲器取出

Next方法,返回前n个byte(slice),原缓冲器变小

//func (b *Buffer) Next(n int) []byte

func main(){
   buf := bytes.NewBufferString("hello world")
   fmt.Println(buf.String())
   b := buf.Next(2)  //取前2个
   fmt.Println(buf.String()) //llo world
   fmt.Println(string(b)) //he
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值