get
ב- put
HashMap?". כאן אנסה להסביר את הפונקציונליות הפנימית באמצעות דוגמה פשוטה. מבלי להיכנס יותר מדי לתיאוריה, נתחיל עם דוגמה כדי שתוכל להבין טוב יותר ולאחר מכן לראות כיצד שיטות עובדות 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 ובחר לצפות . תראה טבלה כזו: כאן אנו רואים את הדברים הבאים:
-
ישנו מערך
Entry[]
של 16 תאים בשםtable
; -
מערך זה מאחסן אובייקטים של המחלקה
Entry
. לכיתהHashMap
יש כיתה פנימית -Entry
. ומופעים של מחלקה זו הם זוגות מפתח-ערך. בואו נסתכל על מבנה הכיתהEntry
: -
בכל פעם שאנו מנסים ליצור זוג מפתח-ערך במפת hash, אובייקט מחלקה ייווצר עבור אותו זוג
Entry
והוא יאוחסן בטבלה שלמעלהEntry[]
. ועכשיו אתם צריכים לתהות איפה בדיוק בטבלה הזו האובייקט הזה ייכתב (באיזה תא). עבור מפתח בזוג מפתח-ערך, קוד hash מחושב באמצעות ה-hashcode()
. וקוד ה-hash הזה משמש לחישוב מספר תא הטבלהEntry[]
; -
כעת, אם תסתכל בתא 10 של הטבלה, תראה אובייקט מחלקה
Entry
בשםHashMap$Entry
; - הוספנו 4 זוגות מפתח-ערך, אבל יש רק 2 במערך!!! הסיבה לכך היא שאם ל-2 אובייקטים יש את אותו קוד Hash, אז הם יאוחסנו באותו תא. אבל איך? אובייקטים יאוחסנו כרשימה מקושרת (
LinkedList
).
static class Entry implements Map.Entry
{
final K key;
V value;
Entry next;
final int hash;
...//продолжение codeа
}
Hashcode for Japan = 95 так How длина слова Japan имеет нечетное количество букв.
Hashcode for India = 95 так How длина слова India имеет нечетное количество букв.
HashCode for Russia = 31 так How длина слова Russia имеет четное количество букв.
HashCode for France = 31 так How длина слова France имеет четное количество букв.
האיור הבא יסביר את הרעיון של רשימה מקושרת: כעת, לאחר שכבר יש לך הבנה לגבי המבנה של מפות גיבוב, נעבור לשיטות 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;
}
עכשיו בואו ננסה להבין את הקוד הזה צעד אחר צעד:
-
אנו בודקים את
key
השוויון באובייקטnull
. אם כן, אז האובייקטkey
יאוחסן במיקוםtable[0]
מכיוון שקוד ה-hash עבורnull
הוא תמיד 0; -
key
לאחר מכן, אנו קוראים לשיטת האובייקטhashcode()
, שתחשב את קוד ה-hash שלו. קוד ה-hash הזה משמש כדי לקבוע את תא המערך שבו יאוחסן אובייקט המחלקהEntry
. לפעמים קורה שהפונקציה הזוhashcode
לא כתובה במיומנות רבה, אז מפתחי JDK יצרו פונקציה אחרת -hash()
, שלוקחת את קוד ה-hash שחושב קודם לכן כארגומנט. אם אתה מעוניין לקרוא על פונקציה זו ביתר פירוט, אתה יכול לעקוב אחר הקישור ; -
indexFor(hash,table.length)
משמש להגדרת תא ספציפי במערךtable
שבו יוגדר אובייקט מחלקה לאחסוןEntry
; -
כפי שראינו בדוגמה שלנו, אם לשני אובייקטים
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:
-
חפץ
אקי נבחן לשוויון null
. אם כן, אזי הערך של האובייקט המאוחסן בתא יוחזרtable[0]
; -
לאובייקט המפתח יש שיטה שנקראת
hashcode()
שמחשבת את קוד ה-hash; -
indexFor(hash,table.length)
משמש לקביעת תא מערך ספציפיtable
שממנו לקחת אובייקט מחלקהEntry
; -
לאחר קבלת מספר תא המערך,
table
הוא יעבור דרך הרשימה וישווה בין המפתחות בשיטהequals()
. אם התוצאה נכונה, אזי הערך של האובייקט יוחזרEntry
, אחרת -null
.
דברים שכדאי לזכור:
-
לכיתה
HashMap
יש מחלקה פנימיתEntry
המאחסנת זוגות מפתח-ערך; -
אובייקטים של המחלקה
Entry
מאוחסנים במערךEntry[ ]
שנקראtable
; -
תא מערך נקרא דלי ומאחסן את האלמנט הראשון של רשימה מקושרת;
-
שיטת
hashcode()
האובייקטkey
משמשת כדי למצוא את הדלי של אובייקט מחלקה זהEntry
; -
אם למפתחות של שני אובייקטים יש אותו קוד hash, הם יאוחסנו באותו דלי מערך
table
; -
נעשה שימוש בשיטה
equals()
של אובייקטkey
כדי לאשר את ייחודו; -
לא נעשה שימוש כלל בשיטות
equals()
ובאובייקטיםhashcode()
.value
GO TO FULL VERSION