大數據

Map接口 | 帶你學《Java語言高級特性》之一百零七

上一篇:集合輸出 | 帶你學《Java語言高級特性》之一百零六
【本節目標】
本節介紹了Map接口以及它的子類HashMap、LinkedHashMap、HashTable等,需要掌握他們的基本用法。

Map接口

之前已經學習了Collection接口以及其對應的子接口,可以發現在Collection接口之中所保存的數據全部都只是單個對象,而在數據結構中除了可以進行單個對象的保存外,也可以進行二元偶對象的保存(key=value)的形式來存儲,而存儲二元偶對象的核心意義在於需要通過key獲取對應的value。

在開發中,Collection集合保存數據的目的是為了輸出,Map集合保存數據的目的是為了進行key的查找。

Map接口是進行二元偶對象保存的最大父接口。該接口定義如下:

public interface Map<K,V>

該接口為一個獨立的父接口,並且在進行接口對象實例化的時候需要設置Key與Value的類型,也就是在整體操作的時候需要保存兩個內容,在Map接口中定義有許多操作方法,但是需要記住以下的核心操作方法:

No. 方法名稱 類型 描述
01 public V put​(K key,V value) 普通 向集合中保存數據
02 public V get​(Object key) 普通 根據key查詢數據
03 public Set<Map.Entry<K,V>> entrySet() 普通 將Map集合轉為Set集合
04 public boolean containsKey​(Object key) 普通 查詢指定的key是否存在
05 public Set<K> keySet() 普通 將Map集合中的key轉為Set集合
06 public V remove​(Object key) 普通 根據key刪除指定的數據

從JDK1.9之後Map接口裡面也擴充了一些靜態方法供用戶使用。
範例:觀察Map集合的特點

import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
//        Map<String,Integer> map=Map.of("one",1,"two",2);
//        System.out.println(map);     //{one=1,two=2}
//       
//        Map<String,Integer> map=Map.of("one",1,"two",2,"one",101);
//        System.out.println(map);    //Exception in thread "main" java.lang.IllegalArgumentException: duplicate key: one
        
        Map<String,Integer> map=Map.of("one",1,"two",2,null,0);
        System.out.println(map);  //Exception in thread "main" java.lang.NullPointerException
    }
}

在Map集合之中數據的保存就是按照“key=value”的形式存儲的,並且使用of()方法操作時裡面的數據是不允許重複,如果重複則會出現“IllegalArgumentException”異常,如果設置的內容為null,則會出現“NullPointerException”異常。

對於現在見到的of()方法嚴格意義上來說並不是Map集合的標準用法,因為正常的開發中需要通過Map集合的子類來進行接口對象的實例化,而常用的子類:HashMap、HashTable、TreeMap、LinkedHashMap。

HashMap子類

HashMap是Map接口中最為常見的一個子類,該類的主要特點是無序存儲,通過Java文檔首先來觀察一下HashMap子類的定義:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

該類的定義繼承形式符合之前的集合定義形式,依然提供有抽象類並且依然需要重複實現Map接口。

image.png
HashMap子類

範例:觀察Map集合的使用

import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String,Integer> map = new HashMap<String,Integer>();
        map.put("one",1);
        map.put("two",2);
        map.put("one",101);    //key重複
        map.put(null,0);       //key為null
        map.put("zero",null);    //value為null
        System.out.println(map.get("one"));    //key存在:101
        System.out.println(map.get(null));    //key存在:0
        System.out.println(map.get("ten"));     //key不存在:null
    }
}

以上的操作形式為Map集合使用的最標準的處理形式,通過代碼可以發現,通過HashMap實例化的Map接口可以針對key或者value保存null的數據,同時也可以發現即便保存數據的key重複,那麼也不會出現錯誤,而是出現內容的替換。

但是對於Map接口中提供的put()方法本身是提供有返回值的,那麼這個返回值指的是在重複key的情況下返回舊的value。
範例:觀察put()方法

import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new HashMap<String,Integer>();
        System.out.println(map.put("one", 1));    //key不重複,返回null:null
        System.out.println(map.put("one", 101));   //key重複,返回舊數據:1
    }
}

在設置了相同key的內容的時候,put()方法會返回原始的數據內容。

清楚了HashMap的基本功能之後,接下來就需要研究一下HashMap中給出的源代碼。HashMap之中肯定需要存儲大量的數據,那麼對於數據的存儲,來看看HashMap是怎樣操作的:

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

當使用無參構造的時候,會出現有一個loadFactor屬性,並且該屬性默認的內容為“0.75”(static final float DEFAULT_LOAD_FACTOR = 0.75f;

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

在使用put()方法進行數據保存時會調用一個putVal()方法,同時會將key進行hash處理(生成一個hash碼),而對於putVal()方法中會發現會提供一個Node節點類進行數據的保存,而在使用putVal()方法操作的過程中,會調用一個resize()方法可以進行容量的擴充。

面試題:在進行HashMap的put()操作時,如何實現容量擴充?

  • 在HashMap類中提供了一個“DEFAULT_INITIAL_CAPACITY”的常量,作為初始化的容量配置,而這個常量的默認大小為16個元素,也就是說默認的可以保存的最大內容是16;
  • 當保存的內容的容量超過了一個閾值(DEFAULT_LOAD_FACTOR=0.75f),相當於“容量*閾值=12”保存12個元素的時候就會進行容量的擴充;
  • 在進行擴充的時候HashMap採用的是成倍的擴充模式,即:每一次都擴充2倍的容量。

面試題:請解釋HashMap的工作原理(JDK1.8之後開始的)

  • 在HashMap中進行數據存儲依然是利用Node類完成的,那麼這種情況下就證明可以使用的數據結構只有兩種:鏈表(時間複雜度“O(n)”)、二叉樹(時間複雜度“O(logn)”);
  • 從JDK1.8開始,HashMap的實現出現了改變,因為其要適應於大數據時代的海量數據問題,所以對其存儲發生了變化,並且在HashMap類的內部提供有一個常量:“static final int TREEIFY_THRESHOLD = 8;”,在使用HashMap進行數據保存時,如果保存的數據沒有超過閾值8(TREEIFY_THRESHOLD),那麼會按照鏈表的形式進行存儲,如果超過了閾值,則會將鏈表轉為紅黑樹以實現樹的平衡,並且利用左旋與右旋保證數據的查詢性能。

LinkedHashMap子類

HashMap雖然是Map集合中最為常用的子類,但是其本身保存的數據都是無序的(有序與否對Map沒有影響),如果現在希望Map集合中的保存的數據的順序為其增加順序,則就可以更換子類為LinkedHashMap(基於鏈表實現的),觀察LinkedHashMap類的定義形式:

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

既然是鏈表保存,所以一般在使用LinkedHashMap類時數據量不要特別大,因為會造成時間複雜度攀升,通過繼承的結構可以發現LinkedHashMap是HashMap的子類,繼承關係如下:

image.png
LinkedHashMap

範例:使用LinkedHashMap

import java.util.LinkedHashMap;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new LinkedHashMap<String, Integer>();
        map.put("one", 1);
        map.put("two", 2);
        map.put("one", 101);
        map.put("null", 0);
        map.put("zero", null);
        System.out.println(map);    //{one=101, two=2, null=0, zero=null}
    }
}

通過此時的程序執行可以發現當使用LinkedHashMap進行存儲之後所有數據的保存順序為添加順序。

HashTable子類

HashTable類是從JDK1.0時提供的,與Vector、Enumeration屬於最早的一批動態數組的實現類,後來為了將其繼續保留下來,所以讓其多實現了一個Map接口,HashTable類的定義如下:

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable

HashTable的繼承結構如下:

image.png
HashTable子類

範例:觀察HashTable子類的使用

import java.util.Hashtable;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new Hashtable<String,Integer>();
        map.put("one", 1);
        map.put("two", 2);
        map.put("one", 101);
        // map.put(null, 0);     //不能為空
        // map.put("zero",null);   //不能為空,Exception in thread "main" java.lang.NullPointerException
        System.out.println(map);  // {two=2, one=101}
    }
}

通過觀察可以發現在HashTable中進行數據存儲時設置的key或value都不允許為null,否則會出現NullPointerException異常。

面試題:請解釋HashMap與HashTable的區別?

  • HashMap中的方法都屬於異步操作,非線程安全,HashMap允許保存有null的數據;
  • HashTable都屬於同步方法(線程安全),HashTable不允許保存null,否則會出現NullPointerException異常;

想學習更多的Java的課程嗎?從小白到大神,從入門到精通,更多精彩不容錯過!免費為您提供更多的學習資源。
本內容視頻來源於阿里雲大學

更多Java面向對象編程文章查看此處

Leave a Reply

Your email address will not be published. Required fields are marked *