private UTF8Row buildRow() {
            if (curRowIgnore) {
                return null;
            }
            if (tagField != null && !acceptNoneTag && !curRowHasTagField) {
                // if accept tags exists but tag field in this event not found,
                // then this event should be ignored.
                return null;
            }
            switch (ignoreStrategy) {
                case EventIgnoreStrategy.ID_IGNORE_EMPTY:
                    if (isEmpty()) {
                        return null;
                    }
                    break;
                default:
                    // Do nothing.
                    break;
            }
            // Set default value.
            for (int index = 0; index < columnCount; index++) {
                if (curRowSetMark[index]) {
                    continue;
                }
                onColumnIndex(index);
                switch (indexTypes[index].dataType) {
                    case ColumnType.STRING:
                        onStringValue(strDefaultValues[index]);
                        break;
                    default:
                        onNumberValue(numDefaultValues[index]);
                }
            }

            // Now move values from buffer into curRow.
            int totalRowSize = (columnCount << 3) + curRowRawValueBytes;

            // Use off-heap memory to store row data.
            // We don't want those rows stored in JVM headp as they can put too much pressure on GC.
            // Besides those rows' lifecycle can be easily managed.

            // Copied from java.nio.DirectByteBuffer

            long rowDataMemoryBase;
            long rowDataAddr;

            boolean pa = VM.isDirectMemoryPageAligned();
            int ps = MemoryUtil.pageSize();
            long size = Math.max(1L, (long) totalRowSize + (pa ? ps : 0));
            rowDataMemoryBase = MemoryUtil.allocate(size);
            MemoryUtil.setMemory(rowDataMemoryBase, size, (byte) 0);

            if (pa && (rowDataMemoryBase % ps != 0)) {
                // Round up to page boundary
                rowDataAddr = rowDataMemoryBase + ps - (rowDataMemoryBase & (ps - 1));
            } else {
                rowDataAddr = rowDataMemoryBase;
            }

            if (hasDims) {
                // Put dims values and raw values together, convient for equal check.
                int dimValueSize = dimCount << 3;
                Platform.copyMemory(valuesBuffer, LONG_ARRAY_OFFSET, null, rowDataAddr, dimValueSize);

                int curRawDataOffset = dimValueSize;
                for (int dimId = 0; dimId < dimCount; dimId++) {
                    byte type = indexTypes[dimId].dataType;
                    if (type == ColumnType.STRING) {
                        long offsetAndLen = MemoryUtil.getLong(rowDataAddr + (dimId << 3));
                        int offset = (int) (offsetAndLen >>> 32);
                        int len = (int) offsetAndLen;
                        int newOffset = curRawDataOffset;
                        curRawDataOffset += len;

                        // Copy raw data into row data.
                        Platform.copyMemory(rawValuesBuffer, BYTE_ARRAY_OFFSET + offset, null, rowDataAddr + newOffset, len);
                        // The raw data is moved, we need to specify the new offset and len.
                        MemoryUtil.setLong(rowDataAddr + (dimId << 3), (((long) newOffset) << 32) | (long) len);
                    } else {
                        // Number value doesn't have raw value.
                    }
                }
                int dimDataSize = curRawDataOffset;

                // Now handle metric values.
                int metricCount = (columnCount - dimCount);
                int metricValueSize = metricCount << 3;
                Platform.copyMemory(valuesBuffer, LONG_ARRAY_OFFSET + dimValueSize, null, rowDataAddr + dimDataSize, metricValueSize);

                curRawDataOffset += metricValueSize;
                for (int metricId = 0; metricId < metricCount; metricId++) {
                    byte type = indexTypes[dimCount + metricId].dataType;
                    if (type == ColumnType.STRING) {
                        long offsetAndLen = MemoryUtil.getLong(rowDataAddr + dimDataSize + (metricId << 3));
                        int offset = (int) (offsetAndLen >>> 32);
                        int len = (int) offsetAndLen;
                        int newOffset = curRawDataOffset;
                        curRawDataOffset += len;

                        // Copy raw data into row data.
                        Platform.copyMemory(rawValuesBuffer, BYTE_ARRAY_OFFSET + offset, null, rowDataAddr + newOffset, len);
                        // The raw data is moved, we need to specify the new offset and len.
                        MemoryUtil.setLong(rowDataAddr + dimDataSize + (metricId << 3), (((long) newOffset) << 32) | (long) len);
                    } else {
                        // Number value doesn't have raw value.
                    }
                }

                long code = grouping ? 0 : nextRowId++;
                return new UTF8Row(code, this, rowDataMemoryBase, rowDataAddr, totalRowSize, dimDataSize);
            } else {
                int valueSize = columnCount << 3;
                Platform.copyMemory(valuesBuffer, LONG_ARRAY_OFFSET, null, rowDataAddr, valueSize);

                int curRawDataOffset = valueSize;
                for (int index = 0; index < columnCount; index++) {
                    byte type = indexTypes[index].dataType;
                    if (type == ColumnType.STRING) {
                        long offsetAndLen = MemoryUtil.getLong(rowDataAddr + (index << 3));
                        int offset = (int) (offsetAndLen >>> 32);
                        int len = (int) offsetAndLen;
                        int newOffset = curRawDataOffset;
                        curRawDataOffset += len;

                        // Copy raw data into row data.
                        Platform.copyMemory(rawValuesBuffer, BYTE_ARRAY_OFFSET + offset, null, rowDataAddr + newOffset, len);
                        // The raw data is moved, we need to specify the new offset and len.
                        MemoryUtil.setLong(rowDataAddr + (index << 3), (((long) newOffset) << 32) | (long) len);
                    } else {
                        // Number value doesn't have raw value.
                    }
                }

                return new UTF8Row(nextRowId++, this, rowDataMemoryBase, rowDataAddr, totalRowSize, 0);
            }
        }
A row with strings stored in UTF-8 format.

This class is <b>NOT</b> multi-thread safe.

Data structure:
If grouping:
<pre>
     |........................|.................|.....................|
        dim values               dim raw values        metric values
                                                      (metric values are all numbers)
</pre>

no grouping:
<pre>
     |........................|.................|
          values                   raw values
</pre>

The value, if number type, represents the uniform value;
if string type, higher 32 bits represents the offset of raw value, lower 32 bits represents the len.

The rows is sorted by dims if exists. And if grouping is true, those rows with the same dims will be
merged into one.

Node: An UTF8Row should call {@link #free()} to free it memory after done with, otherwise will lead to memory leak.
indexr-segment/src/main/java/io/indexr/segment/rt/UTF8Row.java