Chapter 13 data structure and algorithm

Chapter 13 data structure and algorithm

primary coverage

  • data structure

Learning objectives

  • Have a preliminary understanding of data structure
  • Master the implementation of dynamic array
  • Master the implementation of single linked list and double linked list
  • Master the implementation of hash table

Chapter 13 data structure and algorithm

13.1 data structure

Data structure is to study the logical and physical structure of data and their relationship, define corresponding operations for this structure, and ensure that the new structure obtained after these operations is still the original structure type.

The logical structure of data refers to the logical relationship between data elements, regardless of their storage location in the computer:

  • Set (the concept of set in Mathematics): there is no other relationship between the elements in the data structure except the mutual relationship of "belonging to the same set";
  • Linear structure: there is a one-to-one relationship between the elements in the data structure;
  • Tree structure: there is a one to many relationship between the elements in the data structure;
  • Graphic structure: there is a many to many relationship between the elements in the data structure.

Physical structure / storage structure of data: it describes the specific storage of data in memory (such as sequential structure, chain structure, index structure, hash structure), etc. a data logical structure can be expressed as one or more physical storage structures.

Data structure and algorithm is a complete and complex course, so today we just briefly discuss several common data structures, so that we can have a preliminary understanding of data structure and algorithm.

13.2 dynamic array

13.2.1 characteristics of dynamic array

Logical structure: linear

Physical structure: sequential structure

Apply for memory: apply for a large section of continuous space at a time. Once applied, the memory will be fixed.

Storage features: all data is stored in this continuous space. Each element in the array is a specific data (or object). All data are closely arranged without interval.

For example: integer array

For example: object array

13.2.2 basic operation of dynamic array

Data operations related to data structures:

  • insert
  • delete
  • modify
  • lookup
  • ergodic
public interface Container<E> extends Iterable<E>{
	void add(E e);
	void insert(int index,E value);
	void delete(E e);
	void delete(int index);
	E update(int index, E value);
	void update(E old, E value);
	boolean contains(E e);
	int indexOf(E e);
	E get(int index);
	Object[] getAll();
	int size();
}

13.2.3 implementation of dynamic array

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class MyArrayList<E> implements Container<E>{
	private Object[] all;
	private int total;
	
	public MyArrayList(){
		all = new Object[5];
	}

	@Override
	public void add(E e) {
		ensureCapacityEnough();
		all[total++] = e;
	}

	private void ensureCapacityEnough() {
		if(total >= all.length){
			all = Arrays.copyOf(all, all.length*2);
		}
	}

	@Override
	public void insert(int index, E value) {
		//Need to expand capacity
		ensureCapacityEnough();
		addCheckIndex(index);
		if(total-index>0) {
			System.arraycopy(all, index, all, index+1, total-index);
		}
		all[index]=value;
		total++;
	}

	private void addCheckIndex(int index) {
		if(index<0 || index>total){
			throw new IndexOutOfBoundsException(index+"Cross the border");
		}
	}

	@Override
	public void delete(E e) {
		int index = indexOf(e);
		if(index==-1){
			throw new NoSuchElementException(e+"non-existent");
		}
		delete(index);
	}

	@Override
	public void delete(int index) {
		checkIndex(index);
		if(total-index-1>0) {
			System.arraycopy(all, index+1, all, index, total-index-1);
		}
		all[--total] = null;
	}

	private void checkIndex(int index) {
		if(index<0 || index>total){
			throw new IndexOutOfBoundsException(index+"Cross the border");
		}
	}

	@Override
	public E update(int index, E value) {
		checkIndex(index);
		E oldValue = get(index);
		all[index]=value;
		return oldValue;
	}
	
	@Override
	public void update(E old, E value) {
		int index = indexOf(old);
		if(index!=-1){
			update(index, value);
		}
	}

	@Override
	public boolean contains(E e) {
		return indexOf(e) != -1;
	}

	@Override
	public int indexOf(E e) {
		int index = -1;
		if(e==null){
			for (int i = 0; i < total; i++) {
				if(e == all[i]){
					index = i;
					break;
				}
			}
		}else{
			for (int i = 0; i < total; i++) {
				if(e.equals(all[i])){
					index = i;
					break;
				}
			}
		}
		return index;
	}

	@SuppressWarnings("unchecked")
	@Override
	public E get(int index) {
		checkIndex(index);
		return (E) all[index];
	}

	@Override
	public Object[] getAll() {
		return Arrays.copyOf(all, total);
	}

	@Override
	public int size() {
		return total;
	}

	@Override
	public Iterator<E> iterator() {
		return new Itr();
	}
	
	private class Itr implements Iterator<E>{
		private int cursor;

		@Override
		public boolean hasNext() {
			return cursor!=total;
		}

		@SuppressWarnings("unchecked")
		@Override
		public E next() {
			return (E) all[cursor++];
		}

		@Override
		public void remove() {
			MyArrayList.this.delete(--cursor);
		}
		
	}
}

13.2.4 dynamic array test

import java.util.Arrays;
import java.util.Iterator;

import org.junit.Test;

public class TestMyArrayList {
	@Test
	public void test01(){
		MyArrayList<String> my = new MyArrayList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		System.out.println("Number of elements:" + my.size());
		Object[] all = my.getAll();
		System.out.println(Arrays.toString(all));
		
		my.insert(2, "Shang Silicon Valley");
		System.out.println("Number of elements:" + my.size());
		all = my.getAll();
		System.out.println(Arrays.toString(all));
	}
	
	@Test
	public void test02(){
		MyArrayList<String> my = new MyArrayList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		my.delete(1);
		System.out.println("Number of elements:" + my.size());
		Object[] all = my.getAll();
		System.out.println(Arrays.toString(all));
		
		my.delete("atguigu");
		System.out.println("Number of elements:" + my.size());
		all = my.getAll();
		System.out.println(Arrays.toString(all));
	}
	
	@Test
	public void test03(){
		MyArrayList<String> my = new MyArrayList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		String update = my.update(3, "Shang Silicon Valley");
		System.out.println("Number of elements:" + my.size());
		System.out.println("Replaced by:" + update);
		Object[] all = my.getAll();
		System.out.println(Arrays.toString(all));
		
		my.update("java", "Java");
		System.out.println("Number of elements:" + my.size());
		System.out.println("Replaced by: java");
		all = my.getAll();
		System.out.println(Arrays.toString(all));
	}
	
	@Test
	public void test04(){
		MyArrayList<String> my = new MyArrayList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		System.out.println(my.contains("java"));
		System.out.println(my.indexOf("java"));
		System.out.println(my.get(0));
	}
	
	@Test
	public void test05(){
		MyArrayList<String> my = new MyArrayList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		for (String string : my) {
			System.out.println(string);
		}
	}
	
	@Test
	public void test06(){
		MyArrayList<String> my = new MyArrayList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		Iterator<String> iterator = my.iterator();
		while(iterator.hasNext()) {
			String next = iterator.next();
			if(next.length()>4) {
				iterator.remove();
			}
		}
		for (String string : my) {
			System.out.println(string);
		}
	}
}

13.2.5 dynamic array in Java core class library

The implementation class of Java's List interface has the implementation of two dynamic arrays: Vector and ArrayList.

1. What is the difference between ArrayList and Vector?

Their underlying physical structures are arrays, which we call dynamic arrays.

  • ArrayList is a new version of dynamic array, which is thread unsafe and efficient. Vector is an old version of dynamic array, which is thread safe and inefficient.
  • The capacity expansion mechanism of dynamic arrays is different. The capacity expansion of ArrayList is 1.5 times that of the original, and the capacity expansion of Vector is 2 times that of the original.
  • The initialization capacity of the array. If the initialization capacity is not explicitly specified when building the collection objects of ArrayList and Vector, the initial capacity of the internal array of Vector is 10 by default, while ArrayList in JDK1.6 and earlier versions is 10, while ArrayList in JDK1.7 and later versions is initialized as an empty array with length of 0. After adding the first element, Then create an array with a length of 10.
  • Because the version of Vector is old, it supports Enumeration iterators. However, the iterator does not support fast failures. Iterator and ListIterator iterators support fast failures. If the Vector is structurally modified at any time after the iterator is created (by any means other than the iterator's own remove or add method), the iterator will throw a ConcurrentModificationException. Therefore, in the face of concurrent modifications, the iterator quickly fails completely, rather than risking arbitrary uncertain behavior at an uncertain time in the future.

2. Source code analysis

(1) Vector source code analysis
    public Vector() {
        this(10);//Specify an initial capacity of 10
    }
	public Vector(int initialCapacity) {
        this(initialCapacity, 0);//Specify a capacityIncrement increment of 0
    }
    public Vector(int initialCapacity, int capacityIncrement Increment is 0) {
        super();
        //The validity of the formal parameter initial capacity initialCapacity is judged
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //Created an array of type Object []
        this.elementData = new Object[initialCapacity];//The default is 10
        //Increment: the default is 0. If it is 0, it will be increased by 2 times later. If it is not 0, it will be increased by the specified increment later
        this.capacityIncrement = capacityIncrement;
    }
//synchronized means thread safe   
	public synchronized boolean add(E e) {
        modCount++;
    	//See if capacity expansion is needed
        ensureCapacityHelper(elementCount + 1);
    	//Store the new element in [elementCount]. After saving, the number of elementCount elements increases by 1
        elementData[elementCount++] = e;
        return true;
    }

    private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        //See if the capacity of the current array is exceeded
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//Capacity expansion
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//Gets the length of the current array
        //If the capacity increment is 0, the new capacity = 2 times the oldCapacity
        //If the capacityIncrement increment is 0, the new capacity = oldCapacity + capacityIncrement increment;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        
        //If the new capacity calculated above is not enough, expand the minCapacity according to the minimum capacity you specify
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        
        //If the new capacity exceeds the maximum array limit, it is processed separately
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        
        //Copy the data from the old array to the new array. The length of the new array is newCapacity
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    public boolean remove(Object o) {
        return removeElement(o);
    }
    public synchronized boolean removeElement(Object obj) {
        modCount++;
        //Find the subscript of obj in the current Vector
        int i = indexOf(obj);
        //If I > = 0, it indicates that it exists. Delete the element at [i]
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }
    public int indexOf(Object o) {
        return indexOf(o, 0);
    }
    public synchronized int indexOf(Object o, int index) {
        if (o == null) {//The element to find is a null value
            for (int i = index ; i < elementCount ; i++)
                if (elementData[i]==null)//If it is a null value, use = = null to judge
                    return i;
        } else {//The element to find is a non null value
            for (int i = index ; i < elementCount ; i++)
                if (o.equals(elementData[i]))//If it is a non null value, use equals to judge
                    return i;
        }
        return -1;
    }
    public synchronized void removeElementAt(int index) {
        modCount++;
        //Judge the legitimacy of subscript
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        
        //j is the number of elements to be moved
        int j = elementCount - index - 1;
        //If the element needs to be moved, call System.arraycopy to move it
        if (j > 0) {
            //Move the index+1 position and the following elements forward
            //Move the element at the position of index+1 to the position of index, and so on
            //Move j in total
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        //The total number of elements decreased
        elementCount--;
        //Empty the location of elementData[elementCount] to add new elements. The elements in the location are waiting to be recycled by GC
        elementData[elementCount] = null; /* to let gc do its work */
    }
(2) ArrayList source code analysis

JDK1.6:

    public ArrayList() {
		this(10);//Specify an initial capacity of 10
    }
    public ArrayList(int initialCapacity) {
		super();
        //Check the validity of the initial capacity
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //The array is initialized to an array of length initialCapacity
		this.elementData = new Object[initialCapacity];
    }

JDK1.7

    private static final int DEFAULT_CAPACITY = 10;//Default initial capacity 10
	private static final Object[] EMPTY_ELEMENTDATA = {};
	public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;//The array is initialized to an empty array
    }
    public boolean add(E e) {
        //Check whether the current array has enough more than one element
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {//If the current array is still empty
            //minCapacity is processed according to the default initial capacity and the maximum value in minCapacity
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
		//See if capacity expansion is required
        ensureExplicitCapacity(minCapacity);
    }
	//...

JDK1.8

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//Initialize to an empty array
    }
    public boolean add(E e) {
        //Check whether the current array has enough more than one element
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        
        //Save the new element to the [size] position, and then increase the size by 1
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        //If the current array is still empty
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //Then minCapacity takes default_ Maximum value of capacity and minCapacity
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
		//Check whether capacity expansion is required
        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//Number of modifications plus 1

        // If the required minimum capacity is larger than the length of the current array, that is, the current array is not enough, expand the capacity
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//Current array capacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);//The capacity of the new array is 1.5 times that of the old array
        //See if 1.5 times the old array is enough
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //See if the 1.5 times of the old array exceeds the maximum array limit
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        
        //Copy a new array
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    public boolean remove(Object o) {
        //First find o the subscript in the array of the current ArrayList
        //Discuss whether o is blank or not
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {//null values are compared with = =
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {//Non null values are compared with equals
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    private void fastRemove(int index) {
        modCount++;//Number of modifications plus 1
        //Number of elements to be moved
        int numMoved = size - index - 1;
        
        //If you need to move elements, use System.arraycopy to move elements
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        
        //Empty the elementData[size-1] position to allow the GC to reclaim space and reduce the number of elements
        elementData[--size] = null; // clear to let GC do its work
    }
    public E remove(int index) {
        rangeCheck(index);//Check whether the index is legal

        modCount++;//Number of modifications plus 1
        
        //Take out the element at [index]. The element at [index] is the element to be deleted. It is used to return the deleted element finally
        E oldValue = elementData(index);
        
		//Number of elements to be moved
        int numMoved = size - index - 1;
        
        //If you need to move elements, use System.arraycopy to move elements
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //Empty the elementData[size-1] position to allow the GC to reclaim space and reduce the number of elements
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
    public E set(int index, E element) {
        rangeCheck(index);//Check whether the index is legal

        //Take out the element at [index]. The element at [index] is the element to be replaced, which is used to return the replaced element finally
        E oldValue = elementData(index);
        //Replace the element at [index] with element
        elementData[index] = element;
        return oldValue;
    }
    public E get(int index) {
        rangeCheck(index);//Check whether the index is legal

        return elementData(index);//Returns the element at the [index] position
    }
    public int indexOf(Object o) {
        //There are two cases: o is empty or not
        if (o == null) {
            //Look from front to back
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    public int lastIndexOf(Object o) {
         //There are two cases: o is empty or not
        if (o == null) {
            //Look from the back
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

13.3 chain storage structure

Logical structure: linear and nonlinear

Physical structure: continuous storage space is not required

Storage features: the data must be encapsulated in the "node". The node contains multiple data items. The data value is only one of the data items, and the other data items are used to record the address of the related node.

For example, here are several common chain storage structures (far more than these, of course)

Linked list

Single linked list

Single linked list node:

class Node{
	Object data;
	Node next;
	public Node(Object data, Node next) {
		this.data = data;
		this.next = next;
	}
}

Single linked list:

public class OneWayLinkedList<E>{
	private Node<E> head;//Head node
	private int total;//Record the actual number of elements
	
	private static class Node<E>{
		E data;
		Node<E> next;
		Node(E data, Node<E> next) {
			this.data = data;
			this.next = next;
		}
	}
}

Double linked list

Double linked list node:

class Node{
	Node prev;
	Object data;
	Node next;
	public Node(Node prev, Object data, Node next) {
		this.prev = prev;
		this.data = data;
		this.next = next;
	}
}

Bidirectional linked list:

public class LinkedList<E>{
	private Node<E> first;//Head node
    private Node<E> last;//Tail node
	private int total;//Record the actual number of elements
	
	private static class Node<E>{
        Node<E> prev;
		E data;
		Node<E> next;
		Node(Node<E> prev, E data, Node<E> next) {
            this.prev = prev;
			this.data = data;
			this.next = next;
		}
	}
}

Binary tree

Basic structure of binary tree implementation

class Node{
	Node parent;
	Node left;
	Object data;
	Node right;
	public Node(Node parent,Node left, Object data, Node right) {
		this.parent = parent;
		this.left = left;
		this.data = data;
		this.right = right;
	}
}

Binary tree

public class BinaryTree<E>{
    private Node<E> root;
    private int total;
    
    private static class Node<E>{
        Node<E> parent;
        Node<E> left;
        E data;
        Node<E> right;
        
        public Node(Node<E> parent, Node<E> left, E data, Node<E> right) {
            this.parent = parent;
            this.left = left;
            this.data = data;
            this.right = right;
        }
	}
}

Binary tree classification

  • Full binary tree: a binary tree in which all nodes on each layer have two child nodes except that the last layer has no child nodes. The number of nodes in the nth layer is the n-1 power of 2 and the n-1 power of 2

  • Complete binary tree: leaf nodes can only appear in the bottom two layers, and the bottom leaf nodes are on the left of the sub bottom leaf nodes.

  • Balanced binary tree: self balancing binary search tree is also known as AVL tree (different from AVL algorithm), and has the following properties: it is an empty tree or the absolute value of the height difference between its left and right subtrees does not exceed 1, and both the left and right subtrees are a balanced binary tree, but non leaf nodes are not required to have two child nodes. The common implementation methods of balanced binary tree include red black tree, AVL, scapegoat tree, tree, extension tree, etc. The node formula of the minimum binary balanced tree is as follows: F(n)=F(n-1)+F(n-2)+1, which is similar to a recursive sequence. You can refer to the Fibonacci sequence. 1 is the root node, F(n-1) is the number of nodes of the left subtree, and F(n-2) is the number of nodes of the right subtree.

For example: Fibonacci: 1, 1, 2, 3, 5, 8, 13

Law: except for the first and second numbers, the latter number is equal to the sum of the first two numbers,

​ f(0) =1,

​ f(1) = 1,

​ f(2) = f(0) + f(1) =2,

​ f(3) = f(1) + f(2) = 3,

​ f(4) = f(2) + f(3) = 5

​ ...

​ f(n) = f(n-2) + f(n-1);

Traversal of binary tree

  • Preorder traversal: left and right
  • Middle order traversal: left, middle, right
  • Post order traversal: left and right middle

Preorder traversal: abdiecfg

Middle order traversal: HDIBEAFCG

Post order traversal: HIDEBFGCA

13.4 single linked list

13.4.1 implementation of single linked list

Logical structure: unidirectional linked list

Physical structure: chain sequential structure

package com.atguigu.test06;

public class OneWayLinkedList<E>{
	private Node<E> head;
	private int total;
	
	private static class Node<E>{
		E data;
		Node<E> next;
		Node(E data, Node<E> next) {
			this.data = data;
			this.next = next;
		}
	}

	public void add(E e) {
		Node<E> newNode = new Node<>(e,null);
		if(head==null){
			head = newNode;
		}else{
			Node<E> node = head;
			while(node.next!=null){
				node = node.next;
			}
			node.next = newNode;
		}
		total++;
	}


	public void delete(E e) {
		Node<E> node = head;
		Node<E> find = null;
		Node<E> last = null;
		
		if(e==null){
			while(node!=null){
				if(node.data==null){
					find = node;
					break;
				}
				last = node;
				node = node.next;
			}
		}else{
			while(node!=null){
				if(e.equals(node.data)){
					find = node;
					break;
				}
				last = node;
				node = node.next;
			}
		}
		
		if(find != null){
			if(last==null){
				head = find.next;
			}else{
				last.next = find.next;
			}
			total--;
		}
	}
	
	public void update(E old, E value) {
		Node<E> node = head;
		Node<E> find = null;
		
		if(old==null){
			while(node!=null){
				if(node.data==null){
					find = node;
					break;
				}
				node = node.next;
			}
		}else{
			while(node!=null){
				if(old.equals(node.data)){
					find = node;
					break;
				}
				node = node.next;
			}
		}
		
		if(find != null){
			find.data = value;
		}
	}

	public boolean contains(E e) {
		return indexOf(e) != -1;
	}

	public int indexOf(E e) {
		int index = -1;
		if(e==null){
			int i=0;
			for(Node<E> node = head; node!=null; node=node.next ){
				if(node.data==e){
					index=i;
					break;
				}
				i++;
			}
		}else{
			int i=0;
			for(Node<E> node = head; node!=null; node=node.next ){
				if(e.equals(node.data)){
					index=i;
					break;
				}
				i++;
			}
		}
		return index;
	}

	public Object[] getAll() {
		Object[] all = new Object[total];
		Node<E> node = head;
		for (int i = 0; i < all.length; i++,node = node.next) {
			all[i] = node.data;
		}
		return all;
	}

	public int size() {
		return total;
	}
}

13.4.2 test of single linked list

import java.util.Arrays;

import org.junit.Test;

public class TestOneWayLinkedList {
	@Test
	public void test01(){
		OneWayLinkedList<String> my = new OneWayLinkedList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		System.out.println("Number of elements:" + my.size());
		Object[] all = my.getAll();
		System.out.println(Arrays.toString(all));
	}
	
	@Test
	public void test02(){
		OneWayLinkedList<String> my = new OneWayLinkedList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		my.delete("hello");
		System.out.println("Number of elements:" + my.size());
		Object[] all = my.getAll();
		System.out.println(Arrays.toString(all));
		
		my.delete("atguigu");
		System.out.println("Number of elements:" + my.size());
		all = my.getAll();
		System.out.println(Arrays.toString(all));
		
		my.delete("data");
		System.out.println("Number of elements:" + my.size());
		all = my.getAll();
		System.out.println(Arrays.toString(all));
	}
	
	@Test
	public void test03(){
		OneWayLinkedList<String> my = new OneWayLinkedList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		my.update("java", "Java");
		System.out.println("Number of elements:" + my.size());
		Object[] all = my.getAll();
		System.out.println(Arrays.toString(all));
	}
	
	@Test
	public void test04(){
		OneWayLinkedList<String> my = new OneWayLinkedList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		System.out.println(my.contains("java"));
		System.out.println(my.indexOf("java"));
	}
	
	@Test
	public void test05(){
		OneWayLinkedList<String> my = new OneWayLinkedList<String>();
		my.add("hello");
		my.add("java");
		my.add("world");
		my.add("atguigu");
		my.add("list");
		my.add("data");
		
		for (String string : my) {
			System.out.println(string);
		}
	}
}

13.5 double linked list

There is an implementation of double linked List in Java: LinkedList, which is the implementation class of List interface.

LinkedList source code analysis

int size = 0;
Node<E> first;//Record the position of the first node
Node<E> last;//Record the position of the last node

    private static class Node<E> {
        E item;//Element data
        Node<E> next;//Next node
        Node<E> prev;//Previous node

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
    public boolean add(E e) {
        linkLast(e);//By default, the new element is linked to the end of the linked list
        return true;
    }
    void linkLast(E e) {
        final Node<E> l = last;//Record the original last node with l
        
        //Create a new node
        final Node<E> newNode = new Node<>(l, e, null);
        //Now the new node is the last node
        last = newNode;
        
        //If l==null, the original linked list is empty
        if (l == null)
            //Then the new node is also the first node
            first = newNode;
        else
            //Otherwise, link the new node to the next of the original last node
            l.next = newNode;
        //Number of elements increased
        size++;
        //Increase in modification times
        modCount++;
    }
    public boolean remove(Object o) {
        //There are two cases: whether o is empty or not
        if (o == null) {
            //Find the node x corresponding to o
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);//Delete x node
                    return true;
                }
            }
        } else {
            //Find the node x corresponding to o
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);//Delete x node
                    return true;
                }
            }
        }
        return false;
    }
    E unlink(Node<E> x) {//x is the node to be deleted
        // assert x != null;
        final E element = x.item;//Data of deleted node
        final Node<E> next = x.next;//The next node of the deleted node
        final Node<E> prev = x.prev;//The previous node of the deleted node

        //If there is no node in front of the deleted node, the deleted node is the first node
        if (prev == null) {
            //Then the next node of the deleted node becomes the first node
            first = next;
        } else {//The deleted node is not the first node
            //The next of the previous node of the deleted node points to the next node of the deleted node
            prev.next = next;
            //Disconnect the link between the deleted node and the previous node
            x.prev = null;//Make GC recycle
        }

        //If there is no node after the deleted node, the deleted node is the last node
        if (next == null) {
            //Then the previous node of the deleted node becomes the last node
            last = prev;
        } else {//The deleted node is not the last node
            //The prev of the next node of the deleted node executes the previous node of the deleted node
            next.prev = prev;
            //Disconnect the deleted node from the next node
            x.next = null;//Make GC recycle
        }
		//The data of the deleted node is also set to be empty, so that GC can recycle
        x.item = null;
        //Decrease in the number of elements
        size--;
        //Increase in modification times
        modCount++;
        //Returns the data of the deleted node
        return element;
    }

The difference between linked list and dynamic array

The underlying physical structure of dynamic array is array, so the efficiency of access according to index is very high, but the efficiency of insertion and deletion according to index is not high, because moving elements are involved, and capacity expansion may be involved in the addition operation, which will increase space-time consumption.

The underlying physical structure of the linked list is a linked list. Therefore, the efficiency of access according to the index is not high, but the efficiency of insertion and deletion is high. Because there is no need to move elements, you only need to modify the pointing relationship between the front and rear elements, and the addition of the linked list will not involve capacity expansion.

13.3 stack and queue

Stack is a structure of first in last out (FILO) or last in first out (LIFI).

A queue is a (but not necessarily) first in first out (FIFO) structure.

13.3.1 Stack type

java.util.Stack is a subclass of the Vector collection.

There are several more methods than Vector

  • (1)peek: View stack top elements without pop-up
  • (2)pop: pop stack
  • (3)push: press into the stack to add to the header of the linked list
	@Test
	public void test3(){
		Stack<Integer> list = new Stack<>();
		list.push(1);
		list.push(2);
		list.push(3);
		
		System.out.println(list);
		
		/*System.out.println(list.pop());
		System.out.println(list.pop());
		System.out.println(list.pop());
		System.out.println(list.pop());//java.util.NoSuchElementException
*/
		
		System.out.println(list.peek());
		System.out.println(list.peek());
		System.out.println(list.peek());
	}

11.3.2 Queue and Deque interfaces

In addition to the basic Collection operations, queue also provides other insert, extract and check operations. Each method has two forms: one throws an exception (when the operation fails) and the other returns a special value (null or false, depending on the operation). Queue implementations typically do not allow insertion of elements, although some implementations, such as, do not prohibit insertion. Even in implementations that allow nulls, you should not insert into a method because it is also used as a special return value of a method, indicating that the queue does not contain elements.

Throw exceptionReturn special value
insertadd(e)offer(e)
removeremove()poll()
inspectelement()peek()

Deque, the name deque is the abbreviation of "double ended queue", which is usually read as "deck". This interface defines methods to access elements at both ends of a two ended queue. Provides methods for inserting, removing, and checking elements. Each method has two forms: one is to throw an exception when the operation fails, and the other is to return a special value (null or false, depending on the operation).

First element (header)Last element (tail)
Throw exceptionSpecial valueThrow exceptionSpecial value
insertaddFirst(e)offerFirst(e)addLast(e)offerLast(e)
removeremoveFirst()pollFirst()removeLast()pollLast()
inspectgetFirst()peekFirst()getLast()peekLast()

This interface extends the Queue interface. When a double ended Queue is used as a Queue, a FIFO (first in, first out) behavior is obtained. Add the element to the end of the double ended Queue and remove the element from the beginning of the double ended Queue. The method inherited from the Queue interface is completely equivalent to the Deque method, as shown in the following table:

Queue methodEquivalent Deque method
add(e)addLast(e)
offer(e)offerLast(e)
remove()removeFirst()
poll()pollFirst()
element()getFirst()
peek()peekFirst()

Dual ended queues can also be used as LIFO stacks. This interface should take precedence over the legacy Stack class. When a double ended queue is used as a Stack, the element is pushed to and ejected from the beginning of the double ended queue. The Stack method is completely equivalent to the Deque method, as shown in the following table:

Stack methodEquivalent Deque method
push(e)addFirst(e)
pop()removeFirst()
peek()peekFirst()

Conclusion: the implementation class of Deque interface can be used as both FILO stack and FIFO queue.

The implementation classes of Deque interface include ArrayDeque and LinkedList, one of which is implemented by array and the other by bidirectional linked list.

13.4 hash table

HashMap and Hashtable are hash tables.

13.4.1 hashCode value

Hash algorithm is a data digest algorithm that can extract its "fingerprint" from any data. It maps any size of data to a fixed size sequence, which is called hash code, data digest or fingerprint. The well-known hash algorithms are MD5 and SHA. Hash is unique and irreversible. Uniqueness means that the hash code generated by the same "object" is always the same.

13.4.2 physical structure of hash table

HashMap and Hashtable are hash tables, in which an Entry type array table with a length of 2 is maintained. Each element of the array is called a bucket. The mapping relationship (key,value) you add is finally encapsulated as an object of Map.Entry type and placed in a table[index] bucket. The purpose of using array is to query and add efficiently. You can directly locate a table[index] according to the index.

1. Array element type: Map.Entry

JDK1.7:

The mapping relationship is encapsulated as the HashMap.Entry type, which implements the Map.Entry interface.

Observe that the HashMap.Entry type is a node type, that is, the mapping relationship under table[index] may be linked to a linked list. Therefore, we call the table[index] a "bucket".

public class HashMap<K,V>{
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    static class Entry<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Entry<K,V> next;
            int hash;
            //... omitted
    }
    //...
}

JDK1.8:

The mapping relationship is encapsulated as HashMap.Node type or HashMap.TreeNode type, both of which directly or indirectly implement the Map.Entry interface.

The objects stored in the table array may be Node objects or TreeNode Node objects, which are also the implementation classes of the Map.Entry interface. That is, the mapping relationship under table[index] may be linked to a linked list or a red black tree (self balanced binary tree).

public class HashMap<K,V>{
    transient Node<K,V>[] table;
    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
            //... omitted
    }
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;
        boolean red;//Is it a red node or a black node
        //... omitted
    }
    //....
}
public class LinkedHashMap<K,V>{
	static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    //...
}

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-7DwdpAar-1637898609356)(imgs/1563799238793.png)]

2. The length of an array is always the nth power of 2

Default initialization length of table array:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

If the table length you manually specify is not the nth power of 2, it will be corrected to the nth power of 2 by the following methods

JDK1.7:

HashMap processing capacity method:

    private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

Integer wrapper class:

    public static int highestOneBit(int i) {
        // HD, Figure 3-1
        i |= (i >>  1);
        i |= (i >>  2);
        i |= (i >>  4);
        i |= (i >>  8);
        i |= (i >> 16);
        return i - (i >>> 1);
    }

JDK1.8:

    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

If the array is not enough, what should we do if it is expanded? It is still the n-th power of 2, because each time the array is expanded, it is twice the original

JDK1.7:

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);//Capacity expansion is 2 times of the original
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }

JDK1.8:

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//oldCap original capacity
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }//Newcap = oldcap < < 1 new capacity = the old capacity is expanded twice as much as the original capacity
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        	}
   		//... other codes are omitted here
	}

So why keep the table array always to the n th power of 2?

3. So how does HashMap determine which bucket exists in a mapping relationship?

Because the hash value is an integer and the length of the array is also an integer, there are two ideas:

① The hash value% table.length will get a value in the [0,table.length-1] range, which is exactly the subscript range. However, using% operation can not ensure uniform storage, which may lead to too many elements in some table[index] buckets and too few elements in others, so it is inappropriate.

② hash value & (table. Length-1). Because table.length is a power of 2, table.length-1 is a binary number whose low order is all 1. Therefore & after the operation, a value in the range of [0,table.length-1] will also be obtained.

JDK1.7:

    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1); //Here h is hash
    }

JDK1.8:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)  // i = (n - 1) & hash
            tab[i] = newNode(hash, key, value, null);
        //... omit a lot of code
}

4. hash is the re operation of hashCode

In both JDK1.7 and JDK1.8, the hashCode value of the key is not directly used to calculate the subscript with table.length-1. Instead, an operation is performed on the hashCode value of the key. The implementation code of hash() in JDK1.7 and JDK1.8 is different, but in any case, it is to improve the bitwise and completion results of hash code value and (table.length-1) and try to distribute evenly.

JDK1.7:

    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

JDK1.8:

	static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

Although the algorithms are different, the idea is to XOR the high-order binary and low-order binary values of hashCode value, and the high-order binary participates in the calculation of index.

Why should the binary high bit of the hashCode value participate in the index calculation?

Because the table array of a HashMap is generally not very large, at least before continuous capacity expansion, most of the high bits of table.length-1 are 0. If you directly use hashcode and table.length-1 for & operation, only the lowest bits will be valid, so it is your hashCode() No matter how well the implementation is, it is difficult to avoid collision. At this time, the significance of involving high bits is reflected. It adds randomness to the low bits of hashcode and mixes some characteristics of high bits, which significantly reduces the occurrence of collision.

5. Resolve [index] conflicts

Although conflicts are minimized from the design of hashCode() to the hash() function of the HashMap above, there are still two different objects that return the same hashCode value, or even if the hashCode value is different, the index calculated by the hash() function will be largely the same. Therefore, there is no complete uniform key distribution. So what happens when there is a collision?

JDK1.8 uses the structure of array + linked list.

After JDK1.8, use the structure of array + linked list / red black tree.

That is, if the hash is the same or the value of hash & (table. Lengt-1) is the same, it will be stored in the same "bucket" table[index] and connected by linked list or red black tree.

6. Why do red black trees and linked lists coexist in JDK1.8?

Because when the conflict is serious, the linked list under table[index] will be very long, which will greatly reduce the search efficiency. If binary tree is selected at this time, the query efficiency can be greatly improved.

However, the structure of binary tree is too complex. If the number of nodes is small, it is easier to select linked list.

Therefore, red and black trees and linked lists will coexist.

7. When will the tree turn? When to reverse tree?

static final int TREEIFY_THRESHOLD = 8;//Tree threshold
static final int UNTREEIFY_THRESHOLD = 6;//De treeing threshold
static final int MIN_TREEIFY_CAPACITY = 64;//Minimum tree capacity
  • When the number of nodes in the linked list under a table[index] reaches 8 and table. Length > = 64, if a new Entry object is added to the table[index], the linked list of table[index] will be trealized.

  • When the number of red and black tree nodes under a table[index] is less than 6, at this time,

    • If you continue to delete the tree nodes under table[index], it will change back to the linked list when you delete less than 2.
    • If you continue to add the mapping relationship to the current map, if the addition causes the table of the map to be resize d again, it will change back to the linked list as long as the tree nodes under table[index] are still < = 6
class MyKey{
	int num;

	public MyKey(int num) {
		super();
		this.num = num;
	}

	@Override
	public int hashCode() {
		if(num<=20){
			return 1;
		}else{
			final int prime = 31;
			int result = 1;
			result = prime * result + num;
			return result;			
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MyKey other = (MyKey) obj;
		if (num != other.num)
			return false;
		return true;
	}
	
}
public class TestHashMap {
	
	@Test
	public void test1(){
		//Here, to demonstrate the effect, we create a special class whose hashCode () method returns a fixed value of 1
		//This can cause conflicts and make them all stored in table[1]
		HashMap<MyKey, String> map = new HashMap<>();
		for (int i = 1; i <= 11; i++) {
			map.put(new MyKey(i), "value"+i);//Tree presentation
		}
    }
   @Test
	public void test2(){
		HashMap<MyKey, String> map = new HashMap<>();
		for (int i = 1; i <= 11; i++) {
			map.put(new MyKey(i), "value"+i);
		}
        for (int i = 1; i <=11; i++) {
			map.remove(new MyKey(i));//Reverse tree demonstration
		}
    }
    @Test
	public void test3(){
		HashMap<MyKey, String> map = new HashMap<>();
		for (int i = 1; i <= 11; i++) {
			map.put(new MyKey(i), "value"+i);
		}

		for (int i = 1; i <=5; i++) {
			map.remove(new MyKey(i));
		}//There are 6 remaining nodes under table[1]
		
		for (int i = 21; i <= 100; i++) {
			map.put(new MyKey(i), "value"+i);//De tree when adding to capacity expansion
		}
	}

13.4.3 source code analysis of put method in jdk1.7

(1) The role of several key constant and variable values:

Initialization capacity:

int DEFAULT_INITIAL_CAPACITY = 1 << 4;//16

① Default load factor

static final float DEFAULT_LOAD_FACTOR = 0.75f;

② Threshold: critical value of capacity expansion

int threshold;
threshold = table.length * loadFactor;

③ Load factor

final float loadFactor;

What is the relationship between the value of load factor?

If it is too large, the threshold will be very large. If the conflict is serious, it will lead to a large number of nodes under the table[index], affecting the efficiency.

If it is too small, the threshold will be very small, the frequency of array expansion will be increased, and the utilization rate of array will be reduced, which will cause a waste of space.

    public HashMap() {
    	//DEFAULT_ INITIAL_ Capability: default initial capacity 16
    	//DEFAULT_LOAD_FACTOR: the default loading factor is 0.75
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(int initialCapacity, float loadFactor) {
        //Verify the validity of initialCapacity
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
        //Verify the validity of initialCapacity (initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //Verify the legitimacy of loadFactor
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
		//Load factor, initialized to 0.75
        this.loadFactor = loadFactor;
        // threshold is the initial capacity                                  
        threshold = initialCapacity;
        init();
    }
public V put(K key, V value) {
        //If the table array is empty, create the array first
        if (table == EMPTY_TABLE) {
            //threshold is initially the value of the initial capacity
            inflateTable(threshold);
        }
        //If the key is null, handle it separately
        if (key == null)
            return putForNullKey(value);
        
        //Interfere with the hashCode of the key and calculate a hash value
        int hash = hash(key);
        
        //Calculate that the new mapping relationship should be saved to table[i],
        //i = hash & table.length-1, which can ensure that i is within the range of [0,table.length-1]
        int i = indexFor(hash, table.length);
        
        //Check whether the key under table[i] duplicates the key of my new mapping relationship. If so, replace value
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //Add a new mapping relationship
        addEntry(hash, key, value, i);
        return null;
    }
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);//The capacity is equal to the nearest n-th power of 2 of the toSize value
		//Calculation threshold = capacity * loading factor
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        //Create Entry [] array with length of capacity
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
	//If the key is null, it is directly stored in the location of [0]
    private V putForNullKey(V value) {
        //Judge whether there are duplicate key s. If there are duplicates, replace value
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        //Store the new mapping relationship in [0], and the hash value of the key is represented by 0
        addEntry(0, null, value, 0);
        return null;
    }
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //Determine whether storage capacity is required
        //Capacity expansion: (1) size reaches the threshold (2) table[i] is not empty
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //The capacity of the table is doubled, and after the capacity expansion, the storage locations of all mapping relationships will be readjusted
            resize(2 * table.length);
            //The hash and index of the new mapping relationship will also be recalculated
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
		//Save in table
        createEntry(hash, key, value, bucketIndex);
    }
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        //The mapping relationship under the original table[i] is used as the new mapping relationship next
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;//Increase in number
    }

1,put(key,value)

(1) When the mapping relationship is added for the first time, the array is initialized to a * * HashMap with a length of 16 E n t r y ∗ ∗ of number group , this individual H a s h M a p An array of Entry * *, this HashMap The HashMapEntry type implements the java.util.Map.Entry interface

(2) Special consideration: if the key is null, the index is directly [0], and the hash is also 0

(3) If the key is not null, a hash(key) operation will be performed on the hashCode() value of the key before calculating the index, which can make the Entry object more hashed and stored in the table

(4) Calculate index = table.length-1 & hash;

(5) If the key of the existing mapping relationship under table[index] is the same as the key of the new mapping relationship I want to add, the old value will be replaced with the new value.

(6) If not, the new mapping relationship will be added to the header of the linked list, and the Entry object under the original table[index] will be connected to the next of the new mapping relationship.

(7) Judge if (size > = threshold & & table [index]! = null) before adding. If the condition is true, the capacity will be expanded

if(size >= threshold  &&  table[index]!=null){

	①Capacity expansion

	②Will recalculate key of hash

	③Will recalculate index

}

(8)size++

2,get(key)

(1) Calculate the hash value of the key. Use this method to hash(key)

(2) Find index = table.length-1 & hash;

(3) If table[index] is not empty, the key of each Entry will be compared one by one, and its value will be returned

3,remove(key)

(1) Calculate the hash value of the key. Use this method to hash(key)

(2) Find index = table.length-1 & hash;

(3) If the table[index] is not empty, compare which Entry has the same key one by one, delete it, and change the value of the next of the Entry in front of it to the next of the deleted Entry

13.4.4 source code analysis of put method in JDK1.8

Several constants and variables:
(1)DEFAULT_INITIAL_CAPACITY: Default initial capacity 16
(2)MAXIMUM_CAPACITY: Maximum capacity 1 << 30
(3)DEFAULT_LOAD_FACTOR: Default load factor 0.75
(4)TREEIFY_THRESHOLD: The default treelization threshold is 8. When the length of the linked list reaches this value, treelization should be considered
(5)UNTREEIFY_THRESHOLD: The default de tree threshold is 6. When the number of nodes in the tree reaches this threshold, it should be considered to become a linked list
(6)MIN_TREEIFY_CAPACITY: Minimum tree capacity 64
		When the number of nodes in a single linked list reaches 8, and table Only when the length reaches 64 can it be trealized.
		When the number of nodes in a single linked list reaches 8, but table If the length of does not reach 64, the capacity will be expanded first
(7)Node<K,V>[] table: array
(8)size: Record the logarithm of the effective mapping relationship, also Entry Number of objects
(9)int threshold: Threshold, when size Consider capacity expansion when the threshold is reached
(10)double loadFactor: The loading factor affects the frequency of capacity expansion
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
        // All other fields are defaulted
    }
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
	//Purpose: to interfere with hashCode value
    static final int hash(Object key) {
        int h;
		//If the key is null, the hash is 0
		//If the key is not null, use the hashCode value of the key to XOR with the hashCode value of the key higher than 16
		//		That is, the high 16 bits and low 16 bits of the hashCode value of the key are used for XOR interference operation
		
		/*
		index = hash & table.length-1
		If you use the original hashCode value of the key to perform bitwise sum with table.length-1, you basically have no chance to use high 16.
		This will increase the probability of conflict. In order to reduce the probability of conflict, the high 16 bits are added to the hash information.
		*/
        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; //array
		Node<K,V> p; //A node
		int n, i;//n is the length of the array and i is the subscript
		
		//tab and table are equivalent
		//If table is empty
        if ((tab = table) == null || (n = tab.length) == 0){
            n = (tab = resize()).length;
            /*
			tab = resize();
			n = tab.length;*/
			/*
			If the table is empty, resize() completes ① creating an array with a length of 16 ② threshold = 12
			n = 16
			*/
        }
		//I = (n - 1) & hash, subscript = array length - 1 & hash
		//p = tab[i] 1st node
		//if(p==null) condition is satisfied, it means that table[i] has no element
		if ((p = tab[i = (n - 1) & hash]) == null){
			//Put the new mapping relationship directly into table[i]
            tab[i] = newNode(hash, key, value, null);
			//The newNode () method creates a new Node of Node type, and the next of the new Node is null
        }else {
            Node<K,V> e; 
			K k;
			//p is the first node in table[i]
			//If (the first node of table [i] duplicates the key of the new mapping relationship)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))){
                e = p;//Record the first node of this table[i] with e
			}else if (p instanceof TreeNode){//If the first node of table[i] is a tree node
                //Handle tree nodes separately
                //If there is a duplicate key in the tree node, return the duplicate node and receive it with E, that is, e= null
                //If there is no duplicate key in the tree node, put the new node into the tree and return null, that is, e=null
				e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            }else {
				//The first node of table[i] is not a tree node, nor does it duplicate the key of the new mapping relationship
				//binCount records the number of nodes under table[i]
                for (int binCount = 0; ; ++binCount) {
					//If the next node of P is empty, it means that the current p is the last node
                    if ((e = p.next) == null) {
						//Connect the new node to the end of table[i]
                        p.next = newNode(hash, key, value, null);
						
						//If bincount > = 8-1, when it reaches 7
                        if (binCount >= TREEIFY_THRESHOLD - 1){ // -1 for 1st
                            //Either expand or tree
							treeifyBin(tab, hash);
						}
                        break;
                    }
					//If the key is repeated, it will jump out of the for loop. At this time, node e records the node with the repeated key
            if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))){
                        break;
					}
                    p = e;//The next cycle, e=p.next, is similar to e=e.next, moving under the linked list
                }
            }
			//If this e is not null, it indicates that there is a duplicate key, so consider replacing the original value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null){
                    e.value = value;
				}
                afterNodeAccess(e);//Nothing
                return oldValue;
            }
        }
        ++modCount;
		
		//Number of elements increased
		//size reached threshold
        if (++size > threshold){
            resize();//Once the capacity is expanded, readjust the position of all mapping relationships
		}
        afterNodeInsertion(evict);//Nothing
        return null;
    }	
	
   final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//oldTab original table
		//oldCap: the length of the original array
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
		
		//oldThr: original threshold
        int oldThr = threshold;//The initial threshold is 0
		
		//newCap, new capacity
		//newThr: new threshold
        int newCap, newThr = 0;
        if (oldCap > 0) {//Description: the original array is not empty
            if (oldCap >= MAXIMUM_CAPACITY) {//Whether the maximum limit of the array has been reached
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY){
				//newCap = old capacity * 2, new capacity < maximum array capacity limit
				//New capacity: 32,64
				//Oldcap > = initial capacity 16
				//New threshold recalculation = 24, 48
                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;//The new capacity is the default initialization capacity of 16
			//New threshold = default load factor * default initialization capacity = 0.75 * 16 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;//The threshold value is assigned to the new threshold value 12, 24...
		
		//A new array is created with a length of newCap, 16, 32, 64..
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
		
		
        if (oldTab != null) {//It is not an empty array
			//Invert the mapping relationship from the original table to the new table
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {//e is the node under the table
                    oldTab[j] = null;//Empty the old table[j] position
                    if (e.next == null)//If it is the last node
                        newTab[e.hash & (newCap - 1)] = e;//Recalculate the storage location of e in the new table and put it into
                    else if (e instanceof TreeNode)//If e is a tree node
						//Disassemble the original tree and put it into a new table
                        ((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;
						/*
						Move the entire linked list under the original table[i] to the new table
						*/
                        do {
                            next = e.next;
                            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;
    }	
	
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
		//Create a new node
	   return new Node<>(hash, key, value, next);
    }

    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; 
		Node<K,V> e;
		//MIN_ TREEIFY_ Capability: minimum tree capacity 64
		//If the table is empty, or the length of the table does not reach 64
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();//Capacity expansion first
        else if ((e = tab[index = (n - 1) & hash]) != null) {
			//Use e to record the address of the node of table[index]
            TreeNode<K,V> hd = null, tl = null;
			/*
			do...while,Change the Node node of the table[index] linked list into a TreeNode type Node
			*/
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;//hd record root node
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
			
            //If the table[index] is not empty
            if ((tab[index] = hd) != null)
                hd.treeify(tab);//Tree the linked list under table[index]
        }
    }	

1. Add process

A. First calculate the hash value of the key. If the key is null, the hash value is 0. If it is null, use (H = key. Hashcode()) ^ (H > > > 16) to get the hash value;

B. If table is empty, initialize the table array first;

C. Calculate the stored index position through the hash value. Index = hash & (table. Length-1)

D. If table[index]==null, you can directly create a Node and store it in table[index]

E. If table [index]= null

a) judge whether the key of the root node of table[index] is "the same" as the new key (the hash value is the same and (the address of the key is the same or the equals of the key returns true)). If so, record the root node with e

b) if the key of the root node of the table[index] is "different" from the new key, and the table[index] is a TreeNode node, it means that there is a red black tree under the table[index]. If the key of a node of the tree is "the same" as the new key (the hash value is the same and (the address of the key is the same or the equals of the key returns true)), then use e to record the same node, otherwise it will be deleted (key,value) is encapsulated as a TreeNode node and connected to the red black tree

c) if the key of the root Node of table[index] is "different" from the new key, and table[index] is not a TreeNode Node, it means that there is a linked list under table[index]. If the key of a Node in the linked list is "the same" as the new key, use e to record the same Node, otherwise encapsulate the new mapping relationship as a Node node and directly link it to the tail of the linked list, And judge that the number of nodes under table[index] reaches TREEIFY_THRESHOLD(8). If the number of nodes under table[index] has reached, judge whether table.length reaches MIN_TREEIFY_CAPACITY(64). If not, expand the capacity first. The expansion will cause all elements to recalculate the index and adjust the position. If the number of nodes under table[index] has reached tree_ Threshold (8) parallel tables. Length has also reached min_ TREEIFY_ Capability (64), the linked list will be turned into a self balanced red black tree.

F. If a new node with the same key is found under table[index], that is, e is not empty, replace the original value with the new value, return the old value, and end the put method

G. If you add a node instead of replacing it, you need size + +, and you need to re judge whether the size reaches the threshold. If so, you need to expand the capacity.

if(size > threshold ){
	①Capacity expansion

	②Will recalculate key of hash

	③Will recalculate index

}

2,remove(key)

(1) Calculate the hash value of the key. Use this method to hash(key)

(2) Find index = table.length-1 & hash;

(3) If the table[index] is not empty, compare which Entry has the same key one by one, delete it, and change the value of the next of the Entry in front of it to the next of the deleted Entry

(4) If there is a red black tree under the table[index], and the number of nodes is less than or equal to 6 after deletion, the red black tree will be changed into a linked list

13.4.5 can the key of mapping relationship be modified?

The hash value of the key will be stored when the mapping relationship is stored in the HashMap, so there is no need to recalculate the hash value of each Entry or Node (TreeNode) every time. Therefore, if the mapping relationship that has been put into the Map is modified, and this attribute participates in the calculation of the hashcode value, it will lead to mismatch.

This rule also applies to the collection of all hash storage structures such as LinkedHashMap, HashSet, LinkedHashSet, Hashtable, etc.

JDK1.7:

public class HashMap<K,V>{
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    static class Entry<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Entry<K,V> next;
            int hash; //Record the hash(key.hashCode()) value of the key of the Entry mapping relationship
            //... omitted
    }
    //...
}

JDK1.8:

public class HashMap<K,V>{
    transient Node<K,V>[] table;
    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;//Record the hash(key.hashCode()) value of the key of the Node mapping relationship
            final K key;
            V value;
            Node<K,V> next;
            //... omitted
    }
    //....
}

Example code:

import java.util.HashMap;

public class TestHashMap {
	public static void main(String[] args) {
		HashMap<ID,String> map = new HashMap<>();
		ID i1 = new ID(1);
		ID i2 = new ID(2);
		ID i3 = new ID(3);
		
		map.put(i1, "haha");
		map.put(i2, "hehe");
		map.put(i3, "xixi");
		
		System.out.println(map.get(i1));//haha
		i1.setId(10);
		System.out.println(map.get(i1));//null
	}
}
class ID{
	private int id;

	public ID(int id) {
		super();
		this.id = id;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ID other = (ID) obj;
		if (id != other.id)
			return false;
		return true;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	
}

Therefore, in actual development, String and Integer are often used as key s because they are immutable objects.

1637898609364)]

2,remove(key)

(1) Calculate the hash value of the key. Use this method to hash(key)

(2) Find index = table.length-1 & hash;

(3) If the table[index] is not empty, compare which Entry has the same key one by one, delete it, and change the value of the next of the Entry in front of it to the next of the deleted Entry

(4) If there is a red black tree under the table[index], and the number of nodes is less than or equal to 6 after deletion, the red black tree will be changed into a linked list

13.4.5 can the key of mapping relationship be modified?

The hash value of the key will be stored when the mapping relationship is stored in the HashMap, so there is no need to recalculate the hash value of each Entry or Node (TreeNode) every time. Therefore, if the mapping relationship that has been put into the Map is modified, and this attribute participates in the calculation of the hashcode value, it will lead to mismatch.

This rule also applies to the collection of all hash storage structures such as LinkedHashMap, HashSet, LinkedHashSet, Hashtable, etc.

JDK1.7:

public class HashMap<K,V>{
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    static class Entry<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Entry<K,V> next;
            int hash; //Record the hash(key.hashCode()) value of the key of the Entry mapping relationship
            //... omitted
    }
    //...
}

JDK1.8:

public class HashMap<K,V>{
    transient Node<K,V>[] table;
    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;//Record the hash(key.hashCode()) value of the key of the Node mapping relationship
            final K key;
            V value;
            Node<K,V> next;
            //... omitted
    }
    //....
}

Example code:

import java.util.HashMap;

public class TestHashMap {
	public static void main(String[] args) {
		HashMap<ID,String> map = new HashMap<>();
		ID i1 = new ID(1);
		ID i2 = new ID(2);
		ID i3 = new ID(3);
		
		map.put(i1, "haha");
		map.put(i2, "hehe");
		map.put(i3, "xixi");
		
		System.out.println(map.get(i1));//haha
		i1.setId(10);
		System.out.println(map.get(i1));//null
	}
}
class ID{
	private int id;

	public ID(int id) {
		super();
		this.id = id;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ID other = (ID) obj;
		if (id != other.id)
			return false;
		return true;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	
}

Therefore, in actual development, String and Integer are often used as key s because they are immutable objects.

Tags: Java Back-end

Posted on Fri, 26 Nov 2021 11:01:34 -0500 by kf