java.util包中,Map旗下的映射类有这六个:HashMap、LinkedHashMap、IdentityHashMap、WeakHashMap、TreeMap、EnumMap,下面一一分析
1.HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
声明,继承了AbstractMap类,实现了Map接口
下面看一组相关联的成员变量和数据类型:
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
从这里我们就可以得出HashMap的数据结构:
首先HashMap是基于数组的,即这个table数组,该数组中存储的每一个元素实质上是一个链表,也称为一个桶,桶里是一些前后链接的Node
table数组的元素是怎样组织的?什么样的Node会被放到一个桶里?我们接着看:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//重点看i = (n - 1) & hash,此即为寻桶算法,hash为key的哈希值,n为table的长度即桶的个数,(n - 1) & hash即为该元素要存入的桶的编号,可以看出具有相同hash值的元素会被存入同一个桶里,但一个桶里不止有一种hash
//若桶里没有元素,则直接放入该元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
//若桶中不存在hash和key都与该元素相等的元素,则把该元素挂在最后
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//若在桶中存在hash和key都与该元素相等的元素,则认为key已存在
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//若key已存在,用新value代替旧value,返回旧value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//找到该元素所在的桶
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//在桶中寻找hash和key与该元素都相等的元素
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
从元素的存取方法中可以看出,table元素(桶)的组织方式是,先算出key的hash值,然后利用(n - 1) & hash这个公式计算出桶的序号,即为该元素要放入的桶。可以看出相同hash值的元素一定会放入同一个桶中,但一个桶中也会包含不同hash值的元素
元素在放入桶之前会被包装成Node类型,在桶中以链表的形式存在。并且会先遍历链表,如果已经有与该元素hash值和key值都相等的结点,则直接修改该结点的值,并返回原值;若没有与该元素hash值和key值都相等的结点,则把该Node挂在链表最后
接下来看其他方法:
//构造方法
public HashMap(int initialCapacity, float loadFactor);
public HashMap(int initialCapacity);
public HashMap();
public HashMap(Map<? extends K, ? extends V> m);
initialCapacity(初始容量)、loadFactor(装填因子)都有默认值,也可以人为指定
初始容量指table数组的容量,即桶的个数
填充比达到装填因子后会进行扩容,即容量变为原来的两倍,每个桶中的链表会按奇偶次序分到两个桶中,减少了链表长度,提高了查找效率
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
这两个阈值的含义分别是:如果链表长度达到8,就把该链表转换为红黑树;如果红黑树结点降低为6个,就把红黑树转换为链表
HashMap的其他方法:
public int size();
public boolean isEmpty();
public V get(Object key);
public boolean containsKey(Object key);
public V put(K key, V value);
public void putAll(Map<? extends K, ? extends V> m);
public V remove(Object key);
public void clear();
public boolean containsValue(Object value);
//三种视图,无序
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();
public V getOrDefault(Object key, V defaultValue);
public V putIfAbsent(K key, V value);
//key和value都相等时才删除
public boolean remove(Object key, Object value);
//key和value都相等时才替换
public boolean replace(K key, V oldValue, V newValue);
public V replace(K key, V value);
//函数式接口编程
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction);
//遍历无序
public void forEach(BiConsumer<? super K, ? super V> action);
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
最后需要注意一点,虽然HashMap的遍历是无序的,但在jdk8中由于hash算法的改变,若key是由Integer或数字型的String组成,则遍历次序呈现出一种按key升序排列的形式,但仍然要认为HashMap的遍历是无序的
2.LinkedHashMap
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
声明,继承了HashMap类,实现了Map接口
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
可以看到其数据结构Entry继承了HashMap的Node,并且新增了before、after两个指针,正是这两个指针保存了元素的插入次序
构造方法:
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
可以看到,如不明确指定,accessOrder参数默认为false
accessOrder参数为false时,视图和遍历方法按元素插入顺序排列
accessOrder参数为true时,视图和遍历方法按元素访问次序排列(在插入顺序的基础上,每访问过一个元素就将它排到最后)
所以LinkedHashMap的三个视图
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();
以及其forEach方法:
public void forEach(BiConsumer<? super K, ? super V> action);
其顺序都与accessOrder参数有关
其他方法与HashMap类似,不再赘述
3.IdentityHashMap
public class IdentityHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, java.io.Serializable, Cloneable
声明,继承了AbstractMap类,实现了Map接口,其内部结构与HashMap类似
看构造方法:
public IdentityHashMap();
public IdentityHashMap(int expectedMaxSize);
public IdentityHashMap(Map<? extends K, ? extends V> m);
其与HashMap的不同点为,HashMap不允许出现key相同的情况,但IdentityHashMap却有可能出现相同的key,我们可以从put方法中看出原因:
public V put(K key, V value) {
final Object k = maskNull(key);
retryAfterResize: for (;;) {
final Object[] tab = table;
final int len = tab.length;
int i = hash(k, len);
for (Object item; (item = tab[i]) != null;
i = nextKeyIndex(i, len)) {
//==运算符比较的是地址,而不是key的实际值
if (item == k) {
@SuppressWarnings("unchecked")
V oldValue = (V) tab[i + 1];
tab[i + 1] = value;
return oldValue;
}
}
final int s = size + 1;
// Use optimized form of 3 * s.
// Next capacity is len, 2 * current capacity.
if (s + (s << 1) > len && resize(len))
continue retryAfterResize;
modCount++;
tab[i] = k;
tab[i + 1] = value;
size = s;
return null;
}
}
对比一下HashMap和IdentityHashMap的put方法中判断key相等的语句:
//HashMap
(k = p.key) == key || (key != null && key.equals(k))
//IdentityHashMap
item == k
可见HashMap当key的equals方法或==运算有一个为真时,就判定key相等;而IdentityHashMap只有==运算为真时,才判定key相等,所以会出现key的值相等的情况
其他方法与HashMap类似,不再赘述
4.WeakHashMap
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
声明,继承了AbstractMap类,实现了Map接口
看构造方法:
public WeakHashMap(int initialCapacity, float loadFactor);
public WeakHashMap(int initialCapacity);
public WeakHashMap();
public WeakHashMap(Map<? extends K, ? extends V> m);
注意这个数据结构:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
可以看出WeakHashMap中的键值对Entry继承了WeakReference,即弱引用,所以可能随时被GC回收,这就是它与HashMap的不同点。这种特性可用于需要缓存的场景
看这个成员变量:
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
被回收的引用保存在这个队列中,以备更新映射
其他方法与HashMap类似,不再赘述
5.TreeMap
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
声明,继承了AbstractMap类,实现了NavigableMap接口
TreeMap是有序的映射,用红黑树进行排序,且有一系列的导航方法
private final Comparator<? super K> comparator;
根据比较器来进行key的排序
看构造方法:
public TreeMap();//无构造器,按key的自然序升序排列
public TreeMap(Comparator<? super K> comparator);
public TreeMap(Map<? extends K, ? extends V> m);
public TreeMap(SortedMap<K, ? extends V> m);
看其他方法:
public int size();
public boolean containsKey(Object key);
public boolean containsValue(Object value);
public V get(Object key);
//返回当前的比较器
public Comparator<? super K> comparator();
public K firstKey();
public K lastKey();
public void putAll(Map<? extends K, ? extends V> map);
public V put(K key, V value);
public V remove(Object key);
public void clear();
public Map.Entry<K,V> firstEntry();
public Map.Entry<K,V> lastEntry();
public Map.Entry<K,V> pollFirstEntry();
public Map.Entry<K,V> pollLastEntry();
public Map.Entry<K,V> lowerEntry(K key);
public K lowerKey(K key);
public Map.Entry<K,V> floorEntry(K key);
public K floorKey(K key);
public Map.Entry<K,V> ceilingEntry(K key);
public K ceilingKey(K key);
public Map.Entry<K,V> higherEntry(K key);
public K higherKey(K key);
public Set<K> keySet() {return navigableKeySet();}
public NavigableSet<K> navigableKeySet();
public NavigableSet<K> descendingKeySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();
public NavigableMap<K, V> descendingMap();
public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive);
public NavigableMap<K,V> headMap(K toKey, boolean inclusive);
public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
public SortedMap<K,V> subMap(K fromKey, K toKey);
public SortedMap<K,V> headMap(K toKey);
public SortedMap<K,V> tailMap(K fromKey);
public boolean replace(K key, V oldValue, V newValue);
public V replace(K key, V value);
public void forEach(BiConsumer<? super K, ? super V> action);
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
6.EnumMap
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable
声明,继承了AbstractMap类
private final Class<K> keyType;
keyType指key所属的枚举类型
看put方法:
public V put(K key, V value) {
typeCheck(key);
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
可以看出value存在vals数组中,其顺序与枚举key的顺序是一致的,所以用key.ordinal()得到key的索引,也就得到了对应的value的索引。故其查找速度要比HashMap快
看构造方法:
public EnumMap(Class<K> keyType);
public EnumMap(EnumMap<K, ? extends V> m);
public EnumMap(Map<K, ? extends V> m);
再看其他方法:
public int size();
public boolean containsValue(Object value);
public boolean containsKey(Object key);
public V get(Object key);
public V put(K key, V value);
public V remove(Object key);
public void putAll(Map<? extends K, ? extends V> m);
public void clear();
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();