查找:
- 根據key(關鍵字)查找相應的value(對應值)
Map API
-
Map屬於Java的集合API
-
Map是面向查找而設計的API,查找表。
-
Map API的查找性能非常好
-
Map API提供了根據key查找value的方法
Map接口
-
Map接口定義了根據key查找value的功能,其全部實現類都提供了根據key查找value的功能
-
Map中的key是不可以重複的,value可以重複
-
每個key對應唯一value
-
根據key查找唯一value
-
Map常見實現類有兩個:
-HashMap 利用散列表算法實現的Map
-TreeMap 利用二叉排序樹算法實現Map
HashMap
-
是Map接口的最常用實現類
-
其內部採用了散列表算法
-
HashMap是計算機中查找速度最快的算法
構造器
Map map = new HashMap();
-
向map集合中添加數據,數據用於查找功能
val = map.put(key,value);
eg:
val = map.put("濟南","天氣熱");//第一次添加成功,返回null
val = map.put("濟南","歷城區");//第二次是替換,返回原有的value"天氣熱"
從map中查找數據
語法一:
Map<String,String> map = new HashMap();
public class MapDome1 {
public static void main(String[] a){
/*
==創建一個HashMap
向map中添加數據
從map查找數據**==**
*/
//創建map集合
Map map = new HashMap();
List<String> JN = new ArrayList<>();
JN.add("好熱");
JN.add("好久不下雨");
JN.add("豔陽高照,受不了");
//向map中添加key-value數據
Object val = map.put("JN","天氣好熱");
//第一次添加數據,返回null
System.out.println(val);
//第二次替換數據
val = map.put("JN",JN);
System.out.println(val);
System.out.println(map.toString());
map.put("HF","涼快");
map.put("BJ","熱死");
val = map.get("JN");
System.out.println(val);
System.out.println(map.toString());
}
}
語法二:
Map<String,List> map = new HashMap();
public class MapDome {
public static void main(String[] agr){
Map<String,List> map = new HashMap<>();
List<String> TJ = new LinkedList<>();
TJ.add("這裏是個好地方");
TJ.add("微風不燥");
TJ.add("時光剛好");
TJ.add("流連忘返");
//System.out.println(TJ);
map.put("TJ",TJ);
List<String> BJ = new LinkedList<>();
BJ.add("國之重地");
BJ.add("北方太平");
BJ.add("霧霾嚴重");
BJ.add("肅然起敬");
map.put("BJ",BJ);
Scanner cin = new Scanner(System.in);
System.out.println("請輸入想要查看的城市");
String city = cin.nextLine();
List<String> see = map.get(city);
System.out.println(see);
int num = map.size();
System.out.println(num);
}
}
如果希望一個key對應多個Value,則可以利用List作爲value存儲多個值。
性能測試
- 相對於 LinkedList HashMap具有極高的查詢性能
public class MapTest {
public static void main(String[] arg){
//縱向比較 HashMap 不同數據量時候的查詢性能
test(1000000);
test(10000000);
//橫向比較 HashMap 和 LinkedList的查詢性能
test(10000000);
Linkedtest(10000000);
//橫向比較 HashMap 和 ArrayList的查詢性能
test(10000000);
ArrayListtest(10000000);
}
public static void test(int n){//寫成方法,方便代碼複用
Map<Integer,String> map = new HashMap<>();
for (int i = 0; i <n; i++) {
map.put(i,"n"+i);
}
long t1 = System.nanoTime();
String val = map.get(n-1);
long t2 = System.nanoTime();
System.out.println("Val:"+val+" , time:"+(t2-t1)+"ns");
}
public static void Linkedtest(int n){
List<String> list = new LinkedList<>();
for (int i = 0; i <n; i++) {
list.add("n"+i);
}
long t1 = System.nanoTime();
String str = list.get(n/2);
long t2 = System.nanoTime();
System.out.println("LStr:"+str+" , time:"+(t2-t1)+"ns");
}
public static void ArrayListtest(int n){
List<String> list = new ArrayList<>();
for (int i = 0; i <n; i++) {
list.add("n"+i);
}
long t1 = System.nanoTime();
String str = list.get(n-1);
long t2 = System.nanoTime();
System.out.println("RStr:"+str+" , time:"+(t2-t1)+"ns");
}
}
HashMap工作原理
- LinkedList 在查詢元素時,採用順序查找,當被查詢元素在頭尾時查詢速度快。但是當被查詢元素處於鏈表中部時,則會出現順序查找,此時性能非常差。
- HashMap 利用散列算法可以直接定位元素位置,所以查找性能優異,因爲不是順序查找,所以散列查找不收數據量的影響。
散列算法的工作原理
- 在HashMap內部有一個數組用於存儲key-Value數據
- 在添加數據時,根據Key的hashCode()返回值計算在數組的存儲位置,將Key-Value數據存儲到數組的對應位置
- 根據Key查找時,根據Key的hashCode()計算數組下標位置,直接定位到數組中Key-Value數據,並返回value值。
HashMap中查找的特點:
- 不進行順序查找,直接利用算法定位數組中的下標位置。因爲避免了順序查找,所以查詢性能好!
hashCode()方法與散列表
-
Java爲了在最底層支持散列表(HashMap 哈希表)算法在Object類上定義了hashCode()方法,用於支持散列表算法。
-
hashCode()方法和equals()是一對方法,需要一同重寫!!!!
——如果不成對重寫這兩個方法,會造成HashMap工作故障 -
如果重寫hashCode時要遵守如下規則
1.當兩個對象equals比較相等時,兩個對象必須有一樣的hashCode值。
2.當兩個對象equals比較不相等時,兩個對象的hashCode儘可能不同。
-
Eclipse及其他的開發工具提供了成對生成equals和hashCode方法,利用工具生成即可。
——一般按照核心關鍵屬性比較兩個對象是否相等。
-
Java的API,包括String、Integer都很好的成對的重寫了equals和hashCode。
對象的默認hashCode值不是對象的地址值
HashMap元素添加過程(put)
-
HashMap中有一個數組用於存儲key-value數據對
1.根據key的hashCode值,利用散列算法計算出數組下標位置。
2.找到數組下標位置
如果位置爲null,則直接將key-value存儲到數組中
如果位置上已有數據,則調用key的equals方法比較數組中存在key
>如果key相等,則替換對應的value值 >如果key不等,則找鏈表中後續的元素並且判斷是否相等 >如果key相等,則替換value >如果不相等則將key-value追加到鏈表後部
概括:根據key的hashCode計算數組下標位置,再用equals比較ke是否相等,相等就替換,不等就插入。
HashMap元素的查找過程(get)
1.根據key的hashCode值,利用散列算法計算出數組下標位置。
2.找到數組下標位置
>如果位置爲null,則直接返回null
>如果位置上已有數據,則調用key的equals方法比較數組中存在key
>如果key相等,則返回value數據
>如果key不等,則找鏈表中後續的元素並且判斷是否相等
>如果key相等,則返回value
>如果不相等則返回null
概述:先根據hashCode計算數組下標位置,在根據key的equals比較key,如果找到則返回value,否則返回null。
HashMap相關術語
- HashMap中保存key-value的數組稱爲 散列表
HashMap中的數據按照散列值存儲和添加順序無關。但不是一個隨機順序
- 根據key的hashCode計算數組下標位置的算法稱爲 散列算法經過散列算法得到的數組下標稱爲散列值
HashMap中的散列算法利用二進制運算實現的,性能極好。
- 當散列值相同時,key-value存儲的鏈表結構稱爲散列桶
散列桶中查詢是順序查找,性能不好,原則上要避免散列桶。
- 當key的hashCode值很分散時候,散列桶會變少。
hashCode的實現規則就是一個分散原則。
- HashMap++元素和數組容量的比值++稱爲加載因子,當加載因子小於等於75%的情況下,經過“統計”發現很少出現散列桶,即便出現散列桶也很少超過3層。
- 當向HashMap中添加數據時,其數據和數組容量的比值大於75%時,HashMap會對數組進行擴容,並重新計算元素的散列值,這個過程稱爲重新散列
重新散列會影響散列表添加()性能。
散列表提供了重載構造器,可以預先設置數組的容量,避免重新散列,提高性能。HashMap可以在重載構造器中設置數組容量,用於減少散列。
重載構造器語法:
Map<String,String> map = new HashMap<>(n+n/2);
該方法建議在已知數組容量的時候使用
經典面試題
HashMap如何存儲的?
概括:根據key的hashCode計算數組下標位置,再用equals比較ke是否相等,相等就替換,不等就插入。
概述:先根據hashCode計算數組下標位置,在根據key的equals比較key,如果找到則返回value,否則返回null。
HashMap 用途
- HashMap的查找性能非常好,在軟件開發中儘量將查找功能交給HashMap完成。
- 案例, 利用map緩存Http協議頭信息:
1.創建文件 headers.txt 保存http協議頭信息:
我這裏用的是有道雲筆記首頁的Request Headers
Host: ursdoccdn.nosdn.127.net
Connection: keep-alive
If-None-Match: e36a35c363d892f3c44cfa821427ce36
If-Modified-Since: Mon, 16 Apr 2018 14:57:44 Asia/Shanghai
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/66.0.3359.181 Chrome/66.0.3359.181 Safari/537.36
Accept: */*
Referer: https://note.youdao.com/web/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
2.讀取文件內容到Map:
/**
* 輸出全部頭信息,利用遍歷map可以輸出全部信息
* map沒有直接提供遍歷接口,map間接提供了遍歷功能
* map.entrySet().iterator()
*/
public class headersDome {
public static void main(String[] arg){
try {
BufferedReader br
= new BufferedReader(
new InputStreamReader(
new FileInputStream("./headrs.txt")));
String line = null;
Map<String,String> map = new HashMap<>();
while ((line = br.readLine()) != null){
//line 代表文件中的每一行
//爲了邏輯嚴禁,跳過可能出現的空行
if(line.trim().isEmpty()){
continue;
}
String[] arr = line.split(":\\s");
map.put(arr[0],arr[1]);
}
System.out.println(map);
3.利用map查詢一個協議頭 信息:
//查詢頭信息
String host = map.get("Host");
System.out.println(host);
4.利用map的變量,顯示全部緩存在map中的信息
try {
Set<Map.Entry<String,String>> entries
= map.entrySet();
for (Map.Entry<String,String> e:entries) {
//獲取全部Entry的集合,每個entry對象包含兩個屬性,分別是key和value
//e 代表map集合中的每個key-value對應e.getKey()方法獲取key值
//e.getValue()方法是獲取value值
System.out.println(e.getKey()+":"+e.getValue());
}
} catch (Exception e) {
e.printStackTrace();
}
br.close();
}catch (Exception e){
System.out.println("文件未找到,請覈對文件位置");
}
}
}
5.測試