Acredito que a maioria das pessoas está familiarizada com a estrutura de dados da tabela de hash e, em muitos lugares, as tabelas de hash são usadas para melhorar a eficiência da pesquisa. Existe um método na classe de objeto de Java:
public nativo int hashcode ();
De acordo com a declaração desse método, pode -se observar que o método retorna um valor numérico do tipo int e é um método local, portanto, nenhuma implementação específica é fornecida na classe de objeto.
Por que a classe de objeto precisa desse método? Qual é a sua função? Hoje discutiremos o método HashCode em detalhes.
1. A função do método HashCode
Para linguagens de programação que contêm tipos de contêineres, o HashCode está basicamente envolvido. O mesmo é verdadeiro em Java. A principal função do método HashCode é funcionar normalmente com conjuntos baseados em hash, como os conjuntos de hash incluem hashset, hashmap e hashtable.
Por que você diz isso? Considere uma situação em que quando um objeto é inserido em uma coleção, como determinar se o objeto já existe na coleção? (Nota: elementos duplicados não são permitidos na coleção)
Talvez a maioria das pessoas pense em chamar o método igual para comparar um por um, e esse método é realmente viável. No entanto, se já houver dez mil dados ou mais dados no conjunto, se o método igual for usado para comparar um por um, a eficiência será inevitavelmente um problema. Neste momento, a função do método HashCode é refletida. Quando um novo objeto deve ser adicionado à coleção, o método HashCode do objeto é chamado primeiro para obter o valor correspondente do HashCode. De fato, na implementação específica do hashmap, uma tabela será usada para salvar o valor de hashcode do objeto que foi salvo. Se não houver valor de hashcode na tabela, ele poderá ser armazenado diretamente sem qualquer comparação. Se o valor do HashCode existir, seu método igual será chamado para comparar com o novo elemento. Se o mesmo for verdadeiro, outros endereços não serão armazenados. Portanto, há um problema de resolução de conflitos aqui. Dessa forma, o número de vezes que o método igual é chamado é bastante reduzido. Para simplificar: o método HashCode em Java mapeia as informações relacionadas ao objeto (como o endereço de armazenamento do objeto, o campo do objeto etc.) em um valor numérico de acordo com certas regras, e esse valor é chamado de valor de hash. O código a seguir é a implementação específica do método put em java.util.hashmap:
public V put (K -Key, V valor) {if (key == null) retorna putformullKey (valor); int hash = hash (key.hashcode ()); int i = indexfor (hash, tabela.length); para (entrada <k, v> e = tabela [i]; e! = null; e = e.next) {objeto k; if (e.hash == hash && ((k = e.Key) == key || key.equals (k))) {v OldValue = e.value; E.Value = Value; E.RecordAccess (isto); retornar OldValue; }} modCount ++; addentry (hash, chave, valor, i); retornar nulo; } O método put é usado para adicionar um novo elemento ao hashmap. A partir da implementação específica do método PUT, podemos saber que o método HashCode será chamado primeiro para obter o valor HashCode do elemento e depois verificar se o valor do HashCode existe na tabela. Se existir, chame o método igual a redeterminar se o elemento existe. Se existir, atualize o valor do valor, caso contrário, adicione o novo elemento ao hashmap. A partir daqui, podemos ver que o método HashCode existe para reduzir o número de chamadas para o método igual e, assim, melhorar a eficiência do programa.
Alguns amigos pensam erroneamente que, por padrão, o HashCode retorna o endereço de armazenamento do objeto. De fato, essa visão é incompleta. É verdade que algumas JVMs retornam diretamente o endereço de armazenamento do objeto quando implementadas, mas esse não é o caso na maioria das vezes. Só se pode dizer que o endereço de armazenamento pode estar relacionado a ele. A seguir, é apresentada a implementação da geração de valores de hash hash na JVM de hotspot:
estático embutido intptr_t get_next_hash (thread * self, oop obj) {intptr_t value = 0; if (hashcode == 0) {// Este formulário usa um parque global não guardado rng, //, é possível que dois threads corram e gerem o mesmo RNG. // No sistema MP, teremos muito acesso RW a um global; portanto, o mecanismo // induz muito tráfego de coerência. value = os :: aleatom (); } else if (hashcode == 1) {// Essa variação tem a propriedade de ser estável (idempotent) // entre operações STW. Isso pode ser útil em alguns dos esquemas de sincronização 1-0 //. intptr_t addrbits = intptr_t (obj) >> 3; value = addrbits ^ (addrbits >> 5) ^ gvars.stwrandom; } else if (hashcode == 2) {value = 1; // para teste de sensibilidade} else if (hashcode == 3) {value = ++ gvars.hcsequence; } else if (hashcode == 4) {value = intptr_t (obj); } else {// Esquema Xor-Shift de Marsaglia com estado específico de threads // Esta é provavelmente a melhor implementação geral-vamos // provavelmente faremos disso o padrão em lançamentos futuros. não assinado t = auto-> _ hashstatex; t ^= (t << 11); Auto-> _ hashStatex = auto-> _ hashstatey; Auto-> _ hashstatey = auto-> _ hashstatez; Auto-> _ hashstatez = auto-> _ hashstatew; não assinado v = auto-> _ hashstatew; v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)); Auto-> _ hashstatew = v; valor = v; } value & = markoopdesc :: hash_mask; if (value == 0) valor = 0xbad; assert (valor! = Markoopdesc :: no_hash, "invariante"); Tevent (HASHCODE: Gere); Valor de retorno;}Esta implementação está localizada no arquivo HotSpot/SRC/Share/VM/RunTime/Synchronizer.cpp.
Portanto, algumas pessoas podem dizer que podemos julgar diretamente se dois objetos são iguais com base no valor do HashCode? Certamente não é possível, porque objetos diferentes podem gerar o mesmo valor de hashcode. Embora não seja possível julgar se dois objetos são iguais com base no valor do hashcode, você pode julgar diretamente que os dois objetos não são iguais com base no valor do hashcode. Se os valores de código de hash dos dois objetos não forem iguais, eles devem ser dois objetos diferentes. Se você deseja determinar se dois objetos são realmente iguais, deve usar o método igual.
Ou seja, para dois objetos, se o resultado obtido chamando o método igual é verdadeiro, os valores de código de hash dos dois objetos devem ser iguais;
Se o resultado obtido pelo método Equals for falso, os valores de código de hash dos dois objetos podem não ser diferentes;
Se os valores de código de hash dos dois objetos não forem iguais, o resultado obtido pelo método Equals deve ser falso;
Se os valores de código de hash dos dois objetos forem iguais, o resultado obtido pelo método igual é desconhecido.
2.Equals Método e método HashCode
Em alguns casos, ao projetar uma classe, os programadores precisam reescrever o método igual, como a classe String, mas observe que, ao reescrever o método igual, eles devem reescrever o método HashCode. Por que você diz isso?
Vamos ver um exemplo abaixo:
pacote com.cxh.test1; importar java.util.hashmap; importar java.util.hashset; importar java.util.set; class People {private string name; private Int Age; Pessoas públicas (nome da string, Int Age) {this.name = name; this.age = idade; } public void setage (int Age) {this.age = Age; } @Override public boolean é igual (object obj) {// TODO Method Auto-Gerated Stub Return This.Name.Equals (((People) obj) .Name) && this.age == ((People) obj) .age; }} classe pública principal {public static void main (string [] args) {pessoas p1 = new People ("Jack", 12); System.out.println (p1.hashcode ()); HashMap <People, Integer> hashmap = new Hashmap <People, Integer> (); hashmap.put (p1, 1); System.out.println (hashmap.get (new People ("Jack", 12));}}Aqui, eu apenas reescrevo o método Equals, o que significa que, se duas pessoas os objetos têm o mesmo nome e idade, eles são considerados a mesma pessoa.
A intenção original deste código é produzir o resultado de "1", mas na verdade ele produz "nulo". Por que? O motivo é que você esquece de reescrever o método HashCode ao reescrever o método igual.
Embora dois objetos com o mesmo nome e idade estejam logicamente determinados como iguais (semelhantes à classe String), deve -se saber que, por padrão, o método HashCode mapeia o endereço de armazenamento do objeto. Então não é de surpreender que o resultado da saída do código acima seja "nulo". O motivo é muito simples, o objeto apontado por P1 e
System.out.println (hashmap.get (novas pessoas ("jack", 12)); as novas pessoas ("jack", 12) nesta frase gera dois objetos, e seus endereços de armazenamento devem ser diferentes. A seguir, é apresentada a implementação específica do método GET do hashmap:
public v get (chave do objeto) {if (key == null) return getFornullKey (); int hash = hash (key.hashcode ()); for (entrada <k, v> e = tabela [indexfor (hash, tabela.length)]; e! = null; e = e.next) {objeto k; if (e.hash == hash && ((k = e.Key) == key || key.equals (k))) return e.value; } retornar nulo; }Portanto, quando o hashmap é usado para obter, porque os valores de hashcdoe obtidos são diferentes (observe que o código acima pode obter o mesmo valor de código de hash em alguns casos, mas a probabilidade é relativamente pequena, porque, embora os endereços de armazenamento dos dois objetos sejam diferentes, é possível obter o mesmo valor de hashcode), o valor de retorno e não será executado.
Portanto, se você deseja que o código acima produz o resultado "1", é muito simples. Você só precisa reescrever o método HashCode e tornar o método Equals e HashCode sempre logicamente consistente.
pacote com.cxh.test1; importar java.util.hashmap; importar java.util.hashset; importar java.util.set; classe People {private String Name; private Int Age; Pessoas públicas (nome da string, Int Age) {this.name = name; this.age = idade; } public void setage (int Age) {this.age = Age; } @Override public int hashCode () {// TODO Método Gerado Auto-Generado Stub Return Name.hashCode ()*37+Age; } @Override public boolean é igual (object obj) {// TODO Method Auto-Gerated Stub Return This.Name.Equals (((People) obj) .Name) && this.age == ((People) obj) .age; }} classe pública principal {public static void main (string [] args) {pessoas p1 = new People ("Jack", 12); System.out.println (p1.hashcode ()); HashMap <People, Integer> hashmap = new Hashmap <People, Integer> (); hashmap.put (p1, 1); System.out.println (hashmap.get (novas pessoas ("jack", 12))); }}Dessa forma, o resultado da saída será "1".
A passagem a seguir é extraída do livro Java eficaz:
Durante a execução do programa, desde que as informações utilizadas na operação de comparação do método igual não fossem modificadas, o método HashCode deve retornar consistentemente o mesmo número inteiro.
Se os dois objetos forem iguais de acordo com o método Equals, chamando o método HashCode dos dois objetos deverá retornar o mesmo resultado inteiro.
Se os dois objetos forem comparados a desigualdade de acordo com o método igual, o método HashCode não retorna necessariamente inteiros diferentes.
É fácil entender o segundo e o terceiro artigos, mas o primeiro artigo é frequentemente ignorado. Há também uma passagem semelhante à primeira na página P495 no livro "Java Programming Thought":
"O fator mais importante ao projetar hashCode () é: sempre que você chama hashcode () no mesmo objeto, o mesmo valor deve ser gerado. Se um objeto for adicionado ao hashmap com put () e outro valor de hash deve ser gerado quando é retirado de que o objeto não pode ser obtido. O método hashcode () gerará um código de hash diferente. "
Aqui está um exemplo:
pacote com.cxh.test1; importar java.util.hashmap; importar java.util.hashset; importar java.util.set; class People {private string name; private Int Age; Pessoas públicas (nome da string, Int Age) {this.name = name; this.age = idade; } public void setage (int Age) {this.age = Age; } @Override public int hashCode () {// TODO Método Gerado Auto-Generado Stub Return Name.hashCode ()*37+Age; } @Override public boolean é igual (object obj) {// TODO Method Auto-Gerated Stub Return This.Name.Equals (((People) obj) .Name) && this.age == ((People) obj) .age; }} classe pública principal {public static void main (string [] args) {pessoas p1 = new People ("Jack", 12); System.out.println (p1.hashcode ()); HashMap <People, Integer> hashmap = new Hashmap <People, Integer> (); hashmap.put (p1, 1); P1.setage (13); System.out.println (hashmap.get (p1)); }}O resultado dessa saída de código é "nulo" e todos devem ser claros sobre os motivos.
Portanto, ao projetar métodos e métodos de código de hashcode, se os dados no objeto forem voláteis, é melhor não confiar nesse campo nos métodos iguais de métodos e hashcode.
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.