HashMap 是 Java 集合框架中使用最频繁的 Map 实现。HashMap 通过 key-value 键值对存储数据,并通过一系列的特有 API 添加、检索、删除数据。
键值对存储在所谓的存储桶中,这些存储桶共同构成了所谓的表,key 是一个内部数组,value 则是一个链表。
增加元素
为了在HashMap中存储一个值,我们调用 put()
方法,它接受两个参数:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
该方法内部调用了 putVal()
函数,参数含义:
- hash – key 的哈希值
- key – key 键
- value – value 值
- onlyIfAbsent – 当要插入的key已存在时是否替换value,false则替换,true则不替换。
- evict – 在HashMap中无作用,只在LinkedHashMap需要。
其中的 hash() 方法:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash() 方法调用了传入 key 的 hashCode() 方法计算原始的哈希值,这也是该方法中的唯一变化量。
不过,我们还看到了 ^ (h >>> 16)
这样一段计算逻辑,按照官方的表述,可以用下面的图示来说明:
将h无符号右移16位相当于将高区16位移动到了低区的16位,再与原hashcode做异或运算,高区的16位与原hashcode相比没有发生变化,低区的16位发生了变化,这样就将高低位二进制特征混合起来,可以进一步降低 hash 值的重复概率。
hash() 方法对入参为 null 的元素分配了哈希值 0,意味着 null 可以作为 HashMap 的一个元素。
实际上,不但 key 可以为 null, value 也可以为 null:
Map<String, String> map = new HashMap<>();
map.put(null, null);
有几点需要注意:
- put 方法有返回值。当我们使用之前已经用过的键来存储值时,它会返回与该键关联的旧值:否则返回 null;
- 所有 Java 集合框架接口都继承自 Collection接口,但 Map 没有。原因是 Map 不像其他集合那样完全存储单个元素,而是键值对的集合。所以 Collection 接口的方法如 add、toArray 方法对Map没有意义。
获取元素
获取 HashMap 中的元素,我们需要知道 key 是什么,然后调用 get()
方法。
String val = map.get("key");
get() 方法签名:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
其内部也先调用了 hash() 方法获取 key 的哈希值。这个哈希值就是存储桶的位置,即数组索引下标。
当返回值为 null 时,可能意味着键对象未与哈希映射中的任何值关联,或者最开始我们就存放了一个 null 值。
元素集合
HashMap 提供了几个方法可以将键或者直接提取出来。
获取所有的 key:
Set<String> keys = map.keySet();
获取所有的 value:
Collection<String> values = map.values();
获取所有的 key-value:
Set<Entry<String, String>> entries = map.entrySet();