映射表是一種資料結構,用於存放索引值對。如果提供了鍵,就能尋找到值。
Map介面的方法:
| Modifier and Type |
Method and Description |
void |
clear()
Removes all of the mappings from this map (optional operation). |
boolean |
containsKey(Object key)
Returns true if this map contains a mapping for the specified key. |
boolean |
containsValue(Object value)
Returns true if this map maps one or more keys to the specified value. |
Set<Map.Entry<K,V>> |
entrySet()
Returns a Set view
of the mappings contained in this map. |
boolean |
equals(Object o)
Compares the specified object with this map for equality. |
V |
get(Object key)
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. |
int |
hashCode()
Returns the hash code value for this map. |
boolean |
isEmpty()
Returns true if this map contains no key-value mappings. |
Set<K> |
keySet()
Returns a Set view
of the keys contained in this map. |
V |
put(K key, V value)
Associates the specified value with the specified key in this map (optional operation). |
void |
putAll(Map<?
extends K,? extends V> m)
Copies all of the mappings from the specified map to this map (optional operation). |
V |
remove(Object key)
Removes the mapping for a key from this map if it is present (optional operation). |
int |
size()
Returns the number of key-value mappings in this map. |
Collection<V> |
values()
Returns a Collection view
of the values contained in this map. |
Java類庫為映射表提供了兩個通用的實現:HashMap(對鍵進行散列)和TreeMap(用鍵的整體順序對元素排序,並將其組織成搜尋樹),這兩個類都實現了Map介面。
HashMap比TreeMap更快,但沒有排序功能。下面重點介紹HashMap:
HashMap
散列表(HashMap)和散列集(HashSet)都是散列表這種資料結構。
散列表(Hash Table)
散列表由鏈表數組實現,每個列表成為一個桶(bucket),一個桶就是一個鏈表。如所示,該圖有16個桶。根據關鍵字的散列碼(HashCode)與桶的總數取餘得到元素的位置。圖中,0~15代表桶的標號,而右邊一些數字代表關鍵字的散列碼。下面這個散列表,其中4個空間被使用,即36%的空間已經被使用。而散列表的裝填因子(load factor)預設為0.75,即當75%的空間已經被使用時進行再散列(rehashed),桶的總數變成雙倍。注意:如果未設定,桶的總數的初始預設值為16。
這裡主要討論HashMap:
散列映射表(HashMap)僅僅是根據關鍵字的散列碼(HashCode)來得到元素要存放在哪條鏈表上,而判斷兩個對象是否相等是根據對象的equals方法確定的。如果想將Employee類的某個對象添加到HashMap中,首先要調用這個對象(Value)所對應的鍵(Key)的hashCode方法計算鍵的散列碼,然後與桶的總數取餘的到這個索引值對的桶的索引。如果返回的散列碼與HashMap中已經存在的某個鍵的散列碼相同,則第二個值就會取代第一個值。因為鍵必須是唯一的,不能對同一個鍵存放兩個值。也就是說,散列映射表中,value完全是key的附屬,一點也不會影響儲存。
HashMap儲存在bucket中的是entry(條目)的引用,一個bucket其實就是一條entry鏈表。entry是一種資料結構,裡麵包含了key,value,next等執行個體域,其中next指的是下一個引用的位置,如果next == null,則表示這條entry鏈結束了。HashMap在執行add操作的時候,總是把最新的元素添加到bucket中entry鏈表的頭部,再將這個最新元素的next指向舊鏈表,所以最早加入的元素一定在鏈表末尾。
其他的知識一定要讀一讀這篇文章:java中HashMap詳解
這裡附加一道OCJP的題目來增強理解:
Given
public class Person { private name; public Person(String name) { this.name = name; } public int hashCode() { return 420; } }
Which statement is true?
A. The time to find the value from HashMap with a Person key depends on the
size of the map.
B. Deleting a Person key from a HashMap will delete all map entries for all keys of type Person.
C. Inserting a second Person object into a HashSet will cause the first Person object to be
removed as a duplicate.
D. The time to determine whether a Person object is contained in a HashSet is constant and does NOT
depend on the size of the map.
Answer: A
下面來解釋一下:
紅色為翻譯
B選項:刪除HashMap中一個Person對象對應的鍵將會刪除這個散列映射表中Person類的全部條目。錯誤,HashMap中Person對象的索引值不是由Person對象決定的,而是程式給定的鍵,例如staff.add("123-345", bob),就是把鍵為123-456的bob對象添加到名為staff的HashMap中,因而HashMap允許添加相同的對象。所以說,刪除一個鍵對應的Person對象並不會刪除所有的條目,他們的key都不同嘛。
C選項:向HashSet中插入另外一個Person對象將會引起第二個對象覆蓋第一個對象。錯誤,雖然Person對象的hashCode方法返回的值都是420,這僅僅表明兩個Person對象在一個entry鏈表中,接下來要調用equals方法,由於Person類沒有equals方法,所以調用Object的equals方法返回對象的儲存地址,很明顯兩個Person對象的儲存地址是不同的。綜上,HashSet中可以添加不同的Person對象,只要equals方法傳回值不同就好。
D選項:判斷一個HashSet中是否存在一個Person對象的次數是常數次,和map的大小無關。錯誤,由於Person對象的hashCode返回的值都是420,所以HashSet中的Person對象都在一個bucket中,組成了一條entry鏈表,查詢速度與entry鏈表的大小息息相關。
A:選項:由key來尋找value的次數與map的大小有關。正確,map越大,即bucket的個數越多,entry鏈的長度相應的來說就越小(hashcode和桶個數取餘後的數一樣的幾率就越小)。
另外,要注意的是HashMap有三個重要的視圖:
- Set<K> keySet();
- Collection<K> values();
- Set<Map.Entry<K,V>> entrySet();
其中第一個方法和第三個方法返回的是一個集(Set),而第二個方法返回的是一個集合(Collection)。
以下例子利用了HashMap的一些重要的方法:
package com.xujin;import java.util.HashMap;import java.util.Map;import java.util.Set;public class Test{public static void main(String...arg){Map<String, Employee> staff = new HashMap<String, Employee>();Employee lily = new Employee("Lily", 4000);Employee john = new Employee("John", 6000);staff.put("1234-9876", lily);staff.put("345-8765", john);Employee e = staff.get("345-8765");System.out.println(e.getName());//JohnSystem.out.println(staff.containsKey("1234-9876"));//trueSystem.out.println(staff.containsValue(lily));//trueSystem.out.println(staff.size());//2//鍵集Set<String> myKeySet = staff.keySet();for(String myKey: myKeySet){System.out.println(myKey);}/*結果: * 345-8765 * 1234-9876*///值集合(不是集set)//staff.values()傳回型別為Collection<Employee>for(Employee myEmployee : staff.values()){System.out.println(myEmployee);}/*結果: * id:2,name:John,salary:6000.0 * id:1,name:Lily,salary:4000.0 * *///索引值對集//staff.entrySet()傳回型別為Set<Entry<String, Employee>>for(Map.Entry<String, Employee> entry: staff.entrySet()){String key = entry.getKey();Employee value = entry.getValue();System.out.println("key: " + key + "\nvalue: " + value);}/*結果: * key: 345-8765 * value: id:2,name:John,salary:6000.0 * key: 1234-9876 * value: id:1,name:Lily,salary:4000.0 * */staff.remove("345-8765");System.out.println(staff.size());//1//putAll方法,Map<String, Manager> otherStaff = new HashMap<String, Manager>();Manager jim = new Manager("Jim", 6000, 1000);Manager gin = new Manager("Gin", 8000, 2000);otherStaff.put("1111-2222", jim);otherStaff.put("2222-3333", gin);staff.putAll(otherStaff);for(Map.Entry<String, Employee> entry: staff.entrySet()){String key = entry.getKey();Employee value = entry.getValue();System.out.println("key: " + key + "\nvalue: " + value);}/*結果: * key: 1111-2222 * value: id:3,name:Jim,salary:6000.0,bonus:1000.0 * key: 2222-3333 * value: id:4,name:Gin,salary:8000.0,bonus:2000.0 * key: 1234-9876 * value: id:1,name:Lily,salary:4000.0 * */}}class Employee{public Employee(String name){this.name = name;id = nextId;nextId++;}public String toString(){return "id:" + id + ",name:" + name +",salary:" + salary;}public Employee(String name, double salary){this(name);//調用另一構造器this.salary = salary;}//定義訪問器方法public final String getName(){return name;}public double getSalary(){return salary;}public final int getId(){return id;}//定義更改器方法public final void setName(String name){this.name = name;}public final void setSalary(double salary){this.salary = salary;}public final void raiseSalary(double percent){this.salary *= (1 + percent);}//定義變數private String name = "";//執行個體域初始化private double salary;private int id;private static int nextId = 1;}class Manager extends Employee{public Manager(String name, double salary, double bonus){super(name, salary);//super在構造器中的使用,可以調用超類的構造器setBonus(bonus);}public String toString(){return super.toString() + ",bonus:" + bonus;}public double getBonus(){return bonus;}//重寫getSalary方法public double getSalary(){double baseSalary = super.getSalary();//調用了超類的getSalary方法return baseSalary + bonus;}public void setBonus(double bonus){this.bonus = bonus;}private double bonus;}