Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1! ?

Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1! ?

△ Hollis, uma pessoa com uma busca única por Codificação △
Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1!  ?
Este é o 254º
autor de compartilhamento original de Hollis l
fonte Hollis l Hollis (ID: hollischuang)
Na base de Java, as classes de coleção são uma peça fundamental do conhecimento e também são de desenvolvimento diário É frequentemente usado quando Por exemplo, Lista e Mapa também são muito comuns no código.
Pessoalmente, acho que os engenheiros do JDK realmente fizeram muitas otimizações para a implementação do HashMap. Se você quiser dizer qual de todo o código-fonte do JDK tem mais ovos enterrados, então acho que o HashMap pode ser pelo menos os cinco primeiros.
É exatamente por isso que muitos detalhes são facilmente esquecidos.Hoje vamos nos concentrar em um dos problemas, que é:
Por que o fator de carga do HashMap é definido como 0,75 em vez de 1 e não 0,5? Quais são as considerações por trás disso?
Não subestime essa pergunta, porque o fator de carga é um conceito muito importante no HashMap e um ponto de teste comum para entrevistas de ponta.
Além disso, vale a pena definir isso, e algumas pessoas vão usá-lo incorretamente. Por exemplo, meu "Alibaba Java Development Manual, há alguns dias, recomendou definir a capacidade inicial ao criar um HashMap, mas quanto é apropriado?" "Neste artigo, alguns leitores responderam assim:
Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1!  ?

Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1!  ?
Já que alguém tentará modificar o fator de carga, é apropriado alterá-lo para 1? Por que o HashMap não usa 1 como o valor padrão do fator de carga?

O que é loadFactor

Primeiro, vamos apresentar o que é o fator de carga (loadFactor). Se o leitor já conhece esta parte, você pode pular este parágrafo diretamente.
Sabemos que quando o HashMap é criado pela primeira vez, sua capacidade será especificada (se não for especificado explicitamente, o padrão é 16, veja por que a capacidade padrão do HashMap é 16?), Então à medida que continuamos a colocar elementos no HashMap Se a capacidade for excedida, é necessário um mecanismo de expansão.
A chamada expansão é para expandir a capacidade do HashMap:

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}

A partir do código, podemos ver que no processo de adição de elementos ao HashMap, se o número de elementos (tamanho) ultrapassar o limite (limite), ele irá expandir (redimensionar) automaticamente e, após a expansão, você precisa Refaça os elementos originais no HashMap, ou seja, redistribua os elementos no intervalo original para o novo intervalo.
No HashMap, o limite (limite) = fator de carga (loadFactor) * capacidade (capacidade).
loadFactor é o fator de carga, indicando o quão cheio está o HashMap. O valor padrão é 0,75f, o que significa que por padrão, quando o número de elementos no HashMap atingir 3/4 da capacidade, ele se expandirá automaticamente. (Para obter detalhes, consulte os conceitos que não são claros no HashMap)

Por que expandir

Lembre-se de que dissemos antes que o HashMap não só precisa expandir sua capacidade durante o processo de expansão, mas também precisa se refazer! Portanto, esse processo é realmente muito demorado e quanto mais elementos no Mapa, mais demorado.
O processo de refazer o hash é equivalente a refazer o hash de todos os elementos nele e recalcular para qual depósito deve ser alocado.
Então, alguém já pensou em uma pergunta, já que é tão problemático, por que você precisa expandir? O HashMap não é uma lista vinculada de matriz? Sem expansão, ele pode ser armazenado infinitamente. Por que expandir?
Na verdade, isso está relacionado à colisão de hash.
Colisão de hash

Sabemos que o HashMap é realmente implementado na parte inferior com base em uma função hash, mas as funções hash têm as seguintes características básicas: se o valor hash calculado de acordo com a mesma função hash for diferente, o valor de entrada deve ser diferente. No entanto, se o valor de hash calculado com base na mesma função de hash for o mesmo, o valor de entrada pode não ser o mesmo.
O fenômeno de dois valores de entrada diferentes terem o mesmo valor hash calculado a partir da mesma função hash é chamado de colisão.
Um indicador importante para medir a qualidade de uma função hash é a probabilidade de colisão e a solução para a colisão.
Para solucionar a colisão de hash, existem vários métodos, entre os quais o mais comum é o método do endereço em cadeia, que também é o método adotado pelo HashMap. Para obter detalhes, consulte o artigo mais completo sobre a análise de hash () no Map em toda a rede, não há outro.
O HashMap combina uma matriz e uma lista vinculada e tira proveito das duas. Podemos entendê-lo como uma matriz de listas vinculadas.
Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1!  ?
HashMap é implementado com base na estrutura de dados de uma matriz de listas vinculadas.
Quando colocamos um elemento no HashMap, precisamos localizar qual lista vinculada na matriz primeiro e, em seguida, pendurar esse elemento atrás da lista vinculada.
Quando obtemos elementos do HashMap, também precisamos localizar qual lista vinculada no array e, em seguida, percorrer os elementos na lista vinculada um por um até encontrar o elemento necessário.
No entanto, se o conflito em um HashMap for muito alto, a lista vinculada da matriz degenerará em uma lista vinculada. Neste momento, a velocidade da consulta será bastante reduzida.
Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1!  ?
Assim, a fim de garantir a velocidade de leitura de HashMap, precisamos encontrar maneiras de garantir que o conflito de HashMap não seja muito alto.
Escalonamento para evitar colisão de hash

Então, como podemos evitar efetivamente as colisões de hash?
Vamos pensar primeiro ao contrário, o que você acha que causará mais colisões de hash no HashMap?
Existem duas situações:
1. A capacidade é muito pequena. Quanto menor for a capacidade, maior será a probabilidade de colisão. Se houver mais lobos e menos carne, haverá competição.
2. O algoritmo de hash não é bom o suficiente. Se o algoritmo não for razoável, ele pode ser dividido no mesmo ou em vários depósitos. A distribuição desigual também pode levar à competição.
Portanto, resolver a colisão de hash no HashMap também começa a partir desses dois aspectos.
Ambos os pontos são bem refletidos no HashMap. Combinar os dois métodos, expandir a capacidade do array no momento certo e, em seguida, calcular em qual array os elementos são alocados por meio de um algoritmo de hash adequado, pode reduzir muito a probabilidade de conflito. Pode evitar o problema de consulta ineficiente.

Por que o loadFactor padrão é 0,75

Neste ponto, sabemos que loadFactor é um conceito importante no HashMap, e ele representa o grau máximo de plenitude deste HashMap.
Para evitar colisões de hash, o HashMap precisa ser expandido quando apropriado. É quando o número de elementos nele atinge um valor crítico, que está relacionado ao loadFactor, conforme mencionado anteriormente. Em outras palavras, definir um loadFactor razoável pode efetivamente evitar conflitos de hash.
Então, qual é a configuração loadFactor apropriada?
Este valor agora é 0,75 no código-fonte JDK:

/**
 * The load factor used when none specified in constructor.
 */

float final estático DEFAULT_LOAD_FACTOR = 0.75f;
Então, por que escolher 0.75? Quais são as considerações por trás? Por que não 1, não 0,8? Não 0,5, mas 0,75?
Na documentação oficial do JDK, existe essa descrição:

As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put).

O significado aproximado é: De modo geral, o fator de carga padrão (0,75) fornece uma boa compensação entre custos de tempo e espaço. Valores mais altos reduzem a sobrecarga de espaço, mas aumentam os custos de pesquisa (refletidos na maioria das operações da classe HashMap, incluindo get e put).
Imagine que, se definirmos o fator de carga como 1 e a capacidade usar o valor inicial padrão de 16, isso significa que um HashMap precisa estar "cheio" antes da expansão.
Então, no HashMap, a melhor situação é que esses 16 elementos caiam em 16 diferentes depósitos depois de passar o algoritmo de hash, caso contrário, ocorrerão inevitavelmente colisões de hash. E com mais elementos, quanto maior a probabilidade de colisões hash, menor a velocidade de pesquisa.

0,75 base matemática

Além disso, podemos calcular o quão apropriado é esse valor por meio de um tipo de pensamento matemático.
Assumimos que a probabilidade de um balde estar vazio e não vazio é 0,5. Usamos s para representar a capacidade en para representar o número de elementos adicionados.
Vamos denotar o tamanho da chave adicionada e o número de n chaves. De acordo com o teorema binomial, a probabilidade de que o balde esteja vazio é:

P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)

Portanto, se o número de elementos no intervalo for menor que o seguinte valor, o intervalo pode estar vazio:

log(2)/log(s/(s - 1))

Quando s tende ao infinito, se o número de chaves adicionadas torna P (0) = 0,5, então n / s rapidamente se aproxima do log (2):

log(2) ~ 0.693...

Portanto, o valor razoável é cerca de 0,7.
É claro que esse método de cálculo matemático não está refletido na documentação oficial do Java e não temos como investigar se existe tal consideração. Assim como não sabemos o que Lu Xun pensou ao escrever o artigo, podemos apenas especular. Essa especulação vem do Stack Overflow ( https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap )

O fator inevitável de 0,75

Em teoria, acreditamos que o fator de carga não deve ser muito grande, caso contrário, ele causará muitas colisões de hash, e não deve ser muito pequeno, o que desperdiçará espaço.
Por meio de um raciocínio matemático, é razoável calcular que esse valor está em torno de 0,7.
Então, por que 0,75 foi selecionado no final?
Lembre-se de que mencionamos uma fórmula anteriormente, ou seja, limite = capacidade do fator de carga (capacidade).
Estamos em "Por que a capacidade padrão do HashMap é 16? Conforme mencionado em ", de acordo com o mecanismo de expansão do HashMap, ele garantirá que o valor da capacidade seja sempre uma potência de 2.
Então, para garantir que o resultado da capacidade do fator de carga (loadFactor)
seja um inteiro, esse valor é 0,75 (3/4) mais razoável, pois o produto deste número por qualquer potência de 2 é um inteiro.

Resumindo

HashMap é um tipo de estrutura KV. Para melhorar a velocidade de consulta e inserção, a camada inferior adota a estrutura de dados de array de lista vinculada.
Mas porque o algoritmo hash precisa ser usado ao calcular a localização do elemento, e o algoritmo hash usado pelo HashMap é o método de endereço em cadeia. Existem dois extremos para essa abordagem.
Se a probabilidade de colisão de hash no HashMap for alta, o HashMap irá degenerar em uma lista vinculada (não degenerará realmente, mas a operação é como a manipulação direta da lista vinculada), e sabemos que a maior desvantagem da lista vinculada é que a velocidade da consulta é relativamente lenta. O cabeçalho da tabela é percorrido um a um.
Portanto, para evitar um grande número de colisões de hash no HashMap, ele precisa ser expandido quando apropriado.
A condição para expansão é quando o número de elementos atinge um valor crítico. O método de cálculo do valor crítico no HashMap:

临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)

O fator de carga representa o grau máximo de plenitude que uma matriz pode atingir. Este valor não deve ser muito grande ou muito pequeno.
O loadFactor é muito grande, por exemplo, igual a 1, então haverá uma alta probabilidade de colisão de hash, o que reduzirá muito a velocidade da consulta.
O loadFactor é muito pequeno, por exemplo, igual a 0,5; então, expansões frequentes resultarão em uma grande perda de espaço.
Portanto, esse valor deve estar entre 0,5 e 1. Calculado de acordo com fórmulas matemáticas. Este valor é razoável no log (2).
Além disso, para melhorar a eficiência da expansão, a capacidade do HashMap tem um requisito fixo, ou seja, deve ser uma potência de 2.
Portanto, se loadFactor for 3/4, o produto de capacidade e capacidade pode ser um número inteiro.
Portanto, em circunstâncias normais, não recomendamos modificar o valor de loadFactor, a menos que haja motivos especiais.
Por exemplo, se eu sei claramente que meu mapa salva apenas 5 kv e nunca mudará, posso considerar a especificação de loadFactor.
Mas, na verdade, não recomendo isso. Podemos atingir esse objetivo especificando a capacidade. Para obter detalhes, consulte o Manual de Desenvolvimento do Alibaba Java sugerindo definir a capacidade inicial ao criar um HashMap, mas quanto é apropriado?
Materiais de referência:
https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap
https://docs.oracle.com/javase/6/docs/api/ java / util / HashMap.html
https://preshing.com/20110504/hash-collision-probabilities/
Sobre o autor: Hollis, tem uma busca única por pessoas de codificação, os atuais especialistas técnicos do Alibaba, blogueiro de tecnologia pessoal, artigos técnicos, a quantidade de leitura de toda a rede de dezenas de milhões, "três classes programador" co-autor.

  • MAIS | Mais artigos maravilhosos - Um
    grande tópico decidiu ficar: Por que a sincronização não pode proibir o rearranjo das instruções, mas pode garantir a ordem?
    Conselho de um diretor técnico: por que proficiente em tantas tecnologias ainda não é bom em fazer um projeto?
    Tecnologia Undertow: por que muitos desenvolvedores Spring Boot abandonam o Tomcat
    , o maior site adulto do mundo, e preservam a consciência final da mídia ocidental

Se você gostou deste artigo,
pressione
Original | Eu disse que entendo coleções, e o entrevistador me perguntou por que o fator de carga do HashMap não está definido como 1!  ?
e segure o código QR e siga o Hollis. Encaminhe para o círculo de amigos. Este é o meu maior apoio.
Bom artigo, estou lendo ❤️

Acho que você gosta

Origin blog.51cto.com/13626762/2544190
Recomendado
Clasificación