Java基礎-HashMap集合
HashMap
Map介面的常用方法
Map介面中常用的方法
1.Map和Collection沒有繼承關係
2.Map集合以key和value的方式儲存資料:鍵值對
key和value都是引用型別
key和value都是儲存物件的地址。
key起到主導的地位,value是key的一個附屬品。
Map介面常用的方法
V put(k key,v value); 向Map集合中新增鍵值對
V get(Object key) 通過key獲取value
void clear(); 清空Map集合
boolean containsKey(Object key) 判斷Map中是否包含某個key
boolean isEmpty() 判斷Map集合中元素個數是否為0
Set keySet() 獲取Map集合中所有的key(所有鍵是一個set集合)
v remove(Object key) 通過key來刪除鍵值對
int size() 獲取Map集合中鍵值對的個數
Collection values() 獲取Map集合中所有的value,返回一個collection集合
set<Map,Entry<K,V>> entrySet() 將Map集合轉換成set集合
對於最後一個方法的解釋:
假設現在有一個Map集合,如下所示
map1集合
key | value |
---|---|
1 | zhangsan |
2 | lisi |
3 | wangwu |
4 | zhaoliu |
Set set=map1.entrySet();
set集合物件
1=zhangsan
2=lisi
3=wangwu
4=zhaoliu
注意:Map集合通過entrySet()方法轉換成的這個set集合,set集合中元素的型別是Map.Entry<K,V>
Map.Entry和String一樣,都是一種型別的名字,只不過Map.entry是靜態內部類,是Map中的
靜態內部類的理解
public class MyClass {
//宣告一個靜態內部類
private static class InnerClass{
//靜態方法
public static void m1(){
System.out.println("靜態內部類的m1方法執行");
}
//例項方法
public void m2(){
System.out.println("靜態內部類的m2方法執行");
}
}
public static void main(String[] args) {
//類名叫做MyClass.InnerClass
MyClass.InnerClass.m1();
MyClass.InnerClass mi=new MyClass.InnerClass();
mi.m2();
//給一個Set集合
//該set集合中儲存的物件是MyClass.InnerClass型別
Set<MyClass.InnerClass> =new HashSet<>();
//該set集合中儲存的物件是String型別
Set<String> set2=new HashSet<>();
}
public class MapTest01 {
public static void main(String[] args) {
//建立Map集合物件
Map<Integer,String> map=new HashMap<>();
//向Map集合中新增鍵值對
map.put(1,"zhangsan");
map.put(1,"lisi");
map.put(1,"wangwu");
map.put(1,"wangliu");
//通過key獲取value
String value=map.get(2);
System.out.println(value);
//獲取鍵值對的數量
System.out.println("鍵值對的數量"+map.size());
//通過key刪除key-value
map.remove(2);
System.out.println("刪除後鍵值對的數量"+map.size());
//contains方法底層呼叫的都是equals方法,所以自定義型別需要重寫equals方法
//判斷是否包含某個key
System.out.println(map.containsKey(4));
//判斷是否包含某個value
System.out.println(map.containsValue("wangwu"));
//獲取所有的value
Collection<String> values=map.values();
for(String s:values){
System.out.println(s);
}
//清空map集合
map.clear();
//判斷是否為空
System.out.println(map.isEmpty()); //true
}
}
2.Map集合的遍歷
第一種方式:獲取所有的key,通過遍歷key,來遍歷value
public class MapTest02 {
public static void main(String[] args) {
//第一種方式:獲取所有的key,通過遍歷key,來遍歷value
Map<Integer,String> map=new HashMap<>();
map.put(1,"zhangsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"wangliu");
map.put(1,"zhouqi");
//遍歷map集合
//獲取所有的key,所有的key其實是一個set集合
Set<Integer> keys=map.keySet();
//遍歷key,通過key獲取value
//迭代器可以
// Iterator<Integer> it=keys.iterator();
// while(it.hasNext()){
// //取出其中的一個key
// Integer key=it.next();
// //通過ke'y獲取value
// String value=map.get(key);
// System.out.println(key+"="+value);
// }
//foreach也可以
for(Integer key:keys){
System.out.println(key+"="+map.get(key));
}
}
}
輸出
1=zhouqi
2=lisi
3=wangwu
4=wangliu
第二種方式:Set<Map,Entry<K,V>> entrySet();
以上這個方法是把Map集合直接全部轉化為set集合
//set集合中的元素型別是Map.Entry
//Map.Entry有Integer key屬性和String value屬性
Set<Map.Entry<Integer, String>> set=map.entrySet();
//遍歷set集合,每一次取出一個node
//迭代器
Iterator <Map.Entry<Integer, String>> it2=set.iterator();
while(it2.hasNext()){
Map.Entry<Integer, String> node=it2.next();
Integer key=node.getKey();
String value=node.getValue();
System.out.println(key+"="+value);
}
如果用foreach怎麼做呢?
3.雜湊表資料結構
陣列和單向連結串列的結合體。
1.hashMap集合底層是雜湊表/散列表的資料結構
2.HashMap是一個怎樣的資料結構呢?
雜湊表是一個數組和單向連結串列的集合體
陣列:在查詢方面效率很高,隨機增刪方面效率很低
單向連結串列:在隨機增刪方面效率較高,在查詢方面效率較低。
雜湊表將以上的兩種資料結構融合在一起,充分發揮他們各自的長處。
類似於
Node[] nodes;
class Node{
Object k;
Object v;
Node next;
}
3.HashMap集合底層的原始碼
public class HashMap{
//HashMap底層實際上就是一個一維陣列
Node<K,V> table;
//靜態的記憶體類HashMap.Node
static class Node<K,V>{
final int hash; //雜湊值(雜湊值是key的hashCode()方法的執行結果。hash值經過雜湊函式/演算法可以轉化為陣列的下標)
final K key; //儲存到Map集合的key
V value; //儲存到Map集合中的哪個value
Node<K,V> next; //下一個節點的記憶體地址
}
}
雜湊表/散列表:一維陣列,這個陣列中每一個元素是一個單向連結串列(陣列和連結串列的結合體)
4.最主要掌握的是map.put(k,v);v=map.get(k);的原理
map.put(k,v)實現原理
第一步:先將k,v封裝到node物件中
第二步:底層會呼叫key的hashCode()方法得出雜湊值,然後通過雜湊函式/雜湊演算法,將hash值轉換成陣列的下標,下標位置上如果沒有任何的元素,就把node新增到這個位置上,如果說下標對應的位置上有資料或者有連結串列。此時會拿著k和連結串列中的每一個結點的k進行equals,如果所有的equals方法返回都是false,那麼這個新節點將會本地新增到連結串列的末尾,如果其中有一個equals方法true,那麼這個結點的value將會被覆蓋。
單向連結串列:查詢效率比較低。
比如,從字典中找到 中 這個字
第一種方式從第一頁開始一頁一頁的找,直到找到為止
第二種方式:從目錄中找zhong漢語拼音對應的頁碼,假設這個頁碼對應的頁數是360頁,從360頁挨著一頁一頁的找,最終找了5頁,在365頁找到了。
第二種方式的效率比較高,因為縮小了掃描範圍。
v=map.get(k)實現原理
先呼叫k的hashcode()方法得出雜湊值,通過雜湊演算法轉換成陣列下標,通過陣列下標快速定位某個位置上。如果這個位置上什麼都沒有,返回null,如果這個位置上有單向連結串列,那麼會拿著引數k和單向連結串列上的每個結點中的k進行equals,如果所有的方法返回false,那麼get方法返回null,只要有一個結點的k和引數k的equals方法返回true,那麼此時這個結點的value就是我們要找的value,get方法最終返回我們要找的value.
為什麼雜湊表的隨機增刪,自己查詢效率都很高
增刪是在連結串列上完成。
查詢也不需要都掃描,只需要部分掃描即可。
重點:通過講解可以得出HashMap集合的key,會先後調動兩個方法,一個方法是hashcode(),一個方法是equals,那麼這兩個方法都需要重寫。
為什麼放在HashMap集合key部分的元素需要重寫equals方法呢?
equals預設比較的是兩個物件的記憶體地址。我們應該比較內容。
5.HashMap的key部分
無序不可重複
為什麼無序?因為不一定掛到哪個單向連結串列上。
不可重複如何保證?equals方法來保證HashMap集合的key不可重複。如果key重複了,value會覆蓋。
放在HashMap集合key部分的元素其實就是放在HashSet集合中了。
所以HashSet集合中的元素也需要同時重寫hashcode()和equals方法()
注意:同一個單向連結串列上,所有結點的雜湊相同,因為他們的陣列下標是一樣的。在同一個連結串列上k和k的equals方法肯定返回的是false.都不相等。
6.雜湊表HashMap使用不當時無法發揮其效能。
假設將所有的hashcode()方法的返回值固定為某個值,那麼會導致底層雜湊表變成了單向連結串列,這種情況我們稱為:雜湊分佈均勻
假設有100個元素,10個單向連結串列,那麼每個單向連結串列上有10個結點,這是最好的,是雜湊分佈均勻的
假設所有的hashcode()方法返回值都設定為不一樣的值,可以嗎,有什麼問題?
不行,因為這樣的話導致雜湊連結串列就成為一維陣列了,沒有連結串列的概念了。
這種情況也稱為雜湊分佈不均勻。
7.重點:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同時重寫hashcode和equals方法
同時重寫hashcode和equals方法
8.HashMap集合的預設初始化容量是16,預設載入因為是0.75,這個預設因子是當HashMap集合底層陣列的容量達到75%的時候,陣列開始擴容。
HashMap集合初始化容量必須是2的倍數,這也是官方推薦的
這是因為達到雜湊均勻,為了提高hashMap集合的存取效率,所必須的。
public static void main(String[] args) {
//測試HashMap集合key部分元素的特點
//Integer是key,重寫了hashCode和equals方法
Map<Integer,String> map=new HashMap<>();
map.put(1111,"zhangsan");
map.put(6666,"lisi");
map.put(7777,"wangwu");
map.put(2222,"zhaoliu");
map.put(2222,"zhangsan"); //key重複的時候,value會自動覆蓋
System.out.println(map.size()); //輸出4
//遍歷map集合
Set<Map.Entry<Integer,String>> set=map.entrySet();
for(Map.Entry<Integer,String> entry:set){
//驗證結果:HashMap集合元素無序不可重複
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
4
7777=wangwu
1111=zhangsan
6666=lisi
2222=zhangsan
向Map集合中存,以及從Map集合中取,都是先呼叫key的hashcode方法,然後再呼叫equals方法
equals方法可能呼叫,也有可能不呼叫
拿put(k,v)舉例,什麼時候equals不會呼叫
k.hashcode()方法返回雜湊值
雜湊值經過雜湊演算法轉換成陣列下標。
陣列下標位置上如果是null,equals不需要執行
拿get(k)舉例,什麼時候equals不會呼叫
陣列下標位置上如果是null,equals不需要執行
class Student{
private String name;
public Student(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(String name) {
super();
this.name = name;
}
public boolean equals(Object obj){
if(obj==null || (obj instanceof Student))
return false;
if(obj==this)
return true;
Student s=(Student)obj;
return this.name.equals(s.name);
}
}
public class HashMapTest04 {
public static void main(String[] args) {
Student s1=new Student("zhangsan");
Student s2=new Student("zhangsan");
//重寫equals方法之前是false
System.out.println(s1.equals(s2)); //false
//重寫equals方法之後是true
System.out.println(s1.equals(s2)); //true
System.out.println("s1的hashcode="+s1.hashCode()); //366712642
System.out.println("s2的hashcode="+s2.hashCode()); //1829164700
//s1.equals(s2)結果已經使true了,表示s1和s2是一樣的,那麼往hashset集合中放的話
//按說只能放一個,(hashset特點,無序不可重複)
Set<Student> students=new HashSet<>();
students.add(s1);
students.add(s2);
System.out.println(students.size()); //這個結果應該是1
}
}
輸出
false
false
s1的hashcode=517938326
s2的hashcode=914424520
2
這個結果按說是1,但是結果是2,顯然顯然不符合hashset集合儲存的特點。
注意:如果一個類的equals方法重寫了,那麼hashcode()方法必須重寫,並且equals方法返回結果
如果是true,hashcode()方法返回的值必須一致。
equals方法返回true,表示兩個物件相同。
在同一個單向連結串列上比較,那麼對於同一個單向連結串列的結點來說,他們的雜湊值是相同的,所以hashcode()方法的返回值也應該相同。
hashcode()方法和equals()方法不需要研究了,可以直接使用eclipse或者idea工具生成,但是這兩個方法必須同時生成。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
輸出
true
true
s1的hashcode=-1432604525
s2的hashcode=-1432604525
1
結論
放在hashMap集合key部分的,以及放在hashset集合中的元素,需要同時重寫hashcode方法和equals方法。
4.Java8對HashMap的改進
如果雜湊表的單向連結串列上超過8個元素,再往下面存的時候,單向連結串列這種資料結構會變成紅黑樹資料結構,當紅黑樹上的節點數量小於6,會重新把紅黑樹變成單向連結串列資料結構。因為樹的查詢效率比較高。這種方式也是為了提高檢索效率,二叉樹的搜尋會再次縮小檢索範圍。提高效率。
初始化容量16,預設載入因子是0.75
5.HashMap和HashTable的區別
HashMap集合的key部分允許為null嗎
允許
但是要注意hashMap的key null值只能有一個
public static void main(String[] args) {
Map map=new HashMap();
//HashMap集合允許key為null
map.put(null,null);
System.out.println(map.size()); //輸出 1
//key重複的話value會覆蓋
map.put(null,100);
System.out.println(map.size()); //輸出 1
//通過key獲取value
System.out.println(map.get(null)); //100
}
HashMapkey可以為 null
hashtable的key和value都是不能為null的
hashMap集合的key和value都是可以為null的
Hashtable方法都帶有sychronized,是執行緒安全的
執行緒安全有其他的方案,這個hashtable對執行緒的處理導致效率較低,使用較少了
hashtable和hashmap底層都是雜湊表資料結構
Map map=new Hashtable();
map.put(null,"124");
map.put(1,null);
Exception in thread "main" java.lang.NullPointerException
at java.util.Hashtable.put(Hashtable.java:465)
at testCollection.HashTable.main(HashTable.java:16)
hashset集合初始化容量是16,初始化容量建議是2的倍數,擴容之後是原容量的2倍。