Core Java (二十一) 映射表(Map接口)

映射表是一种数据结构,用于存放键值对。如果提供了键,就能查找到值。

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> 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 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 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 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 keySet();
  • Collection values();
  • Set> 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 staff = new HashMap();
		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 myKeySet =  staff.keySet();
		for(String myKey: myKeySet){
			System.out.println(myKey);
		}
		/*结果:
		 * 345-8765		
		 * 1234-9876
		*/
		
		
		//值集合(不是集set)
		//staff.values()返回类型为Collection
		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>
		for(Map.Entry 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 otherStaff = new HashMap();
		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 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;
}


你可能感兴趣的:(读书笔记,java)