Principio de implementación de HashMap de la explicación detallada de las preguntas de la entrevista avanzada de Android

texto

inserte la descripción de la imagen aquí

Por favor, hable sobre ArrayList, HashMap, LinkedHashMap.

¿Qué está tratando de investigar esta pregunta?

1. ¿Cuáles son los principios subyacentes de ArrayList, HashMap y LinkedHashMap?

Puntos de conocimiento de inspección

Comprensión del código fuente de ArrayList, HashMap, LinkedHashMap

Cómo responden los candidatos
Lista de arreglo

ArrayList: La estructura subyacente es un arreglo con una longitud inicial de 10. Cuando la capacidad es insuficiente, se expandirá a 1,5 veces el tamaño original, es decir, se expandirá a 15. La estructura subyacente de ArrayList es una lista doblemente enlazada
. La ventaja es que no hay necesidad de expandir la capacidad.La desventaja es que cuando quieres encontrar el N-ésimo elemento, la complejidad real es O(n), que es atravesar N elementos para encontrarlo y la complejidad temporal de ArrayList es O(1)

Lista: los elementos se almacenan en la memoria en un orden ordenado y se pueden repetir

mapa hash

La estructura subyacente es una matriz cuyos elementos son listas enlazadas.Aunque es una matriz, se inserta en la matriz fuera de orden. Insertar basado en el valor hash.
Cuando el hash es el mismo, debe usar la estructura de lista enlazada. El enlace recién insertado con el mismo valor de código hash forma una lista enlazada después de la inserción anterior. Cuando hay demasiados enlaces, se formará un árbol rojo-negro. Los elementos recién añadidos forman un eslabón. Primer final de la cadena

LinkedHashMap

La capa inferior es una matriz cuyo elemento es una lista enlazada + una lista doblemente enlazada formada entre elementos, es decir, una lista doblemente enlazada formada por una lista enlazada unidireccional. La lista doblemente enlazada mantiene el orden de los elementos para que los elementos parezcan estar almacenados en el orden de inserción. Es decir, al recorrer los elementos de la colección LinkedHashSet, HashSet accederá a los elementos de la colección en el orden en que se agregan los elementos, por lo que LinkedHashSet puede garantizar que los elementos salgan en el orden de inserción.

La capa inferior de LinkedHashMap usa una tabla hash y una lista doblemente enlazada para almacenar todos los elementos, y mantiene una lista doblemente enlazada que se ejecuta en todas las entradas (si ha aprendido la lista doblemente enlazada, comprenderá mejor su código fuente), esta lista enlazada define el orden de iteración, el orden de iteración puede ser orden de inserción u orden de acceso

  • Lista vinculada en orden de inserción: después de que LinkedHashMap llama al método get, el orden de salida es el mismo que el de entrada, que es la lista vinculada en orden de inserción, que se ordena en orden de inserción de forma predeterminada.

  • Lista vinculada en orden de acceso: después de que LinkedHashMap llame al método get, los elementos a los que se accedió esta vez se moverán al final de la lista vinculada, y el acceso continuo puede formar una lista vinculada ordenada en orden de acceso. En pocas palabras, ordene por los elementos a los que se accedió menos recientemente (similar al algoritmo LRU).

public static void main(String[] args) {
    
    
    Map<String, String> map = new LinkedHashMap<String, String>();
    map.put("apple", "苹果");
    map.put("watermelon", "西瓜");
    map.put("banana", "香蕉");
    map.put("peach", "桃子");

    Iterator iter = map.entrySet().iterator();
    while (iter.hasNext()) {
    
    
        Map.Entry entry = (Map.Entry) iter.next();
        System.out.println(entry.getKey() + "=" + entry.getValue());
    }
}

// print
apple=苹果 
watermelon=西瓜 
banana=香蕉 
peach=桃子
La diferencia entre LinkedList y ArrayList
  • La capa inferior de LinkedList es una lista doblemente enlazada
    y la capa inferior de ArrayList es una matriz mutable

  • LinkedList no permite acceso aleatorio, es decir, baja eficiencia de consulta
    ArrayList permite acceso aleatorio, es decir, alta eficiencia de consulta

  • LinkedList inserta y elimina rápidamente,
    ArrayList inserta y elimina de manera ineficiente

Para los dos métodos de acceso aleatorio, obtener y establecer funciones, ArrayList es mejor que LinkedList, porque LinkedList necesita mover el puntero; para los dos métodos de agregar y eliminar, agregar y eliminar funciones, LinedList es más dominante porque ArrayList necesita moverse datos.

1.6 Hable sobre el principio de implementación de HashMap, las condiciones para la expansión y las condiciones para convertir una lista vinculada en un árbol rojo-negro.

¿Qué está tratando de investigar esta pregunta?

1. ¿El principio subyacente de HashMap?

2. La condición de expansión de HashMap y la condición de conversión de lista enlazada árbol rojo-negro

Puntos de conocimiento de inspección

Principio de HashMap, comprensión de las condiciones de expansión de HashMap

Cómo responden los candidatos
Principio de implementación de HashMap

HashMap utiliza internamente una matriz con una capacidad predeterminada de 16 para almacenar datos, y cada elemento de la matriz es el nodo principal de una lista vinculada, por lo que, más precisamente, la estructura de almacenamiento interno de HashMap utiliza una estructura de cremallera de tabla hash (matriz + lista enlazada).

inserte la descripción de la imagen aquí

El tamaño de almacenamiento predeterminado en HashMap es una matriz con una capacidad de 16, por lo que cuando creamos un objeto HashMap, incluso si no contiene elementos, debemos asignarle un espacio de memoria y continuamos poniendo datos en el HashMap, cuando se alcanza un cierto límite de capacidad, HashMap se expandirá automáticamente.

Condiciones de expansión de HashMap

Una instancia de HashMap tiene dos parámetros que afectan su rendimiento: "capacidad inicial" y "factor de carga". La capacidad es la cantidad de cubos en la tabla hash y la capacidad inicial es solo la capacidad de la tabla hash cuando se creó. El factor de carga es una medida de cuán llena puede llegar a estar una tabla hash antes de que su capacidad aumente automáticamente. Cuando el número de entradas en la tabla hash excede el producto del factor de carga y la capacidad actual, y ya hay elementos en la ubicación para ser almacenados (colisión hash), estas dos condiciones deben cumplirse antes de que se realice la operación de rehash en la tabla hash, que duplica la capacidad. Por lo general, el factor de carga predeterminado es 0,75, que es un compromiso entre los costos de tiempo y espacio. Aunque el factor de carga es demasiado alto, la sobrecarga de espacio se reduce, pero también aumenta el costo de la consulta.

Veamos el método de función put de HashMap:

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {//如果散列表是空的
        inflateTable(threshold);//会去建一个表
    }
    if (key == null) //hashmap会把key为空的放在数组头部
        return putForNullKey(value);
    int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);//根据key生成hash值
    int i = indexFor(hash, table.length); //生成散列表中的索引,也就是数组下标
    for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {//遍历链表,看是否存在key值一样的对象,如果有的话就替换value值
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    //如果没找到key值一样的,就添加
    addEntry(hash, key, value, i);
    return null;
}

Cómo generar un subíndice de matriz de acuerdo con el valor hash, consulte la función indexFor():

static int indexFor(int h, int length) {
    
    
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
   //为什么长度要是偶数,因为hash值与length的与值不能超过length -1,要不然数组/就越界了,例如hash值是11000111B,length = 1000B,h & (length-1) = 111B,这样得到数组索引肯定不会越界了
}



void resize(int newCapacity) {
    
    
    HashMapEntry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
    
    
        threshold = Integer.MAX_VALUE;
        return;
    }
    HashMapEntry[] newTable = new HashMapEntry[newCapacity];//新建一个数组
    transfer(newTable);//完成新旧数组拷贝
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

Estamos viendo el paso final de escalado:

void transfer(HashMapEntry[] newTable) {
    
    
    int newCapacity = newTable.length;
    for (HashMapEntry<K,V> e : table) {
    
    //遍历整个数组
        while(null != e) {
    
    //将同一个位置的元素按链表顺序取出
            HashMapEntry<K,V> next = e.next;//先将当前元素指向的下一个元素存起来,一个一个存放到新表的位置中,记住不一定是同一位置,因为长度变了
            int i = indexFor(e.hash, newCapacity);//根据新数组长度,重新生成数组索引
            e.next = newTable[i];//将当前位置的元素链表头指向即将新加入的元素,
            newTable[i] = e;//然后放入数组中,完成同一位置元素链表的拼接,最先添加的元素总在链表末尾
            e = next;//然后继续循环,拿出下一个元素
        }
    }
}
Condiciones para convertir una lista enlazada en un árbol rojo-negro

Primero, analice el problema a través del código fuente:

 //用来衡量是否要转红黑树的重要参数 
 static final int TREEIFY_THRESHOLD = 8;
 //转红黑树需要的最小数组长度
static final int MIN_TREEIFY_CAPACITY = 64;
 for (int binCount = 0; ; ++binCount) {
    
    
                    if ((e = p.next) == null) {
    
    
                        p.next = newNode(hash, key, value, null);
                        // TREEIFY_THRESHOLD - 1=7,也就是说一旦binCount=7时就会执行下面的转红黑树代码
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

 final void treeifyBin(Node<K,V>[] tab, int hash) {
    
    
        int n, index; Node<K,V> e;
        //这里的tab指的是本HashMap中的数组,n为数字长度,如果数组为null或者数组长度小于64
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            //则调用resize()方法直接扩容,不转红黑树
            resize();
        //否则走转红黑树逻辑    
        else if ((e = tab[index = (n - 1) & hash]) != null) {
    
    
            TreeNode<K,V> hd = null, tl = null;
            do {
    
    
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
    
    
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

Se puede saber que [TREEIFY_THRESHOLD - 1]=7, por lo que cuando binCount=7, se convertirá en un árbol rojo-negro, y la asignación inicial de binCount es 0, ++count se agrega primero y luego se usa, de hecho , binCount comienza desde 1, 1, 2 , 3, 4, 5, 6, 7, cada valor corresponde a un newNode, por lo que cuando binCount alcanza el valor de 7, se crean 7 nuevos nodos Node, pero no olvide que los nodos que creamos son todos p.next, es decir, el nodo sucesor de p, por lo que añadir el nodo p original también puede entenderse como el primer nodo de la lista enlazada, 7+1=8, es decir, 8 nodos, entonces, cuando el número de elementos en la lista enlazada llegue a 8, comenzará a convertirse en un árbol rojo-negro.

Resumir:

Cuando el número de elementos de la lista vinculada llega a 8 y la longitud de la matriz HashMap es superior a 64, la lista vinculada se convertirá en un árbol rojo-negro; de lo contrario, se expandirá.

Fin del artículo

¡Se pueden escanear más preguntas de entrevistas de Android de forma gratuita!

↓↓↓【Vista previa】↓↓↓

imagen

Supongo que te gusta

Origin blog.csdn.net/Android_XG/article/details/130883623
Recomendado
Clasificación