【Perguntas da entrevista】 Entrevista HashMap com 21 perguntas

Insira a descrição da imagem aqui

1. Visão Geral

Reimpresso: http://www.javastack.cn/article/2020/hashmap-21-questions/

21 perguntas complicadas da entrevista HashMap, desta vez eu vou me ajoelhar!

1: Estrutura de dados do HashMap?

R: A estrutura da tabela de hash (hash de lista vinculada: array + lista vinculada) é implementada, combinando as vantagens de arrays e listas de links. Quando o comprimento da lista vinculada excede 8, a lista vinculada é convertida em uma árvore vermelho-preto.

transient Node<K,V>\[\] table;

2: Como funciona o HashMap?

A camada inferior do HashMap é uma matriz hash e uma lista vinculada individualmente. Cada elemento da matriz é uma lista vinculada, que é implementada pela classe interna do Node (implementando a interface Map.Entry <K, V>). O HashMap é armazenado e recuperado por meio do método put & get.

Ao armazenar um objeto, passe o valor da chave K / V para o método put ():

① Chame o método hash (K) para calcular o valor hash de K e, a seguir, combine o comprimento do array para calcular o subscrito do array;

②. Ajuste o tamanho da matriz (quando o número de elementos no contêiner for maior que a capacidade * fator de carga, o contêiner será expandido e redimensionado para 2n);

③, i. Se o valor hash de K não existir no HashMap, a inserção é realizada e, se existir, ocorre uma colisão;

ii. Se o valor hash de K existe no HashMap, e ambos retornam igual verdadeiro, então atualize o par de valor-chave;

iii. Se o valor hash de K existe no HashMap e ambos retornam falsos iguais, ele é inserido no final da lista vinculada (interpolação de cauda) ou na árvore vermelho-preto (forma como a árvore é adicionada). (A interpolação inicial foi usada antes do JDK 1.7 e a interpolação final foi usada no JDK 1.8) (Nota: Quando a colisão faz com que a lista vinculada seja maior do que TREEIFY_THRESHOLD = 8, a lista vinculada é convertida em uma árvore vermelho-preto)

Ao obter um objeto, passe K para o método get (): ①, chame o método hash (K) (calcule o valor hash de K) para obter o subscrito da matriz da lista vinculada onde o valor da chave está localizado; ②, percorra a lista vinculada sequencialmente, método equals () Procure o valor V correspondente ao valor K na mesma lista vinculada de nós.

HashCode é localizado e armazenado; igual é qualitativo e compara se os dois são iguais.

3. O que acontece quando o hashCode de dois objetos é o mesmo?

Como o hashCode é o mesmo, ele não é necessariamente igual (igual à comparação do método), portanto, os subscritos da matriz onde os dois objetos estão localizados são os mesmos e a "colisão" ocorre. E como o HashMap usa uma lista vinculada para armazenar objetos, esse Nó será armazenado na lista vinculada. Por que reescrever os métodos hashcode e equals? Recomende dar uma olhada.

4. Você conhece a implementação de hash? Por que você deseja alcançar isso?

No JDK 1.8, é realizado pelo alto XOR de 16 bits de hashCode (): (h = k.hashCode ()) ^ (h >>> 16), que é considerado principalmente da perspectiva de velocidade, eficiência e qualidade , Reduz a sobrecarga do sistema e não causará colisões causadas pelos bits altos que não participam do cálculo do subscrito.

5. Por que usar o operador XOR?

É garantido que, enquanto um bit do valor de 32 bits do hashCode do objeto mudar, todo o valor de retorno de hash () será alterado. Reduza as colisões tanto quanto possível.

6. Como determinar a capacidade da tabela HashMap? O que é loadFactor? Como essa capacidade muda? Que problemas essa mudança trará?

O tamanho da matriz da tabela é determinado pelo parâmetro de capacidade, o padrão é 16 ou pode ser passado durante a construção, o limite máximo é 1 << 30;

②, loadFactor é o fator de carga. O objetivo principal é confirmar se a matriz da tabela precisa ser expandida dinamicamente. O valor padrão é 0,75. Por exemplo, quando o tamanho da matriz da tabela é 16 e o ​​fator de carga é 0,75, o limite é 12 e quando o tamanho real da tabela excede 12 , A mesa precisa de expansão dinâmica;

③. Ao expandir, chame o método resize () para dobrar o comprimento da mesa (observe que o comprimento da mesa, não o limite)

④. Se os dados forem grandes, haverá perda de desempenho durante a expansão. Em locais com requisitos de alto desempenho, essa perda pode ser fatal.

Recomendação: Por que a capacidade do HashMap é sempre uma potência de 2?

7. Qual é o processo do método put no HashMap?

Resposta: "Chame a função hash para obter o valor hash correspondente à chave e, em seguida, calcule o subscrito da matriz;

Se não houver conflito de hash, coloque-o diretamente na matriz; se houver um conflito de hash, coloque-o atrás da lista vinculada na forma de uma lista vinculada;

Se o comprimento da lista vinculada exceder o limite (TREEIFY THRESHOLD == 8), a lista vinculada é convertida em uma árvore vermelho-preto e a lista vinculada é menor que 6, a árvore vermelho-preto é convertida de volta na lista vinculada;

Se a chave do nó já existe, basta substituir seu valor;

Se os pares de valores-chave na coleção forem maiores que 12, chame o método resize para expandir a matriz. "

8. Qual é o processo de expansão do array?

Crie um novo array com o dobro da capacidade do array antigo e recalcule o local de armazenamento dos nós no array antigo. Existem apenas duas posições de nós no novo array, a posição do subscrito original ou o subscrito original + o tamanho do array antigo.

9. O problema da lista com links excessivamente profundos causado pelo método zipper, por que não usar a árvore de pesquisa binária em vez da árvore vermelho-preto? Por que não usar sempre árvores vermelho-pretas?

A razão para escolher a árvore vermelha e preta é resolver os defeitos da árvore de pesquisa binária. Em circunstâncias especiais, a árvore de pesquisa binária se tornará uma estrutura linear (é a mesma que a estrutura de lista vinculada original, o que causa um problema profundo), pesquisa transversal Vai ser muito lento. Recomendação: perguntei à árvore vermelho-preta durante a entrevista, meu rosto ficou verde.

A árvore vermelho-preto pode precisar ser canhoto, destro e operações de mudança de cor para manter o equilíbrio após a inserção de novos dados. A árvore vermelho-preto é introduzida para encontrar dados rapidamente e resolver o problema da profundidade da consulta da lista vinculada. Sabemos que a árvore vermelho-preto é uma árvore binária equilibrada. Mas, para manter o "equilíbrio", há um preço a pagar, mas o custo dos recursos é menor do que percorrer a lista vinculada linear, então quando o comprimento for maior que 8, a árvore vermelho-preta será usada, se o comprimento da lista vinculada for muito curto, não é É necessário introduzir árvores vermelhas e pretas, mas a introdução será lenta.

10. Conte-me sobre sua opinião sobre a árvore vermelho e preto?

Cada nó é vermelho ou preto

O nó raiz é sempre preto

Se o nó for vermelho, seus nós filhos devem ser pretos (não necessariamente vice-versa)

Cada nó folha é um nó preto vazio (nó NIL)

Cada caminho do nó raiz ao nó folha ou nó filho vazio deve conter o mesmo número de nós pretos (ou seja, a mesma altura preta)

11. Quais mudanças foram feitas no HashMap no jdk8?

No Java 1.8, se o comprimento da lista vinculada exceder 8, a lista vinculada será convertida em uma árvore vermelho-preto. (O número de baldes deve ser maior que 64. Se for menor que 64, ele só será expandido.) Siga a conta oficial do WeChat: pilha de tecnologia Java e responda em segundo plano: Novos recursos. Você pode obter tutoriais de novos recursos do N Java que eu compilei, todos eles produtos secos.

Quando ocorre uma colisão de hash, java 1.7 será inserido no topo da lista vinculada, e java 1.8 será inserido no final da lista vinculada

No Java 1.8, Entry foi substituído por Node (troca de um colete).

12. Qual é a diferença entre HashMap, LinkedHashMap e TreeMap?

HashMap refere-se a outras questões; preste atenção à conta pública do WeChat: pilha de tecnologia Java, responda em segundo plano: Java, você pode obter os tutoriais da coleção N Java que compilei, todos eles produtos secos.

LinkedHashMap salva a ordem de inserção dos registros. Ao percorrer com o Iterator, o primeiro registro buscado deve ser inserido primeiro; o percurso é mais lento que o HashMap;

TreeMap implementa a interface SortMap, que pode classificar os registros que salva de acordo com a chave (o valor da chave padrão está em ordem crescente e o comparador de classificação também pode ser especificado)

13. Quais são os cenários de uso de HashMap & TreeMap e LinkedHashMap?

Em geral, o mais usado é o HashMap.

HashMap: Ao inserir, excluir e posicionar elementos no Mapa;

TreeMap: No caso em que as chaves precisam ser percorridas em ordem natural ou personalizada;

LinkedHashMap: No caso em que a ordem de saída e a ordem de entrada são as mesmas.

14. Qual é a diferença entre HashMap e HashTable?

①, HashMap não é seguro para thread, HashTable é seguro para thread;

② Devido à segurança do thread, a eficiência do HashTable não é tão eficiente quanto o do HashMap;

③, HashMap permite apenas que a chave de um registro seja nula e permite que o valor de vários registros seja nulo, mas HashTable não permite;

④, o tamanho do array de inicialização padrão do HashMap é 16, o HashTable é 11. Quando o primeiro se expande, ele se expande duas vezes, o último se expande duas vezes + 1;

⑤, o HashMap precisa recalcular o valor do hash, enquanto o HashTable usa diretamente o hashCode do objeto

15. Qual é outra classe thread-safe em Java que é muito semelhante ao HashMap? Também é thread-safe.Qual é a diferença entre ele e HashTable em termos de sincronização de thread?

Classe ConcurrentHashMap (uma implementação de HashMap segura para thread e eficiente fornecida no pacote de simultaneidade Java java.util.concurrent).

HashTable é o princípio de usar a palavra-chave synchronize para bloquear (ou seja, para bloquear o objeto);

Para ConcurrentHashMap, o bloqueio segmentado é adotado no JDK 1.7; CAS (algoritmo livre de bloqueio) + synchronized é adotado diretamente no JDK 1.8.

16. Qual é a diferença entre HashMap e ConcurrentHashMap?

Além do bloqueio, não há muita diferença em princípio. Além disso, o par de valores-chave de HashMap permite nulo, mas ConCurrentHashMap não permite.

17. Por que ConcurrentHashMap é mais eficiente do que HashTable?

HashTable usa um bloqueio (bloqueando toda a estrutura da lista vinculada) para lidar com problemas de simultaneidade.Múltiplos threads competem por um bloqueio, que é fácil de bloquear;

ConcurrentHashMap

No JDK 1.7, o bloqueio segmentado (ReentrantLock + Segment + HashEntry) é usado, o que equivale a dividir um HashMap em vários segmentos e atribuir um bloqueio a cada segmento, que suporta acesso multiencadeado. Granularidade de bloqueio: com base no segmento, contém vários HashEntry.

CAS + synchronized + Node + red-black tree é usado no JDK 1.8. Granularidade de bloqueio: Nó (primeiro nó) (implementa Map.Entry <K, V>). A granularidade do bloqueio é reduzida.

18. Análise específica para o mecanismo de bloqueio ConcurrentHashMap (JDK 1.7 VS JDK 1.8)?

No JDK 1.7, um mecanismo de bloqueio segmentado é usado para implementar operações de atualização simultâneas.A camada inferior usa uma estrutura de armazenamento de array + lista vinculada, incluindo duas classes internas estáticas principais, Segment e HashEntry.

① Segment herda ReentrantLock (bloqueio reentrante) para agir como um bloqueio.Cada objeto Segment guarda vários baldes de cada mapa hash;

②, HashEntry é usado para encapsular os pares de valores-chave da tabela de mapeamento;

③, cada depósito é uma lista vinculada por vários objetos HashEntry

Insira a descrição da imagem aqui

No JDK 1.8, Node + CAS + Synchronized é usado para garantir a segurança da concorrência. Cancele o segmento de classe e use diretamente a matriz da tabela para armazenar pares de valores-chave; quando o comprimento da lista vinculada composta de objetos HashEntry excede TREEIFY_THRESHOLD, a lista vinculada é convertida em uma árvore vermelha e preta para melhorar o desempenho. A camada inferior é alterada para array + lista vinculada + árvore vermelha e preta.

Insira a descrição da imagem aqui

19. ConcurrentHashMap no JDK 1.8, por que usar o bloqueio integrado sincronizado em vez do bloqueio reentrante ReentrantLock?

① O tamanho da partícula é reduzido;

② A equipe de desenvolvimento JVM não desistiu da sincronização, e o espaço de otimização para otimização sincronizada com base em JVM é maior e mais natural.

③. Sob a grande quantidade de operação de dados, para a pressão de memória da JVM, o ReentrantLock baseado em API gastará mais memória.

20. Uma breve introdução ao ConcurrentHashMap?

① Constantes importantes:

private transient volatile int sizeCtl;

Quando é um número negativo, -1 significa inicializando, -N significa N-1 threads estão se expandindo;

Quando é 0, significa que a tabela não foi inicializada;

Quando é outro número positivo, indica o tamanho da inicialização ou próxima expansão.

② Estrutura de dados:

Node é a unidade básica da estrutura de armazenamento, herda o Entry no HashMap, usado para armazenar dados;

TreeNode herda Node, mas a estrutura de dados é substituída por uma estrutura de árvore binária, que é a estrutura de armazenamento da árvore vermelho-preto e é usada para armazenar dados na árvore vermelho-preto;

TreeBin é um contêiner que encapsula TreeNode e fornece algumas condições e controle de bloqueio para converter árvores vermelhas e pretas.

③ Ao armazenar objetos (método put ()):

Se não for inicializado, chame o método initTable () para inicializar;

Se não houver conflito de hash, diretamente a inserção sem bloqueio do CAS;

Se você precisar expandir a capacidade, expanda-a primeiro;

Se houver um conflito de hash, bloqueios são adicionados para garantir a segurança do thread.Há dois casos: um é que a lista vinculada é atravessada diretamente até o final e inserida, o outro é que a árvore vermelho-preto é inserida de acordo com a estrutura da árvore vermelho-preto;

Se o número da lista ligada for maior que o limite 8, ela deve primeiro ser convertida em uma estrutura de árvore vermelha e preta e break entra no loop novamente

Se a adição for bem-sucedida, chame o método addCount () para contar o tamanho e verificar se ele precisa de expansão.

④ Transferência do método de expansão (): A capacidade padrão é 16, ao expandir, a capacidade torna-se o dobro do original.

helpTransfer (): Chame vários threads de trabalho juntos para ajudar a expandir a capacidade, para que a eficiência seja maior.

⑤ Ao obter um objeto (método get ()):

Calcule o valor do hash, localize a posição do índice da tabela e retorne se o primeiro nó corresponder;

Se encontrar expansão, ele chamará o método ForwardingNode.find () do nó que marca o nó de expansão, encontrará o nó e retornará se ele corresponder;

Se nenhuma das opções acima corresponder, percorra o nó para baixo e retorne se a correspondência for correspondida, caso contrário, retornará nulo no final.

21. Qual é a simultaneidade de ConcurrentHashMap?

O número máximo de encadeamentos que podem atualizar ConccurentHashMap ao mesmo tempo enquanto o programa está em execução sem contenção de bloqueio. O padrão é 16 e pode ser definido no construtor.

Quando o usuário define a simultaneidade, ConcurrentHashMap usará a menor potência de 2 expoentes maior ou igual a este valor como a simultaneidade real (se o usuário definir a simultaneidade para 17, a simultaneidade real é 32)

Acho que você gosta

Origin blog.csdn.net/qq_21383435/article/details/108465919
Recomendado
Clasificación