A diferença e o princípio de realização de cada coleção

A diferença entre HashMap e Hashtable
1. A principal diferença entre os dois é que Hashtable é thread-safe, enquanto HashMap não é thread-safe.
2. HashMap pode usar null como a chave, enquanto Hashtable não permite null como a chave ( quando o HashMap usa nulo como a chave, é sempre armazenado no primeiro nó da matriz da tabela)
3. A capacidade inicial do HashMap é 16, e a capacidade inicial do Hashtable é 11, e o fator de preenchimento de ambos é 0,75 por padrão .
4. quando HashMap é expandido, a capacidade actual é dobrada, a saber: a capacidade * 2, quando se expande Hashtable, a capacidade é dobrada 1, a saber: a capacidade * 2 + 1
5. os dois métodos de
cálculo de hash são diferentes. Hashtable calcula o hash usando diretamente o hashcode da chave para modular diretamente o comprimento da matriz da tabela
modulando diretamente o HashMap para calcular o par de hash. O hashcode da chave é hash duas vezes para obter um valor de hash melhor e, em seguida, modulo o comprimento da matriz da tabela


Além de HashMap e Hashtable, há também um conjunto de hash HashSet.
A diferença é que HashSet não é uma estrutura de valor de chave, mas apenas armazena elementos exclusivos, o que é equivalente a uma versão simplificada do HashMap, mas contém apenas as chaves no HashMap.

A estrutura subjacente do HashSet é uma tabela hash. A coleção HashSet usa o método hashCode () e o método equal () do elemento herdado da superclasse Object para determinar se dois objetos são iguais. O método hashCode pode evitar o complicado processo de adicionar igual a cada vez. Portanto, quando definimos o objeto nós mesmos, podemos substituir os dois métodos que o objeto herda de Object, de modo que eles possam julgar se os dois elementos são o mesmo elemento de acordo com nossa vontade.

A camada inferior da coleção TreeSet é uma estrutura de dados de árvore binária. Ele não só não permite que os mesmos elementos existam, mas também nos ajuda a classificá-los. Depois de armazenar os elementos na coleção TreeSet, eles são classificados em ordem natural. E queremos classificar os elementos de acordo com nossa vontade, permitir que os elementos implementem a interface Comparable e, em seguida, implementar o método CompareTo dentro. Retornar 0 significa que os elementos são os mesmos. Caso contrário, a ordem de organização é julgada de acordo com o positivo ou número negativo. TreeSet é assíncrono

 

O princípio de realização de
HashMap e Hashtable A implementação subjacente de HashMap e Hashtable são todas realizadas pela estrutura de árvore de matriz + lista vinculada / vermelho-preto. Quando o comprimento da lista vinculada de um depósito de bits atinge um certo limite, a lista vinculada ser convertido em vermelho e preto A complexidade de tempo da árvore, árvore vermelho-preto é logN. (Quando o número de nós com o mesmo valor hash não é menor que 8, esta é a maior diferença entre a implementação do HashMap no JDK7 e no JDK8.) TreeifyBin () é converter a lista vinculada em uma árvore vermelha e preta.
O nome de Entry no JDK tornou-se Node porque está relacionado à implementação de Red-Black Tree TreeNode.
Ao adicionar, excluir e obter elementos, o hash é calculado primeiro, e o índice é calculado de acordo com o hash e table.length, que é o subscrito da matriz da tabela, e então a operação correspondente é realizada. Tome o mapa como um exemplo: o processo put é calcular o hash primeiro e depois passar o hash e a tabela .length fetch e calcular o valor do índice e, em seguida, colocar a chave na posição da tabela [índice]. Quando houver outros elementos na tabela [índice] , uma lista vinculada será formada na posição da tabela [índice] e o elemento recém-adicionado será colocado na tabela [índice], o elemento recém-adicionado será colocado no primeiro lugar da tabela e o elemento original será vinculado por Digite a próxima, para que o problema de conflito de hash seja resolvido na forma de uma lista encadeada. Quando o número de elementos atinge o valor crítico (fator de capacidade *), a expansão é executada. O comprimento da matriz da tabela torna-se table.length * 2
O processo de get é primeiro calcular o hash, depois calcular o valor do índice por meio de hash e table.length e, em seguida, percorrer a lista encadeada na tabela [índice] até que a chave seja encontrada e, em seguida, retornar
(se o hashCode dos dois objetos é o mesmo. O local do intervalo será encontrado usando o hashCode e, em seguida, o método key.equals () será chamado para encontrar o nó correto na lista vinculada. Por fim, o objeto de valor que você está procurando foi encontrado.)

 

ArrayList: a estrutura de dados subjacente cria a estrutura do array, a velocidade da consulta é rápida, a adição, exclusão e modificação são lentas. A capacidade inicial é 10.
Quando o elemento excede o conteúdo do array, um novo array é gerado, que é estendido em 50% do array original, e os dados do array original são copiados para o novo. No array, adicione novos elementos ao novo array.
Desperdício de espaço

 

LinkList: a estrutura de dados subjacente de LinkedList é baseada em uma lista ligada circular bidirecional e nenhum dado é armazenado no nó principal. A velocidade de adição e exclusão é rápida, e a consulta é um pouco mais lenta (método de pesquisa binária);
pode ser clonado, oferece suporte à serialização e é assíncrono. É obtido por um valor de índice de contagem.
Por exemplo, quando chamamos get (int location), primeiro compararemos "local" e "1/2 do comprimento da lista duplamente vinculada"; se o primeiro for maior, procure no cabeçalho da lista vinculada para a posição de localização; caso contrário, a partir da lista vinculada A pesquisa anterior começa no final até a localização da localização.


Vector: A camada inferior é uma estrutura de array, e a sincronização de threads ArrayList significa que as threads não estão sincronizadas; novo é um desperdício de 100% de memória;
 


Princípio de implementação do ConcurrentHashMap 1.7 O
ConcurrentHashMap permite que várias operações de modificação sejam realizadas simultaneamente (16). A chave está no uso da tecnologia de separação de bloqueio.
Ele usa vários bloqueios para controlar a modificação de diferentes segmentos da tabela de hash. Cada segmento é na verdade uma pequena tabela de hash com seu próprio bloqueio. Desde que ocorra simultaneidade múltipla em segmentos diferentes, eles podem prosseguir simultaneamente.
ConcurrentHashMap trata o valor-chave como um todo na camada inferior. Este todo é um objeto de entrada. O segmento correspondente à chave é usado para processamento.
Ao contrário de HashMap, ConcurrentHashMap usa várias tabelas de sub-Hash, que é o segmento (Segmento) ConcurrentHashMap permite completamente mais Uma operação de leitura é executada simultaneamente e a operação de leitura não precisa ser bloqueada.

https://my.oschina.net/hosee/blog/639352
Alguns métodos precisam abranger segmentos, como size () e containsValue (), eles podem precisar bloquear a tabela inteira em vez de apenas um segmento, o que requer bloqueio sequencial Todos os segmentos, após a operação ser concluída, os bloqueios de todos os segmentos são liberados em sequência. Aqui, "na ordem" é muito importante, caso contrário, é muito provável que ocorra um impasse. Dentro de ConcurrentHashMap, a matriz de segmento é final e suas variáveis ​​de membro são realmente finais. No entanto, não se trata apenas de declarar a matriz como final. Certifique-se de que o os membros da matriz também são finais, o que requer garantias de implementação. Isso garante que não haverá deadlocks, porque a ordem em que os bloqueios são adquiridos é fixa.

Existem muitos arrays de lista HashEntry em Segment. Para uma chave, são necessárias três operações de hash para finalmente localizar a posição do elemento.
Os três hashes são:
1. Para uma chave, execute uma operação de hash primeiro para obter o valor de hash h1, ou seja, h1 = hash1 (chave) ;
2 .Hash os bits altos de h1 pela segunda vez para obter o valor de hash h2, ou seja, h2 = hash2 (bits altos de h1), e h2 pode determinar em qual segmento o elemento é colocado;
3. O h1 obtido Execute o terceiro hash, obtenha o valor de hash h3, ou seja, h3 = hash3 (h1), até h3, você pode determinar em qual HashEntry o elemento é colocado.

concurrencyLevel representa o nível de simultaneidade. Este valor é usado para determinar o número de segmentos. O número de segmentos é maior ou igual aos primeiros 2 de concurrencyLevel elevado à potência de n.
Processo de inicialização
1. Verifique a legalidade dos parâmetros
2. Calcule o número de segmentos
3. Em seguida, use o loop para encontrar o primeiro n-ésimo número ssize que é maior ou igual a concurrencyLevel, que é o tamanho da matriz Segment , e registrar um total de bits à esquerda O número de deslocamentos sshift e definir segmentShift = 32-sshift,
  e o valor de segmentMask é igual a ssize-1, cada bit binário de segmentMask é 1, a finalidade é determinar o índice de o segmento fazendo & operação no valor hash da chave e este valor.
4. Verifique se o valor de capacidade fornecido é maior que o valor máximo permitido e, se for maior que esse valor, defina-o com esse valor. O valor da capacidade máxima é static final int MAXIMUM_CAPACITY = 1 << 30 ;.
5. Em seguida, calcule quantos elementos devem ser colocados em cada segmento em média, este valor c é o valor arredondado. (int c = initialCapacity / ssize;) Por exemplo, a capacidade inicial é 15 e o número de segmentos é 4, e uma média de 4 elementos precisam ser colocados em cada segmento.
6. Finalmente, crie uma instância de Segment e trate-a como o primeiro elemento do array Segment.


A operação de colocação deve ser bloqueada. As etapas da operação são as seguintes:
1. Determine se o valor é nulo; se for nulo, lance uma exceção diretamente.
2. Hash duas vezes por chave para determinar em qual segmento colocar os dados.
5. Coloque valor neste objeto de segmento.Esta operação put é basicamente a mesma etapa (obter o índice HashEntry por meio de & operação e, em seguida, definir).

A operação get não precisa ser bloqueada (se o valor for nulo, readValueUnderLock será chamado, apenas esta etapa será bloqueada) e a segurança dos dados é garantida por meio de volatile e final.
1. Como na operação put, primeiro use a chave para executar dois hashes para determinar qual segmento deve ser usado para buscar dados.
2. Use Unsafe para obter o segmento correspondente e, em seguida, execute uma operação & para obter a posição da lista vinculada de HashEntry e, em seguida, percorra toda a lista vinculada a partir do cabeçalho da lista vinculada (porque Hash pode colidir, então use um link lista para salvar), se você encontrar a chave correspondente, o valor correspondente será retornado. Se a chave correspondente não for encontrada após percorrer a lista vinculada, significa que a chave não está incluída no mapa e nulo é retornado.

size / containsValue:
primeiro dê 3 oportunidades, não bloqueie todos os segmentos, atravesse todos os segmentos, acumule o tamanho de cada segmento para obter o tamanho de todo o Mapa, se um certo dois cálculos adjacentes obtiverem os tempos de atualização de todos os segmentos (modCount) É o mesmo, indicando que não há operação de atualização durante o processo de cálculo, e este valor é retornado diretamente. Se houver uma mudança nos tempos de atualização do mapa durante os três cálculos sem bloqueio, os cálculos subsequentes primeiro bloqueiam todos os segmentos, depois percorrem todos os segmentos para calcular o tamanho do mapa e, finalmente, desbloqueiam todos os segmentos.

Os valores de chave e valor em ConcurrentHashMap não podem ser nulos, a chave em HashMap pode ser nula e a chave em HashTable não pode ser nula.
ConcurrentHashMap é uma classe thread-safe e não garante que as operações que usam ConcurrentHashMap sejam thread-safe!
A operação get de ConcurrentHashMap não precisa ser bloqueada, a operação put precisa ser bloqueada

 

 


Princípio de realização de ConcurrentHashMap 1.8

Um
número negativo de sizeCtl significa que a inicialização ou expansão está em andamento.
-1
significa que está sendo inicializada. -N significa que há N-1 threads realizando expansão. Um
número positivo ou 0 significa que a tabela de hash não foi inicializado. Este valor indica o tamanho da inicialização ou da próxima expansão. Isso é semelhante ao conceito de limite de expansão. Como você pode ver mais tarde, seu valor é sempre 0,75 vezes a capacidade atual do ConcurrentHashMap, que corresponde ao loadfactor.


Node é a classe interna principal. Ele empacota pares chave-valor chave-valor. Todos os dados inseridos no ConcurrentHashMap são empacotados nele. É muito semelhante à definição em HashMap, mas existem algumas diferenças. Ele define um bloqueio de sincronização volátil para o valor e os próximos atributos (igual ao segmento JDK7). Não permite chamar o método setValue para alterar diretamente o campo de valor de Nó. Adiciona find O método auxilia o método map.get ().
Quando o comprimento da lista vinculada for muito longo, ela será convertida em TreeNode. Mas, ao contrário do HashMap, ele não é convertido diretamente em árvores vermelhas e pretas, mas esses nós são empacotados em TreeNode e colocados no objeto TreeBin, e TreeBin completa o empacotamento de árvores vermelhas e pretas. E TreeNode é integrado a partir da classe Node em ConcurrentHashMap, não da classe LinkedHashMap.Entry <K, V> em HashMap, o que significa que TreeNode possui um próximo ponteiro, com o objetivo de facilitar o acesso baseado em TreeBin.
ForwardingNode:
uma classe de nó usada para conectar duas tabelas. Ele contém um ponteiro nextTable para apontar para a próxima tabela. E o próximo ponteiro do valor-chave deste nó é totalmente nulo e seu valor hash é -1. O método find definido aqui é consultar o nó da nextTable em vez de usar a si mesmo como o nó principal.


Para ConcurrentHashMap, chamar seu método de construção é apenas definir alguns parâmetros. A inicialização de toda a tabela ocorre ao inserir elementos no ConcurrentHashMap. Por exemplo, ao chamar put, computeIfAbsent, compute, merge e outros métodos, o tempo da chamada é para verificar table == null.
O método de inicialização usa principalmente o atributo de chave sizeCtl. Se este valor <0, significa que outros threads estão sendo inicializados e esta operação é abandonada. Também pode ser visto aqui que a inicialização de ConcurrentHashMap só pode ser concluída por um encadeamento. Se a permissão de inicialização for obtida, use o método CAS para definir sizeCtl como -1 para evitar que outros threads entrem. Depois de inicializar a matriz, altere o valor de sizeCtl para 0,75 * n.

Para ser adicionado

Acho que você gosta

Origin blog.csdn.net/orzMrXu/article/details/102625132
Recomendado
Clasificación