The List interface is a frequently used interface in Java. If you do not know the features of the Specific List Implementation class, it may lead to a decline in program performance, the following describes the implementation of List in principle:
As you can see, List inherits the Collection interface, while the Collection interface inherits the Iterable interface. There are also the implementation of AbstractCollection and AbstractList, which are used to reuse the common part of the code of the List object:
public interface List<E> extends Collection<E> { // Query Operations
public interface Collection<E> extends Iterable<E> { // Query Operations
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { /** * Sole constructor. (For invocation by subclass constructors, typically * implicit.) */ protected AbstractList() { }
public abstract class AbstractCollection<E> implements Collection<E> { /** * Sole constructor. (For invocation by subclass constructors, typically * implicit.) */ protected AbstractCollection() { }
The three interfaces are defined as follows:
Now let's take a look at the specific features of the List interface:
1. Vector
The Vector class is implemented through arrays and supports thread synchronization. That is, only one thread can write Vector at a time point to avoid inconsistency caused by simultaneous write of multiple threads, however, synchronization requires a high cost. Therefore, the access efficiency is not very high.
Now let's take a look at the member variables of the Vector and the construction methods of some of them:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ /** * The array buffer into which the components of the vector are * stored. The capacity of the vector is the length of this array buffer, * and is at least large enough to contain all the vector's elements. * * <p>Any array elements following the last element in the Vector are null. * * @serial */ protected Object[] elementData; /** * The number of valid components in this {@code Vector} object. * Components {@code elementData[0]} through * {@code elementData[elementCount-1]} are the actual items. * * @serial */ protected int elementCount; /** * The amount by which the capacity of the vector is automatically * incremented when its size becomes greater than its capacity. If * the capacity increment is less than or equal to zero, the capacity * of the vector is doubled each time it needs to grow. * * @serial */ protected int capacityIncrement; /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -2767605614048989439L; /** * Constructs an empty vector with the specified initial capacity and * capacity increment. * * @param initialCapacity the initial capacity of the vector * @param capacityIncrement the amount by which the capacity is * increased when the vector overflows * @throws IllegalArgumentException if the specified initial capacity * is negative */ public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; }
Anyone who knows about the data structure knows that array is used as the underlying storage layer. When the storage space is insufficient, a larger memory space will be allocated, then copy all the data in the original space and release the original space. This will lead to a decrease in program performance or GC consumption (Vector is doubled by default ). Therefore, we need to estimate the amount of data processed, and specify the capacity when initializing the Vector. Adding the synchronized keyword to all the actions of a Vector also results in performance degradation.
The following code adds elements to a Vector:
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */ public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
/** * This implements the unsynchronized semantics of ensureCapacity. * Synchronized methods in this class can internally call this * method for ensuring capacity without incurring the cost of an * extra synchronization. * * @see #ensureCapacity(int) */ private void ensureCapacityHelper(int minCapacity) { int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object[] oldData = elementData; int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2); if (newCapacity < minCapacity) { newCapacity = minCapacity; } elementData = Arrays.copyOf(elementData, newCapacity); } }
2. Stack
The Stack class indicates the object Stack of the last-in-first-out (LIFO. It extends the Vector class through five operations. It is also thread-safe and allows the Vector to be considered as a stack. It provides common push and pop operations, and the peek method for obtaining stack vertices, the empty method for testing whether the stack is empty, the search Method for Finding items in the stack and determining the distance to the top of the stack. An EmptyStackException is thrown when no element exists in the operation set.
publicclass Stack<E> extends Vector<E> { /** * Creates an empty Stack. */ public Stack() { }
3. ArrayList
Similarly, ArrayList is implemented through arrays, which allow fast Random Access to elements. The disadvantage of an array is that there cannot be an interval between each element. When the array size does not meet the requirements, you need to increase the storage capability. It is necessary to copy the data of an array to a new bucket. When an element is inserted or deleted from the middle of the ArrayList, the array needs to be copied, moved, and the cost is relatively high (ArrayList expands by more than 50% when the memory is insufficient ). Therefore, it is suitable for random search and traversal, and is not suitable for insertion and deletion. Because no synchronization is added, it is non-thread-safe and the execution efficiency is much higher than that of Vector.
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. */ private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @exception IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }
public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
4. shortlist
The shortlist uses a two-way linked list structure to store data. It is suitable for dynamic data insertion and deletion, and the random access and traversal speed is relatively slow. In addition, it provides methods not defined in the List interface, which are used to operate the header and table tail elements and can be used as stacks, queues, and bidirectional queues.
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0; /** * Constructs an empty list. */ public LinkedList() { header.next = header.previous = header; }
Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }
Summary: Selecting the corresponding data structure based on the characteristics of different implementation classes can improve the program running efficiency. It should be noted that when a thread iterates over the list elements, if the other thread wants to operate on the elements during the iteration process, such as adding new elements when the conditions are met, concurrentModificationException occurs. Of course, later versions of the CopyOnWrite container solved this problem and will be introduced later.