Understand the implementation class of Collection (List, Set, Map)

提示:以下是本篇文章正文内容,下面案例可供参考

1. Collection collection diagram

insert image description here

1. List

1. ArrayList

1. The underlying implementation of ArrayList

add

    public boolean add(E e) {
    
    
        //判断是否需要扩容   size :数组中已经存在的元素的个数
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }


    private void ensureCapacityInternal(int minCapacity) {
    
    
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

	private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureExplicitCapacity(int minCapacity) {
    
    
    	//修改次数+1
        modCount++;

        // overflow-conscious code
        //元素的个数(含当前添加)减去数组的长度大于0   说明容量不够,需要扩容
        if (minCapacity - elementData.length > 0)
            //数组扩容
            grow(minCapacity);
    }
  1. When adding elements to ArrayList, it will judge whether the capacity is sufficient according to the number of elements in the array + 1;
  2. When the number of elements in the array minus the length of the array is greater than 0 (minCapacity - elementData.length > 0), the array will be expanded. If it is the first time to add elements to an array with a default initial capacity of 10, if it is not the first time to add It will be expanded to 1.5 times the original array;
    扩容的同时需要将原来数组中的数据复制到新数组里,保持元素存储空间连续
  3. When the capacity of the array is sufficient, it is directly added to the position where the index of the array is the total number of elements + 1 (elementData[size++] = e;);

get

    public E get(int index) {
    
    
	    //对比索引是否越界
        rangeCheck(index);
		//返回当前索引的元素
        return elementData(index);
    }

    E elementData(int index) {
    
    
        return (E) elementData[index];
    }
  1. When getting elements from ArrayList, it will compare the number of elements in the array with the obtained index to see if the index is out of bounds;
  2. If the index is out of bounds, an exception will be thrown directly. If there is no out of bounds, the element of the current index in the array will be obtained;

remove

 public E remove(int index) {
    
    
 		//对比索引是否越界
        rangeCheck(index);
		//修改次数+1
        modCount++;
        //获取需要删除的元素
        E oldValue = elementData(index);
		//计算需要移动的元素个数
        int numMoved = size - index - 1;
        //移动的元素个数大于0 
        if (numMoved > 0)
        //进行数组移动 将要删除元素后面的元素向前覆盖 
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
         // clear to let GC do its work     翻译:等待GC回收                 
        elementData[--size] = null;

        return oldValue;
    }
  1. When deleting an ArrayList to obtain elements, it will compare the number of elements in the array with the deleted index to see if the index is out of bounds;
  2. If the index is out of bounds, an exception will be thrown directly. If there is no out of bounds, the element of the current index in the array will be obtained;
  3. Calculate the elements that need to be moved after deletion, move the array behind, and adjust the element subscript;

group

    private void grow(int minCapacity) {
    
    
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //右移运算  进行1.5倍扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //新容量减去最小容量小于0    使用最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
            //最小容量将去默认最大值大于0
        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);
    }

	
	    private static int hugeCapacity(int minCapacity) {
    
    
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

2. Features of ArrayList

  1. ArrayList storage is ordered, the element storage space is continuous, and each element has a subscript;
  2. The data stored in ArrayList can be repeated and null can be stored;
  3. ArrayList has high retrieval efficiency, slow addition and deletion, and high efficiency of adding elements at the end;
  4. ArrayList is not thread-safe;

3. Summary

  1. The bottom layer of ArrayList is based on the Object array. The default initial capacity is 10, and the capacity will automatically grow with the addition of data.
  2. When the add() method is called, it will first determine whether the capacity of the array is sufficient. If the capacity is not sufficient, the grow() expansion mechanism will be called to expand the capacity to 1.5 times the original capacity. If the capacity is sufficient, it will be added directly.
  3. The expansion of ArrayList is not unlimited growth. After expansion, it will judge whether the capacity of the new array is greater than the stored data. If the length of the new array is not enough, the length of the data will be assigned to the length of the new array, and finally the length of the new array will be judged. Do not allow exceeding the default max limit or overflow.
  4. When ArrayList expands, it needs to copy the data in the original array to the new array to keep the element storage space continuous.
  5. Randomly inserting ArrayList into a certain position will make the subscript of the element after the index +1, and the remove() method will make the subscript of the current element -1, and empty the value of the last bit, which is convenient for GC.

4. Think

  1. Why is the expansion factor 1.5 times? Not 2, or 2.5?
    Answer: Because memory is saved, memory waste is avoided, and displacement operations are highly efficient in memory, it can also reduce the number of expansions.

2. Vectors

1. The underlying implementation of Vecto

add

 public synchronized boolean add(E e) {
    
    
        modCount++;
        //判断是否需要扩容 elementCount:数组中已经存在的元素的个数
        ensureCapacityHelper(elementCount + 1);
        //将添加的元素放在最后面
        elementData[elementCount++] = e;
        return true;
    }

 
    private void ensureCapacityHelper(int minCapacity) {
    
    
        // overflow-conscious code
        //元素的个数(含当前添加)减去数组的长度大于0   说明容量不够,需要扩容
        if (minCapacity - elementData.length > 0)
        	//数组扩容
            grow(minCapacity);
    }
  1. When adding elements to Vecto, it will judge whether the capacity is sufficient according to the number of elements in the array + 1;
  2. When the number of elements in the array minus the length of the array is greater than 0 (minCapacity - elementData.length > 0), the array will be expanded;
  3. When the array capacity is sufficient, add it directly to the position where the array index is the total number of elements + 1 (elementData[elementCount++] = e);

get

    public synchronized E get(int index) {
    
    
    	 //对比索引是否越界
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

  1. When getting elements from Vecto, it will compare the number of elements in the array with the obtained index to see if the index is out of bounds;
  2. If the index is out of bounds, an exception will be thrown directly. If there is no out of bounds, the element of the current index in the array will be obtained;

remove

 public synchronized E remove(int index) {
    
    
        modCount++;
         //对比索引是否越界
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index);
		//计算需要移动的个数   大于0则需要移动
        int numMoved = elementCount - index - 1;
        if (numMoved > 0)
            //进行数组移动 将要删除元素后面的元素向前覆盖 
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--elementCount] = null; // Let gc do its work

        return oldValue;
    }
  1. When deleting an ArrayList to obtain elements, it will compare the number of elements in the array with the deleted index to see if the index is out of bounds;
  2. If the index is out of bounds, an exception will be thrown directly. If there is no out of bounds, the element of the current index in the array will be obtained;
  3. Calculate the elements that need to be moved after deletion, move the array behind, and adjust the element subscript;

grow

    private void grow(int minCapacity) {
    
    
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //当增量大于0时,则Vecto数组扩大到原来数组长度+增量  否则扩大2倍
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
    
    
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

2. Features of Vecto

  1. Vecto storage is ordered, the element storage space is continuous, and each element has a subscript;
  2. Vecto is slow to add and delete, fast to query, and thread-safe

3. Summary

与ArrayList的底层实现一样,只不过需要注意两个地方:

  1. Vecto expansion is related to increment. When the capacity of Vecto is not enough and needs to be expanded, the increment will be judged first. If the increment is greater than 0, the Vecto will be expanded to the original array length + increment, otherwise the Vecto will be expanded by 2 times;
  2. Vecto's method uses synchronized for synchronization, so it is thread-safe;

Vecto is basically not used now. If you want to get a thread-safe collection, you can use Collections.synchronizedList() to get it

3. Understand linked list

1. Singly linked list

insert image description here
insert image description here

2. Doubly linked list

insert image description here
insert image description here

4. LinkedList

Binary search for doubly linked list

  1. The underlying layer of LinkedList is implemented based on a doubly linked list, allowing all elements including null to be stored;
  2. The memory of the doubly linked list is discontinuous, has no concept of length, does not need to be initialized, does not need to be expanded, and the storage size is limited by the machine memory;
  3. The doubly linked list consists of three parts: the previous pointer, the element stored in this node, and the next pointer a
    . Previous pointer: point to the previous data
    b. The element stored in this node
    c. Next pointer: point to the next element
    d. first is the head node of the doubly linked list, and the previous node is null;
    e. last is the tail node of the doubly linked list, and the next node is null;
    f. When there is no data in the linked list, first and last are the same node, and the forward and backward points are both null;
    g. When there is only one data in the linked list, first and last are the same node, and the forward and backward points are all null;

1. The underlying implementation of LinkedList

add

    public boolean add(E e) {
    
    
        linkLast(e);
        return true;
    }

    void linkLast(E e) {
    
    
    	//获取双向链表的last
        final Node<E> l = last;
        //将要插入的元素封装为新的Node  上一个指针指向last   下一个指针为null
        final Node<E> newNode = new Node<>(l, e, null);
        //重置last   将新的节点设置为last
        last = newNode;
        //判断之前的last是否为null
        if (l == null)
           //为空:说明第一次插入,新的Node同时为设置为first 节点
            first = newNode;
        else
           //非首次添加,之前的last的下一个指针指向新的Node
            l.next = newNode;
        size++;
        modCount++;
    }
  1. When adding elements to LinkedList, the last node (that is, the last node) in the doubly linked list will be obtained first;
  2. If the last node is null, it means that it is added for the first time, and the current element is set as the first and last nodes, and the pointers before and after are all null;
  3. If the last node is not null, the previous pointer last node and the next pointer of the newly added element are null, and the next pointer of the last node points to the newly added node;
  4. Reset the last node, the newly added node is the last node;

get

    public E get(int index) {
    
    
        // 判断索引是否越界
        checkElementIndex(index);
        return node(index).item;
    }

    private void checkElementIndex(int index) {
    
    
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    Node<E> node(int index) {
    
    
        // assert isElementIndex(index);
		//通过右移运算(折半查找) 判断当前索引是否小于size/2
        if (index < (size >> 1)) {
    
    
            Node<E> x = first;
            //从头部开始查找
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
    
    
            Node<E> x = last;
            //从尾部开始查找
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
  1. When getting elements from LinkedList, it will first judge whether the index is out of bounds, if yes: throw an exception;
  2. Use the right shift operator to judge whether the current index is smaller than the array element, yes: start searching from the head, no: start searching from the tail;

remove

    public E remove(int index) {
    
    
        checkElementIndex(index);
        return unlink(node(index));
    }

    Node<E> node(int index) {
    
    
        // assert isElementIndex(index);
		//通过右移运算(折半查找) 判断当前索引是否小于size/2
        if (index < (size >> 1)) {
    
    
            Node<E> x = first;
            //从头部开始查找
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
    
    
            Node<E> x = last;
            //从尾部开始查找
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

	E unlink(Node<E> x) {
    
    
	        // assert x != null;
	        //获取要删除的元素
	        final E element = x.item;
	        //获取要删除的元素指向的下一个元素
	        final Node<E> next = x.next;
	        //获取要删除的元素指向的上一个元素
	        final Node<E> prev = x.prev;
			
	        if (prev == null) {
    
    
	            //说明删除的是first节点 ,重置first节点
	            first = next;
	        } else {
    
    
	        	//上一个元素的下指针重新关联到下一个元素
	            prev.next = next;
	            //要删除的元素断开与上一个元素的关联
	            x.prev = null;
	        }
	
	        if (next == null) {
    
    
	           //说明删除的是last节点 ,重置last节点
	            last = prev;
	        } else {
    
    
	            //下一个元素的上指针重新关联到上一个元素
	            next.prev = prev;
	            //要删除的元素断开与下一个元素的关联
	            x.next = null;
	        }
	
	        x.item = null;
	        size--;
	        modCount++;
	        return element;
	    }
  1. When deleting elements from LinkedList, it will first judge whether the index is out of bounds, if yes: throw an exception;
  2. Get the upper and lower elements of the element to be deleted;
  3. Determine whether the upper element is empty, if it is empty, it means that the head node is deleted, reset the head node, and replace it with the lower element;
  4. If it is not empty, the lower pointer of the upper element points to the lower element, and the upper pointer of the deleted element is set to null;
  5. Determine whether the next element is empty, if it is empty, it means that the head node is deleted, reset the head node, and replace it with the next element;
  6. If it is not empty, the upper pointer of the lower element points to the upper element, and the lower pointer of the deleted element is set to null;
  7. Delete elements, while the number of elements -1, the number of modifications +1;

2. Features of LinkedList

  1. The bottom layer of LinkedList is based on a two-way linked list. The memory of the linked list is not continuous, and there is no way to use the CPU to cache the pre-read data, and the memory usage is low;
  2. The linked list has no concept of length, does not need to be initialized, does not need to be expanded, and the storage size is related to the machine memory;
  3. LinkedList allows storing all elements including null, and associates data before and after by reference;
  4. LinkedList uses a two-way linked list, which is quick to add and delete, does not require additional overhead to move and copy elements, and is slow to query;
  5. LinkedList is not thread-safe;

2. Set

HashSet stores data based on hash table, and TreeSet stores data based on red-black tree.

1. HashSet

1. The underlying implementation of HashSet

  1. The collection elements in HashSet are actually saved by the key of HashMap
  2. When adding a custom class object to the collection in HashSet, be sure to rewrite the equals method and hashCode method to ensure the uniqueness of the stored object

add

    public boolean add(E e) {
    
    
	    /**
	     * 实际上底层是调用的HashMap的put方法
	     * 集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT
	     */
        return map.put(e, PRESENT)==null;
    }
	/**
	 * 下面的源码请到HashMap观看
	 * 	在此不再赘述
	 */
    public V put(K key, V value) {
    
    
        return putVal(hash(key), key, value, false, true);
    }
  1. HashSet is implemented based on a hash table (actually a HashMap). When adding elements to a HashSet, it will first determine whether the array is empty;
  2. If the array is empty, call the resize() method to initialize a HashMap array with a length of 16 and an expansion factor of 0.75, expand the capacity to the power of 2, calculate the hash value of the key and put it into the corresponding array, and the value of the HashMap stores a PRESENT object, this object is a static Object object, modified by static final;
  3. The array is not empty, calculate the hash value of the key to get the subscript of the array, and judge whether there is data in the position of the subscript array, if not, insert the data directly;
  4. There is data in the position of the subscript array, compare the Hash values ​​of the two objects on the linked list (the same Hash value does not necessarily mean the same object, so it is necessary to call equals) and KEY are the same, if yes: overwrite;
  5. Not the same object, judging whether it is a tree node of a red-black tree (may have been converted into a red-black tree), yes: add directly,
  6. If it is neither the same object nor a tree node, it is added to the end of the linked list to determine whether the length of the linked list is greater than 8, and whether the length of the array reaches 64;
  7. If the length of the linked list is 8 and the length of the array reaches 64, then the linked list will evolve into a red-black tree. If the linked list is 8 and the array length is less than 64, resize() will be performed to double the number of buckets, and the linked list will be split into different buckets to reduce the size of a single linked list.
  8. Finally, it is judged whether the size of the hashmap reaches the threshold, which is: expand resize().

remove

   /**
	* 实际上底层是调用的HashMap的remove方法
	* 集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT
	*/
    public boolean remove(Object o) {
    
    
        return map.remove(o)==PRESENT;
    }
    /**
	 * 下面的源码请到HashMap观看
	 * 	在此不再赘述
	 */
    public V remove(Object key) {
    
    
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
  1. When HashSet calls the remove method, it will call the remove method of HashMap, calculate the hash value of the key to get the subscript of the array, and judge whether there is data in the position of the subscript array, and return null if it does not exist;
  2. If there is data, call hashCode() and equals() to compare the Hash value and KEY with the first element, and if they are the same, delete the first element directly;
  3. If they are not the same, determine whether it belongs to a tree node, yes: directly delete, and judge the number of nodes in the red-black tree, if the number of nodes is less than 6, it will degenerate into a linked list;
  4. If it is neither the first element object nor a tree node, call hashCode() and equals() to compare the Hash value and KEY with each element of the linked list, delete if they are the same, and return null if they are different;
  5. After deletion size-1 (the number of key-value mappings), the linked list is re-associated, and the next Node replaces the currently deleted Node;

2. Features of HashSet

  1. HashSet cannot guarantee the order of elements, because HashMap is unordered;
  2. It is allowed to store null elements, but elements are not allowed to be repeated, and threads are not safe;
  3. The addition and deletion speed is fast, but the query speed is slow;
  4. There is no get method, and elements can be traversed through the iterator() method;

3. think

Why HashSet has no get method

  1. HashSet is implemented based on HashMap (array + linked list + red-black tree). The order of insertion cannot be guaranteed, and elements cannot be obtained directly through indexes. Therefore, the get method is unnecessary, and elements can be traversed through the iterator() method;

Why can't the get method of HashSet be implemented through the get method of HashMap

  1. HashMap uses the key to find the stored value (value), and the value stored by HashSet with the help of HashMap is a PRESENT object. If it is implemented through the get method of HashMap, then the meaning of HashMap to find the value through the key will be lost.

Why does HashSet rewrite the hashCode method and equals method when storing objects?

Rewrite hashCode method and equals method

  1. When adding objects to HashSet, it will first judge whether the hashCode is consistent, and if it is consistent, then call the equals method to determine whether the content is consistent.
  2. If the custom object does not override the hashCode method, the hashCode of the parent class Object will be used. The hashCode of Object will have a different hash value every time an object is new. If it is not rewritten, even the same object can be added.
  3. Even if the hashCode method is rewritten, there is a chance that the same hash value will be returned when the object is stored, that is, a hash collision. At this time, it is necessary to use the equals method to determine whether the object content is the same. If it is the same: do not add; if it is not the same: is added to the linked list under the same index.

2. TreeSet

1. The underlying implementation of TreeSet

  1. The bottom layer of TreeSet is implemented based on TreeMap, so the underlying structure is also a red-black tree, and there is no need to rewrite the hashCode() and equals() methods.
  2. The collection elements in TreeSet are actually saved by the key of TreeMap, and the value of TreeMap stores a PRESENT, which is a static Object object modified by static final;

add

    public boolean add(E e) {
    
    
        //调用TreeMap的put方法    下面的源码请到TreeMap观看
        return m.put(e, PRESENT)==null;
    }
  1. When adding elements to the TreeSet, the put() method of the TreeMap is actually called, which first determines whether the root node is empty;
  2. Empty: create a root node and store elements in the root node;
  3. Not empty: the root node is the initial node to retrieve;
  4. Circularly use the comparator to compare keys. If there is no comparator passed in, use the default comparator to compare the key of the current node with the key of the newly added node until a suitable node is retrieved; a. Comparator returns = 0, then
    both If two key values ​​are equal, the new value will overwrite the old value and return the new value;
    b. If the comparator returns >0, the value of the newly added node is larger, and the right child node of the current node will be used as the new current node; c.
    Compare If the indicator returns <0, the value of the newly added node is smaller, and the left child node of the current node is used as the new current node;
  5. Compare the newly added node with the found node. If the newly added node is larger, it will be added as the right child node, otherwise it will be added as the left child node.
  6. Call the fixAfterInsertion(e) method to repair the red-black tree by changing color and turning left and right to maintain balance, and the number of TreeMaps is +1;

remove

    public boolean remove(Object o) {
    
    
       //调用TreeMap的remove方法    下面的源码请到TreeMap观看
        return m.remove(o)==PRESENT;
    }
  1. When deleting elements in the TreeSet, the remove() method of the TreeMap is actually called. When deleting elements in the TreeMap, the node to be deleted will be obtained first;
  2. Determine whether the comparator is empty, if it is empty, use the default comparator, use the comparator to compare keys, compare the key of the current node with the key of the obtained node, until a suitable node is retrieved; a. The comparator returns = 0
    , If the two key values ​​are equal, return the current node;
    b. If the comparator returns >0, the value is larger for the current node, and returns the element on the right of the node;
    c. If the comparator returns <0, then the value is smaller for the current node, return the element to the left of the node;
  3. Determine whether the obtained node is null? Yes: return null directly;
  4. If it is not a node, call the deleteEntry§ method to delete it according to the red-black tree method;
  5. Call the fixAfterDeletion(replacement) method to repair the red-black tree by changing color and turning left and right to maintain balance, and the number of TreeMaps is -1;

2. The characteristics of TreeSet

  1. TreeSet guarantees the uniqueness of elements, and the elements are ordered, and does not support storing null elements, because the keys of TreeMap are ordered;
  2. The elements in the TreeSet support two sorting methods: natural sorting or sorting according to the Comparator provided when the TreeSet was created. It depends on the constructor used.
  3. TreeSet threads are not synchronized, and multiple threads cannot share data;

Two, Map collection diagram

insert image description here

1. HashMap

1. The underlying implementation of HashMap

  1. HashMap is implemented based on hash table (array + linked list), JDK1.8 introduced red-black tree;
  2. The storage method of HashMap is Key-Value. JDK1.7 uses Entry[ ] as the underlying storage data array, and JDK1.8 adds red-black tree, so it is changed to Node[ ];
  3. The Node node is a one-way linked list structure, which can be connected to the next Node node to resolve Hash conflicts;
    insert image description here

PUT method

    public V put(K key, V value) {
    
    
        return putVal(hash(key), key, value, false, true);
    }
    
    /**
     * 根据key口算hash值
     */
    static final int hash(Object key) {
    
    
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
	                   boolean evict) {
    
    
	        Node<K,V>[] tab; Node<K,V> p; int n, i;
	        //如果数组为null或数组长度为0 
	        if ((tab = table) == null || (n = tab.length) == 0)
	       		//则初始化一个数组
	            n = (tab = resize()).length;
	        //根据hash值和数组长度计算出索引位置且该位置为null
	        if ((p = tab[i = (n - 1) & hash]) == null)
	            //直接插入数据
	            tab[i] = newNode(hash, key, value, null);
	        else {
    
    
	            Node<K,V> e; K k;
	            //如果key的hash值相同且key也相同  说明存入重复
	            if (p.hash == hash &&
	                ((k = p.key) == key || (key != null && key.equals(k))))
	                //直接覆盖
	                e = p;
	            //判断该节点是否为树节点
	            else if (p instanceof TreeNode)
	            	//为树节点 直接存入红黑树
	                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
	            else {
    
    
	               //循环链表
	                for (int binCount = 0; ; ++binCount) {
    
    
	                	//循环到链表最后一个元素,则表示插入元素与链表存在的元素没有相同的
	                    if ((e = p.next) == null) {
    
    
	                       // 插入元素生成新的Node并与上一个Node关联
	                        p.next = newNode(hash, key, value, null);
	                        //判断链表的长度是否大于8
	                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
	                            //树化 转为红黑树 
	                            //treeifyBin方法里判断数组的长度是否达到64,
	                            //如果没有达到进行扩容,不会树化,达到才会树化)  
	                            treeifyBin(tab, hash);
	                        break;
	                    }
	                    //遍历链表,对比链表上的key与插入的key是否一致且hash是否相等 
	                    if (e.hash == hash &&
	                        ((k = e.key) == key || (key != null && key.equals(k))))
	                        break;
	                    p = e;
	                }
	            }
	            if (e != null) {
    
     // existing mapping for key
	                V oldValue = e.value;
	                if (!onlyIfAbsent || oldValue == null)
	                    e.value = value;
	               //将元素添加到双向链表的尾部
	                afterNodeAccess(e);
	                return oldValue;
	            }
	        }
	        ++modCount;
	        //判断是否需要扩容
	        if (++size > threshold)
	            resize();
	        // 这他喵的是干嘛用的啊????   
	        afterNodeInsertion(evict);
	        return null;
	    }
    
  1. When adding elements to the HashMap, it will first determine whether the array is empty;
  2. If the array is empty, call the resize() method to initialize an array with a length of 16 and an expansion factor of 0.75, expand to the power of 2, calculate the hash value of the key and put it into the corresponding array;
  3. The array is not empty, calculate the hash value of the key to get the subscript of the array, and judge whether there is data in the position of the subscript array, if not, insert the data directly;
  4. (Hash值相同不一定是同一个对象所以还要调用equals)There is data in the position of the subscript array, compare whether the Hash value and KEY of the two objects are the same on the linked list , if yes: overwrite;
  5. Not the same object, judging whether it is a tree node of a red-black tree (may have been converted into a red-black tree), yes: add directly,
  6. If it is neither the same object nor a tree node, it is added to the end of the linked list to determine whether the length of the linked list is greater than 8, and whether the length of the array reaches 64;
  7. If the length of the linked list is 8 and the length of the array reaches 64, then the linked list will evolve into a red-black tree. If the linked list is 8 and the array length is less than 64, resize() will be performed to double the number of buckets, and the linked list will be split into different buckets to reduce the size of a single linked list.
  8. Finally, it is judged whether the size of the hashmap reaches the threshold, which is: expand resize().

GET method

    public V get(Object key) {
    
    
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }


    final Node<K,V> getNode(int hash, Object key) {
    
    
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //数组不为空且长度大于0且计算出的索引位置有值
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
    
    
            //与第一个first节点的hash值相等且key也一致
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                //返回first节点
                return first;
             //下一个节点不为null
            if ((e = first.next) != null) {
    
    
                //判断first节点是否属于树节点
                if (first instanceof TreeNode)
                    //直接从树节点中获取
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
    
    
                    //循环遍历链表与链表的每一个元素进行Hash值和KEY的对比
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        //相同则直接返回元素
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

  1. When the get method is called, calculate the hash value of the key to get the subscript of the array, and determine whether there is data in the position of the subscript array, and return null if it does not exist;
  2. If there is data, call hashCode() and equals() to compare the Hash value and KEY with the first element, and return the first element directly if they are the same;
  3. If they are not the same, it is judged whether it belongs to a tree node, yes: return directly;
  4. If it is neither the first element object nor a tree node, call hashCode() and equals() to compare the Hash value and KEY with each element of the linked list. If they are the same, return the element directly, and return null if they are different;

REMOVE method

    public V remove(Object key) {
    
    
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
    
    
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
    
    
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        //数组不为空且长度大于0且计算出的索引位置有值
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
    
    
            Node<K,V> node = null, e; K k; V v;
             //与第一个节点的hash值相等且key也一致
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //返回当前节点
                node = p;
            else if ((e = p.next) != null) {
    
    
               //判断节点是否属于树节点
                if (p instanceof TreeNode)
                   //直接从树节点中查找
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
    
    
                    do {
    
    
                      //循环遍历链表与链表的每一个元素进行Hash值和KEY的对比
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
    
    
                            //相同则直接返回元素
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //节点部位null 则删除节点
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
    
    
                 //属于树节点
                if (node instanceof TreeNode)
                    //从3树中删除
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                //删除为头节点
                else if (node == p)
                    //由链表的下一个节点变为头节点
                    tab[index] = node.next;
                else
                	//被删除节点的上一个节点和下一个节点关联
                    p.next = node.next;
                ++modCount;
                --size;
                //被删除的节点 解除链表关联
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

  1. When the remove method is called, calculate the hash value of the key to get the subscript of the array, and determine whether there is data in the position of the subscript array, and return null if it does not exist;
  2. If there is data, call hashCode() and equals() to compare the Hash value and KEY with the first element, and if they are the same, delete the first element directly;
  3. If they are not the same, determine whether it belongs to a tree node, yes: directly delete, and judge the number of nodes in the red-black tree, if the number of nodes is less than 6, it will degenerate into a linked list;
  4. If it is neither the first element object nor a tree node, call hashCode() and equals() to compare the Hash value and KEY with each element of the linked list, delete if they are the same, and return null if they are different;
  5. After deletion size-1 (the number of key-value mappings), the linked list is re-associated, and the next Node replaces the currently deleted Node;

RESIZE expansion method

    final Node<K,V>[] resize() {
    
    
        Node<K,V>[] oldTab = table;
        //旧数组长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //旧数组扩容阈值
        int oldThr = threshold;
        //新数组长度   新数组扩容阈值
        int newCap, newThr = 0;
        if (oldCap > 0) {
    
    
            //旧数组的长度大于最大默认值
            if (oldCap >= MAXIMUM_CAPACITY) {
    
    
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //旧数组的长度通过左移运算扩大2倍,赋值给新数组的长度且小于最大默认值且旧数组的长度大于初始化的值
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 旧数组扩容阈值通过左移运算扩大2倍赋值给新数组扩容阈值
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold 翻译:初始容量设为阈值
           //旧数组扩容阈值赋值给新数组的长度
            newCap = oldThr;
        else {
    
     // zero initial threshold signifies using defaults 翻译:初始阈值为零表示使用默认值
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //新数组扩容阈值为0
        if (newThr == 0) {
    
    
            //新数组长度乘以扩容因子得到新数组扩容阈值
            float ft = (float)newCap * loadFactor;
            //设置新数组扩容阈值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({
    
    "rawtypes","unchecked"})
        //初始化新的数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
    
    
            for (int j = 0; j < oldCap; ++j) {
    
    
                Node<K,V> e;
                //如果旧数组碎银位置元素部位null
                if ((e = oldTab[j]) != null) {
    
    
                    oldTab[j] = null;
                    //旧数组的下一个元素为null
                    if (e.next == null)
                       //当前位置只有一个数据,并不是链表或树,重新计算索引放入新数组
                        newTab[e.hash & (newCap - 1)] = e;
                    //旧数组的下一个元素不为null  判断是否为树节点
                    else if (e instanceof TreeNode)
                        //为树节点   进行树节点的操作
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else {
    
     // preserve order
                        //当前索引位置为链表,将链表分为高低链表存储
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
    
    
                            next = e.next;
                            //(e.hash & oldCap) == 0为低位链表
                            if ((e.hash & oldCap) == 0) {
    
    
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
    
    
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
    
    
                            loTail.next = null;
                            //低位链表在当前索引的位置
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
    
    
                            hiTail.next = null;
                             //高位链表在当前索引加旧的数组长度位置
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
  1. If the array is empty, initialize the array with a default length of 16, and initialize the threshold (expansion factor * array length);
  2. If the array is not empty and the length of the array is greater than the maximum capacity, set the threshold of the array to the maximum value (threshold = Integer.MAX_VALUE;) Return the array;
  3. If the array is not empty, set the threshold *2 of the array to the threshold of the new array. To expand the array, it is necessary to recalculate the position of each element in the array after expansion;
  4. Determine whether the current index of the original array has a value, if it is empty, continue to judge the next index;
  5. If it is not empty, judge whether the element points to the next link for the element, if not, put it into the array;
  6. If it points to the next link, determine whether the element is a tree node, and if so, perform tree operations;
  7. If it is not a tree node, traverse the linked list, if ((e.hash & oldCap) == 0) it is a low-level linked list, otherwise it is a high-level linked list;
  8. And put elements into linked list, low linked list newTab[j] = loHead, high linked list newTab[j+oldCap] = hiHead;

2. Features of HashMap

  1. HashMap accepts null key (key) and value (value), but there can only be one null key, and the elements are unordered;
  2. The bottom layer of HashMap uses a hash table to combine the advantages of the array structure and the linked list structure, and the addition, deletion, modification and query are very fast;
  3. HashMap is asynchronous (synchronized), thread is not safe, multiple threads cannot share HashMap;

3. think

Why is the length of the HashMap array a power of 2?

  1. In order to minimize collisions and distribute the data as evenly as possible, as long as the hash function is mapped evenly and loosely, collisions are generally difficult to occur.
  2. Convenient bit operation, use binary bit operation when calculating the index position & (tab[i = (n - 1) & hash]);

In the remainder operation, if the divisor is a power of 2, it is equivalent to the operation of subtracting one from the divisor with (&)
hash&(length-1) = hash%length

How did the HashMap high and low linked list come from?
After the array is expanded, the index needs to be recalculated, and the index of the original linked list will be recalculated to the new array. However, since the array length of hashMap is 2 to the nth power, each expansion makes the array length: newlength = 2* oldlength; and the method of calculating the index is: hash & length; these conditions determine the downsizing of all elements in the original array after expansion The subscript has only two values: unchanged, the original subscript + oldlength;
using this property, the linked list corresponding to each index can be split into two linked lists, which are the so-called high and low linked lists;
for example:

In the remainder operation, if the divisor is a power of 2, it is equivalent to the operation of subtracting one from the divisor with (&)
hash&(length-1) = hash%length

The length of the original array is 16, assuming that the hash value of the added element is 1689, calculate according to hash & length(1689%16 or 1689 & 15) and wait until the index is 9;
first expand the array to 32, the element with the index of 9 in the original array needs to be recalculated Index, according to hash & length(1689%32 or 1689 & 31), the index is 25 (original subscript + original array length), according to hash & old_length(1689 & 16 != 0), the element will be allocated to the high-level linked list at this time;
expand the array again To 64, the element whose original array index is 25 needs to recalculate the index. According to hash & length(1689%64 or 1689 & 63), the index is 25, and the index has not changed. According to hash & old_length(1689 & 32==0), the element will be is assigned to the low linked list;

2. TreeMap

1. The underlying implementation of TreeMap

TreeMap is implemented based on the Red-Black tree, and the addition and deletion methods follow the i Red-Black tree;

PUT method

public V put(K key, V value) {
    
    
        Entry<K,V> t = root;
        //根节点为null说明首次添加
        if (t == null) {
    
    
            compare(key, key); // type (and possibly null) check 翻译:类型(可能为null)检查
			//创捷根节点
            root = new Entry<>(key, value, null);
            //设置集合元素为1
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        //比较器
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
    
    
            do {
    
    
              //此处parent不是根节点, 是本次节点的父节点
                parent = t;
                //使用比较器比较key
                cmp = cpr.compare(key, t.key);
                //小于0
                if (cmp < 0)
                    //存在节点的左边
                    t = t.left;
                //大于0
                else if (cmp > 0)
                    //存在节点的右边
                    t = t.right;
                else
                   //当前节点  直接覆盖
                    return t.setValue(value);
            } while (t != null);
        }
        else {
    
    
           //key为null  抛出异常
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
    
    
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        //设置新插入的元素,并关联其父节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        //根据比较器返回的值是否小于0,来判断父节点是左或右关联
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        //插入后修复平衡红黑树
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
  1. When adding elements to the TreeMap, it will first determine whether the root node is empty;
  2. Empty: create a root node and store elements in the root node;
  3. Not empty: the root node is the initial node to retrieve;
  4. Circularly use the comparator to compare keys. If there is no comparator passed in, use the default comparator to compare the key of the current node with the key of the newly added node until a suitable node is retrieved; a. Comparator returns = 0, then
    both If two key values ​​are equal, the new value will overwrite the old value and return the new value;
    b. If the comparator returns >0, the value of the newly added node is larger, and the right child node of the current node will be used as the new current node; c.
    Compare If the indicator returns <0, the value of the newly added node is smaller, and the left child node of the current node is used as the new current node;
  5. Compare the newly added node with the found node. If the newly added node is larger, it will be added as the right child node, otherwise it will be added as the left child node.
  6. Call the fixAfterInsertion(e) method to repair the red-black tree by changing color and turning left and right to maintain balance, and the number of TreeMaps is +1;

GET method

    public V get(Object key) {
    
    
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }

	
    final Entry<K,V> getEntry(Object key) {
    
    
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        //key为null  抛出异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
    
    
            //使用比较器比较key
            int cmp = k.compareTo(p.key);
             //小于0
            if (cmp < 0)
                //存在节点的左边
                p = p.left;
              //大于0
            else if (cmp > 0)
            	//存在节点的右边
                p = p.right;
            else
               //返回当前节点
                return p;
        }
        return null;
    }


    final Entry<K,V> getEntryUsingComparator(Object key) {
    
    
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
    
    
            Entry<K,V> p = root;
            while (p != null) {
    
    
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

  1. When getting elements into the TreeMap, it will first judge whether the comparator is empty, and if it is empty, use the default comparator;
  2. Circularly use the comparator to compare keys, compare the key of the current node with the key of the obtained node, until a suitable node is retrieved; a. If the
    comparator returns = 0, the two key values ​​are equal, and the current node is returned;
    b. The comparator returns >0, the value is larger for the current node, and the element on the right of the node is returned;
    c. If the comparator returns <0, the value is smaller for the current node, and the element on the left of the node is returned;

remove method

It is recommended to understand the addition and deletion of red-black trees first.

    public V remove(Object key) {
    
    
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

	
    final Entry<K,V> getEntry(Object key) {
    
    
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        //key为null  抛出异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
    
    
            //使用比较器比较key
            int cmp = k.compareTo(p.key);
             //小于0
            if (cmp < 0)
                //存在节点的左边
                p = p.left;
              //大于0
            else if (cmp > 0)
            	//存在节点的右边
                p = p.right;
            else
               //返回当前节点
                return p;
        }
        return null;
    }


	private void deleteEntry(Entry<K,V> p) {
    
    
	        modCount++;
	        size--;
	
	        // If strictly internal, copy successor's element to p and then make p
	        // point to successor.
	        if (p.left != null && p.right != null) {
    
    
	           // 获取后继结点
	            Entry<K,V> s = successor(p);
	            //将删除节点与后继节点的值对换
	            p.key = s.key;
	            p.value = s.value;
	            p = s;
	        } // p has 2 children
	
	        // Start fixup at replacement node, if it exists.翻译:如果替换节点存在,则在替换节点启动修复
	        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
			//替换节点不为null
	        if (replacement != null) {
    
    
	            // Link replacement to parent
	            replacement.parent = p.parent;
	            if (p.parent == null)
	                root = replacement;
	            else if (p == p.parent.left)
	                p.parent.left  = replacement;
	            else
	                p.parent.right = replacement;
	
	            // Null out links so they are OK to use by fixAfterDeletion.
	            p.left = p.right = p.parent = null;
	
	            // Fix replacement
	            if (p.color == BLACK)
	                fixAfterDeletion(replacement);
	        } else if (p.parent == null) {
    
     // return if we are the only node.
	            root = null;
	        } else {
    
     //  No children. Use self as phantom replacement and unlink.
	            if (p.color == BLACK)
	                fixAfterDeletion(p);
	
	            if (p.parent != null) {
    
    
	                if (p == p.parent.left)
	                    p.parent.left = null;
	                else if (p == p.parent.right)
	                    p.parent.right = null;
	                p.parent = null;
	            }
	        }
	    }



    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    
    
        if (t == null)
            return null;
        else if (t.right != null) {
    
    
            //获取删除节点的右子节点
            Entry<K,V> p = t.right;
            //获取右子节点的最左边的节点
            while (p.left != null)
                p = p.left;
            return p;
        } else {
    
    
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
    
    
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }
  1. When deleting elements in the TreeMap, the node to be deleted will be obtained first;
  2. Determine whether the comparator is empty, if it is empty, use the default comparator, use the comparator to compare keys, compare the key of the current node with the key of the obtained node, until a suitable node is retrieved; a. The comparator returns = 0
    , If the two key values ​​are equal, return the current node;
    b. If the comparator returns >0, the value is larger for the current node, and returns the element on the right of the node;
    c. If the comparator returns <0, then the value is smaller for the current node, return the element to the left of the node;
  3. Determine whether the obtained node is null? Yes: return null directly;
  4. If it is not a node, call the deleteEntry§ method to delete it according to the red-black tree method;
  5. Call the fixAfterDeletion(replacement) method to repair the red-black tree by changing color and turning left and right to maintain balance, and the number of TreeMaps is -1;

2. Features of TreeMap

  1. TreeMap is implemented based on a red-black tree. The average and worst time complexity of adding, deleting, modifying and checking are both O(logn), and the biggest feature is that the keys are in order.
  2. TreeMap specifies the Comparator, and the key can be null. If the default comparator is used, the key is not allowed to be null.

Guess you like

Origin blog.csdn.net/packge/article/details/126759777