Problemas y análisis relacionados con HashMap

Prólogo :
    Vi algunos videos y materiales de aprendizaje relacionados con HashMap y he estado trabajando en proyectos. Este punto de conocimiento ha estado en espera por un tiempo. Para aprender, debes hacerlo tú mismo y escribir tú mismo.
    Hoy, recordaré las cosas en mi mente y memorizaré los puntos de conocimiento y las preguntas comunes sobre HashMap.
    Esto no analizará el código fuente. Si te gusta mirar el código fuente, espera cuando tengas tiempo y haz una interpretación pura del código.
 
Primero mira la foto
 
 
Observaciones :
  1. Los nodos se dividen en rojo o negro;
  2. El nodo raíz debe ser negro;
  3. Los nodos de las hojas son todos negros y nulos;
  4. Los dos nodos secundarios que conectan el nodo rojo son todos negros (el árbol rojo-negro no tendrá nodos rojos adyacentes);
  5. Comenzando desde cualquier nodo, la ruta a cada nodo hoja contiene el mismo número de nodos negros;
  6. El nodo recién agregado al árbol rojo-negro es el nodo rojo;
     El árbol rojo-negro es un árbol binario equilibrado y debe tener la propiedad de mantener el equilibrio automáticamente.Las 6 reglas anteriores son las reglas dadas por el árbol rojo-negro para mantener el equilibrio automáticamente.
 
Death n incluso preguntó :
 
   ¿Por qué la matriz HashMap tiene una longitud predeterminada? ¿Cuál es la longitud? ¿Por qué son 16?
 
   ¿Por qué el formato de escritura es 1 << 4 en lugar de 16 directamente?
 
   ¿Cuál es su límite superior? ¿Por qué 2 elevado a n y no otros?
 
Análisis:
 
En Jdk1.8, cuando se llama al constructor HashMap para definir el HashMap, se establece la capacidad. En Jdk 1.7, hay que esperar hasta la primera operación put para realizar esta operación. Por defecto, cuando establezcamos la capacidad de inicialización de HashMap, de hecho, HashMap usará la primera potencia de 2 mayor que el valor como inicialización Capacidad, por lo que HashMap no necesariamente usa directamente el valor que pasamos, pero después del cálculo, se obtiene un nuevo valor. El propósito es mejorar la eficiencia del hash
Si no se establece el tamaño de capacidad inicial, a medida que los elementos continúan aumentando, HashMap se expandirá varias veces y el mecanismo de expansión en HashMap determina que la tabla hash debe reconstruirse para cada expansión, lo que afecta en gran medida el rendimiento. Entonces, para mejorar la eficiencia, es necesario establecer la longitud predeterminada
 
El valor de Capacidad en HashMap es 16 ------- escribiendo: 1 << 4;
 
El valor predeterminado es 16 debido a las siguientes consideraciones:
                                                    Reducir la colisión de hash
 
                                                    Mejorar la eficiencia de las consultas de mapas
 
                                                    La asignación es demasiado pequeña para evitar una expansión frecuente
 
                                                    La sobreasignación es una pérdida de recursos
En el proceso de colocación, se llama al método hashcode () de acuerdo con la clave para calcular el valor de Hash correspondiente, y luego el valor int obtenido es módulo la longitud de la matriz . Sin embargo, para considerar el rendimiento, Java siempre usa la operación AND bit a bit para lograr la recuperación. Operación modular, para garantizar que cada posición se pueda usar de manera uniforme, el rango del valor del índice después de tomar el módulo debe ser 0 ~ (2 ^ n) -1, por lo que al calcular el índice, debe asegurarse de que la longitud de la matriz sea la enésima potencia de 2. Y el valor de la operación de bit con el valor Hash es la longitud de la matriz -1, lo que asegura que sea un número impar, asegurando así que el rango del índice esté entre 0 ~ (2 ^ n) -1, asegurando así que cada posición se pueda usar de manera uniforme ; Si no se garantiza que sea un número impar, se puede sacar una conclusión mediante el cálculo, es decir, siempre hay un número en el rango de 0 ~ (2 ^ n) -1 que no se puede obtener mediante la operación de módulo, de modo que el bit de subíndice del valor en la matriz es Si no se usa, aumentará la probabilidad de colisiones hash en otras posiciones, lo que complicará la estructura de datos de los bits de índice de otros índices y afectará la eficiencia de la consulta de todo el mapa.
 
Además, cuando la longitud de la matriz es 2 elevado a n, la probabilidad de que el índice calculado por diferentes claves sea el mismo es pequeña, entonces los datos se distribuyen de manera más uniforme en la matriz, lo que significa que la probabilidad de colisión es pequeña, relativamente, cuando se consulta No es necesario recorrer la lista vinculada en una posición determinada, por lo que la eficiencia de la consulta es mayor
 
El valor máximo de capacidad en HashMap es 1073741824 ------- Escritura: 1 << 30
 
 
El siguiente es el factor de carga
 
¿Cuál es el factor de carga?
         El factor de carga está relacionado con el mecanismo de expansión, lo que significa que si la capacidad actual del contenedor alcanza el umbral que establecemos, se iniciará la operación de expansión.
Por ejemplo, la capacidad actual del contenedor es 16, el factor de carga es 0,75, 16 * 0,75 = 12, es decir, cuando la capacidad llegue a 12 se realizará la operación de expansión
Después de comprender el significado del factor de carga, creo que por qué se necesita el factor de carga sin explicación
¿Por qué el factor de carga es 0,75 en lugar de 0,5 o 1?
    análisis:
       HashMap es solo una estructura de datos. Dado que la consideración principal de la estructura de datos es ahorrar tiempo y espacio, ¿cómo guardarlo?
 
      Factor de carga 1.0 
       Cuando el factor de carga es 1.0, significa que la expansión solo ocurrirá cuando las 16 posiciones de la matriz estén llenas.
      Esto provoca un gran problema, porque los conflictos de hash no se pueden evitar. Cuando el factor de carga es 1.0, significa que habrá muchos conflictos de Hash, y el árbol rojo-negro subyacente se vuelve extremadamente complicado. Es extremadamente perjudicial para la eficiencia de las consultas. En este caso, se sacrifica tiempo para garantizar la utilización del espacio.
 
      Factor de carga 0.5
       Cuando el factor de carga es 0.5, esto también significa que cuando los elementos en la matriz alcanzan la mitad, comienza la expansión. Como hay menos elementos para llenar, el conflicto de hash también se reducirá. Luego, la longitud de la lista vinculada subyacente o la altura del árbol rojo-negro Disminuirá y aumentará la eficiencia de las consultas. Sin embargo, la tasa de utilización del espacio se reducirá enormemente en este momento, originalmente almacenaba 10 millones de datos, ahora requiere 20 millones de espacio.
 
Resumen :
 
Cuando el factor de carga es 0,75, la tasa de utilización del espacio es relativamente alta y se evitan muchos conflictos de Hash, lo que hace que la altura de la lista vinculada subyacente o el árbol rojo-negro sea relativamente baja, lo que mejora la eficiencia del espacio.
 
¿Por qué se convierte en un árbol rojo-negro cuando la longitud de la lista vinculada es 8? ¿Por qué se cambia la lista enlazada a las 6 en punto?
 
        Cuando la discreción de hashCode es muy buena, la probabilidad de utilizar la estructura de árbol es muy pequeña, porque los datos se distribuyen uniformemente en cada lista enlazada y la longitud de una lista enlazada casi nunca alcanzará el umbral.
        Bajo el código hash aleatorio, la discreción puede empeorar, pero el JDK no puede evitar que los usuarios implementen este algoritmo de hash incorrecto, por lo que puede conducir a una distribución de datos desigual.
        Pero idealmente, la frecuencia de distribución de todos los nodos en la lista vinculada en el algoritmo hashCode aleatorio seguirá la distribución de Poisson , es decir, la probabilidad de que la longitud de la lista vinculada alcance los 8 elementos es 0.00000006, lo cual es casi imposible.
 
        Por lo tanto, elegir 8 como el umbral para convertir una lista vinculada en un árbol rojo-negro definitivamente no es un capricho.
 
         Debido a que la operación del árbol rojo-negro implica operaciones para zurdos, diestros y otras, y la lista enlazada individualmente no es necesaria, cuando el nodo necesita ser operado, el costo del árbol rojo-negro es mucho mayor, por lo que para reducir el costo de operación, cuando el número de nodos Cuando es más pequeño, es una mejor opción convertir el árbol rojo-negro en una lista vinculada y luego operar
 
Cambiar el tamaño de HashMap 的
 
    1. Expansión
 
         Al agregar elementos al contenedor, se juzgará la cantidad de elementos en el contenedor actual. Si es mayor o igual al umbral, es decir, cuando la longitud de la matriz actual se multiplica por el valor del factor de carga, se expandirá automáticamente
        En lenguaje sencillo:
        Cuando el número de elementos en el HashMap excede el tamaño de la matriz * loadFactor (0,75 por defecto), la matriz se expandirá.
        Es decir, por defecto, el tamaño de la matriz es 16, luego, cuando el número de elementos en el HashMap excede 16 * 0.75 = 12, el tamaño de la matriz se expande a 2 * 16 = 32, es decir, se duplica, y luego se usa el refrito Calcule la posición de cada elemento en la matriz ( después del refrito , la posición del elemento de la matriz está en la posición original o se mueve a la potencia de 2 en la posición original ) y modifique el umbral
    
Observaciones:
 
      Al repetir en Jdk1.7, cuando la antigua lista vinculada se migra a la nueva lista vinculada, si la posición del índice de matriz de la nueva tabla es la misma, los elementos de la lista vinculada se invertirán y JDK1.8 no se invertirá
 
     Optimización de la expansión de Jdk1.8:
            Compruebe si el bit recién agregado del valor hash original es 1 o 0, si el índice 0 no ha cambiado y si el índice 1 se convierte en "índice original + oldCap", lo que ahorra tiempo para volver a calcular el valor hash. Al mismo tiempo, porque el bit 1 recién agregado es 0 o 1 pueden considerarse aleatorios, por lo que el proceso de cambio de tamaño distribuye uniformemente los nodos en conflicto anteriores al nuevo depósito.
 
   Dos, inicialización
        
       HashMap se inicializará de acuerdo con la capacidad inicial y el factor de carga.Cuando la capacidad inicial es menor que el número máximo de entradas dividido por el factor de carga, se producirá una operación de refrito.
       La operación de refrito es para reconstruir la estructura de datos interna. Generalmente, la longitud de la matriz se duplica. Durante el proceso de refrito, la posición de cada elemento en la matriz se recalcula, lo cual es una operación que consume mucho rendimiento. Por lo tanto, durante el diseño del programa, si hemos predicho el número de elementos en el HashMap, el número preestablecido de elementos puede mejorar efectivamente el rendimiento del HashMap.
 
Todavía hay muchas cosas que vale la pena estudiar en HashMap, no las repetiré aquí. Si lo desea, verifique el código fuente y analícelo y úselo lentamente. ¡Venga!
 
 
 
        
    
 
 

Supongo que te gusta

Origin blog.csdn.net/weixin_43562937/article/details/106589776
Recomendado
Clasificación