índice
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