JavaRush /Java 博客 /Random-ZH /HashMap 在 Java 中如何工作?
gnev
第 24 级

HashMap 在 Java 中如何工作?

已在 Random-ZH 群组中发布
HashMap 在 Java 中如何工作? - 1在面试中,他们经常会问“ HashMap 在 Java 中如何工作?”之类的问题。get”,“HashMap中方法工作的内部机制是什么put?”。在这里我将尝试使用一个简单的示例来解释内部功能。在不涉及太多理论的情况下,我们将从一个示例开始,以便您可以更好地理解并了解方法在 Java 中的工作get原理。put让我们举一个非常简单的例子。我们有一个类Country(英语“国家”),我们将使用类对象Country作为键,并使用该国家的首都名称作为值。下面的例子可以帮助我们理解键值对如何存储在哈希图中。

1. 国家.java

package org.arpit.javapostsforlearning;
public class Country {

 String name;
 long population;

 public Country(String name, long population) {
  super();
  this.name = name;
  this.population = population;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public long getPopulation() {
  return population;
 }
 public void setPopulation(long population) {
  this.population = population;
 }

 // если длина имени в an objectе Country - четное число,
 // то возвращаем 31(любое случайное число), а если нечетное - 95 (любое случайное число).
 // указанный ниже метод - это не самый лучший способ генерации хеш-codeа,
 // но мы воспользуемся им для более четкого понимания хеш-карт.

 @Override
 public int hashCode() {
  if(this.name.length()%2==0)
   return 31;
  else
   return 95;
 }
 @Override
 public boolean equals(Object obj) {

  Country other = (Country) obj;
   if (name.equalsIgnoreCase((other.name)))
   return true;
  return false;
 }
}
如果您想了解和了解有关方法hashcode和等于的更多信息,可以点击此链接

2.HashMapStructure.java(主类)

import java.util.HashMap;
import java.util.Iterator;

public class HashMapStructure {

    /**
     * @author Arpit Mandliya
     */
    public static void main(String[] args) {

        Country india=new Country("India",1000);
        Country japan=new Country("Japan",10000);

        Country france=new Country("France",2000);
        Country russia=new Country("Russia",20000);

        HashMap<country,string> countryCapitalMap=new HashMap<country,string>();
        countryCapitalMap.put(india,"Delhi");
        countryCapitalMap.put(japan,"Tokyo");
        countryCapitalMap.put(france,"Paris");
        countryCapitalMap.put(russia,"Moscow");

        Iterator<country> countryCapitalIter=countryCapitalMap.keySet().iterator();//установите
        //debug-точку на этой строке(23)
        while(countryCapitalIter.hasNext())
        {
            Country countryObj=countryCapitalIter.next();
            String capital=countryCapitalMap.get(countryObj);
            System.out.println(countryObj.getName()+"----"+capital);
            }
        }
}
现在将断点设置在第23行并运行run -> debug as -> java application(译者注——对Eclipse有效)。程序将在第 23 行停止执行,之后右键单击CountryCapitalMap并选择watch。您将看到这样的表格: HashMap 在 Java 中如何工作? - 2这里我们看到以下内容:
  1. 有一个包含Entry[]16 个单元格的数组,名为table

  2. 该数组存储该类的对象Entry。该类HashMap有一个内部类 - Entry。该类的实例是键值对。让我们看一下类结构Entry

  3. static class Entry implements Map.Entry
            {
                    final K key;
                    V value;
                    Entry next;
                    final int hash;
                    ...//продолжение codeа
            }
  4. 每次我们尝试在哈希映射中创建键值对时,都会为该对创建一个类对象Entry,并将其存储在上表中Entry[]。现在您应该想知道该对象将准确写入该表中的哪个位置(在哪个单元格中)。对于键值对中的键,使用 计算哈希码hashcode()。这个哈希码用于计算表单元格数量Entry[]

  5. 现在,如果您查看表的单元格 10,您将看到一个Entry名为 的类对象HashMap$Entry

  6. 我们添加了 4 个键值对,但数组中只有 2 个!!!这是因为如果两个对象具有相同的哈希码,那么它们将存储在同一个单元格中。但如何呢?对象将被存储为链表(LinkedList)。
以下是如何计算键值对的哈希码。
Hashcode for Japan = 95 так How длина слова Japan имеет нечетное количество букв.
Hashcode for India = 95 так How длина слова India имеет нечетное количество букв.
HashCode for Russia = 31 так How длина слова Russia имеет четное количество букв.
HashCode for France = 31 так How длина слова France имеет четное количество букв.
下图将解释链表的思想: HashMap 在 Java 中如何工作? - 3现在您已经了解了哈希映射的结构,让我们继续讨论put和方法get

放:

我们来看看这个方法是如何使用的:
/**
  * Метод связывает указанное meaning с указанным ключом в данной хэш-карте. Если
  * карта до этого уже содержала некоторое meaning, соответствующее этому ключу,
  * то старое meaning заменяется на указанное.
  * @param key
  *            ключ, с которым связывается указанное meaning
  * @param value
  *            meaning, связываемое с указанным ключом
  * @возвращает meaning связанное с <tt>ключом</tt>, or <tt>null</tt>,
  *         если ниHowое meaning не соответствует <tt>ключу</tt>. ( Возврат <tt>null</tt>
  *         может так же говорить о том, что в карте заведомо <tt>null</tt> был связан с
  *         <tt>ключом</tt>.)
  */
 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;
   if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    V oldValue = e.value;
    e.value = value;
    e.recordAccess(this);
    return oldValue;
   }
  }

  modCount++;
  addEntry(hash, key, value, i);
  return null;
 }
现在让我们尝试逐步理解这段代码:
  1. 我们检查对象key是否相等null。如果是,则该对象key将存储在该位置table[0],因为 的哈希码null始终为 0;

  2. 接下来,我们调用该对象的key方法hashcode(),该方法将计算其哈希码。该散列码用于确定将存储类对象的数组单元Entry。有时碰巧这个函数hashcode写得不太熟练,于是JDK开发者创建了一个不同的函数—— hash(),它以之前计算的哈希码作为参数。如果您有兴趣更详细地了解此功能,可以点击链接

  3. indexFor(hash,table.length)用于定义数组中的特定单元格,table其中将定义要存储的类对象Entry

  4. 正如我们在示例中看到的,如果两个对象key具有相同的哈希码(这种情况称为冲突),那么它们将以链表的形式存储。因此,在这个阶段我们迭代我们的列表:

    • 如果新计算的单元格为空,则类对象Entry将直接保存到该单元格中;

    • next如果此单元格已包含某个对象,则迭代到字段等于 的元素null。之后,我们的类对象Entry成为列表中的下一个;

    • 如果我们再次添加同一个对象怎么办key?从逻辑上讲,它应该替换旧值。是的,会这样。equals()在迭代过程中,将使用( ) 方法对键进行比较key.equals(k)。如果结果为 true,则旧值将被当前对象的值替换Entry

得到:

现在我们来看看该方法的应用 得到
/**
  * returns meaning, которое соответствует указанному ключу, or {@code null}, если
  * данная карта не содержит пары с указанным ключом.
  *
  *
  * <p>
  * Более точно, если в данной карте содержится такой ключ {@code k}
  * с соответствующим ему meaningм {@code v}, что {@code (key==null ? k==null : key.equals(k))},
  * то метод возвращает {@code v}; в противном случае возвращается {@code null}.
  * (может быть не более одной такой пары)
  *
  * </p><p>
  * Возвращенное meaning {@code null} не <i>обязательно</i> говорит о том, что
  * в карте нет пары с таким указанным ключом; а возможно, что в карте однозначно
  * указано соответствие этого ключа со meaningм {@code null}.
  * Можно воспользоваться операцией {@link #containsKey containsKey}, чтобы
  * отличить эти два случая
  * @see #put(Object, Object)
  */
 public V get(Object key) {
  if (key == null)
   return getForNullKey();
  int hash = hash(key.hashCode());
  for (Entry<k , V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
   Object k;
   if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
    return e.value;
  }
  return null;
 }
现在您已经了解了 put 方法在哈希图中的工作原理,理解 put 方法的工作原理就 get非常简单了。当您将任何键传递给方法以从哈希映射中获取值时:
  1. 一个东西 Ekey 被测试是否相等 null。如果是,则返回存储在单元格中的对象的值 table[0]

  2. 密钥对象有一个称为hashcode()计算哈希码的方法;

  3. indexFor(hash,table.length)table用于确定从中获取类对象的特定数组单元Entry

  4. 收到数组单元格编号后,table它将迭代列表并使用方法比较键equals()。如果结果为 true,则返回该对象的值Entry,否则返回 - null

要记住的事情:

  • 该类HashMap有一个Entry存储键值对的内部类;

  • 该类的对象Entry存储在一个Entry[ ]名为 的数组中table

  • 数组单元称为桶,存储链表的第一个元素;

  • hashcode()object方法key用于查找该类对象的桶Entry

  • 如果两个对象的key具有相同的哈希码,则它们将被存储在同一个数组桶中table

  • equals()对象的方法key用于确认其唯一性;

  • 根本不使用方法equals()hashcode()对象。value

来源
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION