jodd.util.collection.JoddArrayList Maven / Gradle / Ivy
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.util.collection;
import java.lang.reflect.Array;
import java.util.AbstractList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
/**
* Faster drop-in replacement for ArrayList
and LinkedList
.
* ArrayList
performs slow when elements are added in the middle of the list.
* Adding element to the first position if slow as the whole buffer has to be moved.
* LinkedList
is slow for random access and always takes more memory then
* ArrayList
.
*
* This implementation gives better performances as it can grow both sides: left and right.
* Its not circular, like some other list implementations out there, so the implementation
* can remain rather simple. Moreover, if that make more sense, list will not grow,
* but data will be moved internally to fill up the gaps.
*
* This implementation can be fine-tuned. There are the following parameters:
*
* pivotType
- pivot splits the list on the left and right side. There
* are 3 types of pivots, depending how do you expect the list will be populated
*
* minimalGrowSize
- font grow for less then this number.
* maxFreeSpaceBeforeNormalize
- if list has at least this much of space,
* don't grow it, but move the buffer data.
*
*
*/
public class JoddArrayList extends AbstractList implements RandomAccess, Cloneable {
/**
* Defines pivot point types.
*/
public enum PIVOT_TYPE {
FIRST_QUARTER {
@Override
public int calculate(int value) {
return value >> 2;
}
},
HALF {
@Override
public int calculate(int value) {
return value >> 1;
}
},
LAST_QUARTER {
@Override
public int calculate(int value) {
return value - (value >> 2);
}
};
public abstract int calculate(int value);
}
private static final int DEFAULT_CAPACITY = 16; // default capacity
private static final Object[] EMPTY_BUFFER = {}; // shared empty array instance used for empty instances
protected Object[] buffer; // buffer of elements
protected int size; // size of the list (number of elements it contains)
protected int start; // start index of the first element (inclusive)
protected int end; // end index of the last element (exclusive)
protected int pivotIndex; // pivot index and initial capacity of the buffer (i.e. buffer.length when buffer is initialized)
protected PIVOT_TYPE pivotType = PIVOT_TYPE.FIRST_QUARTER;
protected int minimalGrowSize = 10; // don't do small grows
protected int maxFreeSpaceBeforeNormalize = 32; // max number of free space before we normalize on grow demand
// ---------------------------------------------------------------- ctor
/**
* Constructs an empty list with the specified initial capacity.
*/
public JoddArrayList(int initialCapacity) {
init(initialCapacity);
}
/**
* Constructs fine-tuned list.
*/
public JoddArrayList(int initialCapacity, PIVOT_TYPE pivot_type, int minimalGrowSize, int maxFreeSpaceBeforeNormalize) {
init(initialCapacity);
this.pivotType = pivot_type;
this.minimalGrowSize = minimalGrowSize;
this.maxFreeSpaceBeforeNormalize = maxFreeSpaceBeforeNormalize;
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public JoddArrayList() {
init(DEFAULT_CAPACITY);
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*/
public JoddArrayList(Collection extends E> collection) {
buffer = collection.toArray();
size = buffer.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (buffer.getClass() != Object[].class) {
Object[] copy = new Object[size];
System.arraycopy(buffer, 0, copy, 0, size);
buffer = copy;
}
start = 0;
end = size;
pivotIndex = pivotType.calculate(size);
}
/**
* Constructs a list containing the elements of provided array.
*/
public JoddArrayList(E... array) {
buffer = array.clone();
size = buffer.length;
start = 0;
end = size;
pivotIndex = pivotType.calculate(size);
}
/**
* Initializes the list.
*/
protected void init(int capacity) {
this.pivotIndex = capacity;
this.buffer = EMPTY_BUFFER;
this.size = 0;
this.start = 0;
this.end = 0;
}
// ---------------------------------------------------------------- size
/**
* Trims the capacity of this ArrayList
instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an ArrayList
instance.
*/
public void trimToSize() {
modCount++;
if (size < buffer.length) {
Object[] newBuffer = new Object[size];
System.arraycopy(buffer, start, newBuffer, 0, size);
buffer = newBuffer;
start = 0;
size = buffer.length;
end = size;
pivotIndex = pivotType.calculate(size);
}
}
/**
* Normalizes list by moving the content into ideal position
* balanced over pivot. It is called to prevent growth
* of the buffer when there is enough empty space.
*/
protected void normalize() {
int newPivotIndex = pivotType.calculate(buffer.length);
int newStart = newPivotIndex - pivotType.calculate(size);
int newEnd = newStart + size;
System.arraycopy(buffer, start, buffer, newStart, size);
if (newStart > start) {
for (int i = start; i < newStart; i++) {
buffer[i] = null;
}
} else {
for (int i = Math.max(start, newEnd); i < end; i++) {
buffer[i] = null;
}
}
start = newStart;
end = newEnd;
pivotIndex = newPivotIndex;
}
/**
* Ensures that buffer size will handle addition of elementsToAdd
elements
* on provided index
position.
*/
protected void ensureCapacity(int index, int elementsToAdd) {
if (buffer == EMPTY_BUFFER) {
// first time
if (elementsToAdd <= pivotIndex) {
// initial addition, fits the initial size
buffer = new Object[pivotIndex];
} else {
// initial addition, does not fit the initial size
buffer = new Object[elementsToAdd];
}
pivotIndex = pivotType.calculate(buffer.length);
start = pivotIndex;
end = start;
size = 0;
return;
}
modCount++;
int realIndex = start + index;
if ((realIndex <= pivotIndex) && (realIndex < (end - 1))) {
// check left side
int gap = start;
if (gap < elementsToAdd) {
// we need to grow left
if (buffer.length - size - elementsToAdd > maxFreeSpaceBeforeNormalize) {
// don't grow, we already have enough room, just normalize
normalize();
return;
}
int currentSize = pivotIndex;
int newSize = currentSize + (currentSize >> 1);
int delta = newSize - currentSize;
if (delta < minimalGrowSize) {
delta = minimalGrowSize;
}
int newGap = gap + delta;
// is the new size fit for all new elements?
if (newGap < elementsToAdd) {
// no, then grow more
delta = elementsToAdd - gap;
}
// grow left for delta places
int totalSize = buffer.length + delta;
Object[] newBuffer = new Object[totalSize];
System.arraycopy(buffer, start, newBuffer, newGap, size);
// update pointers
start += delta;
end += delta;
pivotIndex += delta;
buffer = newBuffer;
}
} else {
// check right side
int gap = buffer.length - end;
if (gap < elementsToAdd) {
// we need to grow right
if (buffer.length - size - elementsToAdd > maxFreeSpaceBeforeNormalize) {
// don't grow, we already have enough room, just normalize
normalize();
return;
}
int currentSize = buffer.length - pivotIndex;
int newSize = currentSize + (currentSize >> 1);
int delta = newSize - currentSize;
if (delta < minimalGrowSize) {
delta = minimalGrowSize;
}
int newGap = gap + delta;
// is the new size fit for all new elements?
if (newGap < elementsToAdd) {
// no, then grow more
delta = elementsToAdd - gap;
}
// grow right for delta places
int totalSize = buffer.length + delta;
Object[] newBuffer = new Object[totalSize];
System.arraycopy(buffer, start, newBuffer, start, size);
// no pointers to update
buffer = newBuffer;
}
}
}
// ---------------------------------------------------------------- methods
/**
* Returns the number of elements in this list.
*/
@Override
public int size() {
return size;
}
/**
* Returns true
if this list contains no elements.
*/
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* Returns true
if this list contains the specified element.
*/
@Override
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index i
such that
* (o==null ? get(i)==null : o.equals(get(i)))
,
* or -1 if there is no such index.
*/
@Override
public int indexOf(Object o) {
if (o == null) {
for (int i = start; i < end; i++) {
if (buffer[i] == null) {
return i - start;
}
}
} else {
for (int i = start; i < end; i++) {
if (o.equals(buffer[i])) {
return i - start;
}
}
}
return -1;
}
/**
* Returns the index of the last occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
*/
@Override
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = end - 1; i >= start; i--) {
if (buffer[i] == null) {
return i - start;
}
}
} else {
for (int i = end - 1; i >= start; i--) {
if (o.equals(buffer[i])) {
return i - start;
}
}
}
return -1;
}
// ---------------------------------------------------------------- clone
/**
* Returns a shallow copy of this ArrayList
instance. (The
* elements themselves are not copied.)
*/
@Override
public Object clone() {
try {
@SuppressWarnings("unchecked")
JoddArrayList v = (JoddArrayList) super.clone();
v.buffer = (buffer == EMPTY_BUFFER ? buffer : buffer.clone());
v.modCount = 0;
v.start = start;
v.end = end;
v.size = size;
v.pivotIndex = pivotIndex;
v.pivotType = pivotType;
v.minimalGrowSize = minimalGrowSize;
v.maxFreeSpaceBeforeNormalize = maxFreeSpaceBeforeNormalize;
return v;
} catch (CloneNotSupportedException ignore) {
throw new InternalError(); // this shouldn't happen, since we are Cloneable
}
}
// ---------------------------------------------------------------- to array
/**
* Returns an array containing all of the elements in this list
* in proper sequence (from first to last element).
*/
@Override
public Object[] toArray() {
Object[] copy = new Object[size];
System.arraycopy(buffer, start, copy, 0, size);
return copy;
}
/**
* Returns an array containing all of the elements in this list in proper
* sequence (from first to last element); the runtime type of the returned
* array is that of the specified array. If the list fits in the
* specified array, it is returned therein. Otherwise, a new array is
* allocated with the runtime type of the specified array and the size of
* this list.
*/
@Override
@SuppressWarnings("unchecked")
public T[] toArray(T[] array) {
if (array.length < size) {
Class arrayType = array.getClass();
T[] copy = (arrayType == Object[].class)
? (T[]) new Object[size]
: (T[]) Array.newInstance(arrayType.getComponentType(), size);
System.arraycopy(buffer, start, copy, 0, size);
return copy;
}
System.arraycopy(buffer, start, array, 0, size);
if (array.length > size) {
array[size] = null;
}
return array;
}
// ---------------------------------------------------------------- get/set
/**
* Returns the element at the specified position in this list.
*/
@Override
@SuppressWarnings("unchecked")
public E get(int index) {
rangeCheck(index);
return (E) buffer[start + index];
}
/**
* Returns the first element in the list.
*/
public E getFirst() {
return get(0);
}
/**
* Returns the last element of the list.
*/
public E getLast() {
return get(size - 1);
}
/**
* Replaces the element at the specified position in this list with
* the specified element.
*/
@SuppressWarnings("unchecked")
@Override
public E set(int index, E element) {
rangeCheck(index);
index += start;
E oldValue = (E) buffer[index];
buffer[index] = element;
return oldValue;
}
// ---------------------------------------------------------------- add
/**
* Appends the specified element to the end of this list.
*/
@Override
public boolean add(E e) {
int index = size;
ensureCapacity(index, 1); // increments modCount!!
buffer[end] = e;
end++;
size++;
return true;
}
/**
* Adds the specified element to the beginning of this list.
*/
public boolean addFirst(E e) {
int index = 0;
ensureCapacity(index, 1); // increments modCount!!
if (size > 0) {
start--;
} else {
end++;
}
buffer[start] = e;
size++;
return true;
}
/**
* Appends element to the list.
*/
public boolean addLast(E e) {
return add(e);
}
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*/
@Override
public void add(int index, E element) {
if (index == 0) {
addFirst(element);
return;
}
if (index == size) {
add(element);
return;
}
rangeCheck(index);
ensureCapacity(index, 1); // increments modCount!!
int realIndex = start + index;
if (realIndex <= pivotIndex && (realIndex < (end - 1))) {
// move left
System.arraycopy(buffer, start, buffer, start - 1, realIndex - start);
start--;
realIndex--;
} else {
// move right
System.arraycopy(buffer, realIndex, buffer, realIndex + 1, end - realIndex);
end++;
}
buffer[realIndex] = element;
size++;
}
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the
* specified collection's Iterator.
*/
@Override
public boolean addAll(Collection extends E> collection) {
if (collection.isEmpty()) {
return false;
}
Object[] array = collection.toArray();
return doAddAll(array);
}
/**
* Appends all elements of given array.
*/
public boolean addAll(E... array) {
if (array.length == 0) {
return false;
}
return doAddAll(array);
}
protected boolean doAddAll(Object[] array) {
int numNew = array.length;
ensureCapacity(end, numNew); // increments modCount
System.arraycopy(array, 0, buffer, end, numNew);
size += numNew;
end += numNew;
return true;
}
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements.
* The new elements will appear in the list in the order that they are returned by the
* specified collection's iterator.
*/
@Override
public boolean addAll(int index, Collection extends E> collection) {
rangeCheck(index);
Object[] array = collection.toArray();
return doAddAll(index, array);
}
/**
* Inserts all array elements to this list.
*/
public boolean addAll(int index, E... array) {
rangeCheck(index);
return doAddAll(index, array);
}
protected boolean doAddAll(int index, Object[] array) {
int numNew = array.length;
ensureCapacity(index, numNew); // increments modCount
int realIndex = start + index;
if (realIndex <= pivotIndex) {
// add left
int numMoved = index;
if (numMoved > 0) {
System.arraycopy(buffer, start, buffer, start - numNew, numMoved);
}
realIndex -= numNew;
System.arraycopy(array, 0, buffer, realIndex, numNew);
start -= numNew;
} else {
// add right
int numMoved = end - realIndex;
if (numMoved > 0) {
System.arraycopy(buffer, realIndex, buffer, realIndex + numNew, numMoved);
}
System.arraycopy(array, 0, buffer, realIndex, numNew);
end += numNew;
}
size += numNew;
return numNew != 0;
}
// ---------------------------------------------------------------- clear
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
@Override
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = start; i < end; i++) {
buffer[i] = null;
}
pivotIndex = pivotType.calculate(buffer.length);
start = pivotIndex;
end = start;
size = 0;
}
// ---------------------------------------------------------------- remove
/**
* Removes first element of the list.
*/
public E removeFirst() {
return remove(0);
}
/**
* Removes last element of the list.
*/
public E removeLast() {
return remove(size - 1);
}
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements.
*/
@Override
@SuppressWarnings("unchecked")
public E remove(int index) {
rangeCheck(index);
modCount++;
return doRemove(index);
}
protected E doRemove(int index) {
int realIndex = start + index;
E oldValue = (E) buffer[realIndex];
if ((realIndex <= pivotIndex) && (realIndex < (end - 1))) {
// remove left
int numMoved = index;
if (numMoved > 0) {
System.arraycopy(buffer, start, buffer, start + 1, numMoved);
}
buffer[start] = null;
start++;
size--;
if (start > pivotIndex) {
pivotIndex = start;
}
} else {
// remove right
int numMoved = end - realIndex - 1;
if (numMoved > 0) {
System.arraycopy(buffer, realIndex + 1, buffer, realIndex, numMoved);
}
end--;
size--;
buffer[end] = null;
if (end <= pivotIndex) {
pivotIndex = end - 1;
if (pivotIndex < start) {
pivotIndex = start; // make sure that pivot is always >= start
}
}
}
return oldValue;
}
/**
* Removes the first occurrence of the specified element from this list,
* if it is present.
*/
@Override
public boolean remove(Object o) {
if (o == null) {
for (int index = start; index < end; index++) {
if (buffer[index] == null) {
doRemove(index - start);
return true;
}
}
} else {
for (int index = start; index < end; index++) {
if (o.equals(buffer[index])) {
doRemove(index - start);
return true;
}
}
}
return false;
}
/**
* Removes from this list all of the elements whose index is between
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
* Shifts any succeeding elements to the left (reduces their index).
*/
@Override
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(buffer, start + toIndex, buffer, start + fromIndex, numMoved);
// clear to let GC do its work
int newSize = size - (toIndex - fromIndex);
for (int i = start + newSize; i < start + size; i++) {
buffer[i] = null;
}
size = newSize;
end = start + size;
pivotIndex = start + pivotType.calculate(size);
}
/**
* Removes from this list all of its elements that are contained in the
* specified collection.
*/
@Override
public boolean removeAll(Collection> c) {
return batchRemove(c, false);
}
/**
* Retains only the elements in this list that are contained in the
* specified collection. In other words, removes from this list all
* of its elements that are not contained in the specified collection.
*/
@Override
public boolean retainAll(Collection> c) {
return batchRemove(c, true);
}
protected boolean batchRemove(Collection> collection, boolean complement) {
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++) {
Object element = buffer[start + r];
if (collection.contains(element) == complement) {
buffer[start + w++] = buffer[start + r];
}
}
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// copy leftovers
if (r != size) {
System.arraycopy(buffer, start + r, buffer, start + w, size - r);
w += size - r;
}
// is there a change in the buffer?
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++) {
buffer[start + i] = null;
}
modCount += size - w;
size = w;
modified = true;
end = start + size;
pivotIndex = start + pivotType.calculate(size);
}
}
return modified;
}
// ---------------------------------------------------------------- checks
/**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheck(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
// ---------------------------------------------------------------- iterators
/**
* Returns a list iterator over the elements in this list (in proper
* sequence), starting at the specified position in the list.
*/
@Override
public ListIterator listIterator(int index) {
rangeCheck(index);
return new ListItr(index);
}
/**
* Returns a list iterator over the elements in this list (in proper
* sequence).
* @see #listIterator(int)
*/
@Override
public ListIterator listIterator() {
return new ListItr(0);
}
/**
* Returns an iterator over the elements in this list in proper sequence.
*/
@Override
public Iterator iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
.
*/
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size) {
throw new NoSuchElementException();
}
cursor = i + 1;
lastRet = i;
return (E) buffer[start + i];
}
public void remove() {
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
JoddArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
/**
* An optimized version of AbstractList.ListItr
.
*/
private class ListItr extends Itr implements ListIterator {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0) {
throw new NoSuchElementException();
}
if (i >= size) {
throw new ConcurrentModificationException();
}
cursor = i;
lastRet = i;
return (E) buffer[start + i];
}
public void set(E e) {
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
JoddArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ignore) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
JoddArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ignore) {
throw new ConcurrentModificationException();
}
}
}
// ---------------------------------------------------------------- to string
/**
* Returns string representation of this array.
*/
public String toString() {
StringBuilder sb = new StringBuilder("[");
if (buffer != EMPTY_BUFFER) {
for (int i = start; i < end; i++) {
if (i != start) {
sb.append(',');
}
sb.append(buffer[i]);
}
}
sb.append(']');
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy