1. 程式人生 > >Core Java (二十一) 對映表(Map介面)

Core Java (二十一) 對映表(Map介面)

對映表是一種資料結構,用於存放鍵值對。如果提供了鍵,就能查詢到值。

Map介面的方法:

Modifier and Type Method and Description
void clear() Removes all of the mappings from this map (optional operation).
boolean Returns true if this map contains a mapping for the specified key.
boolean Returns true if this map maps one or more keys to the specified value.
Returns a Set view of the mappings contained in this map.
boolean Compares the specified object with this map for equality.
V Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
int Returns the hash code value for this map.
boolean
Returns true if this map contains no key-value mappings.
Set<K> 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 (Map<? extends K,? extends V> m) Copies all of the mappings from the specified map to this map (optional operation).
V 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.
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指向舊連結串列,所以最早加入的元素一定在連結串列末尾。


這裡附加一道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());//John
		
		System.out.println(staff.containsKey("1234-9876"));//true
		System.out.println(staff.containsValue(lily));//true
		System.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;
}