Interpretation of the source code of Go language io package

Posted by phpnewbie112 on Sat, 25 Dec 2021 21:07:26 +0100

You have to work very hard to look effortless!

WeChat search official account [Coding road], together with From Zero To Hero!

preface

The first two articles Go language io package core interface details,Go language io package basic interface details , we have learned the core interfaces, basic interfaces and composite interfaces in the io package, which are basic interface definitions and specifications. In this article, let's take a look at the use of the above interfaces in the io package, including three structures and some methods. We deepen our understanding of interface definitions by studying the source code.

structural morphology

LimitedReader

LimitedReader limits the length of data to be read, and reads n bytes at most. The specific definitions are as follows:

// Reader R, as the underlying reader, is used to read data;
// n record how many remaining bytes can be read (initialized to n, the number of remaining readable bytes will be updated after each data reading)
type LimitedReader struct {
	R Reader // underlying reader
	N int64  // max bytes remaining
}

correlation method

  1. Return LimitedReader instance
// Returns the LimitReader instance. r is the underlying Reader, and n is the number and size of bytes to be read
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
  1. Read method
// The Read method reads the data into the byte array p, and returns the Read byte length and the generated error
// The number of remaining readable bytes N is updated each time the method is called
func (l *LimitedReader) Read(p []byte) (n int, err error) {

	// If the number of remaining readable bytes n < = 0, EOF error is returned
	if l.N <= 0 {
		return 0, EOF
	}

	// If the provided byte array space is too large, you only need to use n lengths, because it limits the maximum reading of N bytes
	if int64(len(p)) > l.N {
		p = p[0:l.N]
	}

	// Read data to byte data p and subtract N from the number of bytes successfully read
	n, err = l.R.Read(p)
	l.N -= int64(n)
	return
}

SectionReader

SectionReader wraps the ReaderAt type and overrides the Read, Seek, and ReadAt methods. The function of SectionReader is to limit the range of reading data. It can only Read a part (or a paragraph) of the original data. It is defined as follows:

// r: ReaderAt instance for reading data
// base: save the starting position of the readable data range, and the variable value will not change
// off: save the current location. The variable value will change every time the data is read
// limit: save the end position of the readable data range, and the variable value will not change
type SectionReader struct {
	r     ReaderAt
	base  int64
	off   int64
	limit int64
}

correlation method

  1. Initializing the SectionReader instance
// The NewSectionReader returns the initialized SectionReader. It needs to provide a ReaderAt instance, as well as the start position off and the data length n to be read, that is, it limits the data range to be read to [off,off+n)
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader {
	return &SectionReader{r, off, off, off + n}
}
  1. Read method: reads data within the specified range
// Read the data into the byte array p, and return the read byte length and the generated error
func (s *SectionReader) Read(p []byte) (n int, err error) {

	// If the current position exceeds the readable data range, EOF error is returned
	if s.off >= s.limit {
		return 0, EOF
	}

	// If the length of the byte array p is greater than the length of the remaining readable data, reduce the length of p to the length of the remaining readable data
	if max := s.limit - s.off; int64(len(p)) > max {
		p = p[0:max]
	}

	// Read the data from the off position and update the off position
	n, err = s.r.ReadAt(p, s.off)
	s.off += int64(n)
	return
}
  1. Seek method: set the value of the SectionReader off variable according to the provided where and offset
var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset")

// The Seek method sets the value of the SectionReader off variable according to where and offset, and returns the length from the start of the readable range and the generated error
func (s *SectionReader) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	default:
		return 0, errWhence
	// Start seek from the starting position, that is, based on sectionreader base
	case SeekStart:
		offset += s.base
	// Start seek from the current location, i.e. based on sectionreader off
	case SeekCurrent:
		offset += s.off
	// Start seek from the current location, i.e. based on sectionreader limit
	case SeekEnd:
		offset += s.limit
	}

	// If the position of the final offset is before the base, it is an illegal position and error will be returned;
  // If no error is returned after limit, you can refer to the definition of Seek method in Seeker interface
	if offset < s.base {
		return 0, errOffset
	}

	// Modify the value of off and return the length from base
	s.off = offset
	return offset - s.base, nil
}
  1. ReadAt method: Based on the starting position base, calculate the read position offset as base+off according to the input parameter off, and then start from this position to read the data within the readable data range into the byte array p. (independent of the off variable defined in the SectionReader structure)
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) {
	// If the input parameter off < 0 or the off size exceeds the readable data range, EOF error is returned
	if off < 0 || off >= s.limit-s.base {
		return 0, EOF
	}

	// The starting position of this reading is off + s.base
	off += s.base

	// If the length of the byte array p is greater than the readable length s.limit-off, the byte array is reduced to the readable length
	if max := s.limit - off; int64(len(p)) > max {
		p = p[0:max]
		n, err = s.r.ReadAt(p, off)
		// Since the length of the read data is less than the length len(p) of the original byte array p, refer to the definition of the ReadAt method in the ReaderAt interface and return error
		if err == nil {
			err = EOF
		}
		return n, err
	}
	// If the length of the byte array is less than or equal to the readable length s.limit-off, read the data into p
	return s.r.ReadAt(p, off)
}
  1. Size method: returns the range size of readable data

    // Size returns the length of the readable range
    func (s *SectionReader) Size() int64 { return s.limit - s.base }
    

teeReader

teeReader is a packet level private data type because its first letter is lowercase. The function of teeReader is to give the data read by the Reader to the Writer to write to the file, which is equivalent to providing a bridge to connect a Reader and a Writer. teeReader will give all the data read in the Reader to the Writer at one time without buffer. It is defined as follows:

type teeReader struct {
   r Reader
   w Writer
}

correlation method

  1. Initialization method: provide a Reader and Writer for initialization
func TeeReader(r Reader, w Writer) Reader {
	return &teeReader{r, w}
}
  1. Read method: the reader reads the data into the byte array p, and then the Writer writes the data in p to the file. p acts as a transit station for the data.
func (t *teeReader) Read(p []byte) (n int, err error) {
  // Read the data into the byte array p, and return the read data length and error
	n, err = t.r.Read(p)
  
  // According to the definition of the Read method in the Reader interface, the caller should first focus on whether n is greater than 0, rather than err
  // If n > 0, it means that the data has been read, and the data part p[:n] read in p is handed over to Writer w to write
	if n > 0 {
		if n, err := t.w.Write(p[:n]); err != nil {
			return n, err
		}
	}
	return
}

Method definition

WriteString

WriteString is used to write a string to a file

// Input parameter: Writer w, used to write string to file; s: String to write
// Return value: the number of bytes successfully written, n, and the possible error

func WriteString(w Writer, s string) (n int, err error) {
  // If the incoming writer implements the StringWriter interface, directly call the WriteString method of the StringWriter interface
  // Otherwise, the Writer's Write method is called
	if sw, ok := w.(StringWriter); ok {
		return sw.WriteString(s)
	}
	return w.Write([]byte(s))
}

ReadAtLeast

ReadAtLeast: read at least min bytes, that is, use Reader r to read at least min bytes into byte array buf

Input parameter

  • Reader r: used to read data
  • buf []byte: save the read data
  • min int: read at least min bytes

Return value

  • n: Number of bytes successfully read in
  • err: generated error
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {

	// If the length of the byte array buff provided by the method is less than min, it is impossible to meet the definition of the method and directly return ErrShortBuffer error
	if len(buf) < min {
		return 0, ErrShortBuffer
	}

	// N record the number of bytes currently read, and cycle until the read data bytes n > = min
	for n < min && err == nil {
		var nn int
		nn, err = r.Read(buf[n:])
		n += nn
	}

	// If the read data n > = min, nil is returned regardless of whether an error is generated, because at least min bytes have been read
	if n >= min {
		err = nil
	} else if n > 0 && err == EOF {
		//If the read data is less than min bytes, but EOF is generated at the end of the file, the method returns errunexpected EOF error
		err = ErrUnexpectedEOF
	}

	// Other cases: 0 < = n < min, directly return the current err

	return
}

Several error s

  1. If the length of the byte array buff provided by the method is less than min, it is impossible to meet the definition of the method and directly return ErrShortBuffer error.

  2. If the read data is greater than 0 and less than min bytes, but EOF is generated at the end of the file, the method returns errunexpected EOF error

  3. If the read data is greater than or equal to min bytes, but an error occurs, because the method has met the need to read at least min bytes, this error will be discarded and nil will be returned

  4. Other case s: the number of bytes read is 0, or the data read is less than min bytes, but other non EOF error s are generated. The method returns the corresponding error

ReadFull

ReadFull: use Reader r to read data and fill the byte array buf

Input parameter

  • Reader r
  • Byte array buf

Return value

  • n: The number of bytes successfully read and written to the byte array buf
  • err: generated error
func ReadFull(r Reader, buf []byte) (n int, err error) {
  // This method directly calls ReadAtLeast(r, buf, len(buf)), which is equivalent to reading the buf byte array full.
	return ReadAtLeast(r, buf, len(buf))
}

Referring to the definition of ReadAtLeast, the following conclusions can be drawn:

  • If the returned err = EOF, no data must be read, because if the read data n > 0, errunexpected EOF will be returned after encountering EOF
  • If n=len(buf) is returned, err must be nil

copyBuffer

copyBuffer is a private method that uses buffer to copy data from Reader to Writer. Method uses a byte array as a buffer. The Reader reads data into the byte array each time, and then the Writer writes the data in the byte array to the file until the Reader finishes reading data or encounters error. Finally, the number of bytes copied and the error generated during copying are returned. If the buffer array buf =nil is passed when this method is called, a temporary byte array will be allocated.

func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {

	// If the Reader src implements the WriteTo interface, you can call the WriteTo method to complete the copy
	if wt, ok := src.(WriterTo); ok {
		return wt.WriteTo(dst)
	}

	// If the Writer implements the ReaderFrom interface, you can call the ReadFrom method to complete the copy
	if rt, ok := dst.(ReaderFrom); ok {
		return rt.ReadFrom(src)
	}

	// If the buffer passed by the method is nil, a buffer is allocated
	if buf == nil {

		// The default is 32 kb
		size := 32 * 1024

		// If src is a LimitedReader, the buffer size should adapt to the LimitedReader
		if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
			// At least 1
			if l.N < 1 {
				size = 1
			} else {
				// The maximum size of the LimitedReader cannot be exceeded
				size = int(l.N)
			}
		}
		buf = make([]byte, size)
	}

	// Keep cycling, read data from Reader src to buf, and Writer dst writes the read data to buf
	for {
		nr, er := src.Read(buf)
		// According to the definition of the Read method in the Reader interface, process the data first and then error
		// If NR > 0, the dst writes the read data buf[0:nr]
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])

			// Write saves the total copied byte size
			if nw > 0 {
				written += int64(nw)
			}

			// err encountered during writing, end replication
			if ew != nil {
				err = ew
				break
			}

			// The number of bytes written is inconsistent with the number of bytes read. End the copy and return ErrShortWrite
			if nr != nw {
				err = ErrShortWrite
				break
			}
		}

		// If Er is encountered during reading= Nil, if ER= EOF, and finally return the error;
		// If er = EOF and the logic goes here, it means that the above write does not generate error, then the whole file is copied, the err field is not assigned, and the final returned err = nil
		if er != nil {
			if er != EOF {
				err = er
			}
			break
		}
	}
	// Returns the total number of bytes copied and the resulting error
	return written, err
}

Copy

The function of copy is to copy the data of Reader src to Writer dst, and then return the copied bytes and the encountered error. Copy directly calls the copyBuffer method, but does not provide a buffer array. A temporary array will be used inside the copyBuffer method.

According to the definition of copyBuffer above, if the replication is completed successfully, err=nil in the return value instead of err=EOF, because if EOF is encountered, it indicates that the replication has been completed, and err should be nil. See the analysis of the copyBuffer source code above for the specific logic

func Copy(dst Writer, src Reader) (written int64, err error) {
	return copyBuffer(dst, src, nil)
}

CopyBuffer

The difference between copyBuffer and Copy method is that copyBuffer can pass in a byte array as a global buffer; The Copy method does not provide a buffer, but uses the temporary buffer of copyBuffer

func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
	// If the incoming byte buffer is not nil, but the length is 0, it is panic directly
	if buf != nil && len(buf) == 0 {
		panic("empty buffer in io.CopyBuffer")
	}
	// If the passed in byte array buffer = nil, a temporary buffer is generated using copyBuffer
	// If the incoming byte array buf= If nil, len (buf) > 0, buf is used as the global buffer
	return copyBuffer(dst, src, buf)
}

CopyN

The function of CopyN is similar to that of Copy. Both are used to Copy data from the Reader to the Writer. However, CopyN limits the number of bytes copied to n. The method returns the length written and the generated error.

func CopyN(dst Writer, src Reader, n int64) (written int64, err error) {

	// The Copy method is called to Copy data, but the Reader src is encapsulated into a LimitReader that can read up to n bytes,
	// The LimitReader can read up to n bytes, so the copy can complete up to n bytes
	written, err = Copy(dst, LimitReader(src, n))

	// written == n, indicating successful replication, err=nil
	if written == n {
		return n, nil
	}

	//  err == nil, indicating that the Copy method is called above to complete the Copy;
	//  Written < n indicates that LimitReader encountered EOF before reading n bytes. The method did not complete the task of copying n bytes and returned err = EOF
	if written < n && err == nil {
		// src stopped early; must have been EOF.
		err = EOF
	}

	// For other errors, just return the error directly

	return
}

summary

This article analyzes the source code of the three structures and methods in the io package. The main contents are as follows:

  • structural morphology

    • LimitedReader: limits the number of bytes read
    • SectionReader: limits the range of reads
    • teeReader: acts as a bridge between Reader and Writer to complete data transfer
  • method

    • WriteString: write string
    • ReadAtLeast: read min bytes at least
    • ReadFull: fill the passed in byte array
    • copyBuffer: use buffer array to copy data from Reader to Writer
    • Copy: cannot provide global buffer array. Use copyBuffer to complete the copy
    • copyBuffer: you can provide a global buffer array and use copyBuffer to complete replication
    • CopyN: copy n bytes with LimitedReader

See here, do you like me, amazed at the simplicity and subtlety of go language interface design? Let's keep learning together!

more

Personal blog: https://lifelmy.github.io/

WeChat official account: long Coding Road

Topics: Go Back-end