映射表是一种数据结构,用于存放键值对。如果提供了键,就能查找到值。
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 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 |
values()
Returns a
Collection view of the values contained in this map.
|
Java类库为映射表提供了两个通用的实现:HashMap(对键进行散列)和TreeMap(用键的整体顺序对元素排序,并将其组织成搜索树),这两个类都实现了Map接口。
HashMap比TreeMap更快,但没有排序功能。下面重点介绍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?
下面来解释一下:
红色为翻译
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有三个重要的视图:
以下例子利用了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;
}