Análisis del código fuente subyacente de HashTable

Análisis del código fuente subyacente de HashTable

1. Trabajo de preparación

package com.liu.map_;

import java.util.Hashtable;
import java.util.Map;

public class HashTable_debug {
    
    
    public static void main(String[] args) {
    
    
        Map<Object, Object> hashtable = new Hashtable<>();//断点1
        for (int i = 1; i < 10; i++) {
    
    
            hashtable.put(i,"number"+i);//断点2
        }
        System.out.println(hashtable);
    }
}

Segundo, depurar

Constructor predeterminado

Se puede ver que la capacidad de inicialización predeterminada de la tabla hash no es 11 y el factor de expansión predeterminado es 0,75.

/**
 * Constructs a new, empty hashtable with a default initial capacity (11)
 * and load factor (0.75).
 */
public Hashtable() {
    
    
    this(11, 0.75f);
}

Ingrese esto (11, 0.75f), constructor parametrizado

/**
 * Constructs a new, empty hashtable with the specified initial
 * capacity and the specified load factor.
 *
 * @param      initialCapacity   the initial capacity of the hashtable.
 * @param      loadFactor        the load factor of the hashtable.
 * @exception  IllegalArgumentException  if the initial capacity is less
 *             than zero, or if the load factor is nonpositive.
 */
public Hashtable(int initialCapacity, float loadFactor) {
    
    
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load: "+loadFactor);
	//若初始化容量为0,则将容量变为1
    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
    //新建一个 Entry<?,?>[initialCapacity]数组
    table = new Entry<?,?>[initialCapacity];
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

Tabla hash$Entrada

Código fuente de la matriz de entrada en la tabla hash

Map.Entry<K,V> está implementado y todos sus métodos deben reescribirse, por lo que existen los métodos getKey y getValue.

Entrada<K,V> siguiente; solo hay un nodo siguiente y ningún nodo previo, por lo que es una lista enlazada unidireccional

/**
 * Hashtable bucket collision list entry
 */
private static class Entry<K,V> implements Map.Entry<K,V> {
    
    
    final int hash;
    final K key;
    V value;
    Entry<K,V> next;

    protected Entry(int hash, K key, V value, Entry<K,V> next) {
    
    
        this.hash = hash;
        this.key =  key;
        this.value = value;
        this.next = next;
    }

    @SuppressWarnings("unchecked")
    protected Object clone() {
    
    
        return new Entry<>(hash, key, value,
                              (next==null ? null : (Entry<K,V>) next.clone()));
    }

    // Map.Entry Ops

    public K getKey() {
    
    
        return key;
    }

    public V getValue() {
    
    
        return value;
    }

    public V setValue(V value) {
    
    
        if (value == null)
            throw new NullPointerException();

        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    public boolean equals(Object o) {
    
    
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;

        return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
           (value==null ? e.getValue()==null : value.equals(e.getValue()));
    }

    public int hashCode() {
    
    
        return hash ^ Objects.hashCode(value);
    }

    public String toString() {
    
    
        return key.toString()+"="+value.toString();
    }
}

Saltar del constructor Hashtale

boxeo entero

public static Integer valueOf(int i) {
    
    
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

poner método

sincronizado: seguridad del hilo

El valor de la tabla hash no puede estar vacío.

Recorra la lista vinculada en el subíndice actual para ver si hay claves con valores iguales. Si son iguales, sobrescríbalas.

/**
 * Maps the specified <code>key</code> to the specified
 * <code>value</code> in this hashtable. Neither the key nor the
 * value can be <code>null</code>. <p>
 *
 * The value can be retrieved by calling the <code>get</code> method
 * with a key that is equal to the original key.
 *
 * @param      key     the hashtable key
 * @param      value   the value
 * @return     the previous value of the specified key in this hashtable,
 *             or <code>null</code> if it did not have one
 * @exception  NullPointerException  if the key or value is
 *               <code>null</code>
 * @see     Object#equals(Object)
 * @see     #get(Object)
 */
//线程安全
public synchronized V put(K key, V value) {
    
    
    // Make sure the value is not null
    //hashtable的值不能为空
    if (value == null) {
    
    
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    //计算数组下标
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    //遍历当前下标中的链表,看是否有key值相等的,若相等,则覆盖
    for(; entry != null ; entry = entry.next) {
    
    
        if ((entry.hash == hash) && entry.key.equals(key)) {
    
    
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}

Añadir entrada

El recuento es inicialmente 0. Cuando el recuento llega a 8, satisface el umbral de recuento >= (8), es decir, cuando se agrega el noveno elemento, la capacidad se expande.

El recuento se incrementa después de agregar nuevos nodos.

Utilice el método de inserción del encabezado para insertar el nuevo nodo en el encabezado de la pestaña [índice]

Principio del método de interpolación de cabezas: la posición de e es el siguiente puntero, es decir, el siguiente punto del nuevo nodo apunta a e

private void addEntry(int hash, K key, V value, int index) {
    
    
    modCount++;

    Entry<?,?> tab[] = table;
    //若满足条件,进行扩容
    if (count >= threshold) {
    
    
        // Rehash the table if the threshold is exceeded
        rehash();

        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry.
    @SuppressWarnings("unchecked")
    //e记录当前的链表头结点
    Entry<K,V> e = (Entry<K,V>) tab[index];
    //创建结点,采用头插法将新的结点插入到tab[index]的头部
    //原理   e的位置为next指针,即为新节点的next指向e
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

refrito()

Mecanismo de expansión: 2*oldCapacity+1

Calcule la nueva capacidad de expansión: umbral = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);

Vuelva a calcular el subíndice y asígnelo a la nueva matriz

/**
 * Increases the capacity of and internally reorganizes this
 * hashtable, in order to accommodate and access its entries more
 * efficiently.  This method is called automatically when the
 * number of keys in the hashtable exceeds this hashtable's capacity
 * and load factor.
 */
@SuppressWarnings("unchecked")
protected void rehash() {
    
    
    //length表示的是table数组的长度
    int oldCapacity = table.length;
    //记录table
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code
    //扩容机制:2*oldCapacity+1
    
    int newCapacity = (oldCapacity+1 << 1) + 1;
    //容量过大超过Integer.MAX_VALUE - 8
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
    
    
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    //创建并将新容量赋值给newMap
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    //计算新的扩容容量
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    //将新数组赋值给table
    table = newMap;
//重新计算下标,并将其赋值给新数组
    for (int i = oldCapacity ; i-- > 0 ;) {
    
    
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
    
    
            Entry<K,V> e = old;
            old = old.next;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

agregar exitosamente

3. Resumen (puntos clave)

1. La capacidad de inicialización predeterminada de la tabla hash no es 11 y el factor de expansión predeterminado es 0,75.

2. Entrada <K, V> siguiente; solo tiene un nodo siguiente y ningún nodo previo, por lo que es una lista enlazada unidireccional

3. sincronizado: seguridad de subprocesos

4. El valor de la tabla hash no puede estar vacío.

5. Recorra la lista vinculada en el subíndice actual para ver si hay un valor clave igual y, de ser así, sobrescríbalo.

6. El conteo es inicialmente 0. Cuando el conteo llega a 8, satisface el conteo >= umbral (8), es decir, cuando se agrega el noveno elemento, se expande la capacidad.

​El recuento se incrementa después de agregar nuevos nodos.

Utilice el método de inserción del encabezado para insertar el nuevo nodo en el encabezado de la pestaña [índice]

Principio del método de inserción de cabeza

7. Mecanismo de expansión de capacidad: 2*oldCapacity+1

Calcule la nueva capacidad de expansión: umbral = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);

Supongo que te gusta

Origin blog.csdn.net/ABidMan/article/details/121138705
Recomendado
Clasificación