JavaRush /בלוג Java /Random-HE /איך HashMap עובד ב-Java?
gnev
רָמָה

איך HashMap עובד ב-Java?

פורסם בקבוצה
איך HashMap עובד ב-Java?  - 1לעתים קרובות מאוד בראיונות הם שואלים שאלות כמו " איך HashMap עובד בג'אווה?" ", "מהו המנגנון הפנימי של אופן פעולת השיטות getב- putHashMap?". כאן אנסה להסביר את הפונקציונליות הפנימית באמצעות דוגמה פשוטה. מבלי להיכנס יותר מדי לתיאוריה, נתחיל עם דוגמה כדי שתוכל להבין טוב יותר ולאחר מכן לראות כיצד שיטות עובדות getגם putב-Java . ניקח דוגמה מאוד פשוטה. יש לנו מחלקה Country(באנגלית "מדינה"), נשתמש באובייקט המחלקה Countryכמפתח, ובשם הבירה של המדינה הזו כערך. להלן דוגמה שתעזור לנו להבין כיצד זוג מפתח-ערך יאוחסן במפת hash.

1. Country.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 והריצו הרץ -> באגים כ-> אפליקציית java (הערת המתרגם - תקף עבור Eclipse). התוכנית תפסיק את הביצוע בקו 23, ולאחר מכן לחץ לחיצה ימנית על countryCapitalMap ובחר לצפות . תראה טבלה כזו: איך 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. בכל פעם שאנו מנסים ליצור זוג מפתח-ערך במפת hash, אובייקט מחלקה ייווצר עבור אותו זוג Entryוהוא יאוחסן בטבלה שלמעלה Entry[]. ועכשיו אתם צריכים לתהות איפה בדיוק בטבלה הזו האובייקט הזה ייכתב (באיזה תא). עבור מפתח בזוג מפתח-ערך, קוד hash מחושב באמצעות ה- hashcode(). וקוד ה-hash הזה משמש לחישוב מספר תא הטבלה Entry[];

  5. כעת, אם תסתכל בתא 10 של הטבלה, תראה אובייקט מחלקה Entryבשם HashMap$Entry;

  6. הוספנו 4 זוגות מפתח-ערך, אבל יש רק 2 במערך!!! הסיבה לכך היא שאם ל-2 אובייקטים יש את אותו קוד Hash, אז הם יאוחסנו באותו תא. אבל איך? אובייקטים יאוחסנו כרשימה מקושרת ( LinkedList).
כך יחושב קוד ה-hash עבור צמדי המפתח-ערך שלנו.
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]מכיוון שקוד ה-hash עבור nullהוא תמיד 0;

  2. keyלאחר מכן, אנו קוראים לשיטת האובייקט hashcode(), שתחשב את קוד ה-hash שלו. קוד ה-hash הזה משמש כדי לקבוע את תא המערך שבו יאוחסן אובייקט המחלקה Entry. לפעמים קורה שהפונקציה הזו hashcodeלא כתובה במיומנות רבה, אז מפתחי JDK יצרו פונקציה אחרת - hash(), שלוקחת את קוד ה-hash שחושב קודם לכן כארגומנט. אם אתה מעוניין לקרוא על פונקציה זו ביתר פירוט, אתה יכול לעקוב אחר הקישור ;

  3. indexFor(hash,table.length)משמש להגדרת תא ספציפי במערך tableשבו יוגדר אובייקט מחלקה לאחסון Entry;

  4. כפי שראינו בדוגמה שלנו, אם לשני אובייקטים keyיש קוד hash זהה (מצב זה ידוע בתור התנגשות), אז הם יאוחסנו בצורה של רשימה מקושרת. לכן, בשלב זה אנו חוזרים על הרשימה שלנו:

    • אם התא שחושב זה עתה ריק, אזי אובייקט המחלקה Entryיישמר ישירות בתא זה;

    • אם התא הזה כבר מכיל אובייקט כלשהו, ​​אז חוזר אל האלמנט שהשדה שלו nextשווה ל null. לאחר מכן, אובייקט הכיתה שלנו Entryהופך להיות הבא ברשימה;

    • מה אם נוסיף keyשוב את אותו אובייקט? באופן הגיוני, זה אמור להחליף את הערך הישן. כן, זה יהיה כך. במהלך האיטרציה, המפתחות יושוו בשיטת equals()( key.equals(k)). אם התוצאה נכונה, הערך הישן יוחלף בערך של האובייקט הנוכחי 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 במפות hashmaps, ההבנה כיצד פועלת שיטת ה-put היא getפשוטה מאוד. כאשר אתה מעביר מפתח כלשהו לשיטה כדי לקבל ערך ממפת hash:
  1. חפץ אקי נבחן לשוויון null. אם כן, אזי הערך של האובייקט המאוחסן בתא יוחזר table[0];

  2. לאובייקט המפתח יש שיטה שנקראת hashcode()שמחשבת את קוד ה-hash;

  3. indexFor(hash,table.length)משמש לקביעת תא מערך ספציפי tableשממנו לקחת אובייקט מחלקה Entry;

  4. לאחר קבלת מספר תא המערך, tableהוא יעבור דרך הרשימה וישווה בין המפתחות בשיטה equals(). אם התוצאה נכונה, אזי הערך של האובייקט יוחזר Entry, אחרת - null.

דברים שכדאי לזכור:

  • לכיתה HashMapיש מחלקה פנימית Entryהמאחסנת זוגות מפתח-ערך;

  • אובייקטים של המחלקה Entryמאוחסנים במערך Entry[ ]שנקרא table;

  • תא מערך נקרא דלי ומאחסן את האלמנט הראשון של רשימה מקושרת;

  • שיטת hashcode()האובייקט keyמשמשת כדי למצוא את הדלי של אובייקט מחלקה זה Entry;

  • אם למפתחות של שני אובייקטים יש אותו קוד hash, הם יאוחסנו באותו דלי מערך table;

  • נעשה שימוש בשיטה equals()של ​​אובייקט keyכדי לאשר את ייחודו;

  • לא נעשה שימוש כלל בשיטות equals()ובאובייקטים hashcode().value

מָקוֹר
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION