/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.codecs;


import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;

import org.apache.lucene.store.BufferedIndexInput;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.MathUtil;

This abstract class reads skip lists with multiple levels. See MultiLevelSkipListWriter for the information about the encoding of the multi level skip lists. Subclasses must implement the abstract method readSkipData(int, IndexInput) which defines the actual format of the skip data.
@lucene.experimental
/** * This abstract class reads skip lists with multiple levels. * * See {@link MultiLevelSkipListWriter} for the information about the encoding * of the multi level skip lists. * * Subclasses must implement the abstract method {@link #readSkipData(int, IndexInput)} * which defines the actual format of the skip data. * @lucene.experimental */
public abstract class MultiLevelSkipListReader implements Closeable {
the maximum number of skip levels possible for this index
/** the maximum number of skip levels possible for this index */
protected int maxNumberOfSkipLevels;
number of levels in this skip list
/** number of levels in this skip list */
protected int numberOfSkipLevels; // Expert: defines the number of top skip levels to buffer in memory. // Reducing this number results in less memory usage, but possibly // slower performance due to more random I/Os. // Please notice that the space each level occupies is limited by // the skipInterval. The top level can not contain more than // skipLevel entries, the second top level can not contain more // than skipLevel^2 entries and so forth. private int numberOfLevelsToBuffer = 1; private int docCount;
skipStream for each level.
/** skipStream for each level. */
private IndexInput[] skipStream;
The start pointer of each skip level.
/** The start pointer of each skip level. */
private long skipPointer[];
skipInterval of each level.
/** skipInterval of each level. */
private int skipInterval[];
Number of docs skipped per level. It's possible for some values to overflow a signed int, but this has been accounted for.
/** Number of docs skipped per level. * It's possible for some values to overflow a signed int, but this has been accounted for. */
private int[] numSkipped;
Doc id of current skip entry per level.
/** Doc id of current skip entry per level. */
protected int[] skipDoc;
Doc id of last read skip entry with docId <= target.
/** Doc id of last read skip entry with docId &lt;= target. */
private int lastDoc;
Child pointer of current skip entry per level.
/** Child pointer of current skip entry per level. */
private long[] childPointer;
childPointer of last read skip entry with docId <= target.
/** childPointer of last read skip entry with docId &lt;= * target. */
private long lastChildPointer; private boolean inputIsBuffered; private final int skipMultiplier;
Creates a MultiLevelSkipListReader.
/** Creates a {@code MultiLevelSkipListReader}. */
protected MultiLevelSkipListReader(IndexInput skipStream, int maxSkipLevels, int skipInterval, int skipMultiplier) { this.skipStream = new IndexInput[maxSkipLevels]; this.skipPointer = new long[maxSkipLevels]; this.childPointer = new long[maxSkipLevels]; this.numSkipped = new int[maxSkipLevels]; this.maxNumberOfSkipLevels = maxSkipLevels; this.skipInterval = new int[maxSkipLevels]; this.skipMultiplier = skipMultiplier; this.skipStream [0]= skipStream; this.inputIsBuffered = (skipStream instanceof BufferedIndexInput); this.skipInterval[0] = skipInterval; for (int i = 1; i < maxSkipLevels; i++) { // cache skip intervals this.skipInterval[i] = this.skipInterval[i - 1] * skipMultiplier; } skipDoc = new int[maxSkipLevels]; }
Creates a MultiLevelSkipListReader, where skipInterval and skipMultiplier are the same.
/** Creates a {@code MultiLevelSkipListReader}, where * {@code skipInterval} and {@code skipMultiplier} are * the same. */
protected MultiLevelSkipListReader(IndexInput skipStream, int maxSkipLevels, int skipInterval) { this(skipStream, maxSkipLevels, skipInterval, skipInterval); }
Returns the id of the doc to which the last call of skipTo(int) has skipped.
/** Returns the id of the doc to which the last call of {@link #skipTo(int)} * has skipped. */
public int getDoc() { return lastDoc; }
Skips entries to the first beyond the current whose document number is greater than or equal to target. Returns the current doc count.
/** Skips entries to the first beyond the current whose document number is * greater than or equal to <i>target</i>. Returns the current doc count. */
public int skipTo(int target) throws IOException { // walk up the levels until highest level is found that has a skip // for this target int level = 0; while (level < numberOfSkipLevels - 1 && target > skipDoc[level + 1]) { level++; } while (level >= 0) { if (target > skipDoc[level]) { if (!loadNextSkip(level)) { continue; } } else { // no more skips on this level, go down one level if (level > 0 && lastChildPointer > skipStream[level - 1].getFilePointer()) { seekChild(level - 1); } level--; } } return numSkipped[0] - skipInterval[0] - 1; } private boolean loadNextSkip(int level) throws IOException { // we have to skip, the target document is greater than the current // skip list entry setLastSkipData(level); numSkipped[level] += skipInterval[level]; // numSkipped may overflow a signed int, so compare as unsigned. if (Integer.compareUnsigned(numSkipped[level], docCount) > 0) { // this skip list is exhausted skipDoc[level] = Integer.MAX_VALUE; if (numberOfSkipLevels > level) numberOfSkipLevels = level; return false; } // read next skip entry skipDoc[level] += readSkipData(level, skipStream[level]); if (level != 0) { // read the child pointer if we are not on the leaf level childPointer[level] = skipStream[level].readVLong() + skipPointer[level - 1]; } return true; }
Seeks the skip entry on the given level
/** Seeks the skip entry on the given level */
protected void seekChild(int level) throws IOException { skipStream[level].seek(lastChildPointer); numSkipped[level] = numSkipped[level + 1] - skipInterval[level + 1]; skipDoc[level] = lastDoc; if (level > 0) { childPointer[level] = skipStream[level].readVLong() + skipPointer[level - 1]; } } @Override public void close() throws IOException { for (int i = 1; i < skipStream.length; i++) { if (skipStream[i] != null) { skipStream[i].close(); } } }
Initializes the reader, for reuse on a new term.
/** Initializes the reader, for reuse on a new term. */
public void init(long skipPointer, int df) throws IOException { this.skipPointer[0] = skipPointer; this.docCount = df; assert skipPointer >= 0 && skipPointer <= skipStream[0].length() : "invalid skip pointer: " + skipPointer + ", length=" + skipStream[0].length(); Arrays.fill(skipDoc, 0); Arrays.fill(numSkipped, 0); Arrays.fill(childPointer, 0); for (int i = 1; i < numberOfSkipLevels; i++) { skipStream[i] = null; } loadSkipLevels(); }
Loads the skip levels
/** Loads the skip levels */
private void loadSkipLevels() throws IOException { if (docCount <= skipInterval[0]) { numberOfSkipLevels = 1; } else { numberOfSkipLevels = 1+MathUtil.log(docCount/skipInterval[0], skipMultiplier); } if (numberOfSkipLevels > maxNumberOfSkipLevels) { numberOfSkipLevels = maxNumberOfSkipLevels; } skipStream[0].seek(skipPointer[0]); int toBuffer = numberOfLevelsToBuffer; for (int i = numberOfSkipLevels - 1; i > 0; i--) { // the length of the current level long length = skipStream[0].readVLong(); // the start pointer of the current level skipPointer[i] = skipStream[0].getFilePointer(); if (toBuffer > 0) { // buffer this level skipStream[i] = new SkipBuffer(skipStream[0], (int) length); toBuffer--; } else { // clone this stream, it is already at the start of the current level skipStream[i] = skipStream[0].clone(); if (inputIsBuffered && length < BufferedIndexInput.BUFFER_SIZE) { ((BufferedIndexInput) skipStream[i]).setBufferSize(Math.max(BufferedIndexInput.MIN_BUFFER_SIZE, (int) length)); } // move base stream beyond the current level skipStream[0].seek(skipStream[0].getFilePointer() + length); } } // use base stream for the lowest level skipPointer[0] = skipStream[0].getFilePointer(); }
Subclasses must implement the actual skip data encoding in this method.
Params:
  • level – the level skip data shall be read from
  • skipStream – the skip stream to read from
/** * Subclasses must implement the actual skip data encoding in this method. * * @param level the level skip data shall be read from * @param skipStream the skip stream to read from */
protected abstract int readSkipData(int level, IndexInput skipStream) throws IOException;
Copies the values of the last read skip entry on this level
/** Copies the values of the last read skip entry on this level */
protected void setLastSkipData(int level) { lastDoc = skipDoc[level]; lastChildPointer = childPointer[level]; }
used to buffer the top skip levels
/** used to buffer the top skip levels */
private final static class SkipBuffer extends IndexInput { private byte[] data; private long pointer; private int pos; SkipBuffer(IndexInput input, int length) throws IOException { super("SkipBuffer on " + input); data = new byte[length]; pointer = input.getFilePointer(); input.readBytes(data, 0, length); } @Override public void close() { data = null; } @Override public long getFilePointer() { return pointer + pos; } @Override public long length() { return data.length; } @Override public byte readByte() { return data[pos++]; } @Override public void readBytes(byte[] b, int offset, int len) { System.arraycopy(data, pos, b, offset, len); pos += len; } @Override public void seek(long pos) { this.pos = (int) (pos - pointer); } @Override public IndexInput slice(String sliceDescription, long offset, long length) throws IOException { throw new UnsupportedOperationException(); } } }