All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.magicwerk.brownies.collections.BigList Maven / Gradle / Ivy

Go to download

Brownies Collections complements the Java Collections Framework. GapList combines the strengths of both ArrayList and LinkedList. BigList is a list optimized for storing large number of elements. There are specialized List implementations for all primitive data types (IntGapList, IntBigList, IntObjGapList, IntObjBigList). The key collection classes offer support for keys and constraints for lists and collections (KeyList, KeyCollection, KeySet, Key1List, Key1Collection, Key1Set, Key2List, Key2Collection, Key2Set).

There is a newer version: 0.9.3
Show newest version
/*
 * Copyright 2014 by Thomas Mauch
 *
 * Licensed 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.
 *
 * $Id: BigList.java 2964 2015-10-18 22:43:57Z origo $
 */
package org.magicwerk.brownies.collections;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.magicwerk.brownies.collections.helper.MergeSort;

/**
 * BigList is a list optimized for storing large number of elements.
 * It stores the elements in fixed size blocks and the blocks itself are maintained in a tree for fast access.
 * It also offers specialized methods for bulk processing of elements.
 * Also copying a BigList is efficiently possible as its implemented using a copy-on-write approach.

* * Note that this implementation is not synchronized. * Due to data caching used for exploiting locality of reference, performance can decrease if BigList is * accessed by several threads at different positions.

* * Note that the iterators provided are not fail-fast.

* * @author Thomas Mauch * @version $Id: BigList.java 2964 2015-10-18 22:43:57Z origo $ */ public class BigList extends IList { /** UID for serialization */ private static final long serialVersionUID = 3715838828540564836L; /** Default block size */ private static final int DEFAULT_BLOCK_SIZE = 1000; /** If two adjacent blocks both less than MERGE_THRESHOLD*blockSize elements, they are merged */ private static final float MERGE_THRESHOLD = 0.35f; /** * If an element is added to the list at the head or tail, the block is only filled until it * has FILL_THRESHOLD*blockSize elements (so there is room for insertion without need to split). */ private static final float FILL_THRESHOLD = 0.95f; /** Set to true for debugging during developing */ private static final boolean CHECK = false; // -- EMPTY -- // Cannot make a static reference to the non-static type E: // public static BigList EMPTY = BigList.create().unmodifiableList(); // Syntax error: // public static BigList EMPTY = BigList.create().unmodifiableList(); /** Unmodifiable empty instance */ @SuppressWarnings("rawtypes") private static final BigList EMPTY = BigList.create().unmodifiableList(); /** * @return unmodifiable empty instance */ @SuppressWarnings("unchecked") public static BigList EMPTY() { return EMPTY; } /** Number of elements stored at maximum in a block */ private int blockSize; /** Number of elements stored in this BigList */ private int size; /** The root node in the tree */ private BlockNode rootNode; /** Current node */ private BlockNode currNode; /** Block of current node */ /** Start index of current block */ private int currBlockStart; /** End index of current block */ private int currBlockEnd; /** Modify value which must be applied before this block is not current any more */ private int currModify; /** * Constructor used internally, e.g. for ImmutableBigList. * * @param copy true to copy all instance values from source, * if false nothing is done * @param that list to copy */ protected BigList(boolean copy, BigList that) { if (copy) { this.blockSize = that.blockSize; this.currBlockStart = that.currBlockStart; this.currBlockEnd = that.currBlockEnd; this.currNode = that.currNode; this.rootNode = that.rootNode; this.size = that.size; } } // --- Static methods --- /** * Create new list. * * @return created list * @param type of elements stored in the list */ // This separate method is needed as the varargs variant creates the list with specific size public static BigList create() { return new BigList(); } /** * Create new list with specified elements. * * @param coll collection with element * @return created list * @param type of elements stored in the list */ public static BigList create(Collection coll) { return new BigList(coll); } /** * Create new list with specified elements. * * @param elems array with elements * @return created list * @param type of elements stored in the list */ public static BigList create(E... elems) { BigList list = new BigList(); for (E elem: elems) { list.add(elem); } return list; } /** * Default constructor. * The default block size is used. */ public BigList() { this(DEFAULT_BLOCK_SIZE); } /** * Constructor. * * @param blockSize block size */ public BigList(int blockSize) { if (blockSize < 2) { throw new IndexOutOfBoundsException("Invalid blockSize: " + blockSize); } doInit(blockSize, -1); } /** * Create new list with specified elements. * * @param coll collection with element */ @SuppressWarnings("unchecked") public BigList(Collection coll) { if (coll instanceof BigList) { doAssign((BigList) coll); doClone((BigList) coll); } else { blockSize = DEFAULT_BLOCK_SIZE; addBlock(0, new Block()); for (Object obj: coll.toArray()) { add((E) obj); } assert(size() == coll.size()); } } /** * Returns block size used for this BigList. * * @return block size used for this BigList */ public int blockSize() { return blockSize; } /** * Internal constructor. * * @param blockSize default block size * @param firstBlockSize block size of first block */ private BigList(int blockSize, int firstBlockSize) { doInit(blockSize, firstBlockSize); } /** * Initialize BigList. * * @param blockSize default block size * @param firstBlockSize block size of first block */ private void doInit(int blockSize, int firstBlockSize) { this.blockSize = blockSize; // First block will grow until it reaches blockSize Block block; if (firstBlockSize <= 1) { block = new Block(); } else { block = new Block(firstBlockSize); } addBlock(0, block); } /** * Returns a copy of this BigList instance. * The copy is realized by a copy-on-write approach so also really large lists can efficiently be copied. * This method is identical to clone() except that the result is casted to BigList. * * @return a copy of this BigList instance */ @Override public BigList copy() { return (BigList) super.copy(); } /** * Returns a shallow copy of this BigList instance * The copy is realized by a copy-on-write approach so also really large lists can efficiently be copied. * * @return a copy of this BigList instance */ // Only overridden to change Javadoc @Override public Object clone() { return super.clone(); } @Override protected void doAssign(IList that) { BigList list = (BigList) that; this.blockSize = list.blockSize; this.currBlockEnd = list.currBlockEnd; this.currBlockStart = list.currBlockStart; this.currNode = list.currNode; this.rootNode = list.rootNode; this.size = list.size; } @Override protected void doClone(IList that) { BigList bigList = (BigList) that; bigList.releaseBlock(); rootNode = copy(bigList.rootNode); currNode = null; currModify = 0; if (CHECK) check(); } /** * Create a copy of the specified node. * * @param node source node * @return newly created copy of source */ private BlockNode copy(BlockNode node) { BlockNode newNode = node.min(); int index = newNode.block.size(); BlockNode newRoot = new BlockNode(null, index, newNode.block.ref(), null, null); while (true) { newNode = newNode.next(); if (newNode == null) { return newRoot; } index += newNode.block.size(); newRoot = newRoot.insert(index, newNode.block.ref()); newRoot.parent = null; } } @Override public E getDefaultElem() { return null; } @Override protected void finalize() { // This list will be garbage collected, so unref all referenced blocks. // As it is not reachable by any live objects, if is safe to access it from // the GC thread without synchronization BlockNode node = rootNode.min(); while (node != null) { node.block.unref(); node = node.next(); } } @Override public int size() { return size; } /** * As BigList grows and shrinks automatically, the term capacity does not really make sense. * Therefore always -1 is returned. */ @Override public int capacity() { return -1; } @Override protected E doGet(int index) { int pos = getBlockIndex(index, false, 0); return currNode.block.doGet(pos); } @Override protected E doSet(int index, E elem) { int pos = getBlockIndex(index, true, 0); E oldElem = currNode.block.doGet(pos); currNode.block.doSet(pos, elem); return oldElem; } @Override protected E doReSet(int index, E elem) { int pos = getBlockIndex(index, true, 0); E oldElem = currNode.block.doGet(pos); currNode.block.doSet(pos, elem); return oldElem; } /** * Release current block and apply modification if pending. */ private void releaseBlock() { if (currModify != 0) { int modify = currModify; currModify = 0; modify(currNode, modify); } currNode = null; } /** * Returns index in block where the element with specified index is located. * This method also sets currBlock to remember this last used block. * * @param index list index (0 <= index <= size()) * @param write true if the block is needed for a write operation (set, add, remove) * @param modify modify instruction (N>0: N elements are added, N<0: N elements are removed, 0 no change) * @return relative index within block */ private int getBlockIndex(int index, boolean write, int modify) { // Determine block where specified index is located and store it in currBlock if (currNode != null) { if (index >= currBlockStart && (index < currBlockEnd || index == currBlockEnd && size == index)) { // currBlock is already set correctly if (write) { if (currNode.block.isShared()) { currNode.block.unref(); currNode.setBlock(new Block(currNode.block)); } } currModify += modify; return index - currBlockStart; } releaseBlock(); } if (index == size) { if (currNode == null || currBlockEnd != size) { currNode = rootNode.max(); currBlockEnd = size; currBlockStart = size - currNode.block.size(); } if (modify != 0) { currNode.relPos += modify; BlockNode leftNode = currNode.getLeftSubTree(); if (leftNode != null) { leftNode.relPos -= modify; } } } else if (index == 0) { if (currNode == null || currBlockStart != 0) { currNode = rootNode.min(); currBlockEnd = currNode.block.size(); currBlockStart = 0; } if (modify != 0) { rootNode.relPos += modify; } } if (currNode == null) { doGetBlock(index, modify); } assert(index >= currBlockStart && index <= currBlockEnd); if (write) { if (currNode.block.isShared()) { currNode.block.unref(); currNode.setBlock(new Block(currNode.block)); } } return index - currBlockStart; } /** * @return true if there is only the root block, false otherwise */ private boolean isOnlyRootBlock() { return rootNode.left == null && rootNode.right == null; } /** * Determine node/block for the specified index. * The fields currNode, currBlockStart, and currBlockEnd are set. * During the traversing the tree node, the nodes relative positions are changed according to the modify instruction. * * @param index list index for which block must be determined * @param modify modify instruction (N>0: N elements are added, N<0: N elements are removed, 0 no change) */ private void doGetBlock(int index, int modify) { currNode = rootNode; currBlockEnd = rootNode.relPos; if (currNode.relPos == 0) { // Empty tree if (modify != 0) { currNode.relPos += modify; } } else { // Traverse non-empty tree until right node has been found boolean wasLeft = false; while (true) { assert(index >= 0); int leftIndex = currBlockEnd-currNode.block.size(); assert(leftIndex >= 0); if (index >= leftIndex && index < currBlockEnd) { // Correct node has been found if (modify != 0) { BlockNode leftNode = currNode.getLeftSubTree(); if (currNode.relPos > 0) { currNode.relPos += modify; if (leftNode != null) { leftNode.relPos -= modify; } } else { if (leftNode != null) { leftNode.relPos -= modify; } } } break; } // Further traversal needed to find the correct node BlockNode nextNode; if (index < currBlockEnd) { // Traverse the left node nextNode = currNode.getLeftSubTree(); if (modify != 0) { if (nextNode == null || !wasLeft) { if (currNode.relPos > 0) { currNode.relPos += modify; } else { currNode.relPos -= modify; } wasLeft = true; } } if (nextNode == null) { break; } } else { // Traverse the right node nextNode = currNode.getRightSubTree(); if (modify != 0) { if (nextNode == null || wasLeft) { if (currNode.relPos > 0) { currNode.relPos += modify; BlockNode left = currNode.getLeftSubTree(); if (left != null) { left.relPos -= modify; } } else { currNode.relPos -= modify; } wasLeft = false; } } if (nextNode == null) { break; } } currBlockEnd += nextNode.relPos; currNode = nextNode; } } currBlockStart = currBlockEnd - currNode.block.size(); } /** * Adds a new element to the list. * * @param index the index to add before * @param obj the element to add */ private void addBlock(int index, Block obj) { if (rootNode == null) { rootNode = new BlockNode(null, index, obj, null, null); } else { rootNode = rootNode.insert(index, obj); rootNode.parent = null; } } @Override protected boolean doAdd(int index, E element) { if (index == -1) { index = size; } // Insert int pos = getBlockIndex(index, true, 1); // If there is still place in the current block: insert in current block int maxSize = (index == size || index == 0) ? (int)(blockSize*FILL_THRESHOLD) : blockSize; // The second part of the condition is a work around to handle the case of insertion as position 0 correctly // where blockSize() is 2 (the new block would then be added after the current one) if (currNode.block.size() < maxSize || (currNode.block.size() == 1 && currNode.block.size() < blockSize)) { currNode.block.doAdd(pos, element); currBlockEnd++; } else { // No place any more in current block Block newBlock = new Block(blockSize); if (index == size) { // Insert new block at tail newBlock.doAdd(0, element); // Subtract 1 because getBlockIndex() has already added 1 modify(currNode, -1); addBlock(size+1, newBlock); BlockNode lastNode = currNode.next(); currNode = lastNode; currBlockStart = currBlockEnd; currBlockEnd++; } else if (index == 0) { // Insert new block at head newBlock.doAdd(0, element); // Subtract 1 because getBlockIndex() has already added 1 modify(currNode, -1); addBlock(1, newBlock); BlockNode firstNode = currNode.previous(); currNode = firstNode; currBlockStart = 0; currBlockEnd = 1; } else { // Split block for insert int nextBlockLen = blockSize/2; int blockLen = blockSize - nextBlockLen; GapList.transferRemove(currNode.block, blockLen, nextBlockLen, newBlock, 0, 0); // Subtract 1 more because getBlockIndex() has already added 1 modify(currNode, -nextBlockLen-1); addBlock(currBlockEnd-nextBlockLen, newBlock); if (pos < blockLen) { // Insert element in first block currNode.block.doAdd(pos, element); currBlockEnd = currBlockStart+blockLen+1; modify(currNode, 1); } else { // Insert element in second block currNode = currNode.next(); modify(currNode, 1); currNode.block.doAdd(pos-blockLen, element); currBlockStart += blockLen; currBlockEnd++; } } } size++; if (CHECK) check(); return true; } /** * Modify relativePosition of all nodes starting from the specified node. * * @param node node whose position value must be changed * @param modify modify value (>0 for add, <0 for delete) */ private void modify(BlockNode node, int modify) { if (node == currNode) { modify += currModify; currModify = 0; } else { releaseBlock(); } if (modify == 0) { return; } if (node.relPos < 0) { // Left node BlockNode leftNode = node.getLeftSubTree(); if (leftNode != null) { leftNode.relPos -= modify; } BlockNode pp = node.parent; assert(pp.getLeftSubTree() == node); boolean parentRight = true; while (true) { BlockNode p = pp.parent; if (p == null) { break; } boolean pRight = (p.getLeftSubTree() == pp); if (parentRight != pRight) { if (pp.relPos > 0) { pp.relPos += modify; } else { pp.relPos -= modify; } } pp = p; parentRight = pRight; } if (parentRight) { rootNode.relPos += modify; } } else { // Right node node.relPos += modify; BlockNode leftNode = node.getLeftSubTree(); if (leftNode != null) { leftNode.relPos -= modify; } BlockNode parent = node.parent; if (parent != null) { assert(parent.getRightSubTree() == node); boolean parentLeft = true; while (true) { BlockNode p = parent.parent; if (p == null) { break; } boolean pLeft = (p.getRightSubTree() == parent); if (parentLeft != pLeft) { if (parent.relPos > 0) { parent.relPos += modify; } else { parent.relPos -= modify; } } parent = p; parentLeft = pLeft; } if (!parentLeft) { rootNode.relPos += modify; } } } } private BlockNode doRemove(BlockNode node) { BlockNode p = node.parent; BlockNode newNode = node.removeSelf(); BlockNode n = newNode; while (p != null) { assert(p.left == node || p.right == node); if (p.left == node) { p.left = newNode; } else { p.right = newNode; } node = p; node.recalcHeight(); newNode = node.balance(); p = newNode.parent; } rootNode = newNode; return n; } @Override protected boolean doAddAll(int index, IList list) { if (list.size() == 0) { return false; } if (index == -1) { index = size; } if (CHECK) check(); int oldSize = size; if (list.size() == 1) { return doAdd(index, list.get(0)); } int addPos = getBlockIndex(index, true, 0); Block addBlock = currNode.block; int space = blockSize - addBlock.size(); int addLen = list.size(); if (addLen <= space) { // All elements can be added to current block currNode.block.addAll(addPos, list); modify(currNode, addLen); size += addLen; currBlockEnd += addLen; } else { if (index == size) { // Add elements at end for (int i=0; i 0) { Block nextBlock = new Block(blockSize); int add = Math.min(todo, blockSize); for(int i=0; i 0) { Block nextBlock = new Block(blockSize); int add = Math.min(todo, blockSize); for(int i=0; i list2 = GapList.create(); // TODO avoid unnecessary copy list2.addAll(list); int remove = currNode.block.size()-addPos; if (remove > 0) { list2.addAll(currNode.block.getAll(addPos, remove)); currNode.block.remove(addPos, remove); modify(currNode, -remove); size -= remove; currBlockEnd -= remove; } // Calculate how many blocks we need for the elements int numElems = currNode.block.size() + list2.size(); int numBlocks = (numElems-1)/blockSize+1; assert(numBlocks > 1); int has = currNode.block.size(); int should = numElems / numBlocks; int listPos = 0; if (has < should) { // Elements must be added to first block int add = should-has; List sublist = list2.getAll(0, add); listPos += add; currNode.block.addAll(addPos, sublist); modify(currNode, add); assert(currNode.block.size() == should); numElems -= should; numBlocks--; size += add; currBlockEnd += add; } else if (has > should) { // Elements must be moved from first to second block Block nextBlock = new Block(blockSize); int move = has-should; nextBlock.addAll(currNode.block.getAll(currNode.block.size()-move, move)); currNode.block.remove(currNode.block.size()-move, move); modify(currNode, -move); assert(currNode.block.size() == should); numElems -= should; numBlocks--; currBlockEnd -= move; should = numElems / numBlocks; int add = should-move; assert(add >= 0); List sublist = list2.getAll(0, add); nextBlock.addAll(move, sublist); listPos += add; assert(nextBlock.size() == should); numElems -= should; numBlocks--; size += add; addBlock(currBlockEnd, nextBlock); currNode = currNode.next(); assert(currNode.block == nextBlock); assert(currNode.block.size() == add+move); currBlockStart = currBlockEnd; currBlockEnd += add+move; } else { // Block already has the correct size numElems -= should; numBlocks--; } if (CHECK) check(); while (numBlocks > 0) { int add = numElems / numBlocks; assert(add > 0); List sublist = list2.getAll(listPos, add); listPos += add; Block nextBlock = new Block(); nextBlock.addAll(sublist); assert(nextBlock.size() == add); numElems -= add; addBlock(currBlockEnd, nextBlock); currNode = currNode.next(); assert(currNode.block == nextBlock); assert(currNode.block.size() == add); currBlockStart = currBlockEnd; currBlockEnd += add; size += add; numBlocks--; if (CHECK) check(); } } } assert(oldSize + addLen == size); if (CHECK) check(); return true; } @Override protected void doClear() { finalize(); rootNode = null; currBlockStart = 0; currBlockEnd = 0; currModify = 0; currNode = null; size = 0; doInit(blockSize, 0); } @Override protected void doRemoveAll(int index, int len) { // Handle special cases if (len == 0) { return; } if (index == 0 && len == size) { doClear(); return; } if (len == 1) { doRemove(index); return; } // Remove range int startPos = getBlockIndex(index, true, 0); BlockNode startNode = currNode; @SuppressWarnings("unused") int endPos = getBlockIndex(index+len-1, true, 0); BlockNode endNode = currNode; if (startNode == endNode) { // Delete from single block getBlockIndex(index, true, -len); currNode.block.remove(startPos, len); if (currNode.block.isEmpty()) { BlockNode oldCurrNode = currNode; releaseBlock(); BlockNode node = doRemove(oldCurrNode); merge(node); } else { currBlockEnd -= len; merge(currNode); } size -= len; } else { // Delete from start block if (CHECK) check(); int startLen = startNode.block.size()-startPos; getBlockIndex(index, true, -startLen); startNode.block.remove(startPos, startLen); assert(startNode == currNode); if (currNode.block.isEmpty()) { releaseBlock(); doRemove(startNode); startNode = null; } len -= startLen; size -= startLen; while (len > 0) { currNode = null; getBlockIndex(index, true, 0); int s = currNode.block.size(); if (s <= len) { modify(currNode, -s); BlockNode oldCurrNode = currNode; releaseBlock(); doRemove(oldCurrNode); if (oldCurrNode == endNode) { endNode = null; } len -= s; size -= s; if (CHECK) check(); } else { modify(currNode, -len); currNode.block.remove(0, len); size -= len; break; } } releaseBlock(); if (CHECK) check(); getBlockIndex(index, false, 0); merge(currNode); } if (CHECK) check(); } /** * Merge the specified node with the left or right neighbor if possible. * * @param node candidate node for merge */ private void merge(BlockNode node) { if (node == null) { return; } final int minBlockSize = Math.max((int)(blockSize*MERGE_THRESHOLD), 1); if (node.block.size() >= minBlockSize) { return; } BlockNode oldCurrNode = node; BlockNode leftNode = node.previous(); if (leftNode != null && leftNode.block.size() < minBlockSize) { // Merge with left block int len = node.block.size(); int dstSize = leftNode.getBlock().size(); for (int i=0; i rightNode = node.next(); if (rightNode != null && rightNode.block.size() < minBlockSize) { // Merge with right block int len = node.block.size(); for (int i=0; i oldCurrNode = currNode; releaseBlock(); doRemove(oldCurrNode); } } else if (index != 0 && index != size-1) { // Do not merge if remove happens at head or tail. // Reason: if removing continues, we can remove the whole block without merging merge(currNode); } } size--; if (CHECK) check(); return oldElem; } @Override public BigList unmodifiableList() { // Naming as in java.util.Collections#unmodifiableList return new ImmutableBigList(this); } @Override protected void doEnsureCapacity(int minCapacity) { if (isOnlyRootBlock()) { if (minCapacity > blockSize) { minCapacity = blockSize; } rootNode.block.doEnsureCapacity(minCapacity); } } /** * Pack as many elements in the blocks as allowed. * An application can use this operation to minimize the storage of an instance. */ @Override public void trimToSize() { doModify(); if (isOnlyRootBlock()) { rootNode.block.trimToSize(); } else { BigList newList = new BigList(blockSize); BlockNode node = rootNode.min(); while (node != null) { newList.addAll(node.block); remove(0, node.block.size()); node = node.next(); } doAssign(newList); } } @Override protected IList doCreate(int capacity) { if (capacity <= blockSize) { return new BigList(this.blockSize); } else { return new BigList(this.blockSize, capacity); } } @Override public void sort(int index, int len, Comparator comparator) { checkRange(index, len); if (isOnlyRootBlock()) { rootNode.block.sort(index, len, comparator); } else { MergeSort.sort(this, comparator, index, index+len); } } @SuppressWarnings("unchecked") @Override public int binarySearch(int index, int len, K key, Comparator comparator) { checkRange(index, len); if (isOnlyRootBlock()) { return rootNode.block.binarySearch(key, comparator); } else { return Collections.binarySearch((List) this, key, comparator); } } // --- Serialization --- /** * Serialize a BigList object. * * @serialData block size (int), number of elements (int), followed by all of its elements * (each an Object) in the proper order * @param oos output stream for serialization * @throws IOException if serialization fails */ private void writeObject(ObjectOutputStream oos) throws IOException { oos.writeInt(blockSize); int size = size(); oos.writeInt(size); for (int i=0; i node) { assert((node.block.size() > 0 || node == rootNode) && node.block.size() <= blockSize); BlockNode child = node.getLeftSubTree(); assert(child == null || child.parent == node); child = node.getRightSubTree(); assert(child == null || child.parent == node); } private void checkHeight(BlockNode node) { BlockNode left = node.getLeftSubTree(); BlockNode right = node.getRightSubTree(); if (left == null) { if (right == null) { assert(node.height == 0); } else { assert(right.height == node.height-1); checkHeight(right); } } else { if (right == null) { assert(left.height == node.height-1); } else { assert(left.height == node.height-1 || left.height == node.height-2); assert(right.height == node.height-1 || right.height == node.height-2); assert(right.height == node.height-1 || left.height == node.height-1); } checkHeight(left); } } private void check() { if (currNode != null) { assert(currBlockStart >= 0 && currBlockEnd <= size && currBlockStart <= currBlockEnd); assert(currBlockStart + currNode.block.size() == currBlockEnd); } if (rootNode == null) { assert(size == 0); return; } checkHeight(rootNode); BlockNode oldCurrNode = currNode; int oldCurrModify = currModify; if (currModify != 0) { currNode = null; currModify = 0; modify(oldCurrNode, oldCurrModify); } BlockNode node = rootNode; checkNode(node); int index = node.relPos; while (node.left != null) { node = node.left; checkNode(node); assert(node.relPos < 0); index += node.relPos; } Block block = node.getBlock(); assert(block.size() == index); int lastIndex = index; while (lastIndex < size()) { node = rootNode; index = node.relPos; int searchIndex = lastIndex+1; while (true) { checkNode(node); block = node.getBlock(); assert(block.size() > 0); if (searchIndex > index-block.size() && searchIndex <= index) { break; } else if (searchIndex < index) { if (node.left != null && node.left.height extends GapList { private AtomicInteger refCount = new AtomicInteger(1); public Block() { } public Block(int capacity) { super(capacity); } public Block(Block that) { super(that.capacity()); addAll(that); } /** * @return true if block is shared by several BigList instances */ public boolean isShared() { return refCount.get() > 1; } /** * Increment reference count as block is used by one BigList instance more. */ public Block ref() { refCount.incrementAndGet(); return this; } /** * Decrement reference count as block is no longer used by one BigList instance. */ public void unref() { refCount.decrementAndGet(); } } // --- BlockNode --- /** * Implements an AVLNode storing a Block. * The nodes don't know the index of the object they are holding. They do know however their * position relative to their parent node. This allows to calculate the index of a node while traversing the tree. * There is a faedelung flag for both the left and right child * to indicate if they are a child (false) or a link as in linked list (true). */ static class BlockNode { /** Pointer to parent node (null for root) */ BlockNode parent; /** The left child node or the predecessor if {@link #leftIsPrevious}.*/ BlockNode left; /** Flag indicating that left reference is not a subtree but the predecessor. */ boolean leftIsPrevious; /** The right child node or the successor if {@link #rightIsNext}. */ BlockNode right; /** Flag indicating that right reference is not a subtree but the successor. */ boolean rightIsNext; /** How many levels of left/right are below this one. */ int height; /** Relative position of node relative to its parent, root holds absolute position. */ int relPos; /** The stored block */ Block block; /** * Constructs a new node. * * @param parent parent node (null for root) * @param relativePosition the relative position of the node (absolute position for root) * @param block the block to store * @param rightFollower the node following this one * @param leftFollower the node leading this one */ private BlockNode(BlockNode parent, int relPos, Block block, BlockNode rightFollower, BlockNode leftFollower) { this.parent = parent; this.relPos = relPos; this.block = block; rightIsNext = true; leftIsPrevious = true; right = rightFollower; left = leftFollower; } /** * Gets the block stored by this node. * * @return block stored by this node */ private Block getBlock() { return block; } /** * Sets block to store by this node. * * @param block the block to store */ private void setBlock(Block block) { this.block = block; } /** * Gets the next node in the list after this one. * * @return the next node */ private BlockNode next() { if (rightIsNext || right == null) { return right; } return right.min(); } /** * Gets the node in the list before this one. * * @return the previous node */ private BlockNode previous() { if (leftIsPrevious || left == null) { return left; } return left.max(); } /** * Inserts new node holding specified block at the position index. * * @param index index of the position relative to the position of the parent node * @param obj object to store in the position * @return this node or node replacing this node in the tree (if tree must be rebalanced) */ private BlockNode insert(int index, Block obj) { assert(relPos != 0); int relIndex = index - relPos; if (relIndex < 0) { return insertOnLeft(relIndex, obj); } else { return insertOnRight(relIndex, obj); } } /** * Inserts new node holding specified block on the node's left side. * * @param index index of the position relative to the position of the parent node * @param obj object to store in the position * @return this node or node replacing this node in the tree (if tree must be rebalanced) */ private BlockNode insertOnLeft(int relIndex, Block obj) { if (getLeftSubTree() == null) { int pos; if (relPos >= 0) { pos = -relPos; } else { pos = -block.size(); } setLeft(new BlockNode(this, pos, obj, this, left), null); } else { setLeft(left.insert(relIndex, obj), null); } if (relPos >= 0) { relPos += obj.size(); } BlockNode ret = balance(); recalcHeight(); return ret; } /** * Inserts new node holding specified block on the node's right side. * * @param index index of the position relative to the position of the parent node * @param obj object to store in the position * @return this node or node replacing this node in the tree (if tree must be rebalanced) */ private BlockNode insertOnRight(int relIndex, Block obj) { if (getRightSubTree() == null) { setRight(new BlockNode(this, obj.size(), obj, right, this), null); } else { setRight(right.insert(relIndex, obj), null); } if (relPos < 0) { relPos -= obj.size(); } BlockNode ret = balance(); recalcHeight(); return ret; } /** * Gets the left node, returning null if its a faedelung. */ private BlockNode getLeftSubTree() { return leftIsPrevious ? null : left; } /** * Gets the right node, returning null if its a faedelung. */ private BlockNode getRightSubTree() { return rightIsNext ? null : right; } /** * Gets the rightmost child of this node. * * @return the rightmost child (greatest index) */ private BlockNode max() { return getRightSubTree() == null ? this : right.max(); } /** * Gets the leftmost child of this node. * * @return the leftmost child (smallest index) */ private BlockNode min() { return getLeftSubTree() == null ? this : left.min(); } private BlockNode removeMax() { if (getRightSubTree() == null) { return removeSelf(); } setRight(right.removeMax(), right.right); recalcHeight(); return balance(); } private BlockNode removeMin(int size) { if (getLeftSubTree() == null) { return removeSelf(); } setLeft(left.removeMin(size), left.left); if (relPos > 0) { relPos -= size; } recalcHeight(); return balance(); } /** * Removes this node from the tree. * * @return the node that replaces this one in the parent (can be null) */ private BlockNode removeSelf() { BlockNode p = parent; BlockNode n = doRemoveSelf(); if (n != null) { assert(p != n); n.parent = p; } return n; } private BlockNode doRemoveSelf() { if (getRightSubTree() == null && getLeftSubTree() == null) { return null; } if (getRightSubTree() == null) { if (relPos > 0) { left.relPos += relPos + (relPos > 0 ? 0 : 1); } else { left.relPos += relPos; } left.max().setRight(null, right); return left; } if (getLeftSubTree() == null) { if (relPos < 0) { right.relPos += relPos - (relPos < 0 ? 0 : 1); } right.min().setLeft(null, left); return right; } if (heightRightMinusLeft() > 0) { // more on the right, so delete from the right final BlockNode rightMin = right.min(); block = rightMin.block; int bs = block.size(); if (leftIsPrevious) { left = rightMin.left; } right = right.removeMin(bs); relPos += bs; left.relPos -= bs; } else { // more on the left or equal, so delete from the left final BlockNode leftMax = left.max(); block = leftMax.block; if (rightIsNext) { right = leftMax.right; } final BlockNode leftPrevious = left.left; left = left.removeMax(); if (left == null) { // special case where left that was deleted was a double link // only occurs when height difference is equal left = leftPrevious; leftIsPrevious = true; } else { if (left.relPos == 0) { left.relPos = -1; } } } recalcHeight(); return this; } /** * Balances according to the AVL algorithm. */ private BlockNode balance() { switch (heightRightMinusLeft()) { case 1 : case 0 : case -1 : return this; case -2 : if (left.heightRightMinusLeft() > 0) { setLeft(left.rotateLeft(), null); } return rotateRight(); case 2 : if (right.heightRightMinusLeft() < 0) { setRight(right.rotateRight(), null); } return rotateLeft(); default : throw new RuntimeException("tree inconsistent!"); } } /** * Gets the relative position. */ private int getOffset(BlockNode node) { if (node == null) { return 0; } return node.relPos; } /** * Sets the relative position. */ private int setOffset(BlockNode node, int newOffest) { if (node == null) { return 0; } final int oldOffset = getOffset(node); node.relPos = newOffest; return oldOffset; } /** * Sets the height by calculation. */ private void recalcHeight() { height = Math.max( getLeftSubTree() == null ? -1 : getLeftSubTree().height, getRightSubTree() == null ? -1 : getRightSubTree().height) + 1; } /** * Returns the height of the node or -1 if the node is null. */ private int getHeight(final BlockNode node) { return node == null ? -1 : node.height; } /** * Returns the height difference right - left */ private int heightRightMinusLeft() { return getHeight(getRightSubTree()) - getHeight(getLeftSubTree()); } /** * Rotate tree to the left using this node as center. * * @return node which will take the place of this node */ private BlockNode rotateLeft() { assert(!rightIsNext); final BlockNode newTop = right; // can't be faedelung! final BlockNode movedNode = getRightSubTree().getLeftSubTree(); final int newTopPosition = relPos + getOffset(newTop); final int myNewPosition = -newTop.relPos; final int movedPosition = getOffset(newTop) + getOffset(movedNode); BlockNode p = this.parent; setRight(movedNode, newTop); newTop.setLeft(this, null); newTop.parent = p; this.parent = newTop; setOffset(newTop, newTopPosition); setOffset(this, myNewPosition); setOffset(movedNode, movedPosition); assert(newTop.getLeftSubTree() == null || newTop.getLeftSubTree().relPos < 0); assert(newTop.getRightSubTree() == null || newTop.getRightSubTree().relPos > 0); return newTop; } /** * Rotate tree to the right using this node as center. * * @return node which will take the place of this node */ private BlockNode rotateRight() { assert(!leftIsPrevious); final BlockNode newTop = left; // can't be faedelung final BlockNode movedNode = getLeftSubTree().getRightSubTree(); final int newTopPosition = relPos + getOffset(newTop); final int myNewPosition = -newTop.relPos; final int movedPosition = getOffset(newTop) + getOffset(movedNode); BlockNode p = this.parent; setLeft(movedNode, newTop); newTop.setRight(this, null); newTop.parent = p; this.parent = newTop; setOffset(newTop, newTopPosition); setOffset(this, myNewPosition); setOffset(movedNode, movedPosition); assert(newTop.getLeftSubTree() == null || newTop.getLeftSubTree().relPos < 0); assert(newTop.getRightSubTree() == null || newTop.getRightSubTree().relPos > 0); return newTop; } /** * Sets the left field to the node, or the previous node if that is null * * @param node the new left subtree node * @param previous the previous node in the linked list */ private void setLeft(BlockNode node, BlockNode previous) { assert(node != this && previous != this); leftIsPrevious = node == null; if (leftIsPrevious) { left = previous; } else { left = node; left.parent = this; } recalcHeight(); } /** * Sets the right field to the node, or the next node if that is null * * @param node the new right subtree node * @param next the next node in the linked list */ private void setRight(BlockNode node, BlockNode next) { assert(node != this && next != this); rightIsNext = node == null; if (rightIsNext) { right = next; } else { right = node; right.parent = this; } recalcHeight(); } /** * Used for debugging. */ @Override public String toString() { return new StringBuilder() .append("BlockNode(") .append(relPos) .append(',') .append(getRightSubTree() != null) .append(',') .append(block) .append(',') .append(getRightSubTree() != null) .append(", height ") .append(height) .append(" )") .toString(); } } // --- ImmutableBigList --- /** * An immutable version of a BigList. * Note that the client cannot change the list, * but the content may change if the underlying list is changed. */ protected static class ImmutableBigList extends BigList { /** UID for serialization */ private static final long serialVersionUID = -1352274047348922584L; /** * Private constructor used internally. * * @param that list to create an immutable view of */ protected ImmutableBigList(BigList that) { super(true, that); } @Override protected boolean doAdd(int index, E elem) { error(); return false; } @Override protected E doSet(int index, E elem) { error(); return null; } @Override protected E doReSet(int index, E elem) { error(); return null; } @Override protected E doRemove(int index) { error(); return null; } @Override protected void doRemoveAll(int index, int len) { error(); } @Override protected void doClear() { error(); } @Override protected void doModify() { error(); } /** * Throw exception if an attempt is made to change an immutable list. */ private void error() { throw new UnsupportedOperationException("list is immutable"); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy