Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1. ?

Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1. ?

△ Hollis, una persona con una búsqueda única de Codificación △
Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1.  ?
Este es el
autor original número 254 de Hollis l
Fuente de Hollis l Hollis (ID: hollischuang)
En la fundación Java, las clases de recopilación son una pieza clave de conocimiento y también son un desarrollo diario Se usa a menudo cuando Por ejemplo, List y Map también son muy comunes en el código.
Personalmente, creo que los ingenieros de JDK en realidad hicieron muchas optimizaciones para la implementación de HashMap. Si quieres decir cuál de todo el código fuente de JDK tiene los huevos más enterrados, entonces creo que HashMap puede estar al menos entre los cinco primeros.
Es precisamente por esto que muchos detalles se pasan por alto fácilmente.Hoy nos centraremos en uno de los temas, a saber:
¿Por qué el factor de carga de HashMap se establece en 0,75 en lugar de 1 y no 0,5? ¿Cuáles son las consideraciones detrás de esto?
No subestime esta pregunta, porque el factor de carga es un concepto muy importante en HashMap y un punto de prueba común para entrevistas de alto nivel.
Además, vale la pena configurarlo, y algunas personas lo usarán mal. Por ejemplo, mi "Alibaba Java Development Manual hace unos días recomendó configurar la capacidad inicial al crear un HashMap, pero ¿cuánto es apropiado?" "En este artículo, algunos lectores respondieron así:
Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1.  ?

Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1.  ?
Dado que alguien intentará modificar el factor de carga, ¿es apropiado cambiarlo a 1? ¿Por qué HashMap no usa 1 como valor predeterminado del factor de carga?

Que es loadFactor

Primero, introduzcamos qué es el factor de carga (loadFactor), si el lector ya conoce esta parte, puede omitir este párrafo directamente.
Sabemos que cuando se crea el HashMap por primera vez, se especificará su capacidad (si no se especifica explícitamente, el valor predeterminado es 16, vea por qué la capacidad predeterminada de HashMap es 16), luego, a medida que continuamos colocando elementos en el HashMap Si se excede la capacidad, es necesario un mecanismo de expansión.
La llamada expansión consiste en ampliar la capacidad de HashMap:

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

En el código, podemos ver que en el proceso de agregar elementos al HashMap, si la cantidad de elementos (tamaño) excede el umbral (umbral), se expandirá (redimensionará) automáticamente y, después de la expansión, es necesario Repita los elementos originales en el HashMap, es decir, redistribuya los elementos del depósito original al nuevo depósito.
En HashMap, el umbral (umbral) = factor de carga (loadFactor) * capacidad (capacidad).
loadFactor es el factor de carga, que indica qué tan lleno está el HashMap. El valor predeterminado es 0.75f, lo que significa que, de manera predeterminada, cuando el número de elementos en el HashMap alcanza 3/4 de la capacidad, se expandirá automáticamente. (Para obtener más detalles, consulte los conceptos que no están claros en HashMap)

Por qué expandirse

Recuerde que dijimos antes que HashMap no solo necesita expandir su capacidad durante el proceso de expansión, ¡sino que también necesita refrito! Por lo tanto, este proceso en realidad requiere mucho tiempo, y cuantos más elementos haya en el Mapa, más tiempo llevará.
El proceso de repetición es equivalente a volver a aplicar el hash a todos los elementos que contiene y recalcular a qué depósito se debe asignar.
Entonces, ¿alguien ha pensado en una pregunta, ya que es tan problemática, por qué necesita expandirse? ¿No es HashMap una lista vinculada a una matriz? Sin expansión, se puede almacenar infinitamente. ¿Por qué expandirse?
En realidad, esto está relacionado con las colisiones de hash.
Colisión hash

Sabemos que HashMap se implementa en la parte inferior en función de una función hash, pero las funciones hash tienen las siguientes características básicas: si el valor hash calculado según la misma función hash es diferente, el valor de entrada debe ser diferente. Sin embargo, si el valor hash calculado en base a la misma función hash es el mismo, es posible que el valor de entrada no sea el mismo.
El fenómeno de que dos valores de entrada diferentes tengan el mismo valor hash calculado a partir de la misma función hash se llama colisión.
Un indicador importante para medir la calidad de una función hash es la probabilidad de colisión y la solución a la colisión.
Para resolver la colisión de hash, existen muchos métodos, entre los cuales el más común es el método de dirección en cadena, que también es el método adoptado por HashMap. Para obtener más información, consulte el artículo más completo sobre el análisis de hash () en Map en toda la red.
HashMap combina una matriz y una lista enlazada, y se aprovecha de las dos. Podemos entenderlo como una matriz de listas enlazadas.
Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1.  ?
HashMap se implementa en base a la estructura de datos de una matriz de listas enlazadas.
Cuando colocamos un elemento en el HashMap, primero debemos ubicar qué lista vinculada en la matriz y luego colgar este elemento detrás de la lista vinculada.
Cuando obtenemos elementos de HashMap, también necesitamos ubicar qué lista vinculada en la matriz y luego recorrer los elementos en la lista vinculada uno por uno hasta encontrar el elemento requerido.
Sin embargo, si el conflicto en un HashMap es demasiado alto, la lista enlazada de la matriz degenerará en una lista enlazada. En este momento, la velocidad de consulta se reducirá considerablemente.
Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1.  ?
 Entonces, para garantizar la velocidad de lectura de HashMap, necesitamos encontrar formas de garantizar que el conflicto de HashMap no sea demasiado alto.
Escalado para evitar la colisión de hash

Entonces, ¿cómo podemos evitar eficazmente las colisiones de hash?
Primero pensemos hacia atrás, ¿qué crees que causará más colisiones de hash en HashMap?
Hay dos situaciones:
1. La capacidad es demasiado pequeña. Cuanto menor sea la capacidad, mayor será la probabilidad de colisión. Si hay más lobos y menos carne, habrá competencia.
2. El algoritmo hash no es lo suficientemente bueno. Si el algoritmo no es razonable, puede dividirse en el mismo grupo o en varios. La distribución desigual también puede generar competencia.
Por lo tanto, la resolución de la colisión de hash en HashMap también parte de estos dos aspectos.
Ambos puntos están bien reflejados en HashMap. Combinar los dos métodos, expandir la capacidad de la matriz cuando sea apropiado y luego calcular a qué matriz se asignan los elementos mediante un algoritmo hash adecuado, puede reducir en gran medida la probabilidad de conflicto. Puede evitar el problema de la consulta ineficiente.

Por qué el loadFactor predeterminado es 0,75

En este punto, sabemos que loadFactor es un concepto importante en HashMap, y representa el grado máximo de plenitud de este HashMap.
Para evitar colisiones de hash, HashMap debe expandirse cuando sea apropiado. Es entonces cuando la cantidad de elementos en él alcanza un valor crítico, que está relacionado con loadFactor como se mencionó anteriormente. En otras palabras, establecer un loadFactor razonable puede evitar efectivamente conflictos de hash.
Entonces, ¿cuál es la configuración de loadFactor adecuada?
Este valor ahora es 0,75 en el código fuente de JDK:

/**
 * The load factor used when none specified in constructor.
 */

static final float DEFAULT_LOAD_FACTOR = 0.75f;
Entonces, ¿por qué elegir 0.75? ¿Cuáles son las consideraciones detrás? ¿Por qué no 1, no 0,8? ¿No 0,5, sino 0,75?
En la documentación oficial de JDK, hay una descripción de este tipo:

As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put).

El significado aproximado es: En términos generales, el factor de carga predeterminado (0,75) proporciona una buena compensación entre los costos de tiempo y espacio. Los valores más altos reducen la sobrecarga de espacio, pero aumentan los costos de búsqueda (reflejados en la mayoría de las operaciones de la clase HashMap, incluidas la obtención y la colocación).
Imagine que si establecemos el factor de carga en 1, y la capacidad usa el valor inicial predeterminado de 16, entonces significa que un HashMap debe estar "lleno" antes de la expansión.
Luego, en HashMap, la mejor situación es que estos 16 elementos caen en 16 cubos diferentes después de pasar el algoritmo hash; de lo contrario, inevitablemente se producirán colisiones hash. Y cuanto más elementos, mayor es la probabilidad de colisiones hash, menor es la velocidad de búsqueda.

0,75 base matemática

Además, podemos calcular qué tan apropiado es este valor a través de una especie de pensamiento matemático.
Suponemos que la probabilidad de que un balde esté vacío y no vacío es 0.5, usamos s para representar la capacidad y n para representar el número de elementos agregados.
Sea s el tamaño de la clave agregada y el número de n claves. Según el teorema del binomio, la probabilidad de que el cubo esté vacío es:

P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)

Por lo tanto, si la cantidad de elementos en el depósito es menor que el siguiente valor, el depósito puede estar vacío:

log(2)/log(s/(s - 1))

Cuando s tiende a infinito, si el número de claves aumentadas hace que P (0) = 0.5, entonces n / s se acerca rápidamente a log (2):

log(2) ~ 0.693...

Por tanto, el valor razonable es aproximadamente 0,7.
Por supuesto, este método de cálculo matemático no está reflejado en la documentación oficial de Java, y no tenemos forma de investigar si existe tal consideración. Así como no sabemos qué pensó Lu Xun al escribir el artículo, solo podemos especular. Esta especulación proviene de Stack Overflow ( https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap )

El factor inevitable de 0,75

En teoría, creemos que el factor de carga no debería ser demasiado grande, de lo contrario provocará muchas colisiones de hash, y no debería ser demasiado pequeño, lo que desperdiciará espacio.
A través de un razonamiento matemático, es razonable calcular que este valor ronda el 0,7.
Entonces, ¿por qué se seleccionó 0,75 al final?
Recuerde que mencionamos una fórmula anteriormente, es decir, umbral = capacidad del factor de carga (capacidad).
Estamos en "¿Por qué la capacidad predeterminada de HashMap es 16? Como se menciona en ", de acuerdo con el mecanismo de expansión de HashMap, se asegurará de que el valor de la capacidad sea siempre una potencia de 2.
Entonces, para asegurar que el resultado de la capacidad del factor de carga (loadFactor)
sea ​​un número entero, este valor es 0.75 (3/4) más razonable, porque el producto de este número y cualquier potencia de 2 es un número entero.

para resumir

HashMap es una especie de estructura de KV. Para mejorar la velocidad de consulta e inserción, la capa inferior adopta la estructura de datos de la matriz de lista enlazada.
Pero debido a que el algoritmo hash debe usarse al calcular la ubicación del elemento, y el algoritmo hash usado por HashMap es el método de dirección en cadena. Hay dos extremos en este enfoque.
Si la probabilidad de colisión de hash en HashMap es alta, HashMap degenerará en una lista enlazada (no realmente degenerada, pero la operación es como una manipulación directa de la lista enlazada), y sabemos que la mayor desventaja de la lista enlazada es que la velocidad de consulta es relativamente lenta. El encabezado de la tabla se recorre uno por uno.
Por lo tanto, para evitar una gran cantidad de colisiones hash en HashMap, debe expandirse cuando sea apropiado.
La condición para la expansión es cuando el número de elementos alcanza un valor crítico. El método de cálculo del valor crítico en HashMap:

临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)

El factor de carga representa el grado máximo de plenitud que puede alcanzar una matriz. Este valor no debe ser ni demasiado grande ni demasiado pequeño.
El loadFactor es demasiado grande, por ejemplo, igual a 1, entonces habrá una alta probabilidad de colisión de hash, lo que reducirá en gran medida la velocidad de la consulta.
El loadFactor es demasiado pequeño, por ejemplo igual a 0.5, entonces las expansiones frecuentes resultarán en una gran pérdida de espacio.
Por lo tanto, este valor debe estar entre 0,5 y 1. Calculado según fórmulas matemáticas. Este valor es razonable en log (2).
Además, para mejorar la eficiencia de expansión, la capacidad de HashMap tiene un requisito fijo, es decir, debe ser una potencia de 2.
Entonces, si loadFactor es 3/4, entonces el producto de capacidad y capacidad puede ser un número entero.
Por lo tanto, en circunstancias normales, no recomendamos modificar el valor de loadFactor, a menos que existan razones especiales.
Por ejemplo, si sé claramente que mi mapa solo ahorra 5 kv y nunca cambiará, entonces puedo considerar especificar loadFactor.
Pero, de hecho, no lo recomiendo. Podemos lograr este objetivo especificando la capacidad. Para obtener más información, consulte el Manual de desarrollo de Java de Alibaba que sugiere establecer la capacidad inicial al crear un HashMap, pero ¿cuánto es apropiado?
Materiales de referencia:
https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap
https://docs.oracle.com/javase/6/docs/api/ java / util / HashMap.html
https://preshing.com/20110504/hash-collision-probabilities/
Sobre el autor: Hollis, tiene una búsqueda única de codificación de personas, los actuales expertos técnicos de Alibaba, blogger de tecnología personal, artículos técnicos, la cantidad de lectura de toda la red de decenas de millones, autor conjunto "programador de tres clases".

  • MÁS | Más artículos maravillosos: un
    gran tema decidió quedarse: ¿Por qué la sincronización no puede prohibir la reordenación de instrucciones, pero puede garantizar el orden?
    El consejo de un director técnico: ¿Por qué ser competente en tantas tecnologías todavía no es bueno para hacer un proyecto?
    Tecnología Undertow: por qué muchos desarrolladores de Spring Boot abandonan Tomcat
    , el sitio web para adultos más grande del mundo, y preservan la conciencia final de los medios occidentales

Si te gusta este artículo, mantén presionado el
código QR y sigue a Hollis.
Original | Dije que entiendo las colecciones y el entrevistador me preguntó por qué el factor de carga de HashMap no está establecido en 1.  ?
Reenvíalo al círculo de amigos. Este es mi mayor apoyo.
Buen articulo, estoy leyendo ❤️

Supongo que te gusta

Origin blog.51cto.com/13626762/2544190
Recomendado
Clasificación