文章目錄
- Map接口及其實作類
-
- 1. Map接口介紹
- 2. Map接口中常用方法
-
- 2.1 元素的添加 、 删除、修改操作
- 2.2 元素查詢的操作
- 2.3 周遊Map的三種方式☆
- 3. Map接口的實作類
-
- 3.1 HashMap集合
-
-
- HashMap源碼中的重要常量☆
- HashMap的儲存結構☆☆
-
- JDK7
- JDK8
- 小結
- HashMap存儲自定義類型鍵值
-
- Student.java
- 測試類HashMapTest
- LinkedHashMap集合☆
-
- LinkedHashMap的靜态内部類Entry
-
- 3.2 TreeMap集合
- 3.3 HashTable集合
-
-
- Properties類
-
- ☆
Map接口及其實作類
1. Map接口介紹
- 現實生活中,我們常會看到這樣的一種集合:
位址與主機名,身份證号與個人,系統使用者名與系統使用者對象等,這種
IP
的關系,就叫做映射。
一一對應
提供了專門的集合類用來存放這種對象關系的對象,即
Java
接口。
java.util.Map
- Map與Collection并列存在。用于儲存具有
映射關系的資料:key-value。
- Map 中的 key 和 value 都可以是任何引用類型的資料
Map 中的 key 用Set來存放, 不允許重複,即同一個 Map 對象所對應的類,須重寫hashCode()和equals()方法
常用String類作為Map的“鍵”。
- key 和 value 之間存在單向一對一關系,即通過指定的 key 總能找到唯一的、确定的 value。
- Map接口中的集合都有兩個泛型變量,在使用時,要為兩個泛型變量賦予資料類型。兩個泛型變量的資料類型可以相同,也可以不同。
- Map接口的常用實作類:
、
HashMap
、
TreeMap
和
LinkedHashMap
。
Properties
其中HashMap是 Map 接口使用頻率最高的實作類
2. Map接口中常用方法
2.1 元素的添加 、 删除、修改操作
将指定key-value
Object put(Object key,Object value):
目前map對象中。
添加到(或修改)
将m中的所有key-value對存放到目前map中。
void putAll(Map m):
移除指定key的key-value對,并傳回value。
Object remove(Object key):
清空目前map中的所有資料。
void clear():
2.2 元素查詢的操作
擷取指定key對應的value。
Object get(Object key):
是否包含指定的key。
boolean containsKey(Object key):
是否包含指定的value。
boolean containsValue(Object value):
傳回map中key-value對的個數。
int size():
判斷目前map是否為空。
boolean isEmpty():
判斷目前mp和參數對象obj是否相等。
boolean equals(Object obj):
2.3 周遊Map的三種方式☆
: 擷取Map集合中所有的鍵,存儲到Set集合中。
public Set<K> keySet()
- 擷取
中所有的鍵,由于鍵是唯一的,是以傳回一個
Map
集合存儲所有的鍵。方法提示:
Set
keyset()
- 周遊鍵的
集合,得到每一個鍵。
Set
- 根據鍵,擷取鍵所對應的值。方法提示:
get(K key)
傳回所有value構成的Collection集合
Collection values():
: 擷取到Map集合中所有的鍵值對對象的集合(Set集合)。
public Set<Map.Entry<K,V>> entrySet()
- 我們已經知道,
中存放的是兩種對象,一種稱為key(鍵),一種稱為
Map
(值),它們在在
value
中是一一對應關系,這一對對象又稱做
Map
中的一個
Map
(項) 。
Entry
将鍵值對的對應關系封裝成了對象。即鍵值對對象,這樣我們在周遊
Entry
集合時,就可以從每一個鍵值對(
Map
)對象中擷取對應的鍵與對應的值。既然
Entry
表示了一對鍵和值,那麼也同樣提供了擷取對應鍵和對應值得方法:
Entry
- Map集合不能直接使用疊代器或者foreach進行周遊。但是轉成Set之後就可以使用了。
- public K getKey() :擷取Entry對象中的鍵。
- public V getValue() :擷取Entry對象中的值。
/**
* @Date 2020/11/13 17:29
* @Version 10.21
* @Author DuanChaojie
*/
public class TestMap {
public static void main(String[] args) {
// 集合初始化時,指定集合初始值大小
// 說明:HashMap使用如下構造方法進行初始化,如果暫時無法确定集合大小,那麼指定預設值(16)即可
Map map = new HashMap();
//map.put(..,..)省略
System.out.println("map的所有key:");
// HashSet
Set keys = map.keySet();
for (Object key : keys) {
System.out.println(key + "->" + map.get(key));
}
System.out.println("map的所有的value:");
Collection values = map.values();
Iterator iter = values.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
System.out.println("map所有的映射關系:");
// 映射關系的類型是Map.Entry類型,它是Map接口的内部接口
Set mappings = map.entrySet();
for (Object mapping : mappings) {
Map.Entry entry = (Map.Entry) mapping;
System.out.println("key是:" + entry.getKey() + ",value是:" + entry.getValue());
}
}
}
3. Map接口的實作類
3.1 HashMap集合
HashMap是 Map 接口 使用頻率最高的實作類。
- 允許使用null鍵和null值,與HashSet一樣,不保證映射的順序。
所有的key構成的集合是Set:無序的、不可重複的。是以,key所在的類要重寫:equals()和hashCode()。
- 所有的value構成的集合是Collection:無序的、可以重複的。
是以,value所在的類要重寫:equals()。
- 一個key-value構成一個entry
- 所有的entry構成的集合是Set:無序的、不可重複的
- HashMap
:兩個 key 通過 equals() 方法傳回 true,hashCode 值也相等。
判斷兩個 key 相等的标準是
- 因為key不允許有重複。
- HashMap
:兩個 value 通過 equals() 方法傳回 true。
判斷兩個 value 相等的标準是
HashMap源碼中的重要常量☆
HashMap的預設容量,16
DEFAULT_INITIAL_CAPACITY :
HashMap的最大支援容量,2^30
MAXIMUM_CAPACITY :
HashMap的預設加載因子0.75
DEFAULT_LOAD_FACTOR :
Bucket中連結清單長度大于該預設值8,轉化為紅黑樹
TREEIFY_THRESHOLD :
Bucket中紅黑樹存儲的Node小于該預設值6,轉化為連結清單。
UNTREEIFY_THRESHOLD :
桶中的Node被樹化時最小的hash表容量。(當桶中Node的數量大到需要變紅黑樹時,若hash表容量小于MIN_TREEIFY_CAPACITY時,此時應執行resize擴容操作這個MIN_TREEIFY_CAPACITY的值
MIN_TREEIFY_CAPACITY :
至少是TREEIFY_THRESHOLD的4倍。)
(預設為64)
存儲元素的數組,總是2的n次幂
table :
存儲具體元素的集
entrySet:
HashMap中存儲的鍵值對的數量
size :
HashMap擴容和結構改變的次數。
modCount :
擴容的臨界值(吞吐臨界值)12=容量*填充因子
threshold :
填充比(負載因子)。
loadFactor:
- 負載因子值的大小,對HashMap有什麼影響?
- 負載因子的大小決定了HashMap的資料密度。
- 負載因子越大密度越大,發生碰撞的幾率越高,數組中的連結清單越容易長,造成查詢或插入時的比較次數增多,性能會下降。
- 負載因子越小,就越容易觸發擴容,資料密度也越小,意味着發生碰撞的幾率越小,數組中的連結清單也就越短,查詢和插入時比較的次數也越小,性能會更高。但是會浪費一定的内容空間。而且經常擴容也會影響性能,建議初始化預設大一點的空間。
- 按照其他語言的參考及研究經驗,會考慮将負載因子設定為0.7~0.75,此時平均檢索長度接近于常數。
HashMap的儲存結構☆☆
- JDK 7及以前版本:HashMap是數組+連結清單結構(即為鍊位址法)。
- JDK 8版本釋出以後:HashMap是數組+連結清單+紅黑樹實作。
JDK7
- HashMap的内部存儲結構其實是 數組和連結清單的結合。當執行個體化一個HashMap時,系統會建立一個長度為Capacity的Entry數組,這個長度在哈希表中被稱為容量(Capacity),
每個bucket都有自己的索引,系統可以根據索引快速的查找bucket中的元素。
在這個數組中可以存放元素的位置我們稱之為“桶”(bucket),
,但每一個Entry對象可以帶一個引用變量,用于指向下一個元素,是以,在一個桶中,就有可能生成一個Entry鍊。而且新添加的元素作為連結清單的head。
每個bucket中存儲一個元素,即一個Entry對象
HashMap靜态内部類Entry
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; final int hash; Entry<K,V> next; // .... }
- 添加元素的過程:
- 向HashMap中添加
,需要首先計算entry1中key的哈希值(
entry1(key,value)
),此哈希值經過處理以後,得到在底層Entry[]數組中要
根據key所在類的hashCode()計算得到
。
存儲的位置i
- 如果位置i上沒有元素,則entry1直接添加成功。如果位置i上已經存在entry2(或還有連結清單存在的entry3,entry4),則需要通過循環的方法,依次比較entry1中key和其他的entry。如果彼此hash值不同,則直接添加成功。
如果hash值不同,繼續比較二者是否equals。如果傳回值為true,則使用entry1的value
去替換equals為true的entry的value。
- 如果周遊一遍以後,發現所有的equals傳回都為false,則entry1仍可添加成功。entry1指向原有的entry元素。
- HashMap 的擴容:
- 當HashMap中的元素越來越多的時候,hash沖突的幾率也就越來越高,因為數組的長度是固定的。是以為了提高查詢的效率,就要對HashMap的數組進行擴容,
而在HashMap數組擴容之後,最消耗性能的點就出現了:原數組中的資料必須重新計算其在新數組中的位置,并放進去,這就是resize。
- 那麼HashMap 什麼時候進行擴容呢 ?
- 當HashMap中的元素個數超過數組大小(數組總大小length,不是數組中個數size)loadFactor 時 , 就 會 進 行 數 組 擴 容 , loadFactor 的 默 認 值(DEFAULT_LOAD_FACTOR)為0.75,這是一個折中的取值。
- 也就是說,預設情況下,數組大小(DEFAULT_INITIAL_CAPACITY)為16,那麼當HashMap中元素個數超過
(這個值就是代碼中的threshold值,也叫做臨界值)的時候,就把數組的大小擴充為 2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,
16*0.75=12
是以如果我們已經預知HashMap中元素的個數,那麼預設元素的個數能夠有效的提高HashMap的性能。
JDK8
- HashMap的内部存儲結構其實是
的結合。當執行個體化一個HashMap時,會初始化initialCapacity和loadFactor,在put第一對映射關系時,系統會建立一個長度為initialCapacity的
數組+ 連結清單+ 樹
Node數組
,這個長度在哈希表中被稱為容量(Capacity),在這個數組中可以存放元素的位置我們稱之為
“桶”(bucket),每個bucket都有自己的索引,系統可以根據索引快速的查找bucket中的元素。
但每一個Node對象可以帶一個引用變量next,用于指向下一個元素,
每個bucket中存儲一個元素,即一個Node對象,
每一個TreeNode對象可以有兩個葉子結點left和right,是以,在一個桶中,就有可能生成一個TreeNode樹。
是以,在一個桶中,就有可能生成一個Node鍊。也可能是一個一個TreeNode對象,
而新添加的元素作為連結清單的last,或樹的葉子結點。
- HashMap的靜态内部類Node
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
- 那麼HashMap 什麼時候進行擴容和樹形化呢 ?
- 當HashMap中的元素個數超過數組大小(數組總大小length,不是數組中個數size)loadFactor 時 , 就會 進 行 數 組 擴 容 , loadFactor 的 默 認 值(DEFAULT_LOAD_FACTOR)為0.75,這是一個折中的取值。也就是說,預設情況下,數組大小(DEFAULT_INITIAL_CAPACITY)為16,那麼當HashMap中元素個數超過
(這個值就是代碼中的threshold值,也叫做臨界值)的時候,就把數組的大小擴充為 2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,是以如果我們已經預知HashMap中元素的個數,那麼預設元素的個數能夠有效的提高HashMap的性能。
16*0.75=12
當HashMap中的其中一個鍊的對象個數如果達到了8個,此時如果capacity沒有達到64,那麼HashMap會先擴容解決,如果已經達到了64,那麼這個鍊會變成樹,結點類型由Node變成TreeNode類型。當然,如果當映射關系被移除後,下次resize方法時判斷樹的結點個數低于6個,也會把樹再轉為連結清單。
- 如果不了解這段,可以看一下HashMap源碼中的重要常量
。
MIN_TREEIFY_CAPACITY
- 關于映射關系的key 是否可以修改 ?
- 答案是:不要修改!
映射關系存儲到HashMap中會存儲key的hash值,這樣就不用在每次查找時重新計算每一個Entry或Node(TreeNode)的hash值了,是以如果已經put到Map中的映射關系,再修改key的屬性,而這個屬性又參與hashcode值的計算,那麼會導緻比對不上。
小結
JDK1.8相較于之前的變化:
- HashMap map = new HashMap();//預設情況下,先不建立長度為16的數組。
當首次調用map.put()時,再建立長度為16的數組。
數組為Node類型,在jdk7中稱為Entry類型。
形成連結清單結構時,新添加的key-value對在連結清單的尾部(七上八下)。
當數組指定索引位置的連結清單長度>8時,且map中的數組的長度> 64時,此索引位置上的所有key-value對使用紅黑樹進行存儲。
HashMap存儲自定義類型鍵值
- 每位學生(姓名,年齡)都有自己的家庭住址。那麼,既然有對應關系,則将學生對象和家庭住址存儲到
集合中。學生作為鍵, 家庭住址作為值。
map
- 注意,學生姓名相同并且年齡相同視為同一名學生。
Student.java
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
測試類HashMapTest
public class HashMapTest {
public static void main(String[] args) {
//1,建立Hashmap集合對象。
Map<Student,String>map = new HashMap<Student,String>();
//2,添加元素。
map.put(newStudent("lisi",28), "上海");
map.put(newStudent("wangwu",22), "北京");
map.put(newStudent("zhaoliu",24), "成都");
map.put(newStudent("zhouqi",25),"廣州");
map.put(newStudent("wangwu",22), "南京");
//3,取出元素。鍵找值方式
Set<Student>keySet = map.keySet();
for(Student key: keySet){
Stringvalue = map.get(key);
System.out.println(key.toString()+"....."+value);
}
}
}
- 當給
中存放自定義對象時,如果自定義對象作為
HashMap
存在,這時要保證對象唯一,必須複寫對象的
key
和
hashCode
方法(如果忘記,請回顧
equals
存放自定義對象)。
HashSet
- 如果要保證
中存放的
map
和取出的順序一緻,可以使用
key
集合來存放。
java.util.LinkedHashMap
LinkedHashMap集合☆
- 我們知道HashMap保證成對元素唯一,并且查詢速度很快,可是成對元素存放進去是沒有順序的,那麼我們要保證有序,還要速度快怎麼辦呢?
- LinkedHashMap 是 HashMap 的子類,它是連結清單和哈希表組合的一個資料存儲結構。
public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
map.put("鄧超", "孫俪");
map.put("李晨", "範冰冰");
map.put("劉德華", "朱麗倩");
Set<Entry<String, String>> entrySet = map.entrySet();
for (Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
結果:
鄧超 孫俪
李晨 範冰冰
劉德華 朱麗倩
LinkedHashMap的靜态内部類Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
3.2 TreeMap集合
- TreeMap存儲 Key-Value 對時,需要根據 key-value 對進行排序。TreeMap 可以保證所有的 Key-Value 對處于有序狀态。
- TreeSet底層使用 紅黑樹 結構存儲資料。
- TreeMap 的 Key 的排序:
TreeMap 的所有的 Key 必須實作 Comparable 接口,而且所有的 Key 應該是同一個類的對象,否則将會抛出 ClasssCastException。
自然排序:
建立 TreeMap 時,傳入一個 Comparator 對象,該對象負責對TreeMap 中的所有 key 進行排序。此時不需要 Map 的 Key 實作Comparable 接口
定制排序:
TreeMap判斷兩個key 相等的标準:兩個key通過compareTo()方法或者compare()方法傳回0。
3.3 HashTable集合
- HashTable是個古老的 Map 實作類,JDK1.0就提供了。
不同于HashMap,Hashtable是線程安全的。
- Hashtable實作原理和HashMap相同,功能相同。底層都使用哈希表結構,查詢速度快,很多情況下可以互用。
與HashMap不同,Hashtable 不允許使用 null 作為 key 和 value
- 與HashMap一樣,Hashtable 也不能保證其中 Key-Value 對的順序
- Hashtable判斷兩個key相等、兩個value相等的标準,與HashMap一緻。
Properties類
- Properties 類是 Hashtable 的子類,該對象用于處理屬性檔案由于屬性檔案裡的 key、value 都是字元串類型,是以 Properties 裡的 key和 value 都是字元串類型
- 存取資料時,建議使用
和
setProperty(String key,String value)方法
getProperty(String key)方法。
Properties pros = new Properties(); pros.load(new FileInputStream("jdbc.properties")); String user = pros.getProperty("user"); System.out.println(user);