/*
 * 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.util;


import java.util.Arrays;

A pool for int blocks similar to ByteBlockPool
@lucene.internal
/** * A pool for int blocks similar to {@link ByteBlockPool} * @lucene.internal */
public final class IntBlockPool { public final static int INT_BLOCK_SHIFT = 13; public final static int INT_BLOCK_SIZE = 1 << INT_BLOCK_SHIFT; public final static int INT_BLOCK_MASK = INT_BLOCK_SIZE - 1;
Abstract class for allocating and freeing int blocks.
/** Abstract class for allocating and freeing int * blocks. */
public abstract static class Allocator { protected final int blockSize; public Allocator(int blockSize) { this.blockSize = blockSize; } public abstract void recycleIntBlocks(int[][] blocks, int start, int end); public int[] getIntBlock() { return new int[blockSize]; } }
A simple Allocator that never recycles.
/** A simple {@link Allocator} that never recycles. */
public static final class DirectAllocator extends Allocator {
Creates a new DirectAllocator with a default block size
/** * Creates a new {@link DirectAllocator} with a default block size */
public DirectAllocator() { super(INT_BLOCK_SIZE); } @Override public void recycleIntBlocks(int[][] blocks, int start, int end) { } }
array of buffers currently used in the pool. Buffers are allocated if needed don't modify this outside of this class
/** array of buffers currently used in the pool. Buffers are allocated if needed don't modify this outside of this class */
public int[][] buffers = new int[10][];
index into the buffers array pointing to the current buffer used as the head
/** index into the buffers array pointing to the current buffer used as the head */
private int bufferUpto = -1;
Pointer to the current position in head buffer
/** Pointer to the current position in head buffer */
public int intUpto = INT_BLOCK_SIZE;
Current head buffer
/** Current head buffer */
public int[] buffer;
Current head offset
/** Current head offset */
public int intOffset = -INT_BLOCK_SIZE; private final Allocator allocator;
Creates a new IntBlockPool with a default Allocator.
See Also:
/** * Creates a new {@link IntBlockPool} with a default {@link Allocator}. * @see IntBlockPool#nextBuffer() */
public IntBlockPool() { this(new DirectAllocator()); }
Creates a new IntBlockPool with the given Allocator.
See Also:
/** * Creates a new {@link IntBlockPool} with the given {@link Allocator}. * @see IntBlockPool#nextBuffer() */
public IntBlockPool(Allocator allocator) { this.allocator = allocator; }
Resets the pool to its initial state reusing the first buffer. Calling nextBuffer() is not needed after reset.
/** * Resets the pool to its initial state reusing the first buffer. Calling * {@link IntBlockPool#nextBuffer()} is not needed after reset. */
public void reset() { this.reset(true, true); }
Expert: Resets the pool to its initial state reusing the first buffer.
Params:
  • zeroFillBuffers – if true the buffers are filled with 0. This should be set to true if this pool is used with SliceWriter.
  • reuseFirst – if true the first buffer will be reused and calling nextBuffer() is not needed after reset iff the block pool was used before ie. nextBuffer() was called before.
/** * Expert: Resets the pool to its initial state reusing the first buffer. * @param zeroFillBuffers if <code>true</code> the buffers are filled with <tt>0</tt>. * This should be set to <code>true</code> if this pool is used with * {@link SliceWriter}. * @param reuseFirst if <code>true</code> the first buffer will be reused and calling * {@link IntBlockPool#nextBuffer()} is not needed after reset iff the * block pool was used before ie. {@link IntBlockPool#nextBuffer()} was called before. */
public void reset(boolean zeroFillBuffers, boolean reuseFirst) { if (bufferUpto != -1) { // We allocated at least one buffer if (zeroFillBuffers) { for(int i=0;i<bufferUpto;i++) { // Fully zero fill buffers that we fully used Arrays.fill(buffers[i], 0); } // Partial zero fill the final buffer Arrays.fill(buffers[bufferUpto], 0, intUpto, 0); } if (bufferUpto > 0 || !reuseFirst) { final int offset = reuseFirst ? 1 : 0; // Recycle all but the first buffer allocator.recycleIntBlocks(buffers, offset, 1+bufferUpto); Arrays.fill(buffers, offset, bufferUpto+1, null); } if (reuseFirst) { // Re-use the first buffer bufferUpto = 0; intUpto = 0; intOffset = 0; buffer = buffers[0]; } else { bufferUpto = -1; intUpto = INT_BLOCK_SIZE; intOffset = -INT_BLOCK_SIZE; buffer = null; } } }
Advances the pool to its next buffer. This method should be called once after the constructor to initialize the pool. In contrast to the constructor a reset() call will advance the pool to its first buffer immediately.
/** * Advances the pool to its next buffer. This method should be called once * after the constructor to initialize the pool. In contrast to the * constructor a {@link IntBlockPool#reset()} call will advance the pool to * its first buffer immediately. */
public void nextBuffer() { if (1+bufferUpto == buffers.length) { int[][] newBuffers = new int[(int) (buffers.length*1.5)][]; System.arraycopy(buffers, 0, newBuffers, 0, buffers.length); buffers = newBuffers; } buffer = buffers[1+bufferUpto] = allocator.getIntBlock(); bufferUpto++; intUpto = 0; intOffset += INT_BLOCK_SIZE; }
Creates a new int slice with the given starting size and returns the slices offset in the pool.
See Also:
  • SliceReader
/** * Creates a new int slice with the given starting size and returns the slices offset in the pool. * @see SliceReader */
private int newSlice(final int size) { if (intUpto > INT_BLOCK_SIZE-size) { nextBuffer(); assert assertSliceBuffer(buffer); } final int upto = intUpto; intUpto += size; buffer[intUpto-1] = 1; return upto; } private static final boolean assertSliceBuffer(int[] buffer) { int count = 0; for (int i = 0; i < buffer.length; i++) { count += buffer[i]; // for slices the buffer must only have 0 values } return count == 0; } // no need to make this public unless we support different sizes // TODO make the levels and the sizes configurable
An array holding the offset into the LEVEL_SIZE_ARRAY to quickly navigate to the next slice level.
/** * An array holding the offset into the {@link IntBlockPool#LEVEL_SIZE_ARRAY} * to quickly navigate to the next slice level. */
private final static int[] NEXT_LEVEL_ARRAY = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9};
An array holding the level sizes for int slices.
/** * An array holding the level sizes for int slices. */
private final static int[] LEVEL_SIZE_ARRAY = {2, 4, 8, 16, 32, 64, 128, 256, 512, 1024};
The first level size for new slices
/** * The first level size for new slices */
private final static int FIRST_LEVEL_SIZE = LEVEL_SIZE_ARRAY[0];
Allocates a new slice from the given offset
/** * Allocates a new slice from the given offset */
private int allocSlice(final int[] slice, final int sliceOffset) { final int level = slice[sliceOffset]; final int newLevel = NEXT_LEVEL_ARRAY[level-1]; final int newSize = LEVEL_SIZE_ARRAY[newLevel]; // Maybe allocate another block if (intUpto > INT_BLOCK_SIZE-newSize) { nextBuffer(); assert assertSliceBuffer(buffer); } final int newUpto = intUpto; final int offset = newUpto + intOffset; intUpto += newSize; // Write forwarding address at end of last slice: slice[sliceOffset] = offset; // Write new level: buffer[intUpto-1] = newLevel; return newUpto; }
A SliceWriter that allows to write multiple integer slices into a given IntBlockPool. @see SliceReader @lucene.internal
/** * A {@link SliceWriter} that allows to write multiple integer slices into a given {@link IntBlockPool}. * * @see SliceReader * @lucene.internal */
public static class SliceWriter { private int offset; private final IntBlockPool pool; public SliceWriter(IntBlockPool pool) { this.pool = pool; } /** * */ public void reset(int sliceOffset) { this.offset = sliceOffset; }
Writes the given value into the slice and resizes the slice if needed
/** * Writes the given value into the slice and resizes the slice if needed */
public void writeInt(int value) { int[] ints = pool.buffers[offset >> INT_BLOCK_SHIFT]; assert ints != null; int relativeOffset = offset & INT_BLOCK_MASK; if (ints[relativeOffset] != 0) { // End of slice; allocate a new one relativeOffset = pool.allocSlice(ints, relativeOffset); ints = pool.buffer; offset = relativeOffset + pool.intOffset; } ints[relativeOffset] = value; offset++; }
starts a new slice and returns the start offset. The returned value should be used as the start offset to initialize a SliceReader.
/** * starts a new slice and returns the start offset. The returned value * should be used as the start offset to initialize a {@link SliceReader}. */
public int startNewSlice() { return offset = pool.newSlice(FIRST_LEVEL_SIZE) + pool.intOffset; }
Returns the offset of the currently written slice. The returned value should be used as the end offset to initialize a SliceReader once this slice is fully written or to reset the this writer if another slice needs to be written.
/** * Returns the offset of the currently written slice. The returned value * should be used as the end offset to initialize a {@link SliceReader} once * this slice is fully written or to reset the this writer if another slice * needs to be written. */
public int getCurrentOffset() { return offset; } }
A SliceReader that can read int slices written by a SliceWriter
@lucene.internal
/** * A {@link SliceReader} that can read int slices written by a {@link SliceWriter} * @lucene.internal */
public static final class SliceReader { private final IntBlockPool pool; private int upto; private int bufferUpto; private int bufferOffset; private int[] buffer; private int limit; private int level; private int end;
Creates a new SliceReader on the given pool
/** * Creates a new {@link SliceReader} on the given pool */
public SliceReader(IntBlockPool pool) { this.pool = pool; }
Resets the reader to a slice give the slices absolute start and end offset in the pool
/** * Resets the reader to a slice give the slices absolute start and end offset in the pool */
public void reset(int startOffset, int endOffset) { bufferUpto = startOffset / INT_BLOCK_SIZE; bufferOffset = bufferUpto * INT_BLOCK_SIZE; this.end = endOffset; upto = startOffset; level = 1; buffer = pool.buffers[bufferUpto]; upto = startOffset & INT_BLOCK_MASK; final int firstSize = IntBlockPool.LEVEL_SIZE_ARRAY[0]; if (startOffset+firstSize >= endOffset) { // There is only this one slice to read limit = endOffset & INT_BLOCK_MASK; } else { limit = upto+firstSize-1; } }
Returns true iff the current slice is fully read. If this method returns true readInt() should not be called again on this slice.
/** * Returns <code>true</code> iff the current slice is fully read. If this * method returns <code>true</code> {@link SliceReader#readInt()} should not * be called again on this slice. */
public boolean endOfSlice() { assert upto + bufferOffset <= end; return upto + bufferOffset == end; }
Reads the next int from the current slice and returns it.
See Also:
  • endOfSlice.endOfSlice()
/** * Reads the next int from the current slice and returns it. * @see SliceReader#endOfSlice() */
public int readInt() { assert !endOfSlice(); assert upto <= limit; if (upto == limit) nextSlice(); return buffer[upto++]; } private void nextSlice() { // Skip to our next slice final int nextIndex = buffer[limit]; level = NEXT_LEVEL_ARRAY[level-1]; final int newSize = LEVEL_SIZE_ARRAY[level]; bufferUpto = nextIndex / INT_BLOCK_SIZE; bufferOffset = bufferUpto * INT_BLOCK_SIZE; buffer = pool.buffers[bufferUpto]; upto = nextIndex & INT_BLOCK_MASK; if (nextIndex + newSize >= end) { // We are advancing to the final slice assert end - nextIndex > 0; limit = end - bufferOffset; } else { // This is not the final slice (subtract 4 for the // forwarding address at the end of this new slice) limit = upto+newSize-1; } } } }