diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/blocktree/SegmentTermsEnumFrame.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/blocktree/SegmentTermsEnumFrame.java index c1552c8ce607..d67cb041421a 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/blocktree/SegmentTermsEnumFrame.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/blocktree/SegmentTermsEnumFrame.java @@ -25,6 +25,7 @@ import org.apache.lucene.store.ByteArrayDataInput; import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.FixedBitSet; import org.apache.lucene.util.fst.FST; final class SegmentTermsEnumFrame { @@ -51,6 +52,13 @@ final class SegmentTermsEnumFrame { byte[] suffixLengthBytes; final ByteArrayDataInput suffixLengthsReader; + int[] suffixLengths; + int[] suffixLengthPositions; + int[] suffixOffsets; + int[] termBlockOrds; + int[] lastSubIndices; + FixedBitSet termExists; + long[] subCodes; byte[] statBytes = new byte[64]; int statsSingletonRunLength = 0; final ByteArrayDataInput statsReader = new ByteArrayDataInput(); @@ -199,6 +207,89 @@ void loadBlock() throws IOException { suffixLengthsReader.reset(suffixLengthBytes, 0, numSuffixLengthBytes); totalSuffixBytes = ste.in.getFilePointer() - startSuffixFP; + // Prepare suffixes, offsets to binary search. + if (allEqual) { + if (isLeafBlock) { + suffix = suffixLengthsReader.readVInt(); + } else { + // Handle subCode for non leaf block. + suffixLengthPositions = new int[entCount]; + termExists = new FixedBitSet(entCount); + subCodes = new long[entCount]; + termBlockOrds = new int[entCount]; + lastSubIndices = new int[entCount]; + int termBlockOrd = 0; + int lastSubIndex = -1; + // read first vint to set suffix, byt the way, set termExist, subCode. + code = suffixLengthsReader.readVInt(); + suffix = code >>> 1; + if ((code & 1) == 0) { + termExists.set(0); + termBlockOrd++; + } else { + // read subCode. + subCodes[0] = suffixLengthsReader.readVLong(); + lastSubIndex = 0; + } + termBlockOrds[0] = termBlockOrd; + suffixLengthPositions[0] = suffixLengthsReader.getPosition(); + lastSubIndices[0] = lastSubIndex; + for (int i = 1; i < suffixLengths.length; i++) { + code = suffixLengthsReader.readVInt(); + suffixLengths[i] = code >>> 1; + if ((code & 1) == 0) { + termExists.set(i); + termBlockOrd++; + } else { + // read subCode. + subCodes[i] = suffixLengthsReader.readVLong(); + lastSubIndex = i; + } + termBlockOrds[i] = termBlockOrd; + suffixLengthPositions[i] = suffixLengthsReader.getPosition(); + lastSubIndices[i] = lastSubIndex; + } + } + // Reset suffixLengthsReader's position. + suffixLengthsReader.setPosition(0); + } else { + suffixLengths = new int[entCount]; + suffixLengthPositions = new int[entCount]; + if (isLeafBlock) { + for (int i = 0; i < suffixLengths.length; i++) { + suffixLengths[i] = suffixLengthsReader.readVInt(); + suffixLengthPositions[i] = suffixLengthsReader.getPosition(); + } + } else { + // Handle subCode for non leaf block. + termExists = new FixedBitSet(entCount); + subCodes = new long[entCount]; + termBlockOrds = new int[entCount]; + lastSubIndices = new int[entCount]; + int termBlockOrd = 0; + int lastSubIndex = -1; + for (int i = 0; i < suffixLengths.length; i++) { + code = suffixLengthsReader.readVInt(); + suffixLengths[i] = code >>> 1; + if ((code & 1) == 0) { + termExists.set(i); + termBlockOrd++; + } else { + // read subCode. + subCodes[i] = suffixLengthsReader.readVLong(); + lastSubIndex = i; + } + termBlockOrds[i] = termBlockOrd; + suffixLengthPositions[i] = suffixLengthsReader.getPosition(); + lastSubIndices[i] = lastSubIndex; + } + } + // Reset suffixLengthsReader's position. + suffixLengthsReader.setPosition(0); + suffixOffsets = getOffsets(suffixLengths); + assert assertOffset(suffixLengths, suffixOffsets); + } + /*if (DEBUG) { if (arc == null) { System.out.println(" loadBlock (next) fp=" + fp + " entCount=" + entCount + " prefixLen=" + prefix + " isLastInFloor=" + isLastInFloor + " leaf?=" + isLeafBlock); @@ -526,15 +617,9 @@ public void scanToSubBlock(long subFP) { // NOTE: sets startBytePos/suffix as a side effect public SeekStatus scanToTerm(BytesRef target, boolean exactOnly) throws IOException { - if (isLeafBlock) { - if (allEqual) { - return binarySearchTermLeaf(target, exactOnly); - } else { - return scanToTermLeaf(target, exactOnly); - } - } else { - return scanToTermNonLeaf(target, exactOnly); - } + return isLeafBlock + ? binarySearchTermLeaf(target, exactOnly) + : binarySearchTermNonLeaf(target, exactOnly); } private int startBytePos; @@ -542,14 +627,46 @@ public SeekStatus scanToTerm(BytesRef target, boolean exactOnly) throws IOExcept private long subCode; CompressionAlgorithm compressionAlg = CompressionAlgorithm.NO_COMPRESSION; - // Target's prefix matches this block's prefix; we - // scan the entries to check if the suffix matches. - public SeekStatus scanToTermLeaf(BytesRef target, boolean exactOnly) throws IOException { + private int[] getOffsets(int[] suffixes) { + int[] offsets = new int[suffixes.length]; + offsets[0] = 0; + for (int i = 1; i < suffixes.length; i++) { + offsets[i] = offsets[i - 1] + suffixes[i - 1]; + } + return offsets; + } - // if (DEBUG) System.out.println(" scanToTermLeaf: block fp=" + fp + " prefix=" + prefix + - // " nextEnt=" + nextEnt + " (of " + entCount + ") target=" + - // ToStringUtils.bytesRefToString(target) + - // " term=" + ToStringUtils.bytesRefToString(term)); + // Used only by assert + private boolean assertOffset(int[] suffixes, int[] offsets) { + if (offsets[0] != 0) { + return false; + } + for (int i = 1; i < offsets.length; i++) { + if (suffixes[i - 1] != offsets[i] - offsets[i - 1]) { + return false; + } + } + return true; + } + + // Target's prefix matches this leaf block's prefix; + // we binary search the entries to check if the suffix matches. + public SeekStatus binarySearchTermLeaf(BytesRef target, boolean exactOnly) throws IOException { + return allEqual + ? binarySearchTermLeafAllEqual(target, exactOnly) + : binarySearchTermLeafUnEqual(target, exactOnly); + } + + // Target's prefix matches this leaf block's prefix; + // And all suffixes have the same length in this block, + // we binary search the entries to check if the suffix matches. + private SeekStatus binarySearchTermLeafAllEqual(BytesRef target, boolean exactOnly) + throws IOException { + // if (DEBUG) System.out.println(" binarySearchTermLeafAllEqual: block fp=" + fp + " prefix=" + // + + // prefix + " + // nextEnt=" + nextEnt + " (of " + entCount + ") target=" + brToString(target) + " term=" + + // brToString(term)); assert nextEnt != -1; @@ -565,26 +682,19 @@ public SeekStatus scanToTermLeaf(BytesRef target, boolean exactOnly) throws IOEx assert prefixMatches(target); - // Loop over each entry (term or sub-block) in this block: - do { - nextEnt++; - - suffix = suffixLengthsReader.readVInt(); - - // if (DEBUG) { - // BytesRef suffixBytesRef = new BytesRef(); - // suffixBytesRef.bytes = suffixBytes; - // suffixBytesRef.offset = suffixesReader.getPosition(); - // suffixBytesRef.length = suffix; - // System.out.println(" cycle: term " + (nextEnt-1) + " (of " + entCount + ") suffix=" - // + ToStringUtils.bytesRefToString(suffixBytesRef)); - // } - - startBytePos = suffixesReader.getPosition(); - suffixesReader.skipBytes(suffix); + // TODO early terminate when target length unequals suffix + prefix. + // But we need to keep the same status with scanToTermLeaf. + int start = nextEnt; + int end = entCount - 1; + // Binary search the entries (terms) in this leaf block: + int cmp = 0; + while (start <= end) { + int mid = (start + end) >>> 1; + nextEnt = mid + 1; + startBytePos = mid * suffix; - // Loop over bytes in the suffix, comparing to the target - final int cmp = + // Binary search bytes in the suffix, comparing to the target. + cmp = Arrays.compareUnsigned( suffixBytes, startBytePos, @@ -592,32 +702,21 @@ public SeekStatus scanToTermLeaf(BytesRef target, boolean exactOnly) throws IOEx target.bytes, target.offset + prefix, target.offset + target.length); - if (cmp < 0) { - // Current entry is still before the target; - // keep scanning + start = mid + 1; } else if (cmp > 0) { - // Done! Current entry is after target -- - // return NOT_FOUND: - fillTerm(); - - // if (DEBUG) System.out.println(" not found"); - return SeekStatus.NOT_FOUND; + end = mid - 1; } else { // Exact match! - - // This cannot be a sub-block because we - // would have followed the index to this - // sub-block from the start: - + suffixesReader.setPosition(startBytePos + suffix); fillTerm(); // if (DEBUG) System.out.println(" found!"); return SeekStatus.FOUND; } - } while (nextEnt < entCount); + } // It is possible (and OK) that terms index pointed us - // at this block, but, we scanned the entire block and + // at this block, but, we searched the entire block and // did not find the term to position to. This happens // when the target is after the last term in the block // (but, before the next term in the index). EG @@ -626,21 +725,36 @@ public SeekStatus scanToTermLeaf(BytesRef target, boolean exactOnly) throws IOEx // was fooz (and, eg, first term in the next block will // bee fop). // if (DEBUG) System.out.println(" block end"); - if (exactOnly) { + SeekStatus seekStatus; + if (end < entCount - 1) { + seekStatus = SeekStatus.NOT_FOUND; + // If binary search ended at the less term, and greater term exists. + // We need to advance to the greater term. + if (cmp < 0) { + startBytePos += suffix; + nextEnt++; + } + suffixesReader.setPosition(startBytePos + suffix); fillTerm(); + } else { + seekStatus = SeekStatus.END; + suffixesReader.setPosition(startBytePos + suffix); + if (exactOnly) { + fillTerm(); + } } - // TODO: not consistent that in the // not-exact case we don't next() into the next // frame here - return SeekStatus.END; + return seekStatus; } - // Target's prefix matches this block's prefix; - // And all suffixes have the same length in this block, + // Target's prefix matches this leaf block's prefix; + // But these suffixes' length are not same, // we binary search the entries to check if the suffix matches. - public SeekStatus binarySearchTermLeaf(BytesRef target, boolean exactOnly) throws IOException { - // if (DEBUG) System.out.println(" binarySearchTermLeaf: block fp=" + fp + " prefix=" + + private SeekStatus binarySearchTermLeafUnEqual(BytesRef target, boolean exactOnly) + throws IOException { + // if (DEBUG) System.out.println(" binarySearchTermLeafUnEqual: block fp=" + fp + " prefix=" // prefix + " // nextEnt=" + nextEnt + " (of " + entCount + ") target=" + brToString(target) + " term=" + // brToString(term)); @@ -658,10 +772,8 @@ public SeekStatus binarySearchTermLeaf(BytesRef target, boolean exactOnly) throw } assert prefixMatches(target); + assert assertOffset(suffixLengths, suffixOffsets); - suffix = suffixLengthsReader.readVInt(); - // TODO early terminate when target length unequals suffix + prefix. - // But we need to keep the same status with scanToTermLeaf. int start = nextEnt; int end = entCount - 1; // Binary search the entries (terms) in this leaf block: @@ -669,9 +781,10 @@ public SeekStatus binarySearchTermLeaf(BytesRef target, boolean exactOnly) throw while (start <= end) { int mid = (start + end) >>> 1; nextEnt = mid + 1; - startBytePos = mid * suffix; + startBytePos = suffixOffsets[mid]; + suffix = suffixLengths[mid]; - // Binary search bytes in the suffix, comparing to the target. + // Binary search bytes in the suffix, comparing to the target cmp = Arrays.compareUnsigned( suffixBytes, @@ -686,7 +799,7 @@ public SeekStatus binarySearchTermLeaf(BytesRef target, boolean exactOnly) throw end = mid - 1; } else { // Exact match! - suffixesReader.setPosition(startBytePos + suffix); + setSuffixesReaderUnEqual(); fillTerm(); // if (DEBUG) System.out.println(" found!"); return SeekStatus.FOUND; @@ -709,29 +822,39 @@ public SeekStatus binarySearchTermLeaf(BytesRef target, boolean exactOnly) throw // If binary search ended at the less term, and greater term exists. // We need to advance to the greater term. if (cmp < 0) { - startBytePos += suffix; nextEnt++; } - suffixesReader.setPosition(startBytePos + suffix); + setSuffixesReaderUnEqual(); fillTerm(); } else { seekStatus = SeekStatus.END; - suffixesReader.setPosition(startBytePos + suffix); + setSuffixesReaderUnEqual(); if (exactOnly) { fillTerm(); } } + // TODO: not consistent that in the // not-exact case we don't next() into the next // frame here return seekStatus; } - // Target's prefix matches this block's prefix; we - // scan the entries to check if the suffix matches. - public SeekStatus scanToTermNonLeaf(BytesRef target, boolean exactOnly) throws IOException { + // Target's prefix matches this non-leaf block's prefix; + // we binary search the entries to check if the suffix matches. + public SeekStatus binarySearchTermNonLeaf(BytesRef target, boolean exactOnly) throws IOException { + return allEqual + ? binarySearchTermNonLeafAllEqual(target, exactOnly) + : binarySearchTermNonLeafUnEqual(target, exactOnly); + } - // if (DEBUG) System.out.println(" scanToTermNonLeaf: block fp=" + fp + " prefix=" + prefix + + // Target's prefix matches this non-leaf block's prefix; + // And all suffixes have the same length in this block, + // we binary search the entries to check if the suffix matches. + private SeekStatus binarySearchTermNonLeafAllEqual(BytesRef target, boolean exactOnly) + throws IOException { + // if (DEBUG) System.out.println(" binarySearchTermNonLeafAllEqual: block fp=" + fp + " + // prefix=" + prefix + // " nextEnt=" + nextEnt + " (of " + entCount + ") target=" + // ToStringUtils.bytesRefToString(target) + // " term=" + ToStringUtils.bytesRefToString(term)); @@ -747,37 +870,21 @@ public SeekStatus scanToTermNonLeaf(BytesRef target, boolean exactOnly) throws I } assert prefixMatches(target); + assert assertOffset(suffixLengths, suffixOffsets); - // Loop over each entry (term or sub-block) in this block: - while (nextEnt < entCount) { - - nextEnt++; - - final int code = suffixLengthsReader.readVInt(); - suffix = code >>> 1; - - // if (DEBUG) { - // BytesRef suffixBytesRef = new BytesRef(); - // suffixBytesRef.bytes = suffixBytes; - // suffixBytesRef.offset = suffixesReader.getPosition(); - // suffixBytesRef.length = suffix; - // System.out.println(" cycle: " + ((code&1)==1 ? "sub-block" : "term") + " " + - // (nextEnt-1) + " (of " + entCount + ") suffix=" + - // ToStringUtils.bytesRefToString(suffixBytesRef)); - // } + int start = nextEnt; + int end = entCount - 1; + // Binary search the entries (terms) in this non leaf block: + int cmp = 0; + int mid = 0; - startBytePos = suffixesReader.getPosition(); - suffixesReader.skipBytes(suffix); - ste.termExists = (code & 1) == 0; - if (ste.termExists) { - state.termBlockOrd++; - subCode = 0; - } else { - subCode = suffixLengthsReader.readVLong(); - lastSubFP = fp - subCode; - } + while (start <= end) { + mid = (start + end) >>> 1; + nextEnt = mid + 1; + startBytePos = mid * suffix; - final int cmp = + // Binary search bytes in the suffix, comparing to the target + cmp = Arrays.compareUnsigned( suffixBytes, startBytePos, @@ -785,43 +892,136 @@ public SeekStatus scanToTermNonLeaf(BytesRef target, boolean exactOnly) throws I target.bytes, target.offset + prefix, target.offset + target.length); - if (cmp < 0) { - // Current entry is still before the target; - // keep scanning + start = mid + 1; } else if (cmp > 0) { - // Done! Current entry is after target -- - // return NOT_FOUND: + end = mid - 1; + } else { + // Exact match! + + setSuffixesReaderAllEqual(); + setSubStates(); + // This cannot be a sub-block because we + // would have followed the index to this + // sub-block from the start: + assert ste.termExists; + fillTerm(); + // if (DEBUG) System.out.println(" found!"); - // if (DEBUG) System.out.println(" maybe done exactOnly=" + exactOnly + - // " ste.termExists=" + ste.termExists); - - if (!exactOnly && !ste.termExists) { - // System.out.println(" now pushFrame"); - // TODO this - // We are on a sub-block, and caller wants - // us to position to the next term after - // the target, so we must recurse into the - // sub-frame(s): - ste.currentFrame = ste.pushFrame(null, ste.currentFrame.lastSubFP, prefix + suffix); + return SeekStatus.FOUND; + } + } + + // It is possible (and OK) that terms index pointed us + // at this block, but, we scanned the entire block and + // did not find the term to position to. This happens + // when the target is after the last term in the block + // (but, before the next term in the index). EG + // target could be foozzz, and terms index pointed us + // to the foo* block, but the last term in this block + // was fooz (and, eg, first term in the next block will + // bee fop). + // if (DEBUG) System.out.println(" block end"); + SeekStatus seekStatus; + if (end < entCount - 1) { + seekStatus = SeekStatus.NOT_FOUND; + // If binary search ended at the less term, and greater term exists. + // We need to advance to the greater term. + if (cmp < 0) { + nextEnt++; + } + setSuffixesReaderAllEqual(); + setSubStates(); + + fillTerm(); + if (!exactOnly && !termExists.get(nextEnt - 1)) { + // System.out.println(" now pushFrame"); + // TODO this + // We are on a sub-block, and caller wants + // us to position to the next term after + // the target, so we must recurse into the + // sub-frame(s): + ste.currentFrame = ste.pushFrame(null, ste.currentFrame.lastSubFP, prefix + suffix); + ste.currentFrame.loadBlock(); + while (ste.currentFrame.next()) { + ste.currentFrame = ste.pushFrame(null, ste.currentFrame.lastSubFP, ste.term.length()); ste.currentFrame.loadBlock(); - while (ste.currentFrame.next()) { - ste.currentFrame = ste.pushFrame(null, ste.currentFrame.lastSubFP, ste.term.length()); - ste.currentFrame.loadBlock(); - } } + } + } else { + seekStatus = SeekStatus.END; + setSuffixesReaderAllEqual(); + setSubStates(); + if (exactOnly) { + fillTerm(); + } + } + // TODO: not consistent that in the + // not-exact case we don't next() into the next + // frame here + return seekStatus; + } + + // Target's prefix matches this non-leaf block's prefix; + // But these suffixes' length are not same, + // we binary search the entries to check if the suffix matches. + private SeekStatus binarySearchTermNonLeafUnEqual(BytesRef target, boolean exactOnly) + throws IOException { + + // if (DEBUG) System.out.println(" binarySearchTermNonLeafUnEqual: block fp=" + fp + " + // prefix=" + prefix + + // " nextEnt=" + nextEnt + " (of " + entCount + ") target=" + + // ToStringUtils.bytesRefToString(target) + + // " term=" + ToStringUtils.bytesRefToString(term)); + + assert nextEnt != -1; - // if (DEBUG) System.out.println(" not found"); - return SeekStatus.NOT_FOUND; + if (nextEnt == entCount) { + if (exactOnly) { + fillTerm(); + ste.termExists = subCode == 0; + } + return SeekStatus.END; + } + + assert prefixMatches(target); + assert assertOffset(suffixLengths, suffixOffsets); + + int start = nextEnt; + int end = entCount - 1; + // Binary search the entries (terms) in this non leaf block: + int cmp = 0; + int mid = 0; + + while (start <= end) { + mid = (start + end) >>> 1; + nextEnt = mid + 1; + startBytePos = suffixOffsets[mid]; + suffix = suffixLengths[mid]; + + // Binary search bytes in the suffix, comparing to the target + cmp = + Arrays.compareUnsigned( + suffixBytes, + startBytePos, + startBytePos + suffix, + target.bytes, + target.offset + prefix, + target.offset + target.length); + if (cmp < 0) { + start = mid + 1; + } else if (cmp > 0) { + end = mid - 1; } else { // Exact match! - + setSuffixesReaderUnEqual(); + setSubStates(); // This cannot be a sub-block because we // would have followed the index to this // sub-block from the start: - assert ste.termExists; + fillTerm(); // if (DEBUG) System.out.println(" found!"); return SeekStatus.FOUND; @@ -838,14 +1038,87 @@ public SeekStatus scanToTermNonLeaf(BytesRef target, boolean exactOnly) throws I // was fooz (and, eg, first term in the next block will // bee fop). // if (DEBUG) System.out.println(" block end"); - if (exactOnly) { + SeekStatus seekStatus; + if (end < entCount - 1) { + seekStatus = SeekStatus.NOT_FOUND; + // If binary search ended at the less term, and greater term exists. + // We need to advance to the greater term. + if (cmp < 0) { + nextEnt++; + } + + setSuffixesReaderUnEqual(); + setSubStates(); + fillTerm(); + if (!exactOnly && !termExists.get(nextEnt - 1)) { + // System.out.println(" now pushFrame"); + // TODO this + // We are on a sub-block, and caller wants + // us to position to the next term after + // the target, so we must recurse into the + // sub-frame(s): + ste.currentFrame = ste.pushFrame(null, ste.currentFrame.lastSubFP, prefix + suffix); + ste.currentFrame.loadBlock(); + while (ste.currentFrame.next()) { + ste.currentFrame = ste.pushFrame(null, ste.currentFrame.lastSubFP, ste.term.length()); + ste.currentFrame.loadBlock(); + } + } + } else { + seekStatus = SeekStatus.END; + setSuffixesReaderUnEqual(); + setSubStates(); + if (exactOnly) { + fillTerm(); + } } - // TODO: not consistent that in the // not-exact case we don't next() into the next // frame here - return SeekStatus.END; + return seekStatus; + } + + // Set sub block's states. + private void setSubStates() { + int currentEnt = nextEnt - 1; + + // Set blockOrd, termExists. + state.termBlockOrd = termBlockOrds[currentEnt]; + ste.termExists = termExists.get(currentEnt); + + // Set subCode. + subCode = subCodes[currentEnt]; + + // Set lastSubFP. + if (lastSubIndices[currentEnt] != -1) { + lastSubFP = fp - subCodes[lastSubIndices[currentEnt]]; + } else { + lastSubFP = -1; + } + } + + // Set suffixesReader's position. + private void setSuffixesReaderAllEqual() { + int currentEnt = nextEnt - 1; + + startBytePos = currentEnt * suffix; + suffixesReader.setPosition(startBytePos + suffix); + + // Set suffixLengthsReader's position. + suffixLengthsReader.setPosition(suffixLengthPositions[currentEnt]); + } + + // Set suffixesReader's position. + private void setSuffixesReaderUnEqual() { + int currentEnt = nextEnt - 1; + + startBytePos = suffixOffsets[currentEnt]; + suffix = suffixLengths[currentEnt]; + suffixesReader.setPosition(startBytePos + suffix); + + // Set suffixLengthsReader's position. + suffixLengthsReader.setPosition(suffixLengthPositions[currentEnt]); } private void fillTerm() { diff --git a/lucene/core/src/java/org/apache/lucene/store/DataInput.java b/lucene/core/src/java/org/apache/lucene/store/DataInput.java index 427e81f2df24..9d7115340fc2 100644 --- a/lucene/core/src/java/org/apache/lucene/store/DataInput.java +++ b/lucene/core/src/java/org/apache/lucene/store/DataInput.java @@ -189,6 +189,21 @@ public void readInts(int[] dst, int offset, int length) throws IOException { } } + // TODO: read VLongs, etc. + /** + * Reads a specified number of vints into an array at the specified offset. + * + * @param dst the array to read bytes into + * @param offset the offset in the array to start storing ints + * @param length the number of vints to read + */ + public void readVInts(int[] dst, int offset, int length) throws IOException { + Objects.checkFromIndexSize(offset, length, dst.length); + for (int i = 0; i < length; ++i) { + dst[offset + i] = readVInt(); + } + } + /** * Reads a specified number of floats into an array at the specified offset. *