code: func NewBufferingReaderAt(r io.ReaderAt, blockSize int, lruSize int) io.ReaderAt {
  	if blockSize < 1 {
  		panic(fmt.Sprintf("block size should be >= 1, not %d", blockSize))
  	}
  	if lruSize < 1 {
  		panic(fmt.Sprintf("lru size should be >= 1, not %d", lruSize))
  	}
  	reader := &bufferingReaderAt{
  		r:         r,
  		blockSize: blockSize,
  		// We actually pool at most 1 buffer, since buffers are grabbed from the
  		// pool immediately after they are evicted from LRU.
  		pool: make([][]byte, 0, 1),
  	}
  	reader.lru.init(lruSize)
  	reader.lru.evicted = func(b block) { reader.recycleBuf(b.data) }
  	return reader
  }
docs: // NewBufferingReaderAt returns an io.ReaderAt that reads data in blocks of
  // configurable size and keeps LRU of recently read blocks.
  //
  // It is great for cases when data is read sequentially from an io.ReaderAt,
  // (e.g. when extracting files using zip.Reader), since by setting large block
  // size we can effectively do lookahead reads.
  //
  // For example, zip.Reader reads data in 4096 byte chunks by default. By setting
  // block size to 512Kb and LRU size to 1 we reduce the number of read operations
  // significantly (128x), in exchange for the modest amount of RAM.
  //
  // The reader is safe to user concurrently (just like any ReaderAt), but beware
  // that the LRU is shared and all reads from the underlying reader happen under
  // the lock, so multiple goroutines may end up slowing down each other.