Interpretación del código fuente de HashMap

1. Introducción

En pocas palabras, HashMap es una estructura de datos en Java que se utiliza para almacenar pares clave-valor.

También podemos ver la descripción específica a través de los comentarios de HashMap:
Insertar descripción de la imagen aquí

La traducción es :
1. HashMap se basa en la implementación de la interfaz Map de la tabla hash. Esta implementación proporciona todas las operaciones de mapeo opcionales y permite valores nulos para clave y valor. (La clase HashMap es aproximadamente equivalente a Hashtable, excepto que no está sincronizada y permite valores nulos. Esta clase no garantiza el orden del mapa y, en particular, no garantiza que el orden permanezca sin cambios con el tiempo.

2. Esta implementación proporciona un rendimiento en tiempo constante para operaciones básicas (obtener y colocar), suponiendo que la función hash distribuya los elementos correctamente entre los depósitos. Iterar sobre una vista de colección lleva un tiempo proporcional a la "capacidad" de la instancia de HashMap (número de depósitos) más su tamaño (número de mapas de valores clave). Por lo tanto, si el rendimiento de la iteración es importante, no establezca la capacidad inicial demasiado alta (ni el factor de carga demasiado bajo).

3. Una instancia de HashMap tiene dos parámetros que afectan su rendimiento: capacidad inicial y factor de carga. La capacidad es la cantidad de depósitos en la tabla hash, la capacidad inicial es solo la capacidad cuando se creó la tabla hash. El factor de carga es una medida de qué tan llena se permite que esté una tabla hash antes de que su capacidad aumente automáticamente. Cuando el número de entradas en la tabla hash excede el factor de carga multiplicado por la capacidad actual, la tabla hash se repite (es decir, se reconstruye la estructura de datos interna) para que la tabla hash tenga aproximadamente el doble de la cantidad de depósitos.

4. Como regla general, el factor de carga predeterminado (0,75) proporciona un buen equilibrio entre los costos de tiempo y espacio. Los valores más altos reducen la sobrecarga de espacio pero aumentan los costos de búsqueda (lo que se refleja en la mayoría de las operaciones de la clase HashMap, incluidas get y put). La cantidad esperada de entradas en el mapa y su factor de carga deben tenerse en cuenta al establecer su capacidad inicial, minimizando la cantidad de operaciones de repetición. Si la capacidad inicial es mayor que el número máximo de entradas dividido por el factor de carga, no se producirá ninguna operación de reorganización.

5. Si está almacenando muchas asignaciones en una instancia de HashMap, crearla con una capacidad lo suficientemente grande hará que el almacenamiento de asignaciones sea más eficiente, en lugar de permitirle realizar un refrito automático según sea necesario para hacer crecer la tabla. Tenga en cuenta que usar muchas claves con el mismo hashCode() es una forma segura de ralentizar el rendimiento de cualquier tabla hash. Para mejorar el impacto, esta clase puede utilizar el orden de comparaciones entre claves para ayudar a romper vínculos cuando las claves son comparables.

6. Tenga en cuenta que esta implementación no está sincronizada. Si varios subprocesos acceden a un mapa hash al mismo tiempo y al menos uno de ellos modifica estructuralmente el mapa, la sincronización debe realizarse externamente. (Una modificación estructural es cualquier operación que agrega o elimina uno o más mapas; simplemente cambiar el valor asociado con una clave que una instancia ya contiene no es una modificación estructural). Esto generalmente se logra sincronizando en algún objeto que encapsule naturalmente el mapa. . Si no existe tal objeto, el mapa debe "envolverse" con una colección. método de mapa sincronizado. Es mejor hacerlo en el momento de la creación para evitar el acceso accidental y no sincronizado al mapa:

 Map m = Collections.synchronizedMap(new HashMap(...));

7. Los iteradores devueltos por todos los "métodos de vista de colección" de esta clase son rápidos: si el mapa se modifica de alguna manera en cualquier momento después de que se crea el iterador, excepto a través del método de eliminación del iterador, el iterador arrojará ConcurrentModificationException. arrojado. Por lo tanto, ante modificaciones concurrentes, el iterador falla rápida y claramente, en lugar de correr el riesgo de comportarse arbitrariamente y no determinista en un momento indeterminado en el futuro.

8. Tenga en cuenta que el comportamiento a prueba de fallas de los iteradores no está garantizado porque, en general, es imposible hacer garantías estrictas en presencia de modificaciones simultáneas no sincronizadas. Los iteradores a prueba de fallos lanzan ConcurrentModificationException en el mejor de los casos. Por lo tanto, es un error escribir programas que se basen en esta excepción: el comportamiento rápido de los iteradores solo debe usarse para detectar errores.

Resumen:
puede ver que los comentarios en el código fuente son muy detallados, incluidos métodos de implementación, información que acepta valores NULL, clasificación, información de capacidad, información de parámetros, información de sincronización, etc., lo que puede ayudarnos a comprender mejor esta clase.

2.Proposito

HashMap hereda AbstractMap e implementa Map <K, V>, Cloneable, Serializable,
mientras que AbstractMap también implementa Map <K, V>, y el mapa es equivalente a un contenedor, que es particularmente adecuado para escenarios donde los pares clave-valor deben procesarse rápidamente. encontrado e insertado. , que se puede utilizar para las siguientes funciones:

1. Almacenamiento en caché: algunos datos de uso frecuente se pueden almacenar en HashMap, lo que puede acelerar el acceso y reducir el número de accesos a bases de datos o archivos 2. Procesamiento de datos: HashMap puede
contar fácilmente la cantidad de ciertos datos, como la aparición de palabras, tiempos, visitas a sitios web, etc.
3. Almacenar datos: evita la necesidad de escribir clases de entidad para recibir datos de un objeto. Es muy conveniente para consultas de informes o uso temporal, y el mapa tiene la función de genéricos personalizados, por lo que cuando El tipo de parámetros de recepción tiene una adaptabilidad muy alta
4. Programación de red: puede usar HashMap para almacenar la sesión entre el cliente y el servidor. Cada cliente corresponde a un par clave-valor único, lo cual es conveniente para la búsqueda. 5. Programación GUI
: Puede usar HashMap para almacenar sesiones, administrar elementos de la interfaz, por ejemplo, usar el nombre del componente como clave para encontrar el objeto del componente correspondiente
6. Programación concurrente: puede usar ConcurrentHashMap para implementar un HashMap seguro para subprocesos, admite múltiples -Acceso y modificación por subprocesos, y evitar inconsistencias de datos causadas por el acceso concurrente.

En resumen, HashMap es una estructura de datos muy utilizada y se utiliza en casi todos los programas Java. Es una muy buena opción en términos de eficiencia operativa y simplicidad de código.

3. Código fuente

1. constante

//The default initial capacity - MUST be a power of two.
//默认初始容量-必须是2的幂
//例如你设置了一个初始容量大小为6的map,则最终的容量会被调整为8,不设置初始容量默认16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//The maximum capacity, used if a higher value is implicitly specified by either of the constructors with arguments. MUST be a power of two <= 1<<30
//最大容量,如果任何一个具有参数的构造函数隐式指定了更高的值,则使用该容量。 必须是2的幂<=1<<30,也就是1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;

//The load factor used when none specified in constructor
//在构造函数中指定none时使用的负载因子,当map中数量等于0.75*当前容量触发扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/*
The bin count threshold for using a tree rather than list for a bin. 
Bins are converted to trees when adding an element to a bin with at least this many nodes. 
The value must be greater than 2 and should be at least 8 to mesh with assumptions in tree removal about conversion back to plain bins upon shrinkage
*/
/*
使用树而不是列表的bin计数阈值。 当向至少有这么多节点的bin中添加元素时,bin会转换为树。 
该值必须大于2,并且应该至少为8,以便与树移除中关于收缩时转换回普通箱的假设相啮合
就是链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,则将链表转换成红黑树
*/
static final int TREEIFY_THRESHOLD = 8;

/*
The bin count threshold for untreeifying a (split) bin during a resize operation. 
Should be less than TREEIFY_THRESHOLD, and at most 6 to mesh with shrinkage detection under removal
*/
/*
在调整大小操作期间取消压缩(拆分)bin的bin计数阈值。 应小于TREEIFY_THRESHOLD,最多为6以去除下的收缩检测网格
就是红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 < 6时,则将 红黑树转换成链表
*/
static final int UNTREEIFY_THRESHOLD = 6;

/*
The smallest table capacity for which bins may be treeified. (Otherwise the table is resized if too many nodes in a bin.)
Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts between resizing and treeification thresholds.
*/
/*
最小的表容量,其桶可以被树化. (否则,如果bin中的节点太多,则会调整表的大小。)应至少为4*TREEIFY_THRESHOLD,以避免调整大小和treeification阈值之间的冲突。
就是当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)
*/
static final int MIN_TREEIFY_CAPACITY = 64;

2. campo

//The table, initialized on first use, and resized as necessary. When allocated, length is always a power of two. (We also tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.)
//该表在首次使用时初始化,并根据需要调整大小。 分配时,长度总是2的幂。 (在某些操作中,我们还允许长度为零,以允许当前不需要的自举机制。)
transient Node<K,V>[] table;

//Holds cached entrySet(). Note that AbstractMap fields are used for keySet() and values()
//保存缓存的entrySet()。 注意AbstractMap字段用于keySet()和values()
transient Set<Map.Entry<K,V>> entrySet;

//The number of key-value mappings contained in this map
//此映射中包含的键值映射的数量(key值的数量)
transient int size;

// The number of times this HashMap has been structurally modified Structural modifications are those that change the number of mappings in the HashMap or otherwise modify its internal structure (e.g., rehash).
// This field is used to make iterators on Collection-views of the HashMap fail-fast. (See ConcurrentModificationException).
//这个HashMap被结构修改的次数结构修改是那些改变HashMap中映射的数量或以其他方式修改其内部结构(例如,rehash)的次数。 此字段用于使HashMap的集合视图上的迭代器快速失败。 (请参阅ConcurrentModificationException)。
transient int modCount;

//The next size value at which to resize (capacity * load factor)
//(The javadoc description is true upon serialization.Additionally, if the table array has not been allocated, this field holds the initial array capacity, or zero signifying DEFAULT_INITIAL_CAPACITY.)
//要调整大小的下一个大小值(容量*负载因子),例如设置5自动调整为2<<2 = 8
//Javadoc描述在序列化时是真实的。此外,如果尚未分配表数组,则此字段保存初始数组容量,或表示DEFAULT_INITIAL_CAPACITY的零。
int threshold;

//The load factor for the hash table
// 哈希表的负载因子,默认0.75
final float loadFactor;

3. Método

3.1 Crear un nuevo método hashMap
/*
Constructs an empty HashMap with the specified initial capacity and load factor.
Params:
initialCapacity – the initial capacity
loadFactor – the load factor
Throws:
IllegalArgumentException – if the initial capacity is negative or the load factor is nonpositiv 
*/
//构造具有指定初始容量和负载因子的空HashMap
public HashMap(int initialCapacity, float loadFactor) {
    
    
		//1、初始容量小于0,抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                              	initialCapacity);
        //2、初始容量超过最大容量,指定初始容量为最大容量1<<30
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //3、负载因子小于0 或者 负载因子是一个"非数字的值",抛出异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //4、指定负载因子,指定调整数量
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

//Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).
//如果不指定负载因子,则默认创建一个负载因子为0.75的hashMap
public HashMap(int initialCapacity) {
    
    
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

//Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
//如果不指定hashMap的初始容量和负载因子,则默认创建一个初始容量16和负载因子为0.75的hashMap
public HashMap() {
    
    
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

//Constructs a new HashMap with the same mappings as the specified Map. The HashMap is created with default load factor (0.75) and an initial capacity sufficient to hold the mappings in the specified Map.
//使用与指定映射相同的映射构造一个新的HashMap。 HashMap使用默认加载因子(0.75)和足以在指定映射中保存映射的初始容量创建
//说简单点就是会创建一个新map,然后将参数map的信息放到新map中,继承参数map的容量
public HashMap(Map<? extends K, ? extends V> m) {
    
    
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

/*
Params:
m – the map(参数map)
evict – false when initially constructing this map, else true (relayed to method afterNodeInsertion)
(在最初构造此映射时为false,否则为true)
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    
    
        int s = m.size();
        //如果map的容量大于0
        if (s > 0) {
    
    
        	//如果table未初始化
            if (table == null) {
    
     // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                // 计算阈值
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                 计算得到的t大于阈值,则初始化阈值
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            //已初始化,并且m元素个数大于阈值,进行扩容处理(扩容在后面会讲)
            else if (s > threshold)
                resize();
            //循环放入map中的元素
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
    
    
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

3.2 método de obtención
/*
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
More formally, if this map contains a mapping from a key k to a value v such that (key==null ? k==null : key.equals(k)), then this method returns v; otherwise it returns null. (There can be at most one such mapping.)
A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases
*/
/*
返回指定键映射到的值,如果此映射不包含键的映射,则返回null。
更正式地说,如果此映射包含从键k到值v的映射,则(key==null ? k==null:key.equals(k)),则此方法返回v;否则返回null。 (最多可以有一个这样的映射。)
返回值为null不一定表示映射不包含键的映射;映射也可能显式地将键映射为null。 ContainsKey操作可用于区分这两种情况
最常用的map.get方法,会返回key中的value,不过需要注意hashMap是允许key和val为空的,所以可能存在key为null的键,val返回null也不一定指的key不存在,可能val就是null
*/
public V get(Object key) {
    
    
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

//1、先来看里面的hash方法,首先key为null,它的hash值就是0,否则返回key的一个异或运算
//h会先等于key的hashCode 再和 key的hashCode>>>16计算,例如key为2,则返回的hash值就是50 ^ (50>>>16) = 50,
static final int hash(Object key) {
    
    
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

//2、再看外面的getNode方法,将上一步获取的hash值和key进行计算,返回一个Node
final Node<K,V> getNode(int hash, Object key) {
    
    
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 判断表是否为空,表大小是否大于零,并且根据此 key 对应的表内是否存在 Node节点
        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;
            // 桶中不止一个结点
            if ((e = first.next) != null) {
    
    
            	//如果是红黑树
                if (first instanceof TreeNode)
                // 则从红黑树中查找结果
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
    
    
                // 否则循环遍历链表,while循环,直到命中结束或者遍历结束
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

Resumen del método get de hashMap:

  • 1. Realice una operación hash en el hashCode () de la clave. El valor hash es (h = k.hashCode()) ^ (h >>> 16)
  • 2. Si la matriz de almacenamiento no está vacía y el elemento en la posición calculada no está vacío. Continuar; de lo contrario, devolver nulo
  • 3. Si los primeros elementos (elementos de matriz) en el depósito son iguales, regrese directamente
  • 4. Si los valores clave de los elementos obtenidos no son iguales, busque el elemento en el siguiente nodo.
  • 5. Si es un árbol rojo-negro, busque en el árbol, la complejidad del espacio es O (logn)
  • 6. Si es una lista vinculada, recorra y busque key.equals (k) en la lista vinculada, la complejidad del espacio es O (n)
3.3 método de colocación
//Associates the specified value with the specified key in this map.
//If the map previously contained a mapping for the key, the old value is replaced.
//将指定的值与此map中的指定键相关联。 如果这个map包含键的映射,则替换旧值(最常用的put方法)
public V put(K key, V value) {
    
    
        return putVal(hash(key), key, value, false, true);
    }

/*
Params:
hash – hash for key(要存储key的hash值)
key – the key(要存储的key值)
value – the value to put(要存储的value值)
onlyIfAbsent – if true, don't change existing value(如果true,将不会替代当前已经存在的对应value,默认false)
evict – if false, the table is in creation mode(表是否在创建模式,如果为false,则表是存在创建模式,默认true)
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    
    
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //1、检查table是否为空,如果为空就初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //2、检查table中位置为(n -1 ) & hash 是否为空
        if ((p = tab[i = (n - 1) & hash]) == null)
       		//如果为空,直接放入数组
            tab[i] = newNode(hash, key, value, null);
        else {
    
    
        //已经存在元素,代表了这个位置发生了碰撞
            Node<K,V> e; K k;
            //3、如果桶中存在的元素的hash 和 key 都相等,则直接覆盖旧value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //4、如果存放该元素的链表是红黑树,放入树中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
    
    
            	//5、循环链表,从链表末尾插入元素
                for (int binCount = 0; ; ++binCount) {
    
    
                	//如果链表下一个元素为空,也就是到达链表的尾部
                    if ((e = p.next) == null) {
    
    
                    	//6、 在尾部插入新结点
                        p.next = newNode(hash, key, value, null);
                        //7、如果结点数量到达红黑树的阈值,则转换为红黑树,直接跳出循环
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //8、看链表中是否存在hash和key与要插入进来的元素相同,如果存在相同的元素跳出循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //9、遍历桶中的链表
                    p = e;
                }
            }
            //10、如果存在着key值、hash值与插入元素相等的结点
            if (e != null) {
    
     // existing mapping for key
                V oldValue = e.value;
                // onlyIfAbsent为false或者旧值为null
                if (!onlyIfAbsent || oldValue == null)
                	//覆盖旧的值
                    e.value = value;
                // 访问后回调
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }
        //11、将记录修改次数加1,并判断是否需要扩容
        ++modCount;
        //12、如果容量超过了临界点,就扩容
        if (++size > threshold)
            resize();
        // 插入后回调
        afterNodeInsertion(evict);
        return null;
    }

Resumen del método put de hashMap:

  • 1. Calcule la clave y obtenga el hash de la clave.
  • 2. Si la tabla hash está vacía, llame a resize() para inicializar la tabla hash.
  • 3. Si no se produce ninguna colisión, agregue elementos directamente a la tabla hash.
  • 4. Si se produce una colisión, se juzgará en las tres situaciones siguientes:
    • 4.1. Si el hash y la clave de los elementos presentes en el depósito son iguales, el valor anterior se sobrescribirá directamente.
    • 4.2 Si es una estructura de árbol rojo-negro, llame al método de inserción del árbol.
    • 4.3 Si se trata de una estructura de lista vinculada, recorra hasta que un nodo en la lista vinculada esté vacío e insértelo utilizando el método de inserción de cola. Después de la inserción, se juzga si el número de listas vinculadas ha alcanzado el umbral de 8 para convertirse en un árbol rojo-negro, también puede atravesar hasta que haya un nodo e insertarlo. El valor hash y el contenido del elemento son los mismos, sobrescriba
  • 5. Si el depósito está lleno y excede el umbral, se utilizará el cambio de tamaño para ampliar la capacidad.

Ilustración de poner
Insertar descripción de la imagen aquí
Fuente de la imagen: https://www.cnblogs.com/xiaoxi/p/7233201.html

3.4 método de cambio de tamaño
/*
Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. 
Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table.
*/
/*
初始化表或加倍表大小。 如果为null,则按照字段阈值中保持的初始容量目标进行分配。 
否则,因为我们使用的是两次幂的扩展,所以每个bin中的元素必须保持在同一个索引,或者在新表中以两次幂的偏移量移动。
目前基本上所有的集合或者map都使用了位运算进行扩容,hashMap使用了size<<1的计算方式,将容量提升至原来的2倍,所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置
另外,resize()函数在size > threshold时被调用,也就是容量大于map临界点的时候
*/
final Node<K,V>[] resize() {
    
    
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //oldCap大于 0 代表原来的 table 表非空(oldCap 为原表的大小)
        if (oldCap > 0) {
    
    
        	//判断旧表是否超过最大长度
            if (oldCap >= MAXIMUM_CAPACITY) {
    
    
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //旧表向左偏移一位小于最大值 并且旧表长度大于等于16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold(双倍阈值)
        }
        // 如果旧表的阈值大于0,新容量直接等于旧阈值
        else if (oldThr > 0) // initial capacity was placed in threshold (初始容量被置于阈值)
            newCap = oldThr;
        else {
    
                   // zero initial threshold signifies using defaults(初始阈值为0使用map容量默认值16)
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //阈值等于初始容量*默认负载因子
        }
        //如果新的阈值等于0,需要重新计算阈值
        if (newThr == 0) {
    
    
            float ft = (float)newCap * loadFactor;
            // 当新表小于最大容量 并且新表*0.75 小于最大容量时,等于新表*0.75,否则等于最大容量
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //调整后的大小 等于 新阈值
        threshold = newThr;
        @SuppressWarnings({
    
    "rawtypes","unchecked"})
        //新建hash桶数组
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
    
    
        	//如果旧的表不为空,开始扩容操作,遍历旧的hash表
            for (int j = 0; j < oldCap; ++j) {
    
    
                Node<K,V> e;
                //如果旧的hash桶数组在j结点处不为空,复制给e
                if ((e = oldTab[j]) != null) {
    
    
                	//将旧表的元素设为空,方便JVM后续进行垃圾回收
                    oldTab[j] = null;
                    //如果后面没有Node结点
                    if (e.next == null)
                    	//直接对结点的hash值对新的数组长度求模获得存储位置
                        newTab[e.hash & (newCap - 1)] = e;
                        //如果结点属于红黑树,那么添加到红黑树中
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else {
    
     // preserve order(保留旧hash表中链表的顺序)
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
    
    
                        	//将Node结点的next赋值给next
                            next = e.next;
                            //如果结点e的hash值与原hash桶数组的长度作与运算为0
                            if ((e.hash & oldCap) == 0) {
    
    
                            	//如果loTail为空,将e结点赋值给loHead
                                if (loTail == null)
                                    loHead = e;
                                //否则将e赋值给loTail.next,loTail也一并赋值
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //如果结点e的hash值与原hash桶数组的长度作与运算不为0
                            else {
    
    
                            	//如果hiTail 为空,将e结点赋值给hiHead 
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                //否则将e赋值给hiTail.next,hiTail也一并赋值
                                    hiTail.next = e;
                                hiTail = e;
                            }
                         //直到e为空
                        } while ((e = next) != null);
                        //如果loTail 不等于空,loTail.next就为空,将loHead赋值给新的hash桶数组[j]处
                        if (loTail != null) {
    
    
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //如果hiTail 不等于空,hiTail.next就为空,将hiHead赋值给新的hash桶数组[j+旧hash桶数组长度]处
                        if (hiTail != null) {
    
    
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

Resumen del método de cambio de tamaño de hashMap:

  • El método de cambio de tamaño consiste en llamar al método de cambio de tamaño para expandir la capacidad cuando el par clave-valor en el mapa hash es mayor que el umbral o durante la inicialización.
  • Cada vez que se expande, se expande a 2 veces el tamaño original.
  • Después de la expansión, la posición del objeto Nodo está en la posición original o se mueve a una posición dos veces mayor que el desplazamiento original.

Diagrama de expansión
Insertar descripción de la imagen aquí

3.5 método de eliminación
//Removes the mapping for the specified key from this map if present
//从该map中删除指定键的映射(如果存在)
public V remove(Object key) {
    
    
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

/*
Implements Map.remove and related methods
Params:
hash – hash for key 	key的hash值
key – the key 			key值
value – the value to match if matchValue, else ignored 要匹配的值,没有则忽略(默认null)
matchValue – if true only remove if value is equal 如果为true,则仅在值相等时删除(默认false)
movable – if false do not move other nodes while removing 如果为false,则在删除时不要移动其他节点(默认true)
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
    
    
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        //如果当前table已经初始化且index位置上的元素不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
    
    
            Node<K,V> node = null, e; K k; V v;
            //如果当前index位置上第一个元素就是需要被删除的元素,将node指定为p
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            //如果p结点的下一个元素不为空,则需要判断类型
            else if ((e = p.next) != null) {
    
    
            	//如果p结点属于红黑树,则调用树的查找
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
    
    
                //否则遍历查找链表
                    do {
    
    
                    	//如果hash值相等,node结点等于e,跳出循环
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
    
    
                            node = e;
                            break;
                        }
                        //否则将p结点指定为e
                        p = e;
                    //当下一个元素不为空,继续循环
                    } while ((e = e.next) != null);
                }
            }
            //如果node不为null,代表已经查找到了key值对应的元素
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
    
    
                //如果node属于红黑树,调用树的删除方法
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                //如果node等于p,则代表是index当前位置的第一个元素被查找到,直接将index位置的元素后移
                else if (node == p)
                    tab[index] = node.next;
                //否则将查找到元素的前一个元素的next指针指向被查找到的元素的next元素
                else
                    p.next = node.next;
                //操作数加一
                ++modCount;
                //map中元素数减一
                --size;
                //回调以允许LinkedHashMap后操作
                afterNodeRemoval(node);
                //返回被删除的元素
                return node;
            }
        }
        return null;
    }

Resumen del método de eliminación de hashMap:

  • 1. Primero buscará el nodo eliminado.
    • El primer elemento en la posición de índice actual, véalo como el elemento que debe eliminarse
    • Si el primer elemento no es un elemento que deba eliminarse, vaya al árbol rojo-negro o a la lista vinculada para encontrar el nodo.
  • 2. Si el nodo no está vacío, elimínelo.
    • Si es un árbol rojo-negro, llame al método de eliminación del árbol.
    • Si es el primer elemento, mueva directamente el elemento en la posición del índice hacia atrás.
    • De lo contrario, el siguiente puntero del elemento antes del elemento encontrado apunta al siguiente elemento del elemento encontrado.
3.6 Otros métodos
//Returns the number of key-value mappings in this map
//返回map中k-v的数量
 public int size() {
    
    
        return size;
    }

//Returns true if this map contains no key-value mappings
//如果map的size为0说明这个map是空的,返回true
public boolean isEmpty() {
    
    
        return size == 0;
    }
    
//Returns true if this map contains a mapping for the specified key
//如果map中包含这个key则返回true,会调用getNode获取出来是否为空
public boolean containsKey(Object key) {
    
    
        return getNode(hash(key), key) != null;
    }

//Copies all of the mappings from the specified map to this map. These mappings will replace any mappings that this map had for any of the keys currently in the specified map
//将指定map中的所有k-v映射复制到此map。 这些映射将替换此map对当前在指定map中的任何键所具有的任何映射
//相当于原来map中的k会被参数中的map覆盖一遍,如果原map存在映射就被覆盖,否则就会在原map中添加参数map对应的映射
public void putAll(Map<? extends K, ? extends V> m) {
    
    
        putMapEntries(m, true);
    }

//Removes all of the mappings from this map. The map will be empty after this call returns
//从该map中删除所有映射。 此调用返回后,map将为空
public void clear() {
    
    
        Node<K,V>[] tab;
        //操作数加1
        modCount++;
        //如果表不为空并且元素数大于0,设置元素数为0,所有表元素都设置为空
        if ((tab = table) != null && size > 0) {
    
    
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

/*
Returns true if this map maps one or more keys to the specified value.
Params:
value – value whose presence in this map is to be tested
Returns:
true if this map maps one or more keys to the specified value
*/
//如果map里的所有值里一旦包含这个value,则返回true
public boolean containsValue(Object value) {
    
    
        Node<K,V>[] tab; V v;
        //如果表不为空并且元素数大于0
        if ((tab = table) != null && size > 0) {
    
    
        	//遍历表
            for (int i = 0; i < tab.length; ++i) {
    
    
            	//当表中的元素不为空继续遍历
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
    
    
                	//如果有值相等,直接返回true
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }

/*
Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. 
If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. 
The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.
Returns:
a set view of the keys contained in this map
*/
/*
返回此map中包含的键的集合。 集合由map支持,因此对map的更改反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的移除操作),则迭代的结果是未定义的。 该集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作。
*/
public Set<K> keySet() {
    
    
        Set<K> ks = keySet;
        if (ks == null) {
    
    
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }

//HashMap的keySet实现了AbstractSet,这边就不详细分析了,总之知道这个方法会返回一个set集合就好(不会重复)
  final class KeySet extends AbstractSet<K> {
    
    
        public final int size()                 {
    
     return size; }
        public final void clear()               {
    
     HashMap.this.clear(); }
        public final Iterator<K> iterator()     {
    
     return new KeyIterator(); }
        public final boolean contains(Object o) {
    
     return containsKey(o); }
        public final boolean remove(Object key) {
    
    
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator() {
    
    
            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super K> action) {
    
    
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
    
    
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
    
    
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.key);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

/*
Returns a Collection view of the values contained in this map. The collection is backed by the map, so changes to the map are reflected in the collection, and vice-versa. 
If the map is modified while an iteration over the collection is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. 
The collection supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Collection.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
Returns:
a view of the values contained in this map
*/
/*
返回此map中包含的值的集合。 集合由map支持,因此对map的更改会反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的移除操作),则迭代的结果是未定义的。 集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作。
*/
public Collection<V> values() {
    
    
        Collection<V> vs = values;
        if (vs == null) {
    
    
            vs = new Values();
            values = vs;
        }
        return vs;
    }

//HashMap的values方法同样实现AbstractCollection,知道这个方法会获取map中的所有值就可以了(注意这个方法会重复)
final class Values extends AbstractCollection<V> {
    
    
        public final int size()                 {
    
     return size; }
        public final void clear()               {
    
     HashMap.this.clear(); }
        public final Iterator<V> iterator()     {
    
     return new ValueIterator(); }
        public final boolean contains(Object o) {
    
     return containsValue(o); }
        public final Spliterator<V> spliterator() {
    
    
            return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super V> action) {
    
    
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
    
    
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
    
    
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

/*
Returns a Set view of the mappings contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. 
If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined. 
The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
Returns:
a set view of the mappings contained in this map
*/
/*
返回此map中包含的映射集合。 集合由map支持,因此对map的更改反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的remove操作,或者通过迭代器返回的map条目上的setValue操作),迭代的结果是未定义的。 
该集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作
*/
public Set<Map.Entry<K,V>> entrySet() {
    
    
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

//HashMap的entrySet方法实现AbstractCollection,会获取到每一组的kv值,通常用于遍历map
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    
    
        public final int size()                 {
    
     return size; }
        public final void clear()               {
    
     HashMap.this.clear(); }
        public final Iterator<Map.Entry<K,V>> iterator() {
    
    
            return new EntryIterator();
        }
        public final boolean contains(Object o) {
    
    
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
    
    
            if (o instanceof Map.Entry) {
    
    
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
    
    
            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
    
    
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
    
    
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
    
    
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

//Overrides of JDK8 Map extension methods
//JDK8Map扩展方法的复盖,如果这个map里没有获取到key对应的结点,则返回defaultValue,否则获取对应node的值(此方法不会将defaultValue保存到map中)
 @Override
    public V getOrDefault(Object key, V defaultValue) {
    
    
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
    }

//如果map中存在对应的结点则不更新值,如果不存在对应的结点将节点和值放入map中
 @Override
    public V putIfAbsent(K key, V value) {
    
    
        return putVal(hash(key), key, value, true, true);
    }

//remove的重载方法,如果map中存在key对应的node且node对应的值要等于参数传的value,才会删除这个结点
@Override
    public boolean remove(Object key, Object value) {
    
    
        return removeNode(hash(key), key, value, true, true) != null;
    }

//map的替代方法
 @Override
    public boolean replace(K key, V oldValue, V newValue) {
    
    
        Node<K,V> e; V v;
        //如果key的node结点不为空 且 节点的值等于oldValue且不为空
        if ((e = getNode(hash(key), key)) != null &&
            ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
    
    
            //将新的值赋值给node结点的值
            e.value = newValue;
            //回调操作
            afterNodeAccess(e);
            return true;
        }
        return false;
    }

//同一个重载方法,取消了oldValue的限制,只要key对应的node结点存在就赋值
 @Override
    public V replace(K key, V value) {
    
    
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) != null) {
    
    
            V oldValue = e.value;
            e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
        return null;
    }

//computeIfAbsent是java8的新方法,有着和putIfAbsent类似的功能(有着更全面的功能,java8中引入了函数式接口,在各大底层普遍使用,不然作者也不会特意发布这个方法)
@Override
    public V computeIfAbsent(K key,
                             Function<? super K, ? extends V> mappingFunction) {
    
    
        //函数式接口为空的直接抛出异常
        if (mappingFunction == null)
            throw new NullPointerException();
        //计算key的hash值
        int hash = hash(key);
        Node<K,V>[] tab; Node<K,V> first; int n, i;
        int binCount = 0;
        TreeNode<K,V> t = null;
        Node<K,V> old = null;
        //如果元素数超过临界值 或 表为空 或表的长度等于0
        if (size > threshold || (tab = table) == null ||
            (n = tab.length) == 0)
            //进行初始化,n等于初始化的长度
            n = (tab = resize()).length;
        
        if ((first = tab[i = (n - 1) & hash]) != null) {
    
    
            if (first instanceof TreeNode)
                old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
            else {
    
    
                Node<K,V> e = first; K k;
                do {
    
    
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
    
    
                        old = e;
                        break;
                    }
                    ++binCount;
                } while ((e = e.next) != null);
            }
            V oldValue;
            if (old != null && (oldValue = old.value) != null) {
    
    
                afterNodeAccess(old);
                return oldValue;
            }
        }
        V v = mappingFunction.apply(key);
        if (v == null) {
    
    
            return null;
        } else if (old != null) {
    
    
            old.value = v;
            afterNodeAccess(old);
            return v;
        }
        else if (t != null)
            t.putTreeVal(this, tab, hash, key, v);
        else {
    
    
            tab[i] = newNode(hash, key, v, first);
            if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
        }
        ++modCount;
        ++size;
        afterNodeInsertion(true);
        return v;
    }

持续更新中。。。

4. Implementar un HashMap simple

Actualizado continuamente. . .

Supongo que te gusta

Origin blog.csdn.net/weixin_52796198/article/details/131414057
Recomendado
Clasificación