HashMap原理
HashMap是Map的一個常用的子類實現。其實使用散列算法實現的。
HashMap內部維護着一個散列數組(就是一個存放元素的數組),我們稱其爲散列桶,而當我們向HashMap中存入一組鍵值對時,HashMap首先獲取key這個對象的hashcode()方法的返回值,然後使用該值進行一個散列算法,得出一個數字,這個數字就是這組鍵值對要存入散列數組中的下標位置。
那麼得知了下標位置後,HashMap還會查看散列數組當前位置是否包含該元素。(這裏要注意的是,散列數組中每個元素並非是直接存儲鍵值對的,而是存入了一個鏈表,這個鏈表中的每個節點纔是真實保存這組鍵值對的。)檢查是否包含該元素時根據當前要存入的key在當前散列數組對應位置中的鏈表裏是否已經包含這個key,若不包含則將這組鍵值對存入鏈表,否則就替換value。
那麼在獲取元素時,HashMap同樣先根據key的hashcode值進行散列算法,找到它在散列數組中的位置,然後遍歷該位置的鏈表,找到該key所對應的value之後返回。
看到這裏可能有個疑問,鏈表中應該只能存入一個元素,那麼HashMap是如何將key-value存入鏈表的某個節點的呢?實際上,HashMap會將每組鍵值對封裝爲一個Entry的實例,然後將該實例存入鏈表。
如圖所示:
HashMap的存取是依賴於key的hashcode方法的返回值的,而hashcode方法實際上是在Object中定義的。其定義如下:
int hashCode()
代碼如下:
public class HashMapDemo {
public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<String,Integer>();
hashMap.put("one", 1);
hashMap.put("two", 2);
hashMap.put("three", 3);
hashMap.put("four", 4);
hashMap.put("five", 5);
hashMap.put("six", null);
//遍歷Map中的key的hashMap
for(String str : hashMap.keySet()) {
System.out.println(str +":"+ str.hashCode() );
}
}
}
運行結果:
six:113890
four:3149094
one:110182
two:115276
three:110339486
five:3143346
遍歷每一組鍵值對
Set
public class HashMapDemo {
public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<String,Integer>();
hashMap.put("one", 1);
hashMap.put("two", 2);
hashMap.put("three", 3);
hashMap.put("four", 4);
hashMap.put("five", 5);
hashMap.put("six", null);
//遍歷鍵值對
Set<Entry<String, Integer>> set = hashMap.entrySet();
for(Entry<String, Integer> e : set) {
System.out.print("key:"+e.getKey()+"\t");
System.out.println("value:"+e.getValue());
}
}
}
運行結果:
key:six value:null
key:four value:4
key:one value:1
key:two value:2
key:three value:3
key:five value:5
重寫一個類的hashcode()方法有以下注意事項:
1、若一個類重寫了equals方法,那麼就應當重寫hashcode()方法。
2、若兩個對象的equals方法比較爲true,那麼它們應當具有相同的hashcode值。
3、對於同一個對象而言,在內容沒有發生改變的情況下,多次調用hashCode()方法應當總是返回相同的值。
4、對於兩個對象equals比較爲false的,並不要求其hashcode值一定不同,但是應儘量保證不同,這樣可以提高散列表性能。
代碼如下:
public class HashMapDemo {
public static void main(String[] args) {
Map<Card, Person> map = new HashMap<Card, Person>();
Person p1 = new Person(new Card("001"),"張三");
Person p2 = new Person(new Card("002"),"李四");
map.put(p1.getCard(), p1);
map.put(p2.getCard(), p2);
System.out.println("HashMap 中存放的人員信息:\n"+map);
//張三改名爲張山,身份證號不變。
Person p3 = new Person(new Card("001"),"張山");
map.put(p3.getCard(), p3);
System.out.println("張三改名爲張山後 HashMap 中存放的人員信息:\n"+map);
//查找身份證爲001 的人員信息
System.out.println("查找身份證爲:001 的人員信息:"+map.get(new Card("001")));
}
}
class Card {
private final String IdCard;
public Card(String idCard) {
super();
IdCard = idCard;
}
public String getIdCard() {
return IdCard;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((IdCard == null) ? 0 : IdCard.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
// 如果地址一樣,則兩個對象相同
if (this == obj) {
return true;
}
// 如果兩個對象是同一類型,則比較其屬性值是否都相同
//如果都相同,則說明兩個對象也相同;
//否則,說明這兩個對象不相同。
if(obj instanceof Card) {
Card card = (Card)obj;
return card.IdCard .equals(this.IdCard);
}
return false;
}
@Override
public String toString() {
return "Card [IdCard=" + IdCard + "]";
}
}
class Person {
private Card card;
private String name;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((card == null) ? 0 : card.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/**
* 重寫equals()方法 當兩個人得身份證號相同以及姓名相同時,表示這兩個人是同一個人。
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Person) {
Person p = (Person) obj;
return this.card.equals(p.card) && this.name.equals(p.name);
}
return false;
}
public Person(Card card, String name) {
super();
this.card = card;
this.name = name;
}
public Card getCard() {
return card;
}
public void setCard(Card card) {
this.card = card;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}
運行結果:
HashMap 中存放的人員信息:
{Card [IdCard=001]=Person [name=張三], Card [IdCard=002]=Person [name=李四]}
張三改名爲張山後 HashMap 中存放的人員信息:
{Card [IdCard=001]=Person [name=張山], Card [IdCard=002]=Person [name=李四]}
查找身份證爲:001 的人員信息:Person [name=張山]
裝載因子及HashMap優化
在散列表中有一下名詞需要了解:
Capacity:容量, hash表裏bucket(桶)的數量, 也就是散列數組大小.
Initial capacity:初始容量, 創建hash表的時 初始bucket的數量, 默認構建容量是16. 也可以使用特定容量.
Size : 大小, 當前散列表中存儲數據的數量.
Load factor:加載因子, 默認值0.75(就是75%), 向散列表增加數據時如果 size/capacity 的值大於Load factor則發生擴容並且重新散列(rehash).
那麼當加載因子較小時候散列查找性能會提高, 同時也浪費了散列桶空間容量. 0.75是性能和空間相對平衡結果. 在創建散列表時候指定合理容量, 從而可以減少rehash提高性能。
例如:
Map<String, Integer> map = new HashMap<String, Integer>(10000);
存入大量數據時可以使用該方法,減少擴容次數,提高性能
如果數據量很小,不推薦使用此方法,會造成空間浪費,用默認無參的即可
LinkedHashMap實現有序的Map
Map 接口的哈希表和鏈表實現,具有可預知的迭代順序。此實現與 HashMap 的不同之處在於,LinkedHashMap維護着一個雙向循環鏈表。此鏈表定義了迭代順序,該迭代順序通常就是將存放元素的順序。
需要注意的是,如果在Map中重新存入以有的key,那麼key的位置會不會發生改變,只是將value值替換。
總結:
HashMap和LinkedHashMap區別在於HashMap是無序的,LinkedHashMap是有序的。在使用HashMap時,要根據實際情況選擇最優的解決方案,降低性能和空間上的浪費。