Estrutura de dados HashMap e algoritmo

índice

Recursos do HashMap

HashMap antes de JDK1.8 (lista vinculada resolve conflito de colisão de hash)

Após HashMap JDK1.8 (árvore binária de árvore vermelha e preta para resolver a colisão de hash)

Por que o JDK1.8 não usa a árvore AVL, mas a árvore vermelho e preto?


Recursos do HashMap

Matriz [Entrada], lista vinculada [Entrada []], árvore vermelho-preto (jdk1.8 dispara quando o comprimento da lista vinculada é maior que 8) 

  • Armazenamento rápido (colocar)
  • Pesquisa rápida (complexidade de tempo O (1))
  • Escalável (loadFactor = 0,75, tamanho padrão = 16, 2 vezes a expansão: 16, 16 * 0,75-> 32, 32 * 0,75-> 64, 64 * 0,75-> 128 .....)
  • Não seguro para thread (colocar, remover, etc. são métodos comuns, e os métodos HashTable são tratados pela palavra-chave synchronized)
 /* ---------------- Public operations -------------- */

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

O problema mais clássico de HashMap -> Conflito de Hash:

Conflito de hash: os subscritos da matriz calculados por objetos diferentes são os mesmos (o conceito de slot de bucket)

Lista unida individualmente: uma solução para resolver conflitos de Hash, adicionando um próximo para registrar o próximo nó

 

HashMap antes de JDK1.8 (lista vinculada resolve conflito de colisão de hash)

public V put(K key, V value) {  
        if (key == null)  
            return putForNullKey(value);  
        int hash = hash(key.hashCode());  
        int i = indexFor(hash, table.length);  
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
            Object k;  
            //判断当前确定的索引位置是否存在相同hashcode和相同key的元素,如果存在相同的hashcode和相同的key的元素,那么新值覆盖原来的旧值,并返回旧值。  
            //如果存在相同的hashcode,那么他们确定的索引位置就相同,这时判断他们的key是否相同,如果不相同,这时就是产生了hash冲突。  
            //Hash冲突后,那么HashMap的单个bucket里存储的不是一个 Entry,而是一个 Entry 链。  
            //系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),  
            //那系统必须循环到最后才能找到该元素。  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
                V oldValue = e.value;  
                e.value = value;  
                return oldValue;  
            }  
        }  
        modCount++;  
        addEntry(hash, key, value, i);  
        return null;  
    }  

Após HashMap JDK1.8 (árvore binária de árvore vermelha e preta para resolver a colisão de hash)

 /**
     * 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.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

Por que o JDK1.8 não usa a árvore AVL, mas a árvore vermelho e preto?

A árvore AVL é essencialmente uma árvore de pesquisa binária e suas características são: 

  • 1. Em primeiro lugar, é uma árvore de pesquisa binária.
  • 2. Com condição de equilíbrio: o valor absoluto (fator de equilíbrio) da diferença entre a altura das subárvores esquerda e direita de cada nó é no máximo 1.
  • Essencialmente, uma árvore de pesquisa binária com função de equilíbrio (árvore de classificação binária, árvore de pesquisa binária)

Árvores rubro-negras e árvores AVL são as árvores de pesquisa binárias balanceadas mais comumente usadas , e sua pesquisa, exclusão e modificação são todas em tempo O (lgn)

Existem várias comparações e diferenças entre árvores AVL e árvores vermelho-pretas:
(1) As árvores AVL são mais rigorosamente equilibradas, de modo que podem fornecer velocidades de pesquisa mais rápidas. Geralmente, as árvores AVL são adequadas para ler e pesquisar tarefas intensivas.
(2) A árvore vermelho-preto é mais adequada para inserir e modificar tarefas intensivas.
(3) Geralmente, a rotação da árvore AVL é mais difícil de equilibrar e depurar do que a rotação da árvore vermelho-preto.

Resumo :
(1) AVL e árvores vermelhas e pretas são estruturas de dados de árvore altamente equilibradas. Eles são muito semelhantes, a diferença real está no número de operações de rotação concluídas durante qualquer operação de adicionar / excluir.
(2) Ambas as implementações são escaladas para O (lg N), onde N é o número de folhas, mas na verdade as árvores AVL são mais rápidas em tarefas de pesquisa intensiva: com melhor equilíbrio, a travessia média da árvore é mais curta. Por outro lado, em termos de inserção e exclusão, a árvore AVL é mais lenta: requer um maior número de rotações para reequilibrar corretamente a estrutura de dados ao modificá-la.
(3) Em uma árvore AVL, a diferença entre o caminho mais curto e o caminho mais longo da raiz a qualquer folha é no máximo 1. Na árvore vermelho-preto, a diferença pode ser 2 vezes.
(4) Ambas são pesquisas O (log n), mas o equilíbrio das árvores AVL pode exigir rotações O (log n), enquanto as árvores vermelho-pretas exigirão até duas rotações para atingir o equilíbrio (embora O (log n) possa precisar ser marcada) O nó determina a posição da rotação). A rotação em si é uma operação O (1), porque você está apenas movendo o ponteiro.


Referência: https://blog.csdn.net/21aspnet/article/details/88939297

Acho que você gosta

Origin blog.csdn.net/boonya/article/details/109731265
Recomendado
Clasificación