@Override
    public void encodeBatch(IChunkWriter chunkWriter, BatchEncodeInfo encodeInfo, long[] times) {
      AlignedChunkWriterImpl alignedChunkWriter = (AlignedChunkWriterImpl) chunkWriter;

      // duplicated time or deleted time are all invalid, true if we don't need this row
      BitMap timeDuplicateInfo = null;

      int startIndex = index;
      // time column
      for (; index < rows; index++) {
        if (encodeInfo.pointNumInChunk >= encodeInfo.maxNumberOfPointsInChunk
            || encodeInfo.pointNumInPage >= encodeInfo.maxNumberOfPointsInPage) {
          break;
        }
        // skip empty row
        if (allValueColDeletedMap != null && allValueColDeletedMap.isMarked(getValueIndex(index))) {
          continue;
        }
        if (isTimeDeleted(index)) {
          continue;
        }
        int nextRowIndex = index + 1;
        while (nextRowIndex < rows
            && ((allValueColDeletedMap != null
                    && allValueColDeletedMap.isMarked(getValueIndex(nextRowIndex)))
                || (isTimeDeleted(nextRowIndex)))) {
          nextRowIndex++;
        }
        long time = getTime(index);
        if (nextRowIndex == rows || time != getTime(nextRowIndex)) {
          times[encodeInfo.pointNumInPage++] = time;
          encodeInfo.pointNumInChunk++;
        } else {
          if (Objects.isNull(timeDuplicateInfo)) {
            timeDuplicateInfo = new BitMap(rows);
          }
          timeDuplicateInfo.mark(index);
        }
        index = nextRowIndex - 1;
      }

      int columnCount = dataTypeList.size();
      // value columns
      for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
        ValueChunkWriter valueChunkWriter =
            alignedChunkWriter.getValueChunkWriterByIndex(columnIndex);
        int validColumnIndex = columnIndexList.get(columnIndex);

        // Pair of Time and Index
        Pair<Long, Integer> lastValidPointIndexForTimeDupCheck = null;
        if (Objects.nonNull(timeDuplicateInfo)) {
          lastValidPointIndexForTimeDupCheck = new Pair<>(Long.MIN_VALUE, null);
        }
        for (int sortedRowIndex = startIndex; sortedRowIndex < index; sortedRowIndex++) {
          // skip empty row
          if ((allValueColDeletedMap != null
                  && allValueColDeletedMap.isMarked(getValueIndex(sortedRowIndex)))
              || (isTimeDeleted(sortedRowIndex))) {
            continue;
          }
          long time = getTime(sortedRowIndex);
          // skip time duplicated or totally deleted rows
          if (Objects.nonNull(timeDuplicateInfo)) {
            if (!outer.isNullValue(getValueIndex(sortedRowIndex), validColumnIndex)) {
              lastValidPointIndexForTimeDupCheck.left = getTime(sortedRowIndex);
              lastValidPointIndexForTimeDupCheck.right = getValueIndex(sortedRowIndex);
            }
            if (timeDuplicateInfo.isMarked(sortedRowIndex)) {
              continue;
            }
          }

          // The part of code solves the following problem:
          // Time: 1,2,2,3
          // Value: 1,2,null,null
          // When rowIndex:1, pair(min,null), timeDuplicateInfo:false, write(T:1,V:1)
          // When rowIndex:2, pair(2,2), timeDuplicateInfo:true, skip writing value
          // When rowIndex:3, pair(2,2), timeDuplicateInfo:false, T:2==pair.left:2, write(T:2,V:2)
          // When rowIndex:4, pair(2,2), timeDuplicateInfo:false, T:3!=pair.left:2,
          // write(T:3,V:null)
          int originRowIndex;
          if (Objects.nonNull(lastValidPointIndexForTimeDupCheck)
              && (getTime(sortedRowIndex) == lastValidPointIndexForTimeDupCheck.left)) {
            originRowIndex = lastValidPointIndexForTimeDupCheck.right;
          } else {
            originRowIndex = getValueIndex(sortedRowIndex);
          }

          boolean isNull = outer.isNullValue(originRowIndex, validColumnIndex);
          switch (dataTypeList.get(columnIndex)) {
            case BOOLEAN:
              valueChunkWriter.write(
                  time,
                  !isNull && getBooleanByValueIndex(originRowIndex, validColumnIndex),
                  isNull);
              break;
            case INT32:
            case DATE:
              valueChunkWriter.write(
                  time, isNull ? 0 : getIntByValueIndex(originRowIndex, validColumnIndex), isNull);
              break;
            case INT64:
            case TIMESTAMP:
              valueChunkWriter.write(
                  time, isNull ? 0 : getLongByValueIndex(originRowIndex, validColumnIndex), isNull);
              break;
            case FLOAT:
              valueChunkWriter.write(
                  time,
                  isNull ? 0 : getFloatByValueIndex(originRowIndex, validColumnIndex),
                  isNull);
              break;
            case DOUBLE:
              valueChunkWriter.write(
                  time,
                  isNull ? 0 : getDoubleByValueIndex(originRowIndex, validColumnIndex),
                  isNull);
              break;
            case TEXT:
            case BLOB:
            case STRING:
              valueChunkWriter.write(
                  time,
                  isNull ? null : getBinaryByValueIndex(originRowIndex, validColumnIndex),
                  isNull);
              break;
            default:
              break;
          }
        }
      }
      probeNext = false;
    }
@param rowIndex should be the sorted index.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java