“This is the third day of my participation in the First Challenge 2022. For details: First Challenge 2022”

You have to work really hard to look effortless!

Wechat search public number [long Coding road], together From Zero To Hero!

preface

Bufio.Reader uses a buffer to bridge the gap between the underlying file Reader and the read operation. The advantage of having such a buffer is that it reduces the execution time of the read method most of the time. Although the reading method is sometimes responsible for filling the buffer, the average execution time of the reading method is generally significantly reduced as a result. In this article, we will continue to read the source code, learning the underlying implementation of the method, do know yourself and know the enemy, in order to use freely.

Peek

The Peek method is used to look at the first n bytes of unread data. It does not change the state of bufio.Reader, does not update the read count, and is not a read operation and cannot be used for subsequent rollback operations.

It is important to note that this method returns a slice of the buffer, which may pose a risk of data leakage because the caller can directly modify the value of the buffer through the returned slice; Second, the validity period of returned data is before the next data read, because the next data read may compress and translate, resulting in the position of the current data changed.

func (b *Reader) Peek(n int) ([]byte, error) {
  
  // Invalid parameter
	if n < 0 {
		return nil, ErrNegativeCount
	}

  // the peek method invalidates the rollback
	b.lastByte = - 1
	b.lastRuneSize = - 1
	
  // If the length of unread data is less than the required length n and the buffer is not full, fill the buffer
	for b.w-b.r < n && b.w-b.r < len(b.buf) && b.err == nil {
		b.fill() 
	}

  // if n is greater than the buffer length, all valid data is returned with ErrBufferFull error
	if n > len(b.buf) {
		return b.buf[b.r:b.w], ErrBufferFull
	}

	// in this case, 0 <= n <= len(b.bouf), that is, n is less than the buffer length
  // 1. If the length of valid data is less than n, the buffer is not filled by the previous fill method, so at most all valid data can be returned.
  // And returns the error generated by the fill method
  // 2. If the length of valid data is greater than n, return the first n valid data
	var err error
	if avail := b.w - b.r; avail < n {
		// If there is insufficient valid data, set n to the maximum valid data length
		n = avail
		err = b.readErr()
		if err == nil {
			err = ErrBufferFull
		}
	}
	return b.buf[b.r : b.r+n], err
}
Copy the code

Discard

The Discard method discards n bytes of the buffer and returns the actual number of bytes discarded and the error generated.

For the valid argument n, the method uses a for loop to continuously load the data in order to discard as many bytes as possible. That is, if the valid data length is less than N, the existing data is discarded, and then the fill method is called again to fill in the new data for discarding. If err is encountered during this process, the method terminates and finally returns the actual number of bytes discarded and the error encountered. If the number of valid bytes that buF can discard is greater than n, discard some bytes.

func (b *Reader) Discard(n int) (discarded int, err error) {
  
  // Invalid parameter
   if n < 0 {
      return 0, ErrNegativeCount
   }
  
  // 0 indicates that data is returned without discarding
   if n == 0 {
      return
   }
  
  // Remain Indicates how many bytes remain to be discarded. At the beginning, n bytes remain to be discarded
   remain := n
  
  // If the n passed in is large, many bytes are discarded, but the valid data length of the buffer is not sufficient, requiring multiple discarding
   for {
     
      // skip indicates the current valid length of bytes that can be discarded
      skip := b.Buffered()
     
     // If the current buffer has a valid data length of 0, call the fill method to fill it
      if skip == 0 {
         b.fill()
         skip = b.Buffered()
      }
     
    	// If the current length of valid data is greater than the number of bytes to be discarded, skip the number of bytes to be discarded
      if skip > remain {
         skip = remain
      }
     
     // The read count increases the value of skip, indicating that skip bytes are discarded
      b.r += skip
     
     // Update the number of remaining bytes to be discarded
      remain -= skip
     
     // If the number of bytes to be discarded is 0, the task is complete
      if remain == 0 {
         return n, nil
      }
     
     // Error is generated, which returns the number of bytes discarded, and error
      ifb.err ! =nil {
         return n - remain, b.readErr()
      }
     
     Remain >0, and b. extr ==nil}}Copy the code

Read

The Read method reads data into a byte slice P, returning the number of bytes Read and the error generated.

  • When the valid data of the buffer is not empty, the valid data of the buffer is directly copied to the byte slice P and written as much as there is, without reading the underlying data fill. Therefore, if the valid data length of the current buffer is less than the length of the passed byte slice P, the number of bytes read is n < len(p);
  • When the buffer valid data is empty, the data is read from the underlying file and the byte slice P is filled.
    • When the length of p is less than the buffer length, it is read from the bottomAt a timeData into the buffer, and then copy the buffer’s data into P
    • When the length of p is larger than the length of the buffer, there is an optimization. Instead of first writing to the buffer and then copying to P, this method not only replicates one more time, but also reads less data than the desired length, but directly reads the underlying data into P, which is simple and efficient.

IO.ReadFull(b,p); IO.ReadFull(b,p); IO.

func (b *Reader) Read(p []byte) (n int, err error) {
	n = len(p)

	// The length of the passed byte slice is 0. Check whether the length of the current cache data is greater than 0 to decide whether to return err
	if n == 0 {
		if b.Buffered() > 0 {
			return 0.nil
		}
		return 0, b.readErr()
	}

	// len(p) > 0, buffer valid data is 0
	if b.r == b.w {

		// Cache data is 0, possibly err! =nil
		ifb.err ! =nil {
			return 0, b.readErr()
		}

		// The size of the passed byte slice is greater than the size of the buffer, and the buffer has no valid data
		if len(p) >= len(b.buf) {

			// Write to p directly from the underlying file, instead of writing to the buffer and then copying to p
			n, b.err = b.rd.Read(p)

			if n < 0 {
				panic(errNegativeRead)
			}

			// Read the data, update the rollback
			if n > 0 {
				b.lastByte = int(p[n- 1])
				b.lastRuneSize = - 1
			}

			/ / return
			return n, b.readErr()
		}

		Len (p) < len(b.bouf)

		// Update the read count and write count to 0, compress the invalid data, and then only read the data once and write it to the buffer
		b.r = 0
		b.w = 0
		n, b.err = b.rd.Read(b.buf)
		if n < 0 {
			panic(errNegativeRead)
		}
		if n == 0 {
			return 0, b.readErr()
		}

		// Update the written count
		b.w += n
	}

	// Copy valid data to p
	n = copy(p, b.buf[b.r:b.w])
	b.r += n
	b.lastByte = int(b.buf[b.r- 1])
	b.lastRuneSize = - 1
	return n, nil
}
Copy the code

ReadByte

The ReadByte method reads a byte and returns the ReadByte and the Error generated.

If the buffer’s valid data is empty, it keeps trying to fill the data by calling the fill method, and then returns the first byte of the buffer’s valid data; If an error is generated by calling the fill method, an error is returned.

func (b *Reader) ReadByte(a) (byte, error) {
	b.lastRuneSize = - 1

	// Buffer valid data is empty, will continue to try to fill data until err! =nil, or data was successfully populated
	for b.r == b.w {
		ifb.err ! =nil {
			return 0, b.readErr()
		}

		// Populate the data
		b.fill()
	}

	// The first byte of the valid data section
	c := b.buf[b.r]

	// Add one to the read count
	b.r++

	// Save the byte just read for later rollback operations
	b.lastByte = int(c)
	return c, nil
}
Copy the code

UnreadByte

The UnreadByte method is used for a rollback read operation. That is, the last byte of the previous read operation is left unread and the next read will be the first byte to be read. If the previous operation was not a read, lastByte is set to -1 and the rollback cannot be completed (the Peek method does not count as a read).


func (b *Reader) UnreadByte(a) error {

	// lastByte < 0 indicates that the last operation was not a read operation and cannot be rolled back
	// b.r == 0 &&b.w > 0. If no read operation is performed after compression, the data cannot be rolled back

	if b.lastByte < 0 || b.r == 0 && b.w > 0 {
		return ErrInvalidUnreadByte
	}
	// b.r > 0 || b.w == 0
	if b.r > 0 {
		b.r--
	} else {
		// b.r == 0 && b.w == 0
		b.w = 1
	}
	b.buf[b.r] = byte(b.lastByte)
	b.lastByte = - 1
	b.lastRuneSize = - 1
	return nil
}
Copy the code

conclusion

In this article we introduce the following methods of Bufio. Reader:

  • Peek: Views partial data, but does not change the state of the structure
  • Discard: Discards data
  • Read: Reads data, optimized for one of the cases where the buffer is empty, directly from the underlying file, without passing through the buffer
  • ReadByte: Reads a byte
  • UnreadByte: rolls back one byte

More and more

Personal blog: lifelmy.github. IO /

Wechat official account: Long Coding road