Map
存储一组键值对象,提供key到value的映射。
实现类
java.util.EnumMap
key为枚举,value为Object[],使用枚举的特性,将枚举的ordinal属性作为数组的下标。
- 增加:put(K key, V value)
public V put(K key, V value) {
typeCheck(key);
// 枚举的ordinal属性是根据定义枚举的顺序生成的,从0开始
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
java.util.HashMap
数据结构与算法
HashMap用到数组,链表(单链表),树(红黑树)三种数据结构和哈希算法。
- 数组:数组的内存是连续型的,所以根据下标可以算出内存地址。
例如:int[10]分配的地址为1000到1039,int[i]=1000(首地址)+i*4
- 哈希算法:将Map中的key经过hash函数进行运算之后,让其通过key,可以直接算出下标,实现通过key达到O(1)的性能
static final int hash(Object key) {
int h;
// 异或运算:0^0=0;0^1=1;1^0=1;1^1=0
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 与运算:0&0=0;0&1=0;1&0=0;1&1=1
// 下标计算方法,将数组长度和hash值进行与运算,可以保证下标不会越界
int index = ( size - 1) & hash(key)
- 单链表
- 红黑树
当key发生hash冲突时,就需要使用到单链表,当链表达到一定长度时会转为红黑树,来提升查询效率;当红黑树的节点在指定范围时会转为单链表,具体如下图:
源码阅读
属性
- loadFactor:负载因子,用于计算临界值(threshold)。默认为0.75
- threshold:临界值,用于判断是否要扩容。计算公式为size*loadFactor
- TREEIFY_THRESHOLD:转红黑树临界值常量,值为8。判断是否树化的条件之一。
- UNTREEIFY_THRESHOLD:转链表临界值常量,值为6。转为链表的情况之一。
- MIN_TREEIFY_CAPACITY:转红黑树容量最小值常量,值为64。判断是否树化的条件之一
方法
- 增加:put(K key, V value)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 时间复杂度:O(logn)
// 数组O(1)
// 如果是单链表,则为O(n),但是这个n最大为8,也可以算是O(1)
// 如果是红黑树,则为O(logn)
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;
if ((p = tab[i = (n - 1) & hash]) == null) // 不存在hash冲突
tab[i] = newNode(hash, key, value, null);
else {
// hash冲突
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) {
if ((e = p.next) == null) {
// 追加在链表的尾部
p.next = newNode(hash, key, value, null)