net.pwall.util.ChunkedArrayList Maven / Gradle / Ivy
/*
* @(#) ChunkedArrayList.java
*
* javautil Java Utility Library
* Copyright (c) 2013, 2014 Peter Wall
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.pwall.util;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.RandomAccess;
/**
* A {@link List} implementation optimised for the following case:
*
* - The list may grow very large (several thousand elements), almost exclusively by
* addition at end
* - Insertions in the middle or at the start of the list occur seldom or never
* - Removals are rare (except by {@link #clear()}), particularly from the middle or start
* of the list
* - Access to the list is both random (an individual item) and serial (a sequence starting
* at a nominated point)
*
* The list is implemented as a set of chunks, each of which is an {@link ArrayList}
* pre-allocated to a specified chunk size. This has the advantage over a single
* {@code ArrayList} that growth in the list does not cause constant re-allocation of arrays of
* increasing size, with the consequent copying of the previous entries.
*
* @author Peter Wall
* @param the element type
*/
public class ChunkedArrayList extends AbstractList implements RandomAccess {
public static final int defaultChunkSize = 1000;
public static final int defaultInitialChunks = 20;
public static final int minimumChunkSize = 2;
private List> outerList;
private int chunkSize;
/**
* Construct a {@code ChunkedArrayList} with the specified chunk size and initial number of
* chunks.
*
* @param chunkSize the chunk size
* @param initialChunks the initial number of chunks
* @throws IllegalArgumentException if the chunk size is less than the minimum
*/
public ChunkedArrayList(int chunkSize, int initialChunks) {
if (chunkSize < minimumChunkSize)
throw new IllegalArgumentException("Chunk size " + chunkSize + " too low");
outerList = new ArrayList<>(initialChunks);
this.chunkSize = chunkSize;
}
/**
* Construct a {@code ChunkedArrayList} with the specified chunk size and the default
* initial number of chunks.
*
* @param chunkSize the chunk size
* @throws IllegalArgumentException if the chunk size is less than the minimum
*/
public ChunkedArrayList(int chunkSize) {
this(chunkSize, defaultInitialChunks);
}
/**
* Construct a {@code ChunkedArrayList} with the default chunk size and initial number of
* chunks.
*/
public ChunkedArrayList() {
this(defaultChunkSize);
}
/**
* Construct a {@code ChunkedArrayList} with the default chunk size and initial number of
* chunks, and then populate the list from an existing {@link Collection}.
*
* @param c the {@link Collection} to be copied to this list
*/
public ChunkedArrayList(Collection extends E> c) {
this();
addAll(c);
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
int n = outerList.size();
if (n == 0)
return 0;
n--;
long result = (long)n * chunkSize + outerList.get(n).size();
return result > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean contains(Object o) {
for (List innerList : outerList)
if (innerList.contains(o))
return true;
return false;
}
/**
* Appends the specified element to the end of this list.
*
* @param e {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public boolean add(E e) {
int n = outerList.size();
List innerList;
if (n == 0 || (innerList = outerList.get(n - 1)).size() == chunkSize) {
innerList = new ArrayList<>(chunkSize);
outerList.add(innerList);
}
innerList.add(e);
return true;
}
/**
* {@inheritDoc}
*/
@Override
public boolean remove(Object o) {
for (int i = 0, n = outerList.size(); i < n; i++) {
List innerList = outerList.get(i);
if (innerList.remove(o)) {
if (innerList.size() == 0)
outerList.remove(i);
else {
while (++i < n) {
List innerListNext = outerList.get(i);
innerList.add(innerListNext.remove(0));
if (innerListNext.size() == 0) {
outerList.remove(i);
break;
}
innerList = innerListNext;
}
}
return true;
}
}
return false;
}
/**
* Removes all of the elements from this list. The list will be empty after this call
* returns.
*/
@Override
public void clear() {
for (List innerList : outerList)
innerList.clear();
outerList.clear();
}
/**
* {@inheritDoc}
*/
@Override
public E get(int index) {
if (index < 0 || index >= size())
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return outerList.get(index / chunkSize).get(index % chunkSize);
}
/**
* Replaces the element at the specified position in this list with the specified element.
*
* @throws ClassCastException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public E set(int index, E element) {
if (index < 0 || index >= size())
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return outerList.get(index / chunkSize).set(index % chunkSize, element);
}
/**
* Inserts the supplied 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).
*
* @throws ClassCastException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public void add(int index, E element) {
if (index < 0 || index > size())
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
int i = index % chunkSize;
int j = index / chunkSize;
while (j < outerList.size()) {
List innerList = outerList.get(j);
if (innerList.size() < chunkSize) {
innerList.add(i, element);
return;
}
E movedElement = innerList.remove(chunkSize - 1);
innerList.add(i, element);
element = movedElement;
j++;
i = 0;
}
List innerList = new ArrayList<>(chunkSize);
outerList.add(innerList);
innerList.add(element);
}
/**
* Removes the element at the specified position in this list. Shifts any subsequent
* elements to the left (subtracts one from their indices). Returns the element that was
* removed from the list.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public E remove(int index) {
if (index < 0 || index >= size())
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
int i = index % chunkSize;
int j = index / chunkSize;
List innerList = outerList.get(j);
E result = innerList.remove(i);
if (innerList.size() == 0)
outerList.remove(j);
else {
while (++j < outerList.size()) {
List innerListNext = outerList.get(j);
innerList.add(innerListNext.remove(0));
if (innerListNext.size() == 0) {
outerList.remove(j);
break;
}
innerList = innerListNext;
}
}
return result;
}
/**
* 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.
*
* @param o {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public int indexOf(Object o) {
for (int i = 0, n = outerList.size(); i < n; i++) {
List innerList = outerList.get(i);
int result = innerList.indexOf(o);
if (result >= 0)
return i * chunkSize + result;
}
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. More formally, returns the highest index
* i such that
* (o==null ? get(i)==null : o.equals(get(i))), or -1 if there
* is no such index.
*
* @param o {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public int lastIndexOf(Object o) {
for (int i = outerList.size() - 1; i >= 0; i--) {
List innerList = outerList.get(i);
int result = innerList.lastIndexOf(o);
if (result >= 0)
return i * chunkSize + result;
}
return -1;
}
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy