Java collections
brief introduction
Java collections can be roughly divided into four systems: set, list, Queue and map. Set represents unordered and unrepeatable collections; List represents an ordered and repeated set; Map represents a set with mapping relationship, and Java 5 adds a Queue system set to represent a Queue set implementation. Collection class is mainly responsible for saving and holding other data, so collection class is also called container class. All collection classes are located in
In the java.util package, later, in order to deal with the concurrency security problem in the multithreaded environment, Java 5 also provided some collection classes supported by multithreading under the java utilconcurrent package.
Collection is an interface, and Map does not inherit collection.
Set is very similar to Map. The underlying layer of set is implemented by Map. The implementation of Map and List is more important
Set
01 / HashSet
HashSet is a typical implementation of Set interface. HashSet cannot guarantee the arrangement order of elements. It is non thread safe, and the value of elements in the Set can be null. The bottom layer of HashSet is implemented by HashMap, which saves the element on the key of HashMap, and the corresponding value is an empty object.
How is the underlying data stored?
How can order and disorder be guaranteed?
Capacity expansion mechanism?
//transient, this attribute does not need to be serialized, because only the data is stored on k of the map, that is, v is empty, which wastes space private transient HashMap<E,Object> map; //Bottom storage data //The v in the map stores a fixed PRESENT instead of null, which plays the role of judgment and comparison. Null cannot be compared, and constants can be compared private static final Object PRESENT = new Object(); //Constructor, initializing map public HashSet() { map = new HashMap<>(); } //Method of adding data public boolean add(E e) { return map.put(e, PRESENT)==null; } //remove public boolean remove(Object o) { return map.remove(o)==PRESENT; } //Iterative method public Iterator<E> iterator() { return map.keySet().iterator(); } //Override the serialization method to serialize only the key for (E e : map.keySet()) s.writeObject(e);
02 / TreeSet
TreeSet can ensure that the collection elements are in the sorting state (sorting according to the value of the data, you can customize the sorting rules). TreeSet supports two sorting methods: natural sorting and custom sorting. Its bottom layer is implemented by TreeMap. TreeSet is also non thread safe, but the value of its internal elements cannot be null (to sort, null cannot be compared).
private transient NavigableMap<E,Object> m; //Underlying implementation public TreeSet() { this(new TreeMap<E,Object>()); } //Add and remove, iterator and serialization are the same as the HashSet above
List
01 / ArrayList
ArrayList is an array based List, so ArrayList encapsulates a dynamic Object array that allows redistribution.
The ArrayList object uses the initialCapacity parameter to set the length of the array. When adding elements to the ArrayList exceeds the length of the array, its initialCapacity will increase automatically, that is, the ArrayList will expand automatically. If you need to shrink the ArrayList, you need to actively call trimToSize(). Active capacity expansion but not active capacity reduction
Source code interpretation:
Definition and initialization:
/** * Default initial capacity.Initialize the array for the first time. The default length is 10 */ private static final int DEFAULT_CAPACITY = 10; //Two empty arrays can distinguish between the default length and the specified length when creating a collection. The two methods are different in capacity expansion mechanism private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //Specific elements stored in the array transient Object[] elementData; //Actual array length private int size; /** * Constructs an empty list with an initial capacity of ten.Create an array with a default capacity of 10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //Initialize array public ArrayList(int initialCapacity) { //If the specified capacity is greater than 0, create an array of the specified capacity if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {//Equal to 0, create an empty array this.elementData = EMPTY_ELEMENTDATA; } else {//An error is reported when it is less than 0 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
Capacity expansion mechanism (when the array is added to a certain range, that is, when data is added)
The default length is 10. Each time, it is expanded by 1.5 times based on the previous capacity. Each expansion actually copies the data of the original array to the new array created
public boolean add(E e) { //Capacity expansion first ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e;//Additional data return true; } //How to expand the capacity? Calculate a capacity and wear it after calculation private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //How to calculate the capacity of? private static int calculateCapacity(Object[] elementData, int minCapacity) { //The array is created by the default constructor if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //Take the maximum value between the default capacity and the minimum capacity return Math.max(DEFAULT_CAPACITY, minCapacity); } //When you use a parameterized constructor, return the specified capacity directly return minCapacity; } //In general, the above is whether to consider the default capacity //After calculating the capacity, start capacity expansion private void ensureExplicitCapacity(int minCapacity) { modCount++; // Overflow conscious code first judge whether the capacity to be expanded is greater than the length of the array if (minCapacity - elementData.length > 0) grow(minCapacity); } //Growth expansion private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length;//Gets the old capacity of the array //New capacity = old capacity + half of old capacity (1.5x) int newCapacity = oldCapacity + (oldCapacity >> 1); //Exceeded minimum int capacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //Maximum int capacity exceeded if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: each capacity expansion actually copies the data of the original array to the new array created elementData = Arrays.copyOf(elementData, newCapacity); } //Array capacity overflow processing private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
List iteration
//Two iterators //Because the inheritance relationship has an iterator public Iterator<E> iterator() { return new Itr(); } //list's own iterator public ListIterator<E> listIterator() { return new ListItr(0); } private class Itr implements Iterator<E> { int cursor; //Current cursor int lastRet = -1; //Position of the last iteration (front of the current position? Not necessarily) int expectedModCount = modCount;//The expected number of modifications. In the process of iteration, judge whether the number is equal or not, indicating that you have modified in the process of iteration (that is, judge whether you can modify the data in the process of iteration) //In the process of iteration, judge the modification times before deleting data }
The itertor implements sorting. The ListIterator is added to increase the iterative order. The implementation can iterate from front to back, from back to front, and from the middle
//It is a subclass of itertor and extends some things on its basis private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { //Start iteration at specified location super(); cursor = index; } public E previous() {//Iterate forward, checkForComodification();//First check the number of modifications and inherit int i = cursor - 1; } //To override a value, also check the number of modifications ArrayList.this.set(lastRet, e);//Overwrite the element just accessed //add to public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e);//Will element } }
Shrinkage:
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
Summary:
1. Array implementation, the default length is 10, and each expansion is 1.5 times. Each expansion actually copies the data of the original array to the new array created.
2. Check the number of changes during the iteration. If the data is changed, it cannot be iterated.
3. ListIterator is supported for more flexible iteration.
02 / LinkedList
LinkedList is a List based on two-way linked List. Its internal elements can be nul and repeatable. LinkedList is non thread safe.
Source code interpretation:
Definition and structure:
transient int size = 0;//How many nodes are there in the connection table transient Node<E> first; transient Node<E> last; //Node nodes have precursors and successors, so they are bidirectional linked lists private static class Node<E> { E item; Node<E> next; Node<E> prev; }
Add data:
public boolean add(E e) { linkLast(e); //To the end of the linked list return true; } //Add at specified location public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); }
Get data:
public E get(int index) { checkElementIndex(index); return node(index).item; }
Set value
public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
Delete:
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
The node method gets the index based on the node
Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) {//The node is in the first half Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else {//The node iterates from behind Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
Queue
Queue is used to simulate a queue. It is a first in first out (FIFO) container.
Common methods offer, poll
Deque
Deque stands for double ended Queue, which allows you to operate the elements in the Queue from both ends, and supports stack in and out operations. Deque adds the following two methods on the basis of Queue:
Select one end to operate
01 / ArrayDeque
The bottom layer of ArrayDeque is implemented through an array. In order to meet the requirement that elements can be inserted or deleted at both ends of the array at the same time, the array must also be circular, that is, circular array, that is, any point of the array may be regarded as the starting point or end point. ArrayDeque is non thread safe, and the container does not allow null elements.
Double ended queues are implemented based on arrays,
Source code interpretation:
transient Object[] elements;//Implemented by an array, the data is placed in the array transient int head;//Header index transient int tail;//Tail index //Default minimum capacity private static final int MIN_INITIAL_CAPACITY = 8; //Constructor. The default initialization length is 16 public ArrayDeque() { elements = new Object[16]; } //When specifying the size of the array, it is not as much as you specify. It needs to be calculated private void allocateElements(int numElements) { elements = new Object[calculateSize(numElements)]; } //Calculation capacity (when you specify, it should be expanded to the nearest n-th power of 2 larger than you specify) private static int calculateSize(int numElements) { int initialCapacity = MIN_INITIAL_CAPACITY; // Find the best power of two to hold elements. // Tests "<=" because arrays aren't kept full. if (numElements >= initialCapacity) {//Greater than or equal to the minimum length initialCapacity = numElements; initialCapacity |= (initialCapacity >>> 1); initialCapacity |= (initialCapacity >>> 2); initialCapacity |= (initialCapacity >>> 4); initialCapacity |= (initialCapacity >>> 8); initialCapacity |= (initialCapacity >>> 16); initialCapacity++; if (initialCapacity < 0) // Too many elements, must back off initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements } return initialCapacity; }
The capacity of ArrayDeuqe must be to the n th power of 2
Join the team:
public boolean offer(E e) { return offerLast(e); } public boolean offerLast(E e) { addLast(e); return true; } //Add data to the header public void addFirst(E e) { if (e == null) throw new NullPointerException(); //First calculate the subscript of the header pointer elements[head = (head - 1) & (elements.length - 1)] = e; if (head == tail) doubleCapacity();//Capacity expansion }
Capacity expansion:
private void doubleCapacity() { assert head == tail; int p = head; int n = elements.length;//Length of array //Number of elements to the right of the header pointer int r = n - p; // number of elements to the right of p //The new capacity is twice as large as the original int newCapacity = n << 1; if (newCapacity < 0) throw new IllegalStateException("Sorry, deque too big"); //Array copy: first copy the data on the right side of the header pointer, and then copy the data on the left side of the header pointer Object[] a = new Object[newCapacity]; System.arraycopy(elements, p, a, 0, r); System.arraycopy(elements, 0, a, r, p); elements = a; head = 0; tail = n; }
Out of the team:
public E pollFirst() { int h = head; @SuppressWarnings("unchecked") E result = (E) elements[h]; // Element is null if deque empty if (result == null) return null; //Delete header pointer elements[h] = null; // Must null out slot //Head pointer shift head = (h + 1) & (elements.length - 1); return result; } public E pollLast() { //Tail node int t = (tail - 1) & (elements.length - 1); @SuppressWarnings("unchecked") E result = (E) elements[t]; if (result == null) return null; elements[t] = null; tail = t;//Tail pointer moves left return result; }
Double ended queues are used as stacks
public void push(E e) { addFirst(e);//As mentioned above } public E pop() { return removeFirst();//In and out of one direction is the stack }
02 / LinkedList
In addition to implementing the List interface, LinkedList also implements the Deque interface, which can be used as a double ended queue, so it can be used as either a "stack" or a queue.
Source code interpretation:
public boolean add(E e) { linkLast(e); return true; } //Join the team: add to the front, private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; } //Out of the team public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } //Release the bidirectional relationship of nodes private E unlinkFirst(Node<E> f) { . . . }
Stack method:
public void push(E e) { addFirst(e); } public E pop() { return removeFirst(); }