1. 程式人生 > 其它 >Java基礎-HashMap集合

Java基礎-HashMap集合

技術標籤:集合Javajava

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 containsValue(Object value) 判斷Map中是否包含某個value
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集合

keyvalue
1zhangsan
2lisi
3wangwu
4zhaoliu

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倍。