映射(map) 数据结构就是为此设计的。映射用来存放键 / 值对。如果提供了键, 就能够查找到值。
Java 类库为映射提供了两个通用的实现:HashMap 和 TreeMap。这两个类都实现了 Map 接口。散列映射对键进行散列, 树映射用键的整体顺序对元素进行排序, 并将其组织成搜索树。散列或比较函数只能作用于键。与键关联的值不能进行散列或比较。应该选择散列映射还是树映射呢? 与集一样, 散列稍微快一些, 如果不需要按照排列顺序访问键, 就最好选择散列。
1 Map接口
Map接口提供了一些映射表的基本操作,下面是这些方法的总结:
(1)查询操作
int size(); boolean isEmpty(); boolean containsKey(Object); boolean containsValue(Object); V get(Object);
这些方法的含义都很明确。需要注意的是,containsKey方法、containsValue方法和get方法的参数类型都是Object。
(2)修改方法
V put(K,V); V remove(Object); void putAll(Map<? extends K,? extends V>); void clear();
put方法用于添加一个键值对,如果键已经存在就更新值并返回旧值。remove方法删除给定键的键值对并返回值。putAll方法将一个Map中的所有键值对添加到映射表中。clear方法删除所有元素。
(3)视图方法
Set<K> keySet(); Collection<V> values(); Set<Map.Entry<K,V>> entrySet();
这三个方法返回三个视图:键集、值集合(不是集)和键值对集。对于视图会在后续的文章中作介绍。
在Map接口中还定义了一个子接口:Entry,用来操作键值对。
这个接口主要有一下几个方法:
K getKey(); V getValue(); V setValue(V value); boolean equals(Object o); int hashCode();
(4)Entry: 键值对 对象。(Entry是Map中用来保存一个键值对的,而Map实际上就是多个Entry的集合)
Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry。 在Map类设计是,提供了一个嵌套接口(static修饰的接口):Entry。Entry将键值对的对应关系封装成了对象,即键值对对象,这样我们在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。
Entry为什么是静态的?
Entry是Map接口中提供的一个静态内部嵌套接口,修饰为静态可以通过类名调用。
Map集合遍历键值对的方式:
Set<Map.Entry<K,V>> entrySet(); //返回此映射中包含的映射关系的Set视图
该方法返回值是Set集合,里面装的是Entry接口类型,即将映射关系装入Set集合。
实现步骤:
1,调用Map集合中的entrySet()方法,将集合中的映射关系对象存储到Set集合中
2,迭代Set集合
3,获取Set集合的元素,是映射关系的对象
4,通过映射关系对象的方法,getKey()和getValue(),获取键值对
Map<String, String> map = new HashMap<String, String>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); //第一种:普遍使用,二次取值 System.out.println("通过Map.keySet遍历key和value:"); for (String key : map.keySet()) { System.out.println("key= "+ key + " and value= " + map.get(key)); } //第二种 System.out.println("通过Map.entrySet使用iterator遍历key和value:"); Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = it.next(); System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } //第三种:推荐,尤其是容量大时</span> System.out.println("通过Map.entrySet遍历key和value"); for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } //第四种 System.out.println("通过Map.values()遍历所有的value,但不能遍历key"); for (String v : map.values()) { System.out.println("value= " + v); }
2 散列映射表:HashMap
散列映射表主要用到散列技术,可以快速对一个元素进行查找。HashMap类中的主要域如下:
transient Node<K,V>[] table; transient int size; int threshold; final float loadFactor;
其中使用table来存储元素,size表示映射表中键值对的个数,threshold是一个域值,当元素个数超过这个域值后,就会自动扩展映射表的大小。而loadFactor是一个加载因子,表示threshold与table长度的比值。
可以看到,table是一个数组,数组中存储Node<K,V>类型的值。Node<K,V>表示一个键值对,定义如下:
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(); public final V getValue(); public final String toString(); public final int hashCode(); public final V setValue(V newValue); public final boolean equals(Object o); }
是一个静态内部类,这表示一个键值对,可见HashMap将键值对作为一个整体来操作。
在Node中,有存储键的key,存储值的value,存储散列值的hash,还有一个next引用,可见这是一个链表。
既然有散列值hash,那么这个值是如何计算的呢?方法如下:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
是一个纯粹的数学方式。
有了散列值,HashMap又是如何散列的呢?
HashMap使用hash值确定一个键值对在table中的位置,具体方法是,用hash%table.length,结果就是在table中的下标。如果有多个hash在table中的同一个位置,那么就构成一个链表。存储方式大致是这样的: