@Override
  public ByteBuffer decodeKeyValues(DataInputStream source, HFileBlockDecodingContext decodingCtx)
    throws IOException {
    ByteBuffer sourceAsBuffer = ByteBufferUtils.drainInputStreamToBuffer(source);// waste
    sourceAsBuffer.mark();
    if (!decodingCtx.getHFileContext().isIncludesTags()) {
      sourceAsBuffer.position(sourceAsBuffer.limit() - Bytes.SIZEOF_INT);
      int onDiskSize = sourceAsBuffer.getInt();
      sourceAsBuffer.reset();
      ByteBuffer dup = sourceAsBuffer.duplicate();
      dup.position(sourceAsBuffer.position());
      dup.limit(sourceAsBuffer.position() + onDiskSize);
      return dup.slice();
    } else {
      RowIndexSeekerV1 seeker = new RowIndexSeekerV1(decodingCtx);
      seeker.setCurrentBuffer(new SingleByteBuff(sourceAsBuffer));
      List<ExtendedCell> kvs = new ArrayList<>();
      kvs.add(seeker.getCell());
      while (seeker.next()) {
        kvs.add(seeker.getCell());
      }
      boolean includesMvcc = decodingCtx.getHFileContext().isIncludesMvcc();
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      try (DataOutputStream out = new DataOutputStream(baos)) {
        for (ExtendedCell cell : kvs) {
          KeyValue currentCell = KeyValueUtil.copyToNewKeyValue(cell);
          out.write(currentCell.getBuffer(), currentCell.getOffset(), currentCell.getLength());
          if (includesMvcc) {
            WritableUtils.writeVLong(out, cell.getSequenceId());
          }
        }
        out.flush();
      }
      return ByteBuffer.wrap(baos.getBuffer(), 0, baos.size());
    }
  }
Store cells following every row's start offset, so we can binary search to a row's cells. Format:
flat cells integer: number of rows integer: row0's offset integer: row1's offset .... integer:
dataSize
hbase-common/src/main/java/org/apache/hadoop/hbase/io/encoding/RowIndexCodecV1.java