1. General
ArrayList implements the List interface and is a sequential container, that is, the data stored in the elements is in the same order as that put in. null elements are allowed to be put in, and the bottom layer is realized through array. Except that this class does not realize synchronization, the rest are roughly the same as Vector. Each ArrayList has a capacity, which represents the actual size of the underlying array. The number of storage elements in the container cannot be more than the current capacity. When adding elements to the container, if the capacity is insufficient, the container will automatically increase the size of the underlying array. As mentioned earlier, Java generics are only syntax sugar provided by the compiler, so the array here is an Object array to accommodate any type of Object.
size(), isEmpty(), get(), set() methods can be completed in constant time. The time cost of add() method is related to the insertion position, and the time cost of addAll() method is directly proportional to the number of added elements. Most of the other methods are linear time.
In pursuit of efficiency, ArrayList does not achieve synchronization. If multiple threads need concurrent access, users can synchronize manually or use Vector instead.
- The array will be automatically expanded, and the thread is not safe
- Fast query, slow addition and deletion
2. Underlying implementation
- Object array implementation, the type will be lost when storing elements
// Default capacity private static final int DEFAULT_CAPACITY = 10; // Underlying storage array transient Object[] elementData;
- For the newly created ArrayList, if the initial capacity is not specified when it is created, the default initial capacity is 0. When the first element is added, the extended capacity is 10( The initial capacity of ArrayList is now 0, not 10)
- When the capacity is not 0, if not enough, it will be expanded to 1.5 times the original length
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); // The right shift is equivalent to dividing by 2, and the overall expansion is equivalent to 1.5 times if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
3. Introduction to common methods
3.1 set()
- Function: set the value at the given subscript and return the old value at the coordinate
- Input parameter: subscript, value
- Return value: the old value at the given coordinate
public E set(int index, E element) { rangeCheck(index);//Subscript out of bounds check E oldValue = elementData(index); elementData[index] = element;//When assigned to the specified location, only the reference is copied return oldValue; }
Note: the subscript in the input parameter must be less than the length of the array, otherwise IndexOutOfBoundsException will be reported
3.2 get()
- Purpose: get the value at a specific subscript
- Input parameter: subscript
- Return value: the value at the given coordinate (since the underlying array is Object [], type conversion is required after obtaining the element)
public E get(int index) { rangeCheck(index); return (E) elementData[index];//Note type conversion }
3.3 add()
- Function: add elements to the tail (at a specific subscript)
- Input parameter: (subscript) element
- Return value: true
public boolean add(E e) { ensureCapacityInternal(size + 1); // Space inspection elementData[size++] = e; return true; }
public void add(int index, E element) { rangeCheckForAdd(index); // Subscript check ensureCapacityInternal(size + 1); // Space inspection System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
3.4 addAll()
The addAll() method can add multiple elements at once. There are two versions depending on the location. One is the addall (collection <? Extensions E > C) method added at the end, and the other is the addall (int index, collection <? Extensions E > C) method inserted from the specified location. Follow add() The method is similar. Before inserting, you also need to check the space. If necessary, it will be automatically expanded. If you insert from a specified location, you will also move elements. The time complexity of addAll() is related not only to the number of inserted elements, but also to the location of the inserted elements.
3.5 remove()
The remove() method also has two versions, one is remove(int index) to delete the element at the specified position, and the other is remove(Object o) to delete the first element satisfying o.equals(elementData[index]). The deletion operation is add() In the reverse process of the operation, the element after the deletion point needs to be moved forward by one position. It should be noted that in order for the GC to work, the last position must be explicitly assigned a null value.
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; //Clear the reference at this location and let the GC take effect return oldValue; }
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
Note: if the element type in ArrayList is Integer, the remove method parameter is still interpreted as a subscript!
4. Traversal method
4.1 Iterator method traversal
for(Iterator it = list.iterator();it.hasNext();){ System.out.println(it.next()); }
The deletion method of list cannot be used during traversal, otherwise concurrent modification exceptions will be reported, but the deletion method of iterator can be used
4.2 for loop traversal
for(int i = 0;i < list.size(); i ++){ System.out.println(list.get(i)); }
list can be modified during traversal
4.3 enhanced for loop traversal
for(String tmp:list){ System.out.println(tmp); }
The underlying implementation is based on the iterator. The deletion method of list cannot be used during traversal, otherwise concurrent modification exceptions will be reported, but the deletion method of iterator can be used
5. Analogy LinkedList and Vector
List mainly includes ArrayList, LinkedList and Vector.
These three implement the List interface, and their use methods are also very similar. The main difference is that they have different efficiency for different operations because of different implementation methods.
ArrayList is an array with variable size. When more elements are added to ArrayList, its size will grow dynamically. Internal elements can be accessed directly through get and set methods, because ArrayList is essentially an array
LinkedList is a double linked list. It has better performance than ArrayList when adding and deleting elements, but it is weaker than ArrayList in get and set
Of course, these comparisons refer to the comparison in the case of large amount of data or frequent operations. If the amount of data and operations is very small, the comparison will be meaningless
Vector is similar to ArrayList, but it belongs to strong synchronization class. If your program itself is thread safe (without sharing the same set / object among multiple threads), ArrayList is a better choice.
Vector and ArrayList request more space when more elements are added. Vector requests twice its size each time, while ArrayList increases its size by 50% each time
LinkedList also implements the Queue interface, which provides more methods than List, including offer(),peek(),poll()
Note: by default, the initial capacity of ArrayList is very small, so if you can estimate the amount of data, it is a best practice to allocate a large initial value, which can reduce the cost of resizing.