[Aprendiendo Java de 0 a 1] 12 Interpretación del código fuente del marco de colección de Java (artículo de 6 palabras, ven y recógelo)

imagen-20230825093455169

1. Análisis del código fuente de ArrayList

1.1 El principio subyacente de ArrayList

  1. Usando el conjunto creado con parámetros vacíos, se crea una matriz con una longitud predeterminada de 0 denominada elementData en la capa inferior, y también hay un tamaño de variable de nivel inferior para registrar el número de elementos.
  2. Al agregar el primer elemento, la capa subyacente creará una nueva matriz de longitud 10, con el valor de inicialización predeterminado vacío. Por lo tanto, generalmente pensamos que la longitud predeterminada de la matriz subyacente de ArrayList es 10.
  3. La variable de tamaño subyacente tiene dos significados. El primer significado representa el número actual de elementos, que es la longitud de la colección. El segundo representa la siguiente ubicación de almacenamiento. El tamaño aumentará cada vez que se almacenen datos.
  4. Cuando la matriz original de la colección esté llena y se necesiten más adiciones, la matriz se expandirá automáticamente 1,5 veces y el tamaño será 15 en este momento. Si vuelve a estar lleno, seguirá ampliándose hasta 1,5 veces la longitud actual.
  5. Si se agregan varios elementos a la vez y no pueden caber 1,5 veces, la longitud de la matriz recién creada estará sujeta a la longitud real.

1.2 Análisis del código fuente de ArrayList

Al crear una colección ArrayList con un constructor de parámetros nulos, ArrayList crea una matriz de longitud cero debajo.

/**
 * 当用空参构造创建ArrayList集合的时候,ArrayList在底层创建了一个长度为零的数组
 */
List<String> list = new ArrayList<>();

Por ejemplo, en el siguiente análisis del código fuente, algunos códigos fuente que son fáciles de entender se pegan en ArrayList.

transient Object[] elementData;//集合底层的数组名

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

public ArrayList() {
    
    
    //ArrayList空参构造
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    //这个就相当于,Object[] elementData = {};也是相当于创建了一个长度为零的数组的意思
}

Al llamar a list.add() para agregar el primer elemento, ArrayList crea una nueva matriz de longitud diez bajo el capó.

Los valores de inicialización predeterminados internos son todos nulos.

/**
 * 当用空参构造创建ArrayList集合的时候,ArrayList在底层创建了一个长度为零的数组
 */
List<String> list = new ArrayList<>();
//当调用list.add()添加第一个元素的时候,ArrayList在底层创建了一个新的,长度为十的数组
list.add("nnn");

Análisis de los siguientes pasos del código fuente.

transient Object[] elementData;//集合底层的数组名

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

private int size;//集合中元素个数,集合长度

private static final int DEFAULT_CAPACITY = 10;//默认初始容量

protected transient int modCount = 0;//记录的是list集合被修改的次数,例如每调用一次add方法,就会加一

public ArrayList() {
    
    //ArrayList空参构造
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    //这个就相当于,Object[] elementData = {};创建了一个长度为零的数组
}

public boolean add(E e) {
    
    
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    
    //minCapacity为1
    //这个minCapacity就是上面传下来的size,为1
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
    //  Object[] elementData ={}; 
    // private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
        //private static final int DEFAULT_CAPACITY = 10;  int minCapacity = 1;  
        return Math.max(DEFAULT_CAPACITY, minCapacity);//俩者之间取最大值,返回10
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    
    //int minCapacity = 10;
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    
    //minCapacity=10
    // overflow-conscious code
    int oldCapacity = elementData.length;//旧容量,0;
    int newCapacity = oldCapacity + (oldCapacity >> 1);//这个>>相当于oldCapacity *1.5

    if (newCapacity - minCapacity < 0){
    
    
        newCapacity = minCapacity;
    }


    if (newCapacity - MAX_ARRAY_SIZE > 0){
    
    
        newCapacity = hugeCapacity(minCapacity);
    }

    // minCapacity is usually close to size, so this is a win:
    //创建一个新的长度为10的数组,把原来的元素拷贝进去
    elementData = Arrays.copyOf(elementData, newCapacity);{
    
    },10
}

1.3 Mecanismo de expansión ArrayList

Reglas de expansión

  1. ArrayList()utilizará una matriz de longitud cero

  2. ArrayList(int initialCapacity)Se utilizará una matriz con la capacidad especificada.

  3. public ArrayList(Collection<? extends E> c)El tamaño de c se utilizará como capacidad de la matriz.

  4. add(Object o) La primera expansión es 10 y la segunda expansión es 1,5 veces la última capacidad.

  5. addAll(Collection c)Cuando no hay elementos, la capacidad se expande a Math.max (10, el número real de elementos), y cuando hay elementos, es Math.max (1,5 veces la capacidad original, el número real de elementos)

2. Análisis del código fuente de LinkedList

2.1 Introducción a la lista vinculada

LinkedList se implementa en base a una lista enlazada circular bidireccional (que se puede ver fácilmente en el código fuente). Además de funcionar como una lista enlazada, también se puede utilizar como pila, cola y cola de doble extremo.

LinkedList tampoco es seguro para subprocesos y solo es adecuado para su uso en un solo subproceso.

LinkedList implementa la interfaz Serializable, por lo que admite la serialización y se puede transmitir a través de la serialización, implementa la interfaz Cloneable y se puede clonar.

2.2 Análisis del código fuente de LinkedList

El código fuente de LinkedList es el siguiente (con comentarios más detallados agregados)

package java.util;    
   
public class LinkedList<E>    
    extends AbstractSequentialList<E>    
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable    
{
    
        
    // 链表的表头,表头不包含任何数据。Entry是个链表类数据结构。    
    private transient Entry<E> header = new Entry<E>(null, null, null);    
   
    // LinkedList中元素个数    
    private transient int size = 0;    
   
    // 默认构造函数:创建一个空的链表    
    public LinkedList() {
    
        
        header.next = header.previous = header;    
    }    
   
    // 包含“集合”的构造函数:创建一个包含“集合”的LinkedList    
    public LinkedList(Collection<? extends E> c) {
    
        
        this();    
        addAll(c);    
    }    
   
    // 获取LinkedList的第一个元素    
    public E getFirst() {
    
        
        if (size==0)    
            throw new NoSuchElementException();    
   
        // 链表的表头header中不包含数据。    
        // 这里返回header所指下一个节点所包含的数据。    
        return header.next.element;    
    }    
   
    // 获取LinkedList的最后一个元素    
    public E getLast()  {
    
        
        if (size==0)    
            throw new NoSuchElementException();    
   
        // 由于LinkedList是双向链表;而表头header不包含数据。    
        // 因而,这里返回表头header的前一个节点所包含的数据。    
        return header.previous.element;    
    }    
   
    // 删除LinkedList的第一个元素    
    public E removeFirst() {
    
        
        return remove(header.next);    
    }    
   
    // 删除LinkedList的最后一个元素    
    public E removeLast() {
    
        
        return remove(header.previous);    
    }    
   
    // 将元素添加到LinkedList的起始位置    
    public void addFirst(E e) {
    
        
        addBefore(e, header.next);    
    }    
   
    // 将元素添加到LinkedList的结束位置    
    public void addLast(E e) {
    
        
        addBefore(e, header);    
    }    
   
    // 判断LinkedList是否包含元素(o)    
    public boolean contains(Object o) {
    
        
        return indexOf(o) != -1;    
    }    
   
    // 返回LinkedList的大小    
    public int size() {
    
        
        return size;    
    }    
   
    // 将元素(E)添加到LinkedList中    
    public boolean add(E e) {
    
        
        // 将节点(节点数据是e)添加到表头(header)之前。    
        // 即,将节点添加到双向链表的末端。    
        addBefore(e, header);    
        return true;    
    }    
   
    // 从LinkedList中删除元素(o)    
    // 从链表开始查找,如存在元素(o)则删除该元素并返回true;    
    // 否则,返回false。    
    public boolean remove(Object o) {
    
        
        if (o==null) {
    
        
            // 若o为null的删除情况    
            for (Entry<E> e = header.next; e != header; e = e.next) {
    
        
                if (e.element==null) {
    
        
                    remove(e);    
                    return true;    
                }    
            }    
        } else {
    
        
            // 若o不为null的删除情况    
            for (Entry<E> e = header.next; e != header; e = e.next) {
    
        
                if (o.equals(e.element)) {
    
        
                    remove(e);    
                    return true;    
                }    
            }    
        }    
        return false;    
    }    
   
    // 将“集合(c)”添加到LinkedList中。    
    // 实际上,是从双向链表的末尾开始,将“集合(c)”添加到双向链表中。    
    public boolean addAll(Collection<? extends E> c) {
    
        
        return addAll(size, c);    
    }    
   
    // 从双向链表的index开始,将“集合(c)”添加到双向链表中。    
    public boolean addAll(int index, Collection<? extends E> c) {
    
        
        if (index < 0 || index > size)    
            throw new IndexOutOfBoundsException("Index: "+index+    
                                                ", Size: "+size);    
        Object[] a = c.toArray();    
        // 获取集合的长度    
        int numNew = a.length;    
        if (numNew==0)    
            return false;    
        modCount++;    
   
        // 设置“当前要插入节点的后一个节点”    
        Entry<E> successor = (index==size ? header : entry(index));    
        // 设置“当前要插入节点的前一个节点”    
        Entry<E> predecessor = successor.previous;    
        // 将集合(c)全部插入双向链表中    
        for (int i=0; i<numNew; i++) {
    
        
            Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);    
            predecessor.next = e;    
            predecessor = e;    
        }    
        successor.previous = predecessor;    
   
        // 调整LinkedList的实际大小    
        size += numNew;    
        return true;    
    }    
   
    // 清空双向链表    
    public void clear() {
    
        
        Entry<E> e = header.next;    
        // 从表头开始,逐个向后遍历;对遍历到的节点执行一下操作:    
        // (01) 设置前一个节点为null     
        // (02) 设置当前节点的内容为null     
        // (03) 设置后一个节点为“新的当前节点”    
        while (e != header) {
    
        
            Entry<E> next = e.next;    
            e.next = e.previous = null;    
            e.element = null;    
            e = next;    
        }    
        header.next = header.previous = header;    
        // 设置大小为0    
        size = 0;    
        modCount++;    
    }    
   
    // 返回LinkedList指定位置的元素    
    public E get(int index) {
    
        
        return entry(index).element;    
    }    
   
    // 设置index位置对应的节点的值为element    
    public E set(int index, E element) {
    
        
        Entry<E> e = entry(index);    
        E oldVal = e.element;    
        e.element = element;    
        return oldVal;    
    }    
     
    // 在index前添加节点,且节点的值为element    
    public void add(int index, E element) {
    
        
        addBefore(element, (index==size ? header : entry(index)));    
    }    
   
    // 删除index位置的节点    
    public E remove(int index) {
    
        
        return remove(entry(index));    
    }    
   
    // 获取双向链表中指定位置的节点    
    private Entry<E> entry(int index) {
    
        
        if (index < 0 || index >= size)    
            throw new IndexOutOfBoundsException("Index: "+index+    
                                                ", Size: "+size);    
        Entry<E> e = header;    
        // 获取index处的节点。    
        // 若index < 双向链表长度的1/2,则从前先后查找;    
        // 否则,从后向前查找。    
        if (index < (size >> 1)) {
    
        
            for (int i = 0; i <= index; i++)    
                e = e.next;    
        } else {
    
        
            for (int i = size; i > index; i--)    
                e = e.previous;    
        }    
        return e;    
    }    
   
    // 从前向后查找,返回“值为对象(o)的节点对应的索引”    
    // 不存在就返回-1    
    public int indexOf(Object o) {
    
        
        int index = 0;    
        if (o==null) {
    
        
            for (Entry e = header.next; e != header; e = e.next) {
    
        
                if (e.element==null)    
                    return index;    
                index++;    
            }    
        } else {
    
        
            for (Entry e = header.next; e != header; e = e.next) {
    
        
                if (o.equals(e.element))    
                    return index;    
                index++;    
            }    
        }    
        return -1;    
    }    
   
    // 从后向前查找,返回“值为对象(o)的节点对应的索引”    
    // 不存在就返回-1    
    public int lastIndexOf(Object o) {
    
        
        int index = size;    
        if (o==null) {
    
        
            for (Entry e = header.previous; e != header; e = e.previous) {
    
        
                index--;    
                if (e.element==null)    
                    return index;    
            }    
        } else {
    
        
            for (Entry e = header.previous; e != header; e = e.previous) {
    
        
                index--;    
                if (o.equals(e.element))    
                    return index;    
            }    
        }    
        return -1;    
    }    
   
    // 返回第一个节点    
    // 若LinkedList的大小为0,则返回null    
    public E peek() {
    
        
        if (size==0)    
            return null;    
        return getFirst();    
    }    
   
    // 返回第一个节点    
    // 若LinkedList的大小为0,则抛出异常    
    public E element() {
    
        
        return getFirst();    
    }    
   
    // 删除并返回第一个节点    
    // 若LinkedList的大小为0,则返回null    
    public E poll() {
    
        
        if (size==0)    
            return null;    
        return removeFirst();    
    }    
   
    // 将e添加双向链表末尾    
    public boolean offer(E e) {
    
        
        return add(e);    
    }    
   
    // 将e添加双向链表开头    
    public boolean offerFirst(E e) {
    
        
        addFirst(e);    
        return true;    
    }    
   
    // 将e添加双向链表末尾    
    public boolean offerLast(E e) {
    
        
        addLast(e);    
        return true;    
    }    
   
    // 返回第一个节点    
    // 若LinkedList的大小为0,则返回null    
    public E peekFirst() {
    
        
        if (size==0)    
            return null;    
        return getFirst();    
    }    
   
    // 返回最后一个节点    
    // 若LinkedList的大小为0,则返回null    
    public E peekLast() {
    
        
        if (size==0)    
            return null;    
        return getLast();    
    }    
   
    // 删除并返回第一个节点    
    // 若LinkedList的大小为0,则返回null    
    public E pollFirst() {
    
        
        if (size==0)    
            return null;    
        return removeFirst();    
    }    
   
    // 删除并返回最后一个节点    
    // 若LinkedList的大小为0,则返回null    
    public E pollLast() {
    
        
        if (size==0)    
            return null;    
        return removeLast();    
    }    
   
    // 将e插入到双向链表开头    
    public void push(E e) {
    
        
        addFirst(e);    
    }    
   
    // 删除并返回第一个节点    
    public E pop() {
    
        
        return removeFirst();    
    }    
   
    // 从LinkedList开始向后查找,删除第一个值为元素(o)的节点    
    // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点    
    public boolean removeFirstOccurrence(Object o) {
    
        
        return remove(o);    
    }    
   
    // 从LinkedList末尾向前查找,删除第一个值为元素(o)的节点    
    // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点    
    public boolean removeLastOccurrence(Object o) {
    
        
        if (o==null) {
    
        
            for (Entry<E> e = header.previous; e != header; e = e.previous) {
    
        
                if (e.element==null) {
    
        
                    remove(e);    
                    return true;    
                }    
            }    
        } else {
    
        
            for (Entry<E> e = header.previous; e != header; e = e.previous) {
    
        
                if (o.equals(e.element)) {
    
        
                    remove(e);    
                    return true;    
                }    
            }    
        }    
        return false;    
    }    
   
    // 返回“index到末尾的全部节点”对应的ListIterator对象(List迭代器)    
    public ListIterator<E> listIterator(int index) {
    
        
        return new ListItr(index);    
    }    
   
    // List迭代器    
    private class ListItr implements ListIterator<E> {
    
        
        // 上一次返回的节点    
        private Entry<E> lastReturned = header;    
        // 下一个节点    
        private Entry<E> next;    
        // 下一个节点对应的索引值    
        private int nextIndex;    
        // 期望的改变计数。用来实现fail-fast机制。    
        private int expectedModCount = modCount;    
   
        // 构造函数。    
        // 从index位置开始进行迭代    
        ListItr(int index) {
    
        
            // index的有效性处理    
            if (index < 0 || index > size)    
                throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);    
            // 若 “index 小于 ‘双向链表长度的一半’”,则从第一个元素开始往后查找;    
            // 否则,从最后一个元素往前查找。    
            if (index < (size >> 1)) {
    
        
                next = header.next;    
                for (nextIndex=0; nextIndex<index; nextIndex++)    
                    next = next.next;    
            } else {
    
        
                next = header;    
                for (nextIndex=size; nextIndex>index; nextIndex--)    
                    next = next.previous;    
            }    
        }    
   
        // 是否存在下一个元素    
        public boolean hasNext() {
    
        
            // 通过元素索引是否等于“双向链表大小”来判断是否达到最后。    
            return nextIndex != size;    
        }    
   
        // 获取下一个元素    
        public E next() {
    
        
            checkForComodification();    
            if (nextIndex == size)    
                throw new NoSuchElementException();    
   
            lastReturned = next;    
            // next指向链表的下一个元素    
            next = next.next;    
            nextIndex++;    
            return lastReturned.element;    
        }    
   
        // 是否存在上一个元素    
        public boolean hasPrevious() {
    
        
            // 通过元素索引是否等于0,来判断是否达到开头。    
            return nextIndex != 0;    
        }    
   
        // 获取上一个元素    
        public E previous() {
    
        
            if (nextIndex == 0)    
            throw new NoSuchElementException();    
   
            // next指向链表的上一个元素    
            lastReturned = next = next.previous;    
            nextIndex--;    
            checkForComodification();    
            return lastReturned.element;    
        }    
   
        // 获取下一个元素的索引    
        public int nextIndex() {
    
        
            return nextIndex;    
        }    
   
        // 获取上一个元素的索引    
        public int previousIndex() {
    
        
            return nextIndex-1;    
        }    
   
        // 删除当前元素。    
        // 删除双向链表中的当前节点    
        public void remove() {
    
        
            checkForComodification();    
            Entry<E> lastNext = lastReturned.next;    
            try {
    
        
                LinkedList.this.remove(lastReturned);    
            } catch (NoSuchElementException e) {
    
        
                throw new IllegalStateException();    
            }    
            if (next==lastReturned)    
                next = lastNext;    
            else   
                nextIndex--;    
            lastReturned = header;    
            expectedModCount++;    
        }    
   
        // 设置当前节点为e    
        public void set(E e) {
    
        
            if (lastReturned == header)    
                throw new IllegalStateException();    
            checkForComodification();    
            lastReturned.element = e;    
        }    
   
        // 将e添加到当前节点的前面    
        public void add(E e) {
    
        
            checkForComodification();    
            lastReturned = header;    
            addBefore(e, next);    
            nextIndex++;    
            expectedModCount++;    
        }    
   
        // 判断 “modCount和expectedModCount是否相等”,依次来实现fail-fast机制。    
        final void checkForComodification() {
    
        
            if (modCount != expectedModCount)    
            throw new ConcurrentModificationException();    
        }    
    }    
   
    // 双向链表的节点所对应的数据结构。    
    // 包含3部分:上一节点,下一节点,当前节点值。    
    private static class Entry<E> {
    
        
        // 当前节点所包含的值    
        E element;    
        // 下一个节点    
        Entry<E> next;    
        // 上一个节点    
        Entry<E> previous;    
   
        /**   
         * 链表节点的构造函数。   
         * 参数说明:   
         *   element  —— 节点所包含的数据   
         *   next     —— 下一个节点   
         *   previous —— 上一个节点   
         */   
        Entry(E element, Entry<E> next, Entry<E> previous) {
    
        
            this.element = element;    
            this.next = next;    
            this.previous = previous;    
        }    
    }    
   
    // 将节点(节点数据是e)添加到entry节点之前。    
    private Entry<E> addBefore(E e, Entry<E> entry) {
    
        
        // 新建节点newEntry,将newEntry插入到节点e之前;并且设置newEntry的数据是e    
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);    
        newEntry.previous.next = newEntry;    
        newEntry.next.previous = newEntry;    
        // 修改LinkedList大小    
        size++;    
        // 修改LinkedList的修改统计数:用来实现fail-fast机制。    
        modCount++;    
        return newEntry;    
    }    
   
    // 将节点从链表中删除    
    private E remove(Entry<E> e) {
    
        
        if (e == header)    
            throw new NoSuchElementException();    
   
        E result = e.element;    
        e.previous.next = e.next;    
        e.next.previous = e.previous;    
        e.next = e.previous = null;    
        e.element = null;    
        size--;    
        modCount++;    
        return result;    
    }    
   
    // 反向迭代器    
    public Iterator<E> descendingIterator() {
    
        
        return new DescendingIterator();    
    }    
   
    // 反向迭代器实现类。    
    private class DescendingIterator implements Iterator {
    
        
        final ListItr itr = new ListItr(size());    
        // 反向迭代器是否下一个元素。    
        // 实际上是判断双向链表的当前节点是否达到开头    
        public boolean hasNext() {
    
        
            return itr.hasPrevious();    
        }    
        // 反向迭代器获取下一个元素。    
        // 实际上是获取双向链表的前一个节点    
        public E next() {
    
        
            return itr.previous();    
        }    
        // 删除当前节点    
        public void remove() {
    
        
            itr.remove();    
        }    
    }    
   
   
    // 返回LinkedList的Object[]数组    
    public Object[] toArray() {
    
        
    // 新建Object[]数组    
    Object[] result = new Object[size];    
        int i = 0;    
        // 将链表中所有节点的数据都添加到Object[]数组中    
        for (Entry<E> e = header.next; e != header; e = e.next)    
            result[i++] = e.element;    
    return result;    
    }    
   
    // 返回LinkedList的模板数组。所谓模板数组,即可以将T设为任意的数据类型    
    public <T> T[] toArray(T[] a) {
    
        
        // 若数组a的大小 < LinkedList的元素个数(意味着数组a不能容纳LinkedList中全部元素)    
        // 则新建一个T[]数组,T[]的大小为LinkedList大小,并将该T[]赋值给a。    
        if (a.length < size)    
            a = (T[])java.lang.reflect.Array.newInstance(    
                                a.getClass().getComponentType(), size);    
        // 将链表中所有节点的数据都添加到数组a中    
        int i = 0;    
        Object[] result = a;    
        for (Entry<E> e = header.next; e != header; e = e.next)    
            result[i++] = e.element;    
   
        if (a.length > size)    
            a[size] = null;    
   
        return a;    
    }    
   
   
    // 克隆函数。返回LinkedList的克隆对象。    
    public Object clone() {
    
        
        LinkedList<E> clone = null;    
        // 克隆一个LinkedList克隆对象    
        try {
    
        
            clone = (LinkedList<E>) super.clone();    
        } catch (CloneNotSupportedException e) {
    
        
            throw new InternalError();    
        }    
   
        // 新建LinkedList表头节点    
        clone.header = new Entry<E>(null, null, null);    
        clone.header.next = clone.header.previous = clone.header;    
        clone.size = 0;    
        clone.modCount = 0;    
   
        // 将链表中所有节点的数据都添加到克隆对象中    
        for (Entry<E> e = header.next; e != header; e = e.next)    
            clone.add(e.element);    
   
        return clone;    
    }    
   
    // java.io.Serializable的写入函数    
    // 将LinkedList的“容量,所有的元素值”都写入到输出流中    
    private void writeObject(java.io.ObjectOutputStream s)    
        throws java.io.IOException {
    
        
        // Write out any hidden serialization magic    
        s.defaultWriteObject();    
   
        // 写入“容量”    
        s.writeInt(size);    
   
        // 将链表中所有节点的数据都写入到输出流中    
        for (Entry e = header.next; e != header; e = e.next)    
            s.writeObject(e.element);    
    }    
   
    // java.io.Serializable的读取函数:根据写入方式反向读出    
    // 先将LinkedList的“容量”读出,然后将“所有的元素值”读出    
    private void readObject(java.io.ObjectInputStream s)    
        throws java.io.IOException, ClassNotFoundException {
    
        
        // Read in any hidden serialization magic    
        s.defaultReadObject();    
   
        // 从输入流中读取“容量”    
        int size = s.readInt();    
   
        // 新建链表表头节点    
        header = new Entry<E>(null, null, null);    
        header.next = header.previous = header;    
   
        // 从输入流中将“所有的元素值”并逐个添加到链表中    
        for (int i=0; i<size; i++)    
            addBefore((E)s.readObject(), header);    
    }    
   
}   

2.3 Algunos puntos resumidos

Con respecto al código fuente de LinkedList, aquí hay algunos resúmenes importantes:

1. Del código fuente se desprende claramente que la implementación de LinkedList se basa en una lista enlazada circular bidireccional y no se almacenan datos en el nodo principal, como se muestra a continuación;

2. Preste atención a dos métodos de construcción diferentes. El método de construcción sin parámetros crea directamente una lista vinculada vacía que contiene solo el nodo principal, incluido el método de construcción de la Colección. Primero, se llama al método de construcción sin parámetros para crear una lista vinculada vacía y luego se agregan los datos en la Colección. hasta el final de la lista enlazada.

3. Al buscar y eliminar un elemento, el código fuente se divide en dos casos: el elemento es nulo y no nulo, y se permite que el elemento sea nulo en LinkedList.

4. LinkedList se implementa en base a listas vinculadas, por lo que no hay problema de capacidad insuficiente, por lo que no hay forma de ampliar la capacidad.

5. Preste atención al método de entrada Entry<E>(int index) en el código fuente. Este método devuelve el nodo en la posición especificada en la lista doblemente enlazada, y no hay ningún índice de subíndice en la lista enlazada. Para especificar el elemento en la posición, se debe atravesar la lista enlazada. De la implementación del código fuente, vemos que hay una acción de aceleración aquí. En el código fuente, primero compare el índice con la mitad de la longitud del tamaño. Si el índice <tamaño/2, solo atravesará desde la posición 0 hasta la posición índice. Si el índice>tamaño/2, solo atravesará desde la posición tamaño hasta la posición hacia adelante. .índice. Esto puede reducir algunos recorridos innecesarios y mejorar así cierta eficiencia (en realidad, la eficiencia sigue siendo muy baja).

6. Preste atención a la estructura de datos Entrada correspondiente a la clase de lista vinculada. como sigue;

// 双向链表的节点所对应的数据结构。    
// 包含3部分:上一节点,下一节点,当前节点值。    
private static class Entry<E> {
    
        
    // 当前节点所包含的值    
    E element;    
    // 下一个节点    
    Entry<E> next;    
    // 上一个节点    
    Entry<E> previous;    
  
    /**   
     * 链表节点的构造函数。   
     * 参数说明:   
     *   element  —— 节点所包含的数据   
     *   next     —— 下一个节点   
     *   previous —— 上一个节点   
     */   
    Entry(E element, Entry<E> next, Entry<E> previous) {
    
        
        this.element = element;    
        this.next = next;    
        this.previous = previous;    
    }    
}    

7. LinkedList se implementa en base a listas vinculadas, por lo que la eficiencia de inserción y eliminación es alta, pero la eficiencia de búsqueda es baja (aunque hay una acción de aceleración).

8. Tenga en cuenta que el código fuente también implementa métodos de operación de pila y cola, por lo que también se puede utilizar como pila, cola y cola de doble extremo.

3. Análisis del código fuente de LinkedHashMap

3.1 Introducción a LinkedHashMap

LinkedHashMap es una subclase de HashMap. Tiene la misma estructura de almacenamiento que HashMap, pero agrega el nodo principal de una lista doblemente vinculada, encadenando todos los nodos colocados en LinkedHashmap en una lista vinculada doblemente circular uno por uno, por lo que conserva la inserción de nodos. El orden puede hacer que el orden de salida de los nodos sea el mismo que el orden de entrada.

LinkedHashMap se puede utilizar para implementar el algoritmo LRU (esto se analizará en el código fuente a continuación).

LinkedHashMap tampoco es seguro para subprocesos y solo se puede utilizar en un entorno de un solo subproceso.

3.2 Análisis del código fuente de LinkedHashMap

El código fuente de LinkedHashMap es el siguiente (se agregaron comentarios detallados):

package java.util;  
import java.io.*;  

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
    
    
    private static final long serialVersionUID = 3801124242820219131L;  
  
    //双向循环链表的头结点,整个LinkedHashMap中只有一个header,  
    //它将哈希表中所有的Entry贯穿起来,header中不保存key-value对,只保存前后节点的引用  
    private transient Entry<K,V> header;  
  
    //双向链表中元素排序规则的标志位。  
    //accessOrder为false,表示按插入顺序排序  
    //accessOrder为true,表示按访问顺序排序  
    private final boolean accessOrder;  
  
    //调用HashMap的构造方法来构造底层的数组  
    public LinkedHashMap(int initialCapacity, float loadFactor) {
    
      
        super(initialCapacity, loadFactor);  
        accessOrder = false;    //链表中的元素默认按照插入顺序排序  
    }  
  
    //加载因子取默认的0.75f  
    public LinkedHashMap(int initialCapacity) {
    
      
        super(initialCapacity);  
        accessOrder = false;  
    }  
  
    //加载因子取默认的0.75f,容量取默认的16  
    public LinkedHashMap() {
    
      
        super();  
        accessOrder = false;  
    }  
  
    //含有子Map的构造方法,同样调用HashMap的对应的构造方法  
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
    
      
        super(m);  
        accessOrder = false;  
    }  
  
    //该构造方法可以指定链表中的元素排序的规则  
    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
    
      
        super(initialCapacity, loadFactor);  
        this.accessOrder = accessOrder;  
    }  
  
    //覆写父类的init()方法(HashMap中的init方法为空),  
    //该方法在父类的构造方法和Clone、readObject中在插入元素前被调用,  
    //初始化一个空的双向循环链表,头结点中不保存数据,头结点的下一个节点才开始保存数据。  
    void init() {
    
      
        header = new Entry<K,V>(-1, null, null, null);  
        header.before = header.after = header;  
    }  
  	
    //覆写HashMap中的transfer方法,它在父类的resize方法中被调用,  
    //扩容后,将key-value对重新映射到新的newTable中  
    //覆写该方法的目的是为了提高复制的效率,  
    //这里充分利用双向循环链表的特点进行迭代,不用对底层的数组进行for循环。  
    void transfer(HashMap.Entry[] newTable) {
    
      
        int newCapacity = newTable.length;  
        for (Entry<K,V> e = header.after; e != header; e = e.after) {
    
      
            int index = indexFor(e.hash, newCapacity);  
            e.next = newTable[index];  
            newTable[index] = e;  
        }  
    }  
  	
    //覆写HashMap中的containsValue方法,  
    //覆写该方法的目的同样是为了提高查询的效率,  
    //利用双向循环链表的特点进行查询,少了对数组的外层for循环  
    public boolean containsValue(Object value) {
    
      
        // Overridden to take advantage of faster iterator  
        if (value==null) {
    
      
            for (Entry e = header.after; e != header; e = e.after)  
                if (e.value==null)  
                    return true;  
        } else {
    
      
            for (Entry e = header.after; e != header; e = e.after)  
                if (value.equals(e.value))  
                    return true;  
        }  
        return false;  
    }  
  	
    //覆写HashMap中的get方法,通过getEntry方法获取Entry对象。  
    //注意这里的recordAccess方法,  
    //如果链表中元素的排序规则是按照插入的先后顺序排序的话,该方法什么也不做,  
    //如果链表中元素的排序规则是按照访问的先后顺序排序的话,则将e移到链表的末尾处。  
    public V get(Object key) {
    
      
        Entry<K,V> e = (Entry<K,V>)getEntry(key);  
        if (e == null)  
            return null;  
        e.recordAccess(this);  
        return e.value;  
    }  
  
    //清空HashMap,并将双向链表还原为只有头结点的空链表  
    public void clear() {
    
      
        super.clear();  
        header.before = header.after = header;  
    }  
  
    //Enty的数据结构,多了两个指向前后节点的引用  
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
    
      
        // These fields comprise the doubly linked list used for iteration.  
        Entry<K,V> before, after;  
  
        //调用父类的构造方法  
        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
    
      
            super(hash, key, value, next);  
        }  
  
        //双向循环链表中,删除当前的Entry  
        private void remove() {
    
      
            before.after = after;  
            after.before = before;  
        }  
  
        //双向循环立链表中,将当前的Entry插入到existingEntry的前面  
        private void addBefore(Entry<K,V> existingEntry) {
    
      
            after  = existingEntry;  
            before = existingEntry.before;  
            before.after = this;  
            after.before = this;  
        }  
  		
        //覆写HashMap中的recordAccess方法(HashMap中该方法为空),  
        //当调用父类的put方法,在发现插入的key已经存在时,会调用该方法,  
        //调用LinkedHashmap覆写的get方法时,也会调用到该方法,  
        //该方法提供了LRU算法的实现,它将最近使用的Entry放到双向循环链表的尾部,  
        //accessOrder为true时,get方法会调用recordAccess方法  
        //put方法在覆盖key-value对时也会调用recordAccess方法  
        //它们导致Entry最近使用,因此将其移到双向链表的末尾  
        void recordAccess(HashMap<K,V> m) {
    
      
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
            //如果链表中元素按照访问顺序排序,则将当前访问的Entry移到双向循环链表的尾部,  
            //如果是按照插入的先后顺序排序,则不做任何事情。  
            if (lm.accessOrder) {
    
      
                lm.modCount++;  
                //移除当前访问的Entry  
                remove();  
                //将当前访问的Entry插入到链表的尾部  
                addBefore(lm.header);  
            }  
        }  
  
        void recordRemoval(HashMap<K,V> m) {
    
      
            remove();  
        }  
    }  
  
    //迭代器  
    private abstract class LinkedHashIterator<T> implements Iterator<T> {
    
      
    Entry<K,V> nextEntry    = header.after;  
    Entry<K,V> lastReturned = null;  
  
    /** 
     * The modCount value that the iterator believes that the backing 
     * List should have.  If this expectation is violated, the iterator 
     * has detected concurrent modification. 
     */  
    int expectedModCount = modCount;  
  
    public boolean hasNext() {
    
      
		return nextEntry != header;  
    }  
  
    public void remove() {
    
      
        if (lastReturned == null)  
        	throw new IllegalStateException();  
        if (modCount != expectedModCount)  
        	throw new ConcurrentModificationException();  
  
        LinkedHashMap.this.remove(lastReturned.key);  
        lastReturned = null;  
        expectedModCount = modCount;  
    }  
  
    //从head的下一个节点开始迭代  
    Entry<K,V> nextEntry() {
    
      
        if (modCount != expectedModCount)  
        	throw new ConcurrentModificationException();  
        if (nextEntry == header)  
        	throw new NoSuchElementException();  
  
        Entry<K,V> e = lastReturned = nextEntry;  
        nextEntry = e.after;
        return e;  
    }  
}  
  
    //key迭代器  
    private class KeyIterator extends LinkedHashIterator<K> {
    
      
    	public K next() {
    
     
    		return nextEntry().getKey(); 
    	}  
    }  
  
    //value迭代器  
    private class ValueIterator extends LinkedHashIterator<V> {
    
      
    	public V next() {
    
     
    		return nextEntry().value; 
    	}  
    }  
  
    //Entry迭代器  
    private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
    
      
    	public Map.Entry<K,V> next() {
    
     
    		return nextEntry(); 
    	}  
    }  
  
    // These Overrides alter the behavior of superclass view iterator() methods  
    Iterator<K> newKeyIterator()   {
    
     return new KeyIterator();   }  
    Iterator<V> newValueIterator() {
    
     return new ValueIterator(); }  
    Iterator<Map.Entry<K,V>> newEntryIterator() {
    
     return new EntryIterator(); }  
  
    //覆写HashMap中的addEntry方法,LinkedHashmap并没有覆写HashMap中的put方法,  
    //而是覆写了put方法所调用的addEntry方法和recordAccess方法,  
    //put方法在插入的key已存在的情况下,会调用recordAccess方法,  
    //在插入的key不存在的情况下,要调用addEntry插入新的Entry  
    void addEntry(int hash, K key, V value, int bucketIndex) {
    
      
        //创建新的Entry,并插入到LinkedHashMap中  
        createEntry(hash, key, value, bucketIndex);  
  
        //双向链表的第一个有效节点(header后的那个节点)为近期最少使用的节点  
        Entry<K,V> eldest = header.after;  
        //如果有必要,则删除掉该近期最少使用的节点,  
        //这要看对removeEldestEntry的覆写,由于默认为false,因此默认是不做任何处理的。  
        if (removeEldestEntry(eldest)) {
    
      
            removeEntryForKey(eldest.key);  
        } else {
    
      
            //扩容到原来的2倍  
            if (size >= threshold)  
                resize(2 * table.length);  
        }  
    }  
  
    void createEntry(int hash, K key, V value, int bucketIndex) {
    
      
        //创建新的Entry,并将其插入到数组对应槽的单链表的头结点处,这点与HashMap中相同  
        HashMap.Entry<K,V> old = table[bucketIndex];  
        Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
        table[bucketIndex] = e;  
        //每次插入Entry时,都将其移到双向链表的尾部,  
        //这便会按照Entry插入LinkedHashMap的先后顺序来迭代元素,  
        //同时,新put进来的Entry是最近访问的Entry,把其放在链表末尾 ,符合LRU算法的实现  
        e.addBefore(header);  
        size++;  
    }  
  
    //该方法是用来被覆写的,一般如果用LinkedHashmap实现LRU算法,就要覆写该方法,  
    //比如可以将该方法覆写为如果设定的内存已满,则返回true,这样当再次向LinkedHashMap中put  
    //Entry时,在调用的addEntry方法中便会将近期最少使用的节点删除掉(header后的那个节点)。  
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    
      
        return false;  
    }  
}  

3.3 Algunos puntos resumidos

Con respecto al código fuente de LinkedHashMap, se brindan los siguientes resúmenes importantes:

1. Como se puede ver en el código fuente, se agrega un nodo principal a LinkedHashMap, y todas las entradas insertadas en LinkedHashMap se agregan al final de la lista enlazada circular bidireccional con head como nodo principal en el orden de inserción.

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-YlUzBgYY-1692927385674)(https://image.xiaoxiaofeng.site/blog/2023 /05/18/xxf -20230518175942.png?xxfjava)]

1. En realidad, es una combinación de las estructuras de almacenamiento de las dos clases de colección HashMap y LinkedList. En LinkedHashMapMap, todas las entradas de colocación se almacenan en la tabla hash como se muestra en la primera imagen, pero también define una lista enlazada circular de dos vías vacía con head como nodo principal. Cada vez que se coloca una entrada, además de guardar lo coloca en la posición correspondiente en la tabla hash, también lo inserta en la cola de la lista enlazada doblemente circular.

2. Dado que LinkedHashMap hereda de HashMap, tiene todas las características de HashMap y también permite que la clave y el valor sean nulos.

3. Preste atención al indicador accessOrder en el código fuente. Cuando es falso, significa que los elementos en la lista doblemente enlazada se ordenan según el orden en que se inserta la entrada en LinkedHashMap, es decir, cada entrada se coloca en LinkedHashMap se coloca al final de la lista doblemente vinculada, por lo que al atravesar la lista doblemente vinculada, el orden de salida de Entry es consistente con el orden de inserción, que también es el orden de almacenamiento predeterminado de la lista doblemente vinculada; cuando es cierto, significa que los elementos en la lista doblemente vinculada están organizados en el orden de acceso. Puede ver que aunque el orden en que se inserta la entrada en la lista vinculada todavía está en el orden en que se coloca en LinkedHashMap , pero tanto los métodos put como get llaman al método recordAccess (el método put llama al método recordAccess cuando la clave es la misma y sobrescribe la entrada original). Este método determina el orden de acceso. ¿Es cierto? Si es así, la entrada a la que se accede actualmente ( la Entrada que entra o la Entrada que sale) se mueve al final de la lista doblemente enlazada (cuando las claves son diferentes, al poner una nueva Entrada se llamará a addEntry, que llamará a creatEntry, este método también pone la elemento recién insertado al final de la lista doblemente enlazada, que es consistente con el orden de inserción y el orden de acceso, porque en este momento también se accede a la Entrada), de lo contrario no se hace nada.

4. Preste atención al método de construcción. Los primeros cuatro métodos de construcción establecen accessOrder en falso, lo que indica que el valor predeterminado es ordenar según el orden de inserción. El quinto método de construcción puede personalizar el valor del accessOrder entrante, para que pueda especificar Lista enlazada circular bidireccional. Para las reglas de clasificación de elementos, LinkedHashMap generalmente se usa para implementar el algoritmo LRU, y este método de construcción se usa para establecer accessOrder en verdadero.

5. LinkedHashMap no sobrescribe el método put en HashMap, pero sobrescribe el método addEntry y el método recordAccess llamados en el método put. Regresemos y veamos el método put de HashMap:

// 将“key-value”添加到HashMap中      
public V put(K key, V value) {
    
          
    // 若“key为null”,则将该键值对添加到table[0]中。      
    if (key == null)      
        return putForNullKey(value);      
    // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。      
    int hash = hash(key.hashCode());      
    int i = indexFor(hash, table.length);      
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    
          
        Object k;      
        // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!      
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    
          
            V oldValue = e.value;      
            e.value = value;      
            e.recordAccess(this);      
            return oldValue;      
        }      
    }      
  
    // 若“该key”对应的键值对不存在,则将“key-value”添加到table中      
    modCount++;    
    //将key-value添加到table[i]处    
    addEntry(hash, key, value, i);      
    return null;      
}      

Cuando la clave de la Entrada a colocar ya existe en la tabla hash, se llamará al método recordAccess. Cuando la clave no existe, se llamará al método addEntry para insertar la nueva Entrada en el encabezado de la lista de enlaces individuales la ranura correspondiente.

Veamos primero el método recordAccess:

//覆写HashMap中的recordAccess方法(HashMap中该方法为空),  
//当调用父类的put方法,在发现插入的key已经存在时,会调用该方法,  
//调用LinkedHashmap覆写的get方法时,也会调用到该方法,  
//该方法提供了LRU算法的实现,它将最近使用的Entry放到双向循环链表的尾部,  
//accessOrder为true时,get方法会调用recordAccess方法  
//put方法在覆盖key-value对时也会调用recordAccess方法  
//它们导致Entry最近使用,因此将其移到双向链表的末尾  
void recordAccess(HashMap<K,V> m) {
    
      
	LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
    //如果链表中元素按照访问顺序排序,则将当前访问的Entry移到双向循环链表的尾部,  
    //如果是按照插入的先后顺序排序,则不做任何事情。  
	if (lm.accessOrder) {
    
      
        lm.modCount++;  
        //移除当前访问的Entry  
        remove();  
        //将当前访问的Entry插入到链表的尾部  
        addBefore(lm.header);  
     }  
}  

Este método determinará si accessOrder es verdadero. Si es verdadero, moverá la entrada a la que se accede actualmente (aquí la entrada put) al final de la lista doblemente enlazada, ordenando así los elementos en la lista doblemente enlazada según el orden de acceso. (La entrada a la que se accedió más recientemente se coloca al final de la lista vinculada. Después de hacer esto muchas veces, los elementos al frente son los elementos que no se han visitado recientemente. Al implementar el algoritmo LRU, cuando el número de nodos en la lista doblemente enlazada alcanza el máximo, los elementos anteriores se eliminan. Sí, porque el elemento anterior es el menos utilizado recientemente), de lo contrario no se hace nada.

Veamos el método addEntry:

//覆写HashMap中的addEntry方法,LinkedHashmap并没有覆写HashMap中的put方法,  
//而是覆写了put方法所调用的addEntry方法和recordAccess方法,  
//put方法在插入的key已存在的情况下,会调用recordAccess方法,  
//在插入的key不存在的情况下,要调用addEntry插入新的Entry  
void addEntry(int hash, K key, V value, int bucketIndex) {
    
      
    //创建新的Entry,并插入到LinkedHashMap中  
    createEntry(hash, key, value, bucketIndex);  

    //双向链表的第一个有效节点(header后的那个节点)为近期最少使用的节点  
    Entry<K,V> eldest = header.after;  
    //如果有必要,则删除掉该近期最少使用的节点,  
    //这要看对removeEldestEntry的覆写,由于默认为false,因此默认是不做任何处理的。  
    if (removeEldestEntry(eldest)) {
    
      
        removeEntryForKey(eldest.key);  
    } else {
    
      
        //扩容到原来的2倍  
        if (size >= threshold)  
            resize(2 * table.length);  
    }  
}  

void createEntry(int hash, K key, V value, int bucketIndex) {
    
      
    //创建新的Entry,并将其插入到数组对应槽的单链表的头结点处,这点与HashMap中相同  
    HashMap.Entry<K,V> old = table[bucketIndex];  
    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
    table[bucketIndex] = e;  
    //每次插入Entry时,都将其移到双向链表的尾部,  
    //这便会按照Entry插入LinkedHashMap的先后顺序来迭代元素,  
    //同时,新put进来的Entry是最近访问的Entry,把其放在链表末尾 ,符合LRU算法的实现  
    e.addBefore(header);  
    size++;  
}  

La nueva Entrada también se inserta en el nodo principal de la lista enlazada individualmente correspondiente al espacio correspondiente en la tabla, pero se puede ver que en createEntry, la Entrada recién colocada también se inserta en la cola de la lista doblemente enlazada. la perspectiva del orden de inserción, por ejemplo, cuando se inserta una nueva entrada al final de una lista doblemente enlazada, las entradas se pueden iterar de acuerdo con el orden de inserción. Desde la perspectiva de la secuencia de acceso, la entrada recién colocada es la más Entrada a la que se accedió recientemente, por lo que también debe colocarse al final de la lista doblemente enlazada.

También hay un método removeEldestEntry anterior, que es el siguiente:

//该方法是用来被覆写的,一般如果用LinkedHashmap实现LRU算法,就要覆写该方法,  
//比如可以将该方法覆写为如果设定的内存已满,则返回true,这样当再次向LinkedHashMap中put  
//Entry时,在调用的addEntry方法中便会将近期最少使用的节点删除掉(header后的那个节点)。  
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    
      
    return false;  
}

Este método devuelve falso por defecto. Generalmente anulamos este método cuando usamos LinkedHashMap para implementar el algoritmo LRU. La implementación general es devolver verdadero cuando la memoria establecida (aquí se refiere al número de nodos) alcanza el valor máximo, de modo que se coloque nuevo Cuando la entrada (la clave de la entrada aún no existe en la tabla hash), se llamará al método removeEntryForKey para eliminar el nodo utilizado menos recientemente (el nodo después del encabezado en realidad no se usó recientemente).

6. LinkedHashMap anula el método get de HashMap:

//覆写HashMap中的get方法,通过getEntry方法获取Entry对象。  
//注意这里的recordAccess方法,  
//如果链表中元素的排序规则是按照插入的先后顺序排序的话,该方法什么也不做,  
//如果链表中元素的排序规则是按照访问的先后顺序排序的话,则将e移到链表的末尾处。  
public V get(Object key) {
    
      
    Entry<K,V> e = (Entry<K,V>)getEntry(key);  
    if (e == null)  
        return null;  
    e.recordAccess(this);  
    return e.value;  
}  

Obtenga la entrada primero. Si no es nula, llame también al método recordAccess. Se ha explicado claramente anteriormente y no lo explicaré más aquí.

7. Finalmente, hablemos de cómo LinkedHashMap implementa LRU. En primer lugar, cuando accessOrder es verdadero, se habilitará el modo de clasificación por orden de acceso y se podrá utilizar para implementar el algoritmo LRU. Podemos ver que ya sea el método put o el método get, la entrada de destino se convertirá en la entrada a la que se accedió más recientemente, por lo que la entrada se agrega al final de la lista doblemente vinculada (el método get se implementa llamando al método recordAccess y el método put sobrescribe la existente (en el caso de la clave, también se realiza llamando al método recordAccess. Al insertar una nueva entrada, se realiza mediante el método addBefore en createEntry), de modo que se coloque la entrada utilizada más recientemente. en la parte posterior de la lista doblemente enlazada. Varias veces Después de la operación, la entrada al frente de la lista doblemente enlazada no se ha utilizado recientemente, por lo que cuando el número de nodos está lleno, la primera entrada que se eliminará (la entrada detrás la cabecera) será la entrada utilizada menos recientemente.

4. Análisis del código fuente de HashMap (JDK7)

4.1 Descripción general de HashMap

HashMap se implementa en base a una tabla hash. Cada elemento es un par clave-valor. Los conflictos se resuelven internamente a través de una lista enlazada individualmente. Cuando la capacidad es insuficiente (supera el umbral), también crecerá automáticamente.

HashMap no es seguro para subprocesos y solo se usa en entornos de un solo subproceso. En entornos de subprocesos múltiples, se puede usar concurrentHashMap en el paquete concurrente.

HashMap implementa la interfaz Serializable, por lo que admite la serialización, implementa la interfaz Cloneable y se puede clonar.

4.2 Respuestas a cuatro inquietudes sobre HashMap

punto de enfoque en conclusión
¿HashMap permite vaciar? Tanto la clave como el valor pueden estar vacíos
¿HashMap permite datos duplicados? La clave repetida se sobrescribirá, se permite repetir el valor
¿Está ordenado HashMap? Desordenado: Específicamente, este desorden se refiere al hecho de que al atravesar un HashMap, el orden de los elementos obtenidos es básicamente imposible de ser el orden de colocación.
¿Es seguro el subproceso HashMap? No es seguro para subprocesos

4.3 Análisis del código fuente de HashMap

El código fuente de HashMap es el siguiente (con comentarios más detallados agregados):

package java.util;    
import java.io.*;    
   
public class HashMap<K,V>    
    extends AbstractMap<K,V>    
    implements Map<K,V>, Cloneable, Serializable    
{
    
        
   
    // 默认的初始容量(容量为HashMap中槽的数目)是16,且实际容量必须是2的整数次幂。    
    static final int DEFAULT_INITIAL_CAPACITY = 16;    
   
    // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)    
    static final int MAXIMUM_CAPACITY = 1 << 30;    
   
    // 默认加载因子为0.75   
    static final float DEFAULT_LOAD_FACTOR = 0.75f;    
   
    // 存储数据的Entry数组,长度是2的幂。    
    // HashMap采用链表法解决冲突,每一个Entry本质上是一个单向链表    
    transient Entry[] table;    
   
    // HashMap的底层数组中已用槽的数量    
    transient int size;    
   
    // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)    
    int threshold;    
   
    // 加载因子实际大小    
    final float loadFactor;    
   
    // HashMap被改变的次数    
    transient volatile int modCount;    
   
    // 指定“容量大小”和“加载因子”的构造函数    
    public HashMap(int initialCapacity, float loadFactor) {
    
        
        if (initialCapacity < 0)    
            throw new IllegalArgumentException("Illegal initial capacity: " +    
                                               initialCapacity);    
        // HashMap的最大容量只能是MAXIMUM_CAPACITY    
        if (initialCapacity > MAXIMUM_CAPACITY)    
            initialCapacity = MAXIMUM_CAPACITY;    
        //加载因此不能小于0  
        if (loadFactor <= 0 || Float.isNaN(loadFactor))    
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
   
        // 找出“大于initialCapacity”的最小的2的幂    
        int capacity = 1;    
        while (capacity < initialCapacity)    
            capacity <<= 1;    
   
        // 设置“加载因子”    
        this.loadFactor = loadFactor;    
        // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。    
        threshold = (int)(capacity * loadFactor);    
        // 创建Entry数组,用来保存数据    
        table = new Entry[capacity];    
        init();    
    }    
   
   
    // 指定“容量大小”的构造函数    
    public HashMap(int initialCapacity) {
    
        
        this(initialCapacity, DEFAULT_LOAD_FACTOR);    
    }    
   
    // 默认构造函数。    
    public HashMap() {
    
        
        // 设置“加载因子”为默认加载因子0.75    
        this.loadFactor = DEFAULT_LOAD_FACTOR;    
        // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。    
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);    
        // 创建Entry数组,用来保存数据    
        table = new Entry[DEFAULT_INITIAL_CAPACITY];    
        init();    
    }    
   
    // 包含“子Map”的构造函数    
    public HashMap(Map<? extends K, ? extends V> m) {
    
        
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,    
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);    
        // 将m中的全部元素逐个添加到HashMap中    
        putAllForCreate(m);    
    }    
   
    //求hash值的方法,重新计算hash值  
    static int hash(int h) {
    
        
        h ^= (h >>> 20) ^ (h >>> 12);    
        return h ^ (h >>> 7) ^ (h >>> 4);    
    }    
   
    // 返回h在数组中的索引值,这里用&代替取模,旨在提升效率   
    // h & (length-1)保证返回值的小于length    
    static int indexFor(int h, int length) {
    
        
        return h & (length-1);    
    }    
   
    public int size() {
    
        
        return size;    
    }    
   
    public boolean isEmpty() {
    
        
        return size == 0;    
    }    
   
    // 获取key对应的value    
    public V get(Object key) {
    
        
        if (key == null)    
            return getForNullKey();    
        // 获取key的hash值    
        int hash = hash(key.hashCode());    
        // 在“该hash值对应的链表”上查找“键值等于key”的元素    
        for (Entry<K,V> e = table[indexFor(hash, table.length)];    
             e != null;    
             e = e.next) {
    
        
            Object k;    
            //判断key是否相同  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))    
                return e.value;    
        }  
        //没找到则返回null  
        return null;    
    }    
   
    // 获取“key为null”的元素的值    
    // HashMap将“key为null”的元素存储在table[0]位置,但不一定是该链表的第一个位置!    
    private V getForNullKey() {
    
        
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    
        
            if (e.key == null)    
                return e.value;    
        }    
        return null;    
    }    
   
    // HashMap是否包含key    
    public boolean containsKey(Object key) {
    
        
        return getEntry(key) != null;    
    }    
   
    // 返回“键为key”的键值对    
    final Entry<K,V> getEntry(Object key) {
    
        
        // 获取哈希值    
        // HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值    
        int hash = (key == null) ? 0 : hash(key.hashCode());    
        // 在“该hash值对应的链表”上查找“键值等于key”的元素    
        for (Entry<K,V> e = table[indexFor(hash, table.length)];    
             e != null;    
             e = e.next) {
    
        
            Object k;    
            if (e.hash == hash &&    
                ((k = e.key) == key || (key != null && key.equals(k))))    
                return e;    
        }    
        return null;    
    }    
   
    // 将“key-value”添加到HashMap中    
    public V put(K key, V value) {
    
        
        // 若“key为null”,则将该键值对添加到table[0]中。    
        if (key == null)    
            return putForNullKey(value);    
        // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。    
        int hash = hash(key.hashCode());    
        int i = indexFor(hash, table.length);    
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    
        
            Object k;    
            // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!    
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    
        
                V oldValue = e.value;    
                e.value = value;    
                e.recordAccess(this);    
                return oldValue;    
            }    
        }    
   
        // 若“该key”对应的键值对不存在,则将“key-value”添加到table中    
        modCount++;  
        // 将key-value添加到table[i]处  
        addEntry(hash, key, value, i);    
        return null;    
    }    
   
    // putForNullKey()的作用是将“key为null”键值对添加到table[0]位置    
    private V putForNullKey(V 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;    
            }    
        }    
        // 如果没有存在key为null的键值对,则直接题阿见到table[0]处!    
        modCount++;    
        addEntry(0, null, value, 0);    
        return null;    
    }    
   
    // 创建HashMap对应的“添加方法”,    
    // 它和put()不同。putForCreate()是内部方法,它被构造函数等调用,用来创建HashMap    
    // 而put()是对外提供的往HashMap中添加元素的方法。    
    private void putForCreate(K key, V value) {
    
        
        int hash = (key == null) ? 0 : hash(key.hashCode());    
        int i = indexFor(hash, table.length);    
   
        // 若该HashMap表中存在“键值等于key”的元素,则替换该元素的value值    
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    
        
            Object k;    
            if (e.hash == hash &&    
                ((k = e.key) == key || (key != null && key.equals(k)))) {
    
        
                e.value = value;    
                return;    
            }    
        }    
   
        // 若该HashMap表中不存在“键值等于key”的元素,则将该key-value添加到HashMap中    
        createEntry(hash, key, value, i);    
    }    
   
    // 将“m”中的全部元素都添加到HashMap中。    
    // 该方法被内部的构造HashMap的方法所调用。    
    private void putAllForCreate(Map<? extends K, ? extends V> m) {
    
        
        // 利用迭代器将元素逐个添加到HashMap中    
        for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
    
        
            Map.Entry<? extends K, ? extends V> e = i.next();    
            putForCreate(e.getKey(), e.getValue());    
        }    
    }    
   
    // 重新调整HashMap的大小,newCapacity是调整后的容量    
    void resize(int newCapacity) {
    
        
        Entry[] oldTable = table;    
        int oldCapacity = oldTable.length;   
        //如果就容量已经达到了最大值,则不能再扩容,直接返回  
        if (oldCapacity == MAXIMUM_CAPACITY) {
    
        
            threshold = Integer.MAX_VALUE;    
            return;    
        }    
   
        // 新建一个HashMap,将“旧HashMap”的全部元素添加到“新HashMap”中,    
        // 然后,将“新HashMap”赋值给“旧HashMap”。    
        Entry[] newTable = new Entry[newCapacity];    
        transfer(newTable);    
        table = newTable;    
        threshold = (int)(newCapacity * loadFactor);    
    }    
   
    // 将HashMap中的全部元素都添加到newTable中    
    void transfer(Entry[] newTable) {
    
        
        Entry[] src = table;    
        int newCapacity = newTable.length;    
        for (int j = 0; j < src.length; j++) {
    
        
            Entry<K,V> e = src[j];    
            if (e != null) {
    
        
                src[j] = null;    
                do {
    
        
                    Entry<K,V> next = e.next;    
                    int i = indexFor(e.hash, newCapacity);    
                    e.next = newTable[i];    
                    newTable[i] = e;    
                    e = next;    
                } while (e != null);    
            }    
        }    
    }    
   
    // 将"m"的全部元素都添加到HashMap中    
    public void putAll(Map<? extends K, ? extends V> m) {
    
        
        // 有效性判断    
        int numKeysToBeAdded = m.size();    
        if (numKeysToBeAdded == 0)    
            return;    
   
        // 计算容量是否足够,    
        // 若“当前阀值容量 < 需要的容量”,则将容量x2。    
        if (numKeysToBeAdded > threshold) {
    
        
            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);    
            if (targetCapacity > MAXIMUM_CAPACITY)    
                targetCapacity = MAXIMUM_CAPACITY;    
            int newCapacity = table.length;    
            while (newCapacity < targetCapacity)    
                newCapacity <<= 1;    
            if (newCapacity > table.length)    
                resize(newCapacity);    
        }    
   
        // 通过迭代器,将“m”中的元素逐个添加到HashMap中。    
        for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
    
        
            Map.Entry<? extends K, ? extends V> e = i.next();    
            put(e.getKey(), e.getValue());    
        }    
    }    
   
    // 删除“键为key”元素    
    public V remove(Object key) {
    
        
        Entry<K,V> e = removeEntryForKey(key);    
        return (e == null ? null : e.value);    
    }    
   
    // 删除“键为key”的元素    
    final Entry<K,V> removeEntryForKey(Object key) {
    
        
        // 获取哈希值。若key为null,则哈希值为0;否则调用hash()进行计算    
        int hash = (key == null) ? 0 : hash(key.hashCode());    
        int i = indexFor(hash, table.length);    
        Entry<K,V> prev = table[i];    
        Entry<K,V> e = prev;    
   
        // 删除链表中“键为key”的元素    
        // 本质是“删除单向链表中的节点”    
        while (e != null) {
    
        
            Entry<K,V> next = e.next;    
            Object k;    
            if (e.hash == hash &&    
                ((k = e.key) == key || (key != null && key.equals(k)))) {
    
        
                modCount++;    
                size--;    
                if (prev == e)    
                    table[i] = next;    
                else   
                    prev.next = next;    
                e.recordRemoval(this);    
                return e;    
            }    
            prev = e;    
            e = next;    
        }    
   
        return e;    
    }    
   
    // 删除“键值对”    
    final Entry<K,V> removeMapping(Object o) {
    
        
        if (!(o instanceof Map.Entry))    
            return null;    
   
        Map.Entry<K,V> entry = (Map.Entry<K,V>) o;    
        Object key = entry.getKey();    
        int hash = (key == null) ? 0 : hash(key.hashCode());    
        int i = indexFor(hash, table.length);    
        Entry<K,V> prev = table[i];    
        Entry<K,V> e = prev;    
   
        // 删除链表中的“键值对e”    
        // 本质是“删除单向链表中的节点”    
        while (e != null) {
    
        
            Entry<K,V> next = e.next;    
            if (e.hash == hash && e.equals(entry)) {
    
        
                modCount++;    
                size--;    
                if (prev == e)    
                    table[i] = next;    
                else   
                    prev.next = next;    
                e.recordRemoval(this);    
                return e;    
            }    
            prev = e;    
            e = next;    
        }    
   
        return e;    
    }    
   
    // 清空HashMap,将所有的元素设为null    
    public void clear() {
    
        
        modCount++;    
        Entry[] tab = table;    
        for (int i = 0; i < tab.length; i++)    
            tab[i] = null;    
        size = 0;    
    }    
   
    // 是否包含“值为value”的元素    
    public boolean containsValue(Object value) {
    
        
    // 若“value为null”,则调用containsNullValue()查找    
    if (value == null)    
            return containsNullValue();    
   
    // 若“value不为null”,则查找HashMap中是否有值为value的节点。    
    Entry[] tab = table;    
        for (int i = 0; i < tab.length ; i++)    
            for (Entry e = tab[i] ; e != null ; e = e.next)    
                if (value.equals(e.value))    
                    return true;    
    return false;    
    }    
   
    // 是否包含null值    
    private boolean containsNullValue() {
    
        
    Entry[] tab = table;    
        for (int i = 0; i < tab.length ; i++)    
            for (Entry e = tab[i] ; e != null ; e = e.next)    
                if (e.value == null)    
                    return true;    
    return false;    
    }    
   
    // 克隆一个HashMap,并返回Object对象    
    public Object clone() {
    
        
        HashMap<K,V> result = null;    
        try {
    
        
            result = (HashMap<K,V>)super.clone();    
        } catch (CloneNotSupportedException e) {
    
        
            // assert false;    
        }    
        result.table = new Entry[table.length];    
        result.entrySet = null;    
        result.modCount = 0;    
        result.size = 0;    
        result.init();    
        // 调用putAllForCreate()将全部元素添加到HashMap中    
        result.putAllForCreate(this);    
   
        return result;    
    }    
   
    // Entry是单向链表。    
    // 它是 “HashMap链式存储法”对应的链表。    
    // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数    
    static class Entry<K,V> implements Map.Entry<K,V> {
    
        
        final K key;    
        V value;    
        // 指向下一个节点    
        Entry<K,V> next;    
        final int hash;    
   
        // 构造函数。    
        // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)"    
        Entry(int h, K k, V v, Entry<K,V> n) {
    
        
            value = v;    
            next = n;    
            key = k;    
            hash = h;    
        }    
   
        public final K getKey() {
    
        
            return key;    
        }    
   
        public final V getValue() {
    
        
            return value;    
        }    
   
        public final V setValue(V newValue) {
    
        
            V oldValue = value;    
            value = newValue;    
            return oldValue;    
        }    
   
        // 判断两个Entry是否相等    
        // 若两个Entry的“key”和“value”都相等,则返回true。    
        // 否则,返回false    
        public final boolean equals(Object o) {
    
        
            if (!(o instanceof Map.Entry))    
                return false;    
            Map.Entry e = (Map.Entry)o;    
            Object k1 = getKey();    
            Object k2 = e.getKey();    
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
    
        
                Object v1 = getValue();    
                Object v2 = e.getValue();    
                if (v1 == v2 || (v1 != null && v1.equals(v2)))    
                    return true;    
            }    
            return false;    
        }    
   
        // 实现hashCode()    
        public final int hashCode() {
    
        
            return (key==null   ? 0 : key.hashCode()) ^    
                   (value==null ? 0 : value.hashCode());    
        }    
   
        public final String toString() {
    
        
            return getKey() + "=" + getValue();    
        }    
   
        // 当向HashMap中添加元素时,绘调用recordAccess()。    
        // 这里不做任何处理    
        void recordAccess(HashMap<K,V> m) {
    
        
        }    
   
        // 当从HashMap中删除元素时,绘调用recordRemoval()。    
        // 这里不做任何处理    
        void recordRemoval(HashMap<K,V> m) {
    
        
        }    
    }    
   
    // 新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。    
    void addEntry(int hash, K key, V value, int bucketIndex) {
    
        
        // 保存“bucketIndex”位置的值到“e”中    
        Entry<K,V> e = table[bucketIndex];    
        // 设置“bucketIndex”位置的元素为“新Entry”,    
        // 设置“e”为“新Entry的下一个节点”    
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    
        // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小    
        if (size++ >= threshold)    
            resize(2 * table.length);    
    }    
   
    // 创建Entry。将“key-value”插入指定位置。    
    void createEntry(int hash, K key, V value, int bucketIndex) {
    
        
        // 保存“bucketIndex”位置的值到“e”中    
        Entry<K,V> e = table[bucketIndex];    
        // 设置“bucketIndex”位置的元素为“新Entry”,    
        // 设置“e”为“新Entry的下一个节点”    
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    
        size++;    
    }    
   
    // HashIterator是HashMap迭代器的抽象出来的父类,实现了公共了函数。    
    // 它包含“key迭代器(KeyIterator)”、“Value迭代器(ValueIterator)”和“Entry迭代器(EntryIterator)”3个子类。    
    private abstract class HashIterator<E> implements Iterator<E> {
    
        
        // 下一个元素    
        Entry<K,V> next;    
        // expectedModCount用于实现fast-fail机制。    
        int expectedModCount;    
        // 当前索引    
        int index;    
        // 当前元素    
        Entry<K,V> current;    
   
        HashIterator() {
    
        
            expectedModCount = modCount;    
            if (size > 0) {
    
     // advance to first entry    
                Entry[] t = table;    
                // 将next指向table中第一个不为null的元素。    
                // 这里利用了index的初始值为0,从0开始依次向后遍历,直到找到不为null的元素就退出循环。    
                while (index < t.length && (next = t[index++]) == null)    
                    ;    
            }    
        }    
   
        public final boolean hasNext() {
    
        
            return next != null;    
        }    
   
        // 获取下一个元素    
        final Entry<K,V> nextEntry() {
    
        
            if (modCount != expectedModCount)    
                throw new ConcurrentModificationException();    
            Entry<K,V> e = next;    
            if (e == null)    
                throw new NoSuchElementException();    
   
            // 注意!!!    
            // 一个Entry就是一个单向链表    
            // 若该Entry的下一个节点不为空,就将next指向下一个节点;    
            // 否则,将next指向下一个链表(也是下一个Entry)的不为null的节点。    
            if ((next = e.next) == null) {
    
        
                Entry[] t = table;    
                while (index < t.length && (next = t[index++]) == null)    
                    ;    
            }    
            current = e;    
            return e;    
        }    
   
        // 删除当前元素    
        public void remove() {
    
        
            if (current == null)    
                throw new IllegalStateException();    
            if (modCount != expectedModCount)    
                throw new ConcurrentModificationException();    
            Object k = current.key;    
            current = null;    
            HashMap.this.removeEntryForKey(k);    
            expectedModCount = modCount;    
        }    
   
    }    
   
    // value的迭代器    
    private final class ValueIterator extends HashIterator<V> {
    
        
        public V next() {
    
        
            return nextEntry().value;    
        }    
    }    
   
    // key的迭代器    
    private final class KeyIterator extends HashIterator<K> {
    
        
        public K next() {
    
        
            return nextEntry().getKey();    
        }    
    }    
   
    // Entry的迭代器    
    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
    
        
        public Map.Entry<K,V> next() {
    
        
            return nextEntry();    
        }    
    }    
   
    // 返回一个“key迭代器”    
    Iterator<K> newKeyIterator()   {
    
        
        return new KeyIterator();    
    }    
    // 返回一个“value迭代器”    
    Iterator<V> newValueIterator()   {
    
        
        return new ValueIterator();    
    }    
    // 返回一个“entry迭代器”    
    Iterator<Map.Entry<K,V>> newEntryIterator()   {
    
        
        return new EntryIterator();    
    }    
   
    // HashMap的Entry对应的集合    
    private transient Set<Map.Entry<K,V>> entrySet = null;    
   
    // 返回“key的集合”,实际上返回一个“KeySet对象”    
    public Set<K> keySet() {
    
        
        Set<K> ks = keySet;    
        return (ks != null ? ks : (keySet = new KeySet()));    
    }    
   
    // Key对应的集合    
    // KeySet继承于AbstractSet,说明该集合中没有重复的Key。    
    private final class KeySet extends AbstractSet<K> {
    
        
        public Iterator<K> iterator() {
    
        
            return newKeyIterator();    
        }    
        public int size() {
    
        
            return size;    
        }    
        public boolean contains(Object o) {
    
        
            return containsKey(o);    
        }    
        public boolean remove(Object o) {
    
        
            return HashMap.this.removeEntryForKey(o) != null;    
        }    
        public void clear() {
    
        
            HashMap.this.clear();    
        }    
    }    
   
    // 返回“value集合”,实际上返回的是一个Values对象    
    public Collection<V> values() {
    
        
        Collection<V> vs = values;    
        return (vs != null ? vs : (values = new Values()));    
    }    
   
    // “value集合”    
    // Values继承于AbstractCollection,不同于“KeySet继承于AbstractSet”,    
    // Values中的元素能够重复。因为不同的key可以指向相同的value。    
    private final class Values extends AbstractCollection<V> {
    
        
        public Iterator<V> iterator() {
    
        
            return newValueIterator();    
        }    
        public int size() {
    
        
            return size;    
        }    
        public boolean contains(Object o) {
    
        
            return containsValue(o);    
        }    
        public void clear() {
    
        
            HashMap.this.clear();    
        }    
    }    
   
    // 返回“HashMap的Entry集合”    
    public Set<Map.Entry<K,V>> entrySet() {
    
        
        return entrySet0();    
    }    
   
    // 返回“HashMap的Entry集合”,它实际是返回一个EntrySet对象    
    private Set<Map.Entry<K,V>> entrySet0() {
    
        
        Set<Map.Entry<K,V>> es = entrySet;    
        return es != null ? es : (entrySet = new EntrySet());    
    }    
   
    // EntrySet对应的集合    
    // EntrySet继承于AbstractSet,说明该集合中没有重复的EntrySet。    
    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    
        
        public Iterator<Map.Entry<K,V>> iterator() {
    
        
            return newEntryIterator();    
        }    
        public boolean contains(Object o) {
    
        
            if (!(o instanceof Map.Entry))    
                return false;    
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;    
            Entry<K,V> candidate = getEntry(e.getKey());    
            return candidate != null && candidate.equals(e);    
        }    
        public boolean remove(Object o) {
    
        
            return removeMapping(o) != null;    
        }    
        public int size() {
    
        
            return size;    
        }    
        public void clear() {
    
        
            HashMap.this.clear();    
        }    
    }    
   
    // java.io.Serializable的写入函数    
    // 将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中    
    private void writeObject(java.io.ObjectOutputStream s)    
        throws IOException    
    {
    
        
        Iterator<Map.Entry<K,V>> i =    
            (size > 0) ? entrySet0().iterator() : null;    
   
        // Write out the threshold, loadfactor, and any hidden stuff    
        s.defaultWriteObject();    
   
        // Write out number of buckets    
        s.writeInt(table.length);    
   
        // Write out size (number of Mappings)    
        s.writeInt(size);    
   
        // Write out keys and values (alternating)    
        if (i != null) {
    
        
            while (i.hasNext()) {
    
        
            Map.Entry<K,V> e = i.next();    
            s.writeObject(e.getKey());    
            s.writeObject(e.getValue());    
            }    
        }    
    }    
   
   
    private static final long serialVersionUID = 362498820763181265L;    
   
    // java.io.Serializable的读取函数:根据写入方式读出    
    // 将HashMap的“总的容量,实际容量,所有的Entry”依次读出    
    private void readObject(java.io.ObjectInputStream s)    
         throws IOException, ClassNotFoundException    
    {
    
        
        // Read in the threshold, loadfactor, and any hidden stuff    
        s.defaultReadObject();    
   
        // Read in number of buckets and allocate the bucket array;    
        int numBuckets = s.readInt();    
        table = new Entry[numBuckets];    
   
        init();  // Give subclass a chance to do its thing.    
   
        // Read in size (number of Mappings)    
        int size = s.readInt();    
   
        // Read the keys and values, and put the mappings in the HashMap    
        for (int i=0; i<size; i++) {
    
        
            K key = (K) s.readObject();    
            V value = (V) s.readObject();    
            putForCreate(key, value);    
        }    
    }    
   
    // 返回“HashMap总的容量”    
    int   capacity()     {
    
     return table.length; }    
    // 返回“HashMap的加载因子”    
    float loadFactor()   {
    
     return loadFactor;   }    
}   

4.4 Algunos puntos resumidos

1. Primero, debe comprender la estructura de almacenamiento de HashMap, como se muestra en la siguiente figura:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-Aoj2barK-1692927385674)(https://image.xiaoxiaofeng.site/blog/2023 /05/18/xxf -20230518180052.png?xxfjava)]

En la figura, la parte violeta representa la tabla hash, también llamada matriz hash. Cada elemento de la matriz es el nodo principal de una lista enlazada individualmente. La lista enlazada se utiliza para resolver conflictos. Si diferentes claves se asignan a la misma posición en la matriz, colóquelo en una lista enlazada individualmente.

2. Primero observe la estructura de datos de los nodos en la lista vinculada:

// Entry是单向链表。    
// 它是 “HashMap链式存储法”对应的链表。    
// 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数    
static class Entry<K,V> implements Map.Entry<K,V> {
    
        
    final K key;    
    V value;    
    // 指向下一个节点    
    Entry<K,V> next;    
    final int hash;    
  
    // 构造函数。    
    // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)"    
    Entry(int h, K k, V v, Entry<K,V> n) {
    
        
        value = v;    
        next = n;    
        key = k;    
        hash = h;    
    }    
  
    public final K getKey() {
    
        
        return key;    
    }    
  
    public final V getValue() {
    
        
        return value;    
    }    
  
    public final V setValue(V newValue) {
    
        
        V oldValue = value;    
        value = newValue;    
        return oldValue;    
    }    
  
    // 判断两个Entry是否相等    
    // 若两个Entry的“key”和“value”都相等,则返回true。    
    // 否则,返回false    
    public final boolean equals(Object o) {
    
        
        if (!(o instanceof Map.Entry))    
            return false;    
        Map.Entry e = (Map.Entry)o;    
        Object k1 = getKey();    
        Object k2 = e.getKey();    
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
    
        
            Object v1 = getValue();    
            Object v2 = e.getValue();    
            if (v1 == v2 || (v1 != null && v1.equals(v2)))    
                return true;    
        }    
        return false;    
    }    
  
    // 实现hashCode()    
    public final int hashCode() {
    
        
        return (key==null   ? 0 : key.hashCode()) ^    
               (value==null ? 0 : value.hashCode());    
    }    
  
    public final String toString() {
    
        
        return getKey() + "=" + getValue();    
    }    
  
    // 当向HashMap中添加元素时,绘调用recordAccess()。    
    // 这里不做任何处理    
    void recordAccess(HashMap<K,V> m) {
    
        
    }    
  
    // 当从HashMap中删除元素时,绘调用recordRemoval()。    
    // 这里不做任何处理    
    void recordRemoval(HashMap<K,V> m) {
    
        
    }    
}    

Además de la clave, el valor y el hash, sus elementos estructurales también incluyen el siguiente, que apunta al siguiente nodo. Además, los métodos iguales y hashCode se anulan aquí para garantizar que el par clave-valor sea único.

3. HashMap tiene cuatro métodos de construcción. En el método de construcción se mencionan dos parámetros muy importantes: capacidad inicial y factor de carga. Estos dos parámetros son parámetros importantes que afectan el rendimiento de HashMap. La capacidad representa el número de ranuras en la tabla hash (es decir, la longitud de la matriz hash). La capacidad inicial es la capacidad cuando se crea la tabla hash (como se puede ver en el constructor. Si no se especifica, el valor predeterminado es 16). El factor de carga es una medida de qué tan llena puede estar una tabla hash antes de que su capacidad aumente automáticamente. Cuando el número de entradas en la tabla hash excede el producto del factor de carga y la capacidad actual, entonces es necesario cambiar el tamaño de la tabla hash (es decir, expandirla).

Hablemos del factor de carga: si el factor de carga es mayor, el espacio se utilizará más plenamente, pero la eficiencia de la búsqueda se reducirá (la longitud de la lista vinculada será cada vez más larga); si el factor de carga es demasiado pequeño , entonces los datos de la tabla serán demasiado escasos (muchos El espacio se expande antes de usarse), lo que provoca una grave pérdida de espacio. Si no lo especificamos en el método de construcción, el factor de carga predeterminado del sistema es 0,75, que es un valor ideal y generalmente no necesitamos modificarlo.

Además, no importa cuánta capacidad especifiquemos, el método de construcción establecerá la capacidad real en un número no menor que la potencia de 2 de la capacidad especificada, y el valor máximo no puede exceder 2 elevado a 30.

4. Tanto la clave como el valor en HashMap pueden ser nulos.

5. Concéntrese en analizar los dos métodos más utilizados en HashMap: poner y obtener. Comencemos con el método get más simple, el código fuente es el siguiente:

// 获取key对应的value    
public V get(Object key) {
    
        
    if (key == null)    
        return getForNullKey();    
    // 获取key的hash值    
    int hash = hash(key.hashCode());    
    // 在“该hash值对应的链表”上查找“键值等于key”的元素    
    for (Entry<K,V> e = table[indexFor(hash, table.length)];    
         e != null;    
         e = e.next) {
    
        
        Object k;    
/判断key是否相同  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))    
            return e.value;    
    }  
没找到则返回null  
    return null;    
}    
  
// 获取“key为null”的元素的值    
// HashMap将“key为null”的元素存储在table[0]位置,但不一定是该链表的第一个位置!    
private V getForNullKey() {
    
        
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    
        
        if (e.key == null)    
            return e.value;    
    }    
    return null;    
}    

Primero, si la clave es nula, busque directamente desde la lista vinculada correspondiente a la primera posición de la tabla hash, tabla [0]. Recuerde, el par clave-valor con una clave nula siempre se coloca en la lista vinculada con la tabla [0] como nodo principal. Por supuesto, no necesariamente se almacena en la tabla del nodo principal [0].

Si la clave no es nula, primero busque el valor hash de la clave, busque el índice en la tabla de acuerdo con el valor hash y busque en la lista enlazada individualmente correspondiente al índice si hay un par clave-valor cuya clave sea igual a la clave de destino. Si es así, devuelve el valor correspondiente. Si no, devuelve nulo.

El método de venta es un poco más complicado y el código es el siguiente:

  // 将“key-value”添加到HashMap中    
  public V put(K key, V value) {
    
        
      // 若“key为null”,则将该键值对添加到table[0]中。    
      if (key == null)    
          return putForNullKey(value);    
      // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。    
      int hash = hash(key.hashCode());    
      int i = indexFor(hash, table.length);    
      for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    
        
          Object k;    
          // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!    
          if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    
        
              V oldValue = e.value;    
              e.value = value;    
              e.recordAccess(this);    
              return oldValue;    
          }    
      }    
  
      // 若“该key”对应的键值对不存在,则将“key-value”添加到table中    
      modCount++;  
//将key-value添加到table[i]处  
      addEntry(hash, key, value, i);    
      return null;    
  }   

Si la clave es nula, agréguela a la lista vinculada correspondiente a la tabla [0], el código fuente de putForNullKey es el siguiente:

// putForNullKey()的作用是将“key为null”键值对添加到table[0]位置    
private V putForNullKey(V 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;    
        }    
    }    
    // 如果没有存在key为null的键值对,则直接题阿见到table[0]处!    
    modCount++;    
    addEntry(0, null, value, 0);    
    return null;    
}   

Si la clave no es nula, primero encuentre el valor hash de la clave, obtenga el índice en la tabla de acuerdo con el valor hash y luego recorra la lista enlazada individualmente correspondiente.Si hay un par clave-valor igual a la clave de destino en la lista enlazada individualmente, el nuevo El valor sobrescribe el valor anterior y se devuelve el valor anterior. Si no se puede encontrar un par clave-valor igual a la clave de destino, o la lista enlazada individualmente está vacía, el par clave-valor se inserta en el nodo principal de la lista enlazada individualmente (cada vez que se coloca un nodo recién insertado en el nodo principal), esta operación se implementa mediante el método addEntry y su código fuente es el siguiente:

// 新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。    
void addEntry(int hash, K key, V value, int bucketIndex) {
    
        
    // 保存“bucketIndex”位置的值到“e”中    
    Entry<K,V> e = table[bucketIndex];    
    // 设置“bucketIndex”位置的元素为“新Entry”,    
    // 设置“e”为“新Entry的下一个节点”    
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    
    // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小    
    if (size++ >= threshold)    
        resize(2 * table.length);    
}    

Preste atención al método de construcción en la antepenúltima línea aquí, asigne el par clave-valor clave-valor a la tabla [bucketIndex] y apunte junto al elemento e. Esto coloca el valor clave en el nodo principal y reemplaza el encabezado anterior El nodo está conectado detrás de él. Este método también explica que cada vez que se coloca un par clave-valor, el nuevo par clave-valor siempre se coloca en la tabla [bucketIndex] (es decir, en el nodo principal).

También preste atención a las dos últimas líneas de código. Cada vez que agrega un par clave-valor, debe determinar si el número de ranuras utilizadas actualmente es mayor o igual al umbral (capacidad * factor de carga). Si es mayor Que o igual al valor, amplíe la capacidad al original 2 veces la capacidad.

6. Respecto a la expansión. Arriba vimos el método de expansión y el método de cambio de tamaño. Su código fuente es el siguiente:

// 重新调整HashMap的大小,newCapacity是调整后的单位    
void resize(int newCapacity) {
    
        
    Entry[] oldTable = table;    
    int oldCapacity = oldTable.length;    
    if (oldCapacity == MAXIMUM_CAPACITY) {
    
        
        threshold = Integer.MAX_VALUE;    
        return;    
    }    
  
    // 新建一个HashMap,将“旧HashMap”的全部元素添加到“新HashMap”中,    
    // 然后,将“新HashMap”赋值给“旧HashMap”。    
    Entry[] newTable = new Entry[newCapacity];    
    transfer(newTable);    
    table = newTable;    
    threshold = (int)(newCapacity * loadFactor);    
}    

Obviamente, se crea una nueva matriz subyacente de HashMap y luego se llama al método de transferencia para agregar todos los elementos del HashMap al nuevo HashMap (es necesario volver a calcular la posición del índice de los elementos en la nueva matriz). El código fuente del método de transferencia es el siguiente:

// 将HashMap中的全部元素都添加到newTable中    
void transfer(Entry[] newTable) {
    
        
    Entry[] src = table;    
    int newCapacity = newTable.length;    
    for (int j = 0; j < src.length; j++) {
    
        
        Entry<K,V> e = src[j];    
        if (e != null) {
    
        
            src[j] = null;    
            do {
    
        
                Entry<K,V> next = e.next;    
                int i = indexFor(e.hash, newCapacity);    
                e.next = newTable[i];    
                newTable[i] = e;    
                e = next;    
            } while (e != null);    
        }    
    }    
}    

Obviamente, la expansión es una operación que requiere mucho tiempo porque requiere volver a calcular las posiciones de estos elementos en la nueva matriz y copiarlos. Por lo tanto, cuando usamos HashMap, es mejor estimar la cantidad de elementos en HashMap con anticipación, lo que ayudará a mejorar el rendimiento de HashMap.

7. Preste atención al método contieneKey y al método contieneValue. El primero puede localizar directamente el rango de búsqueda en la lista vinculada correspondiente al índice especificado a través del valor hash de la clave, mientras que el segundo busca en cada lista vinculada de la matriz hash.

8. Centrémonos en analizar los métodos para calcular el valor hash y el valor del índice. Estos dos métodos son la parte central del diseño de HashMap. La combinación de los dos puede garantizar que los elementos de la tabla hash tengan el hash lo más uniformemente posible.

A continuación se explica cómo calcular el valor hash:

static int hash(int h) {
    
      
	h ^= (h >>> 20) ^ (h >>> 12);  
	return h ^ (h >>> 7) ^ (h >>> 4);  
}

Es solo una fórmula matemática. El diseño de IDK para calcular valores hash de esta manera naturalmente tiene sus beneficios. En cuanto a por qué está diseñado de esta manera, no lo investigaremos aquí. Siempre que entendamos que las operaciones de bits utilizadas hacen el cálculo de valores hash es muy eficiente.

El método para encontrar el índice correspondiente a partir del valor hash es el siguiente:

static int indexFor(int h, int length) {
    
      
	return h & (length-1);  
}

Queremos centrarnos en esto. Cuando hacemos hash de una tabla hash, naturalmente pensaremos en usar el valor hash para modular la longitud (es decir, el método de hash de división). Esto también se implementa en Hashtable. Este método básicamente puede garantizar que los elementos El hash en la tabla hash es relativamente uniforme, pero el módulo utilizará operaciones de división, lo cual es muy ineficiente. En HashMap, se usa el método h & (longitud-1) en lugar del módulo, que también logra un hash uniforme, pero La eficiencia es mucho mayor, lo que también es una mejora de HashMap sobre Hashtable.

A continuación, analicemos por qué la capacidad de la tabla hash debe ser una potencia entera de 2. En primer lugar, si la longitud es una potencia entera de 2, h&(longitud-1) es equivalente a la longitud del módulo, lo que garantiza un hash uniforme y mejora la eficiencia; en segundo lugar, si la longitud es una potencia entera de 2, es un número par, por lo que longitud-1 es un número impar y el último dígito del número impar es 1. Esto garantiza que el último dígito de h&(longitud-1) pueda ser 0 o 1 (dependiendo del valor de h), es decir, el El resultado después de AND puede ser un número par o impar, lo que garantiza la uniformidad del hash. Si la longitud es un número impar, es obvio que la longitud-1 es un número par y su último bit es 0, por lo que h&( El último bit de longitud-1) debe ser 0, es decir, solo puede ser un número par. De esta manera, cualquier valor hash solo se aplicará a la posición del subíndice par de la matriz, lo que desperdicia casi la mitad del espacio. Por lo tanto, la longitud toma La potencia entera de 2 es para reducir la probabilidad de colisión de diferentes valores hash, de modo que los elementos se puedan aplicar hash de manera uniforme en la tabla hash.

5. Análisis del código fuente de HashMap (JDK8)

5.1 Estructura de datos subyacente de HashMap

La estructura de datos subyacente de HashMap es matriz + cadena. Como se muestra abajo:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-892DV2KL-1692927385675)(https://image.xiaoxiaofeng.site/spider/2023 /7/20/xxf -1689833106507.png)]

Cuando se cumplen las dos condiciones siguientes, la lista vinculada se convertirá en un árbol rojo-negro:

1. La longitud de la matriz es igual o mayor que 64

2. La longitud de la lista enlazada es igual o mayor que 8

Si la longitud de la matriz es menor que 64 y la longitud de la lista vinculada es igual o mayor que 8, la lista vinculada no se convertirá en un árbol rojo-negro, sino que se expandirá. También es probable que la expansión reduzca la longitud de la lista enlazada.

5.2 Algunas variables miembro importantes de HashMap

// 底层数组,可自动扩容,但是HashMap不支持缩容,长度总是2的N次方
transient Node<K,V>[] table;

// 初始容量大小,1左移4位结果是10000,转为十进制是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * 同时满足“数组长度等于或大于64”、“链表长度等于或大于8” 两个条件,才将链表转为红黑树
 */
// 树化阀值
static final int TREEIFY_THRESHOLD = 8;
// 最小树化容量(树化是指将链表转为红黑树)
static final int MIN_TREEIFY_CAPACITY = 64;

// HashMap的数组最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;

// 扩容的阈值
int threshold;

// 负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

5.3 Constructor HashMap

HashMap tiene 4 constructores. Elegí public HashMap (int initialCapacity, float loadFactor) para explicar

/**
     * 构造函数解析
     */
    public HashMap(int initialCapacity, float loadFactor) {
    
    

        // 判断传入的参数是否合理
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);

        // 设置负载因子,默认是 0.75
        this.loadFactor = loadFactor;

        // 设置扩容阀值
        // initialCapacity(初始容量大小)默认是16
        // 用户设置的initialCapacity可以是任何大于0的数字,tableSizeFor(initialCapacity)返回结果是2的N次方。即HashMap的容量必然是2的N次方
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * tableSizeFor(int cap)方法解析
     * 返回值大于等于cap,且一定是2的次方数
     *
     * 假设 cap = 10
     * n = 10 - 1 => 9 => 0b1001(0b表示二进制数)
     * n |= n >>> 1; 表示 n 等于 n 或上 n右移一位
     * 0b1001 | 0b0100 => 0b1101  // n |= n >>> 1;
     * 0b1101 | 0b0010 => 0b1111  // n |= n >>> 2;
     * 0b1111 | 0b0100 => 0b1111  // n |= n >>> 4;
     * 以此类推,最终 n = 15
     *
     * return 16
     */
    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;
    }

5.4 método put (tecla K, valor V)

La estructura de datos del nodo de la lista vinculada de HashMap es la siguiente

/**
     * 链表的Node
     */
    static class Node<K,V> implements Map.Entry<K,V> {
    
    
        // key的hash值
        final int hash;
        
        // key
        final K key;
        
        // value
        V value;
        
        // 下一个元素
        HashMap.Node<K, V> next;
    }

jajaja Xiaofu-www.xiaoxiaofeng.com

Paso 2 Interpretación del código fuente del método hash (clave de objeto)

/**
     * 如果key是null,则返回0
     * 如果key不是null,则使用 key的hashCode 异或 key的hashCode右移16位
     */
    static final int hash(Object key) {
    
    
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

¿Por qué no devolver directamente el código hash de la clave, sino desplazarlo 16 bits hacia la derecha y luego tomar el resultado "XOR"? Esto está relacionado con la fórmula de enrutamiento de la clave i = (table.length - 1) & node.hash.

El método hashCode () del objeto Java devuelve un int, que ocupa 32 bits, es decir, el int se puede convertir en una representación de número binario de 32 bits. Suponga que el código hash de la clave se usa directamente.

código hash = 1111 0101 1100 0100 1111 0001 1101 0011

Poner hashCode en la fórmula de enrutamiento

i = (longitud de la tabla - 1) & 1111 0101 1100 0100 1111 0001 1101 0011

table.length debe ser 2 elevado a la enésima potencia, luego el resultado de table.length - 1 convertido a binario debe ser que todos los bits altos sean 0 y los bits bajos sean todos 1. Cuando table.length es relativamente pequeño, por ejemplo table.length = 1024, table.length - 1 = 1023, 1023 convertido a binario es 0000 0000 000 0000 0000 0011 1111 1111.

0000 0000 000 0000 0000 0011 1111 1111 1111 ¿Algo especial? El resultado de la operación "Y" entre 0 y cualquier número es 0, y el resultado de la operación "Y" entre 1 y cualquier número permanece sin cambios. Esto hace que la fórmula de enrutamiento de la clave solo use el valor de orden inferior de hashCode y no el valor de orden superior. En particular, cuanto más pequeña sea la longitud de la tabla, menos dígitos del código hash se podrán utilizar. Para utilizar el código hash de alto orden, el autor de HashMap realizó la siguiente operación (h = key.hashCode()) ^ (h >>> 16), de modo que hashCode y hashCode se desplazan 16 bits hacia la derecha para Realice la operación "XOR" ”, de modo que los datos inferiores de 16 bits se mezclen con los datos superiores de 16 bits y los datos inferiores de 16 bits tengan más hash. También se puede considerar que los 16 bits inferiores del valor devuelto por key.hash () se mezclan con toda la información de key.hashCode. La fórmula de enrutamiento i = (table.length - 1) & node.hash, el resultado de estaré más hash.

Análisis del código fuente del método Put (clave K, valor V), el método llama internamente al método putVal (int hash, clave K, valor V, booleano onlyIfAbsent, desalojamiento booleano)

public V put(K key, V value) {
    
    
        return putVal(hash(key), key, value, false, true);
    }
/**
     * putVal方法分析
     * @param hash key的hash值
     * @param key key
     * @param value value
     * @param onlyIfAbsent key已经存在,是否改变value。如果为true,则不更改现有值;为false,修改value
     * @param evict 如果为false,则表处于创建模式
     * @return
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    
    
        // tab: 引用当前HashMap的数组
        // p: 数组的元素
        // n:数组的长度
        // i: 路由寻址的结果
        HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;

        // 执行 new HashMap() 的时候并不会创建数组,节约内存,等首次插入键值对,才创建数组,这属于延迟初始化,所以会有table==null的判断
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        // i = (n - 1) & hash 是key路由公式,tab[i = (n - 1) & hash] 找到key在数组中的位置
        // 如果 tab[i] == null 证明当前位置还没有键值对,创建Node放到tab[i]中
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

            // tab[i]中已经有Node了
        else {
    
    
            // e: 一个临时的Node
            // k: 一个临时的key
            HashMap.Node<K,V> e; K k;

            // key比较,桶位中的第一个元素与插入的key完全一致的情况
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                // e后续要进行替换操作
                e = p;

                // p instanceof TreeNode 桶位是红黑树的情况
            else if (p instanceof TreeNode)
                e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

                // tab[i]是链表,并且链表第一个元素key与插入的key不一致
            else {
    
    
                // 遍历链表
                for (int binCount = 0; ; ++binCount) {
    
    
                    // (e = p.next) == null 表示迭代到了最后的元素
                    if ((e = p.next) == null) {
    
    
                        // 将插入的node放到链表末尾
                        p.next = newNode(hash, key, value, null);
                        // 新node插入到链表末尾,判断是否将链表转为红黑树
                        // 链表长度等于或大于8,执行treeifyBin(tab, hash);
                        // treeifyBin(Node<K,V>[] tab, int hash)方法中会判断数组长度小于MIN_TREEIFY_CAPACITY则执行扩容,否则执行链表变红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 在遍历链表的过程中,找到了key完全相等的node元素
                    // 退出循环,后续进行替换
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

            // e != null 条件成立,说明插入的key在HashMap中已经存在,把值替换为新值即可,然后返回旧值
            if (e != null) {
    
     // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    // 替换value
                    e.value = value;
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }

        // 散列表被修改的次数加一
        // 替换node的value不算被修改,如果是替换操作,在上面的if (e != null)判断中return了,不会运行此处的代码
        ++modCount;
        // HashMap的node数量到达阈值,扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

5.5 método de expansión resize()

Conocimiento previo: una vez expandido HashMap, la clave solo puede estar en dos ubicaciones.

1. La llave puede permanecer en la posición original del cubo y no moverse, es decir, todavía está en la tabla [i].

2. La clave puede estar en el subíndice del depósito original + la longitud de la tabla original.

Ejemplos a continuación

La fórmula de enrutamiento de la clave es i = (table.length - 1) & node.hash, suponiendo que table.length = 16 y la clave está en la posición de la tabla [15]. Sustituir en la fórmula de enrutamiento

15 = (16 - 1) & nodo.hash

Convertir decimal a binario. Utilice "..." para indicar varios. Utilice "xxxx" para representar un número binario de varios dígitos, que puede ser 0 o 1.

00…00 1111 = 00…00 1111 & node.hash. Si la fórmula es verdadera, los 4 bits inferiores de node.hash deben ser todos 1. Solo el resultado de 1 y 1 es 1. node.hash se puede expresar como xxxxxxx 1111

Luego se produce la expansión, table.length se convierte en 32 y table.length se sustituye en la fórmula de enrutamiento.

i = 31 y node.hash, convierta 31 a binario y lo anterior concluyó que node.hash es xxxxxxxx 1111

yo = 00…01 1111 y xxxxxxxxx 1111

当 node.hash = xxxxxxx0 1111, i = 00…01 1111 & xxxxxxx0 1111 = 00…00 1111 = 15

当 node.hash = xxxxxxx1 1111, i = 00…01 1111 & xxxxxxx1 1111 = 00…01 1111 = 31 = 15 + 16

La expansión de otros cubos es la misma, cumpliendo los dos puntos mencionados anteriormente:

1. La llave puede permanecer en la posición original del cubo y no moverse, es decir, todavía está en la tabla [i].

2. La clave puede estar en el subíndice del depósito original + la longitud de la tabla original.

Si le resulta difícil de entender, puede utilizar la siguiente imagen para comprenderlo.

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-dd6XWZaD-1692927385676)(https://image.xiaoxiaofeng.site/spider/2023 /7/20/xxf -1689833109430.png)]

Después de aclarar las reglas clave durante la expansión, aún debe recordar un concepto: en el código fuente de resize(), la lista vinculada de la tabla [15] después de la expansión se llama lista vinculada de orden inferior y lista vinculada de la tabla [31] después de la expansión se llama lista enlazada de orden superior.

resize() análisis del código fuente

// 扩容
    final HashMap.Node<K,V>[] resize() {
    
    
        // oldTab:引用扩容前的数组
        HashMap.Node<K,V>[] oldTab = table;
        // oldCap: 扩容前数组table的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // oldThr:扩容前的扩容阈值
        int oldThr = threshold;
        // newCap:扩容后数组table的大小,先给个初值0
        // newThr:扩容后的扩容阈值,先给个初值0
        int newCap, newThr = 0;

        // oldCap > 0 表示数组table已经初始化过了,是一次正常的扩容
        if (oldCap > 0) {
    
    
            // oldCap >= MAXIMUM_CAPACITY 数组的长度已经到达最大值,没法扩容了,直接return
            if (oldCap >= MAXIMUM_CAPACITY) {
    
    
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }

            // newCap = oldCap << 1 数字左移一位,等同于乘以2,但使用位运算更高效。新容量等于旧容量乘以2
            // 例如:4 * 2 = 8 转为二进制左移操作:100 左移一位变为 1000
            // (newCap = oldCap << 1) < MAXIMUM_CAPACITY -> 数组大小 < 最大限制值 ,这个判断条件基本都是true
            // oldCap >= DEFAULT_INITIAL_CAPACITY 当前数组长度必须大于DEFAULT_INITIAL_CAPACITY
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 扩容阀值也要变化,新扩容阀值 = 旧扩容阀值左移一位,等同于乘以2
                newThr = oldThr << 1; // double threshold
        }

        /**
         * oldCap == 0 && oldThr > 0 的情况
         * 通过 new HashMap(int initialCapacity, float loadFactor)
         *     new HashMap(int initialCapacity)
         *     new HashMap(Map<? extends K, ? extends V> m)
         * 这三种方式创建HashMap,构造函数会初始化oldThr,且 oldThr >= 16
         */
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;

        /**
         * oldCap == 0 && oldThr == 0 的情况
         * 通过 new HashMap() 创建的HashMap,构造函数不会初始化oldThr
         */
        else {
    
                   // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 扩容阈值是 负载因子 * 默认初始容量 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

        /**
         * else if (oldThr > 0) 条件成立
         *
         * else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY
         * && oldCap >= DEFAULT_INITIAL_CAPACITY) 条件不成立
         *
         * 这两种情况下,newThr == 0,需要计算扩容阈值
         */
        if (newThr == 0) {
    
    
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }

        // 得到扩容阈值
        threshold = newThr;

        // 前面的代码主要做两件事
        // 1、计算出本次扩容后,table数组的长度
        // 2、计算出下一次扩容的阈值

        // 创建一个更大的数组,一般情况下是原数组的两倍长度
        @SuppressWarnings({
    
    "rawtypes","unchecked"})
        HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
        table = newTab;

        // oldTab != null 说明扩容前HashMap已经有数据
        if (oldTab != null) {
    
    
            // 遍历老数组
            for (int j = 0; j < oldCap; ++j) {
    
    
                // 临时节点变量
                HashMap.Node<K,V> e;
                // (e = oldTab[j]) != null 当前桶位有数据,但是不知道是 单个Node、链表、红黑树 中的哪一种情况
                if ((e = oldTab[j]) != null) {
    
    
                    // 方便JVM回收内存
                    oldTab[j] = null;

                    // e.next == null 当前桶位只有一个node
                    if (e.next == null)
                        // e.hash & (newCap - 1) 是 key的路由算法
                        // 当前桶位只有一个元素,从未发生碰撞,可直接将当前元素放到新数组中
                        newTab[e.hash & (newCap - 1)] = e;

                    // e instanceof HashMap.TreeNode 桶位元素是红黑树
                    else if (e instanceof HashMap.TreeNode)
                        ((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);

                    // 桶位元素是链表
                    else {
    
     // preserve order

                        // 低位链表
                        // 扩容之后的数组下标位置,与当前数组的下标位置一致
                        // 假设原数组长度是16,table[15].hash = xxx0 1111,扩容后,还是在table[15]中
                        HashMap.Node<K,V> loHead = null, loTail = null;

                        // 高位链表
                        // 扩容之后的数组下标位置 = 原数组下标 + 扩容之前数组的长度
                        // 假设原数组长度是16,table[15].hash = xxx1 1111,扩容后,在table[31]中
                        // 扩容之后的数组下标位置 = 当前数组下标位置 + 扩容之前数组的长度 -> 31 = 15 + 16 -> 1 1111 = 1111 + 10000
                        HashMap.Node<K,V> hiHead = null, hiTail = null;

                        // 临时变量
                        HashMap.Node<K,V> next;

                        do {
    
    
                            next = e.next;
                            // 假设原数组长度oldCap是16 ,转为二进制是 10000
                            // 假设 e.hash = xxx0 1111 ,xxx0 1111 & 10000 = 0 ,扩容后node在低位链表中
                            // 假设 e.hash = xxx1 1111 ,xxx1 1111 & 10000 = 10000 ,扩容后node在高位链表中
                            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);
                        // loTail != null 低位链表有数据
                        if (loTail != null) {
    
    
                            // 新链表的最后一个node.next一定要设置为null
                            // 因为在原链表中node.next可能还指向一个node
                            loTail.next = null;
                            // 低位链表还在原桶位中,即还在table[j]中
                            newTab[j] = loHead;
                        }
                        // hiTail != null 高位链表有数据
                        if (hiTail != null) {
    
    
                            hiTail.next = null;
                            // 高位链表放在 数组下标位置 = 当前数组下标位置 + 扩容之前数组的长度 的位置,即在table[[j + oldCap]]中
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

5.6 método get (clave de objeto)

Después de comprender el método de venta, el método de obtención es relativamente simple.

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

    final HashMap.Node<K,V> getNode(int hash, Object key) {
    
    
        // tab:HashMap底层数组
        // first:桶位中的头元素
        // e: 临时node元素
        // n: table数组长度
        HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;

        // table不为null
        // (n - 1) & hash 是key的路由算法,first = tab[(n - 1) & hash] 找到第一个桶元素
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
    
    

            // 头元素(如果是树,则称为根元素)正好是要查找的元素
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 桶位不是单个node
            if ((e = first.next) != null) {
    
    
                // 桶位是树
                if (first instanceof HashMap.TreeNode)
                    return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);

                // 桶位是链表
                do {
    
    
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

5.7 método de eliminación (clave de objeto)

El método remove (Clave de objeto) no reducirá la matriz subyacente de HashMap. Consulte los comentarios del código para un análisis detallado del método.

final HashMap.Node<K,V> removeNode(int hash, Object key, Object value,
                                       boolean matchValue, boolean movable) {
    
    
        // tab:HashMap底层数组
        // p: 当前node元素
        // n: 数组长度
        // index: 寻址结果
        HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, index;

        // 通过路由公式 (n - 1) & hash 查找到key所在桶位不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
    
    

            // node:查找到的结果
            // e: 当前node的下一个元素
            HashMap.Node<K,V> node = null, e; K k; V v;

            // 要删除的元素是桶位中的第一个元素
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;

            else if ((e = p.next) != null) {
    
    
                // 红黑树查找node
                if (p instanceof HashMap.TreeNode)
                    node = ((HashMap.TreeNode<K,V>)p).getTreeNode(hash, key);

                // 链表的查找
                else {
    
    
                    do {
    
    
                        if (e.hash == hash &&
                                ((k = e.key) == key ||
                                        (key != null && key.equals(k)))) {
    
    
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }

            // 前面只是找到要删除的元素,并将元素赋值给node,下面执行删除操作
            if (node != null && (!matchValue || (v = node.value) == value ||
                    (value != null && value.equals(v)))) {
    
    
                // 红黑树删除元素
                if (node instanceof HashMap.TreeNode)
                    ((HashMap.TreeNode<K,V>)node).removeTreeNode(this, tab, movable);

                // node == p ,则p必然是桶位第一个元素
                // 删除桶位第一个元素
                else if (node == p)
                    tab[index] = node.next;

                // 链表删除node,此时p是node的前一个元素
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

6. La diferencia entre HashMap en JDK7 y JDK8

He leído muchos artículos sobre las diferencias entre HashMap en jdk7 y 8. Aquí hay un análisis en profundidad y por qué existen estas diferencias.

6.1 Diferencias en la estructura de datos

  1. Las estructuras de datos en JDK7 son principalmente: matriz + lista vinculada La clase de implementación de los nodos de matriz y lista vinculada es la clase Entrada.

  2. La estructura de datos en JDK8 es principalmente: matriz + lista enlazada/árbol rojo-negro . Cuando el número de elementos en la lista enlazada es mayor o igual a 8, se convierte en un árbol rojo-negro. Cuando el número de elementos es menor o igual a 6, la estructura de árbol rojo-negro se restaura en una lista y matriz vinculadas. La clase de implementación de los nodos de la lista vinculada es la clase Nodo

Análisis:

1.红黑树是解决链表查询出现的O(n)情况,那么为什么不用其他树呢?
如平衡二叉树等,我们通过以下二方面分析:
	平均插入效率:链表>红黑树>平衡二叉树
	平均查询效率:平衡二叉树>红黑树>链表
可以看出红黑树介于二者之间,hashMap作为各种操作频繁的容器,自然选择综合性能较好的红黑树
2.为什么阈值是6和8呢?
	1.为什么8转红黑树?
	红黑树的平均查找次数是log2(n),
		长度为8时:
			红黑树平均查找次数为3,链表平均查找长度为8/2=4,此时选择红黑树优
		长度为4为:
			红黑树平均查找次数为2,链表平均长度为4/2=2,此时次数一样,红黑树开销大
		至于567我们在这没有讨论的必要
	2.为什么6转回链表?
		若选择7,在7和8链表之间的增删元素,必然会导致频繁进行链表和红黑树的转换

6.2 Diferencias en el cálculo de los valores Hash

  1. JDK7:h^ =(h>>>20)^(h>>>12) return h ^(h>>>7) ^(h>>>4)

  2. JDK8:(key==null)?0:(h=key.hashCode())^(h>>>16)

Análisis:

jdk7中因为要保持hash函数的散列性,所以进行了多次的异或和位运算而,
8中因为链表长度超过等于8会转红黑树,所以我们可以稍微减少元素的散列性,
从而避免很多异或和位运算操作

6.2 Diferencias en la inserción de datos de listas vinculadas

  1. JDK7: se utiliza el método de inserción del encabezado, que es lo opuesto a la posición original después de la expansión (el cambio de tamaño generará una lista enlazada circular)

  2. JDK8: método de inserción de cola utilizado, la posición después de la expansión es la misma que la lista vinculada original

Análisis:

jdk7插入链表头部,因为这样无需遍历链表(需要判断是否为尾部,然后插入尾部),可以直接插入头部
jdk8中插入元素时,要判断个数是否需要构造红黑树,这样已存在了遍历, 所以插入尾部方便,
并且解决了jdk7中头插法导致的环状链表问题

6.2 Diferencias en los mecanismos de expansión.

  1. Condiciones de expansión de JDK7: Número de elementos > Capacidad (16) * Factor de carga (0,75) && Hay elementos en la posición de la matriz insertada
  2. Condiciones de expansión JDK8: número de elementos > capacidad (16) * factor de carga (0,75)

Análisis:

虽然都是进行2倍扩容,但是JDK1.7中扩容的时候,重新计算位置,
JDk8则不会,只要看看原hash值新增的那个bit位是1还是0就好了,是0的话索引没有变,
是1的话索引变成“原索引+oldCap(旧数组大小)

7. Principio de implementación de Hashtable

Hashtable es similar a HashMap y utiliza una tabla hash para almacenar pares clave-valor. Definición de la tabla hash: según la función hash establecida y el método de manejo de conflictos (direccionamiento abierto, área de desbordamiento público, dirección en cadena, hash pesado...), un conjunto de palabras clave se asigna a un conjunto de direcciones continuas limitadas (es decir, una matriz de depósitos) o conjunto de cubos) y utiliza la "imagen" de la clave en la dirección establecida como ubicación de almacenamiento del registro en la tabla. Esta tabla se llama tabla hash.

Cuando ocurre un conflicto de hash, el conflicto se maneja mediante el "método de lista vinculada" o el "método de cremallera", es decir, se utiliza una lista vinculada para almacenar pares clave-valor (Map.Entry). Cada objeto Entrada tiene un puntero siguiente que apunta a la siguiente Entrada con el mismo valor de código hash.

8. Principio de implementación de HashSet

HashSet se implementa a través de HashMap, solo usando claves HashMap y no usando valores HashMap.

hashCode (), valor hash, los elementos HashSet se almacenarán de acuerdo con el valor hash, y los elementos con el mismo valor hash se almacenarán en la misma área, también llamado principio de depósito (bucket), lo que también hará que la búsqueda sea mucho más eficiente. .

Sin embargo, después de agregar el elemento a la colección HashSet, modificar los atributos del elemento que participan en el cálculo del valor hash y luego llamar al método remove() no funcionará, lo que provocará pérdidas de memoria.

Supongo que te gusta

Origin blog.csdn.net/qq_34988304/article/details/132488652
Recomendado
Clasificación