executive summary
Official annotations to the List interface
A {@code List} is a collection which maintains an ordering for its elements. Every element in the {@code List} has an index. Each element can thus be accessed by its index, with the first index being zero. Normally, {@code List}s allow duplicate elements, as compared to Sets, where elements have to be unique. List is a set that keeps its elements in order. Each element in a List has an index index, and each element can be accessed through an index starting from 0. Generally, lists allow elements to repeat, while Sets require that elements be unique.
There are several classes that are commonly used to implement List interface as follows: List interface, List interface, List interface, List interface, List interface, List interface, List interface, List interface, List interface, List interface, List interface and List interface.
ArrayList, LinkedList (also implemented Queue), Vector, Stack, CopyOnWriteArrayList, Volatile Size ArrayList.
ArrayList
"Why, Array List still needs analysis?"
I thought so too. Later I looked at the source code and found that there were still some wonderful places to extract part of the source code.
public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess {
private static final int MIN_CAPACITY_INCREMENT = 12;
transient Object[] array;
@Override public boolean add(E object) {
Object[] a = array;
int s = size;
if (s == a.length) {
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
a[s] = object;
size = s + 1;
modCount++;
return true;
}
@Override public E remove(int index) {
Object[] a = array;
int s = size;
if (index >= s) {
throwIndexOutOfBoundsException(index, s);
}
@SuppressWarnings("unchecked") E result = (E) a[index];
System.arraycopy(a, index + 1, a, index, --s - index);
a[s] = null; // Prevent memory leak
size = s;
modCount++;
return result;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeInt(array.length);
for (int i = 0; i < size; i++) {
stream.writeObject(array[i]);
}
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
int cap = stream.readInt();
if (cap < size) {
throw new InvalidObjectException(
"Capacity: " + cap + " < size: " + size);
}
array = (cap == 0 ? EmptyArray.OBJECT : new Object[cap]);
for (int i = 0; i < size; i++) {
array[i] = stream.readObject();
}
}
private class ArrayListIterator implements Iterator<E> {
private int remaining = size;
private int expectedModCount = modCount;
public boolean hasNext() {
return remaining != 0;
}
@SuppressWarnings("unchecked") public E next() {
ArrayList<E> ourList = ArrayList.this;
int rem = remaining;
if (ourList.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (rem == 0) {
throw new NoSuchElementException();
}
remaining = rem - 1;
return (E) ourList.array[removalIndex = ourList.size - rem];
}
public void remove() {
Object[] a = array;
int removalIdx = removalIndex;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (removalIdx < 0) {
throw new IllegalStateException();
}
System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
a[--size] = null; // Prevent memory leak
removalIndex = -1;
expectedModCount = ++modCount;
}
}
From the source code, you can see a few noteworthy points
- ArrayList uses Object [] to store data, and get reads very quickly.
- In add ition method, there will be conditional expansion. If the current size is less than 6, it will increase by 12, if more than 6, it will increase by half of the original size, and each expansion will be performed once System.arraycopy, which is time-consuming, so we are required to declare the initial size as much as possible when using ArrayList to reduce the number of expansion.
- Every remove must execute System.arraycopy, so if you need to delete elements frequently, please be smart.
- Notice that Object [] is preceded by the addition of transient s that do not allow Serializable to be serialized by default. It's strange why the most critical data set does not allow serialization. In addition, writeObject/readObject is private, and it works because of the reflection mechanism (Serializable itself is a reflection mechanism, so it's better to serialize it yourself). Parcelable is used;
- ArrayList iterator uses a remaining residual size to implement next logic, which is more efficient, but the traversal time is still not as good as just an array[index] (is there any faster?), so traversing ArrayList uses for (int I = 0; I < list. size (); I +) faster than for (Object o: list).
LinkedList
LinkedList is an implementation of {@link List}, backed by a doubly-linked list. All optional operations including adding, removing, and replacing elements are supported. <p>All elements are permitted, including null. <p>This class is primarily useful if you need queue-like behavior. It may also be useful as a list if you expect your lists to contain zero or one element, but still require the ability to scale to slightly larger numbers of elements. In general, though, you should probably use {@link ArrayList} if you don't need the queue-like behavior. LinkedList is a bidirectional linked list implemented by Link. It supports all operations including add, delete, change and check, and all data types including null. This class is generally useful if you need queue-like behavior. It can also be used if you need to expand the list from one or two primary colors to a slightly larger capacity. But in general, if you don't need queue-like behavior, you should use ArrayList.
LinkedList is a linked list. The core of LinkedList is the inner class of Link.
private static final class Link<ET> {
ET data;
Link<ET> previous, next;
Link(ET o, Link<ET> p, Link<ET> n) {
data = o;
previous = p;
next = n;
}
}
Each Link is called a node in the linked list. It contains the data of the element, and the two elements of the element (bi-directional query, so it is called bi-directional table).
Each node of the chain storage structure allocates itself in memory and does not need to be sequentially discharged like a linear table, thus avoiding the array copy operation that leads to inefficient insertion/deletion of linear tables.
So how do linked lists actually work? Look at the source code (just a simple excerpt of instantiation and basic add-delete checks)
/**
* list Size
*/
transient int size = 0;
/**
* Root node, all operations start at the root node
*/
transient Link<E> voidLink;
/**
* Construct method, instantiate voidLink, and point previous/next to itself to form a bidirectional table
*/
public LinkedList() {
voidLink = new Link<E>(null, null, null);
voidLink.previous = voidLink;
voidLink.next = voidLink;
}
/**
* Adding operation, no need to expand, only need to modify two previous/next, high efficiency
*/
@Override
public boolean add(E object) {
Link<E> oldLast = voidLink.previous;
Link<E> newLink = new Link<E>(object, oldLast, voidLink);
voidLink.previous = newLink;
oldLast.next = newLink;
size++;
modCount++;
return true;
}
@Override
public E remove(int location) {
if (location >= 0 && location < size) {
Link<E> link = voidLink;
// Dichotomy, previous/next bidirectional query
if (location < (size / 2)) {
for (int i = 0; i <= location; i++) {
link = link.next;
}
} else {
for (int i = size; i > location; i--) {
link = link.previous;
}
}
Link<E> previous = link.previous;
Link<E> next = link.next;
previous.next = next;
next.previous = previous;
size--;
modCount++;
return link.data;
}
throw new IndexOutOfBoundsException();
}
@Override
public E get(int location) {
if (location >= 0 && location < size) {
Link<E> link = voidLink;
// Dichotomy, previous/next bidirectional query
if (location < (size / 2)) {
for (int i = 0; i <= location; i++) {
link = link.next;
}
} else {
for (int i = size; i > location; i--) {
link = link.previous;
}
}
return link.data;
}
throw new IndexOutOfBoundsException();
}
@Override
public E set(int location, E object) {
if (location >= 0 && location < size) {
Link<E> link = voidLink;
// Dichotomy, previous/next bidirectional query
if (location < (size / 2)) {
for (int i = 0; i <= location; i++) {
link = link.next;
}
} else {
for (int i = size; i > location; i--) {
link = link.previous;
}
}
E result = link.data;
link.data = object;
return result;
}
throw new IndexOutOfBoundsException();
}
It can be seen that deletion checks need to be located to the position position through a dichotomy loop, and the query efficiency is slower than that of ArrayList.
But because LinkedList additions and deletions avoid array copies, the efficiency of additions and deletions is greatly improved when the number of elements is large.
In addition, because of the characteristics of its linked list, the use of iterator for (Object o: list) is much more efficient than for (int I = 0; I < list. size (); I ++).