[summary] Java collection (Set, List, Queue)

Posted by nickdd on Tue, 21 Sep 2021 21:54:37 +0200

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();
    }

Topics: Java Interview list