[Elegante evitación de boxes] ¿Puede new HashMap (list.size ()) especificar el tamaño para evitar por completo la sobrecarga adicional causada por la expansión?

Establecer la capacidad inicial de HashMap

Establecer la capacidad inicial de HashMap es solo el comienzo de la optimización.

HashMapOcupa una posición muy importante en el uso de Java. En el uso normal, creo que muchos programadores de Java saben que al definir HashMap, establecen una capacidad inicial para que reduzca la sobrecarga adicional causada por la expansión de hashMap (redimensionar), como Este código como el mío (zi) (ji):

@Test
public void longLongAGo() {
    int count = 1000000;

    System.out.println("---------------- 不设置hashMap初始容量 ------------");
    long start = System.currentTimeMillis();
    HashMap<Integer, Object> map = new HashMap<>();
    for (int i = 0; i < count; i++) {
        map.put(i, UUID.randomUUID());
    }
    long end = System.currentTimeMillis();
    System.out.println("添加1000000个元素耗时:" + (end - start));

    System.out.println("---------------- 设置hashMap初始容量 -------------------");
    long start1 = System.currentTimeMillis();
    HashMap<Integer, Object> map1 = new HashMap<>(count);
    for (int i = 0; i < count; i++) {
        map1.put(i, UUID.randomUUID());
    }
    long end1 = System.currentTimeMillis();
    System.out.println("添加1000000个元素耗时:" + (end1 - start1));
}
复制代码

 

 

 

Mi colega dijo que estableció la capacidad del mapa durante la inicialización, y no expandirá automáticamente la capacidad durante el proceso de agregar elementos, lo que mejora en gran medida el rendimiento, ¡que es el caso de los resultados!

Por lo tanto, cuando se inicializa la colección, especificar el valor inicial de la colección puede mejorar el rendimiento.

Sin embargo, soy escéptico y comparé la cantidad de expansiones de hashMap cuando se establece la capacidad inicial y cuando no se establece la capacidad inicial. Cuando la capacidad inicial se establece en 1,000,000, el contenedor no se expande como se esperaba, sino que se expande 1 vez:

@SneakyThrows
@Test
public void testing() {
    int count = 1000000;

    System.out.println("---------------- 初始化hashMap容量为1000000 ------------");
    int resizeCount = 0;
    HashMap<Integer, Object> map = new HashMap<>(count);
    Method capacityMethod = map.getClass().getDeclaredMethod("capacity");
    capacityMethod.setAccessible(true);
    int capacity = (int) capacityMethod.invoke(map);
    System.out.println("初始容量:" + capacity);
    for (int i = 0; i < count; i++) {
        map.put(i, UUID.randomUUID());
        int curCapacity = (int) capacityMethod.invoke(map);
        if (curCapacity > capacity) {
            System.out.println("当前容量:" + curCapacity);
            resizeCount++;
            capacity = curCapacity;
        }
    }
    System.out.println("hashMap扩容次数:" + resizeCount);

    System.out.println("---------------- 不初始化hashMap容量 -------------------");
    resizeCount = 0;
    HashMap<Integer, Object> map1 = new HashMap<>();
    Method capacityMethod1 = map1.getClass().getDeclaredMethod("capacity");
    capacityMethod1.setAccessible(true);
    int capacity1 = (int) capacityMethod1.invoke(map1);
    System.out.println("初始容量:" + capacity1);
    for (int i = 0; i < count; i++) {
        map1.put(i, UUID.randomUUID());
        int curCapacity = (int) capacityMethod1.invoke(map1);
        if (curCapacity > capacity1) {
            System.out.println("当前容量:" + curCapacity);
            resizeCount++;
            capacity1 = curCapacity;
        }
    }
    System.out.println("扩容次数:" + resizeCount);
}
复制代码

Dado que no podemos llamar directamente al capacity()método hashMap , usamos la reflexión para ver el cambio de capacidad de cada elemento agregado para monitorear el número de expansiones del hashMap.

//使用反射,调用hashMap的capacity()方法
Method capacityMethod = map.getClass().getDeclaredMethod("capacity");
capacityMethod.setAccessible(true);
int capacity = (int) capacityMethod.invoke(map);
复制代码

En cuanto a la reflexión, bienvenido a leer una de las tecnologías más poderosas de Java: la reflexión . Puede tener una comprensión general del mecanismo de reflexión.

Casi me equivoco, ahora de vuelta al resultado de ejecución del programa anterior:

---------------- 初始化hashMap容量为1000000 ------------
初始容量:1048576
当前容量:2097152
hashMap扩容次数:1
---------------- 不初始化hashMap容量 -------------------
初始容量:16
当前容量:32
当前容量:64
当前容量:128
当前容量:256
当前容量:512
当前容量:1024
当前容量:2048
当前容量:4096
当前容量:8192
当前容量:16384
当前容量:32768
当前容量:65536
当前容量:131072
当前容量:262144
当前容量:524288
当前容量:1048576
当前容量:2097152
扩容次数:17
复制代码

Los resultados de la operación encontraron:

  • El hashMap con la capacidad inicial establecida, la capacidad inicial no es la 1000000 especificada por mí, sino la 1048576 ( 2 ^ 20 )
  • La capacidad de hashMap no es fija, se ampliará cuando se alcancen las condiciones de expansión, de 16 a 32, 64, 128 ... (Hash elegirá la primera potencia de 2 mayor que la capacidad actual como capacidad)
  • Incluso si se establece la capacidad inicial y la capacidad inicial es 1048576, cuando se agregan 1,000,000 elementos (1,000,000 es menor que 1048576), el hashMap aún se expandirá una vez

¿Por qué es tan morado? Con los tres hallazgos anteriores, echemos un vistazo al mecanismo de expansión de HashMap.

Mecanismo de expansión HashMap

Primero mire varias variables miembro de HashMap:

 

Variables de miembro de HashMap

 

 

  • DEFAULT_INITIAL_CAPACITY: la capacidad inicial predeterminada es 2 ^ 4 = 16
  • DEFAULT_LOAD_FACTOR: el factor de carga predeterminado es 0,75, que se utiliza para medir la capacidad total del HashMap
  • transient int size: el número de k, v pares en el mapa
  • loadFactor de flotación final: factor de carga, el valor predeterminado es 0,75
  • int umbral: el siguiente valor de tamaño que se cambiará de tamaño (capacidad × factor de carga). Cuando el número real de kyv excede el umbral, HashMap expandirá la capacidad

Veamos otro método capacity():

final int capacity() {
    return (table != null) ? table.length :
        (threshold > 0) ? threshold :
        DEFAULT_INITIAL_CAPACITY;
}
复制代码

¿Que es esto? ¿No se ha definido anteriormente una variable de tamaño?

Se puede capacityver como el barril HashMap 体积(este volumen es mayor), que sizeactualmente está instalado en este cubo muchas cosas.

La capacidad del depósito está thresholddefinida y la capacidad predeterminada es 2 elevado a la 4ª potencia, que es 16. El código fuente es así:

/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
复制代码

 

 

 

1 << 4 significa desplazar 4 bits a la izquierda, es decir, 2 ^ 4 = 16.

Entonces, ¿cuándo te expandes? Esto es fácil de pensar. Colocamos datos en el depósito de hashMap. Cuando el número de k, v pares en el sizedepósito está casi llenando el depósito, se acerca capacity, ¡el depósito se expandirá!

Los ejemplos anteriores han mostrado, hashMap no solo sizepara capacityexpansión, sino el capacitytiempo de llegada de un cierto valor a expansión, este valor es el thresholdtiempo, hashMap realizado resize(), y esto, mira el código fuente:

 

Código fuente del punto de expansión HashMap

 

 

Se ha plegado parte del código fuente, mostrando principalmente la parte relacionada con la capacidad.

Cuando sizecrece para ser mayor que threshold, se realiza el hashMap resize(), y threshold = loadFactor * capacityde esta manera, puede saber cuándo el bucket hashMap ha expandido automáticamente su volumen.

Evite realmente la expansión de HashMap

Como se analizó anteriormente, cuando size > thresholdse expande el hashMap , usando threshold = loadFactor * capacityesta fórmula, tenemos una dirección en el momento de la inicialización.

En primer lugar, no debe configurarse directamente loadFactor * capacity, porque este número puede no ser una potencia de 2, y la capacidad del contenedor especificada por HashMap debe ser una potencia de 2. En este caso, lo configuro en loadFactor * capacityun número mayor que la primera potencia de 2. :

int initCapacity = 1 + (int) (count / 0.75);
HashMap<Integer, Object> map = new HashMap<>(initCapacity);
复制代码

1 + (int) (count / 0.75)Esta fórmula proviene del código fuente de HashMap:

/**
 * Returns a power of two size for the given target capacity.
 */
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;
}
复制代码

¡Este código es realmente un hada voladora! Su finalidad es: según el valor de capacidad entrante cap, mediante una serie de operaciones de hadas, conseguir la primera potencia de 2 mayor que él y devolverla.

Todas estas son operaciones de bits binarios, desplazando el número a la derecha a su vez y luego tomando el OR con el valor original . Puede encontrar un número y sustituirlo en el código para verificar, ¡y el resultado es la primera potencia de 2 mayor que él!

¿Por qué es así, tal vez porque desplazamiento sin signo de la derecha >>> , o la operación | es rápido ahora!

 

 

 

Verificación de resultados

La fórmula para calcular la capacidad se ha elaborado antes, ahora verifique que sea correcta:

@SneakyThrows
@Test
public void perfect() {
    int count = 1000000;

    int initCapacity = 1 + (int) (count / 0.75);
    HashMap<Integer, Object> map = new HashMap<>(initCapacity);
    Method capacityMethod = map.getClass().getDeclaredMethod("capacity");
    capacityMethod.setAccessible(true);
    int capacity = (int) capacityMethod.invoke(map);
    System.out.println("jdk hashMap default capacity:" + capacity);
    int resizeCount = 0;
    for (int i = 0; i < count; i++) {
        map.put(i, UUID.randomUUID());
        int curCapacity = (int) capacityMethod.invoke(map);
        if (curCapacity > capacity) {
            System.out.println("当前容量:" + curCapacity);
            resizeCount++;
            capacity = curCapacity;
        }
    }
    System.out.println("hashMap扩容次数:" + resizeCount);
复制代码

resultado de la operación:

 

 

 

El número de expansiones es 0, ¡perfecto!

¡Sustituyendo el número initCapacity = 1333334 en el tableSizeFormétodo HashMap puede calcular la capacidad como 2097152 = 2 ^ 21!

No quiero calcular la capacidad inicial, todavía hay otra forma

Guava es una biblioteca Java basada en código abierto que contiene muchas bibliotecas centrales que Google está utilizando en muchos de sus proyectos. Esta biblioteca es para facilitar la codificación y reducir los errores de codificación. Esta biblioteca proporciona métodos prácticos para colecciones, almacenamiento en caché, primitivas de soporte, simultaneidad, anotaciones comunes, procesamiento de cadenas, E / S y verificación.

Hay un método listo para usar para inicializar HashMap en Guava, no requiere que calculemos initCapacity, solo pruébelo.

Primero presenta el paquete de Guayaba:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>
复制代码

prueba:

@SneakyThrows
@Test
public void perfectWithGuava() {
    int count = 1000000;

    HashMap<Integer, Object> map = Maps.newHashMapWithExpectedSize(count);
    Method capacityMethod = map.getClass().getDeclaredMethod("capacity");
    capacityMethod.setAccessible(true);
    int capacity = (int) capacityMethod.invoke(map);
    System.out.println("guava hashMap default capacity:" + capacity);
    int resizeCount = 0;
    for (int i = 0; i < count; i++) {
        map.put(i, UUID.randomUUID());
        int curCapacity = (int) capacityMethod.invoke(map);
        if (curCapacity > capacity) {
            System.out.println("当前容量:" + curCapacity);
            resizeCount++;
            capacity = curCapacity;
        }
    }
    System.out.println("hashMap扩容次数:" + resizeCount);
}
复制代码

resultado de la operación:

 

 

 

¡También puede hacer HashMap sin expansión!

Eche un vistazo al código clave:

... = Maps.newHashMapWithExpectedSize(count);

Supongo que newHashMapWithExpectedSize(int)el código fuente de esto debe return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;calcularse de esta manera similar a HashMap . Echemos un vistazo:

 

Código fuente de Guava Maps

 

 

¡Felicitaciones, todos pueden adivinarlo!

resumen

  • Un hashMap con un conjunto de capacidad inicial, su capacidad inicial real no es necesariamente un valor especificado, pero se calcula internamente por el HashMap
  • La capacidad de hashMap no es fija, se ampliará cuando se alcancen las condiciones de expansión, de 16 a 32, 64, 128 ... (Hash elegirá la primera potencia de 2 mayor que la capacidad actual como capacidad)
  • No crea que si se especifica la capacidad inicial, el hashMap no se expandirá
  • La forma de evitar la expansión de hashMap es pasar un 1 + (int) (count / 0.75)valor inicial calculado
  • También puedes usar GuayabanewHashMapWithExpectedSize(int count)

Supongo que te gusta

Origin blog.csdn.net/weixin_51204715/article/details/108866607
Recomendado
Clasificación