HashMap的核心方法讨论

429 阅读4分钟

介绍

HashMap是一种容器,它的核心功能是用来判断某个是否存在于容器中,并且期望查找和新增元素的时间复杂度是O(1)。

核心字段

int capacity; //容量
  
double threshold;//阈值比例

transient Node<K,V>[] table;//存储键值对的数组

以上是HashMap中影响性能的核心参数。capacity表示HashMap中可以容纳的元素,threshold和capacity的乘积是容量的阈值,当HashMap容器中的元素超过阈值之后,就会进行扩容。threshold的值是0.75,这是1个经验值。这个值不能太大,也不能太小。如果这个值设置的比较大,可以节省空间,但是在get和put操作的时候可能会比较耗时。

table数组用来存储Key,Value键值对,它是HashMap的核心容器。

理想情况下,如果Hash函数能够使元素均匀分布在table中,任何查询只需要1次。比如对于容量为16的HashMap,按threshold为0.75计算,当HashMap中的元素数量超过12(16*0.75)的时候,就会发生扩容。但是12个元素在理想情况下是可以均匀分配到数组之中的。

键值对的封装类

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//键的hash值
        final K key;//键
        V value;//值
        Node<K,V> next;
    }
  • HashMap的作用是通过键拿到值,因此key和value是必须的
  • 当产生哈希碰撞时,需要有解决哈希冲突的方法。HashMap哈希冲突通过链表解决,因此需要有指针指向下1个节点,就是Next
  • 对于任意1个键值对元素,该元素在table中的位置取决于元素key的hash值。通过元素key的hash值定位到元素在table中所处的位置,然后可以修改、删除建键值对。这就要求对于作为key的对象,“相等”对象的hash值必须一致。这种情况下,重写equals方法的时候必须要重写hashcode才能提高HashMap的操作效率。对象的hashcode方法和equals方法本是两个毫无关联的方法,但是因为HashMap需要通过hashcode定位元素在table中的位置,我们只需要从这个位置开始,沿着链表或者红黑树(链表的元素数量超过一定量之后,链表会升级为二叉树)遍历,找到hash值和Key值都相等的元素即可。以查询为例,如果相等“的元素hashcode不同,则根据某个key做查询时,对应的值不会落在预期的桶的位置上,这样我们就只能遍历所有的元素来根据equals方法判断元素是否相等,时间复杂度从O(1)蜕化为O(n)。HashMap本质上要求对象同时实现equals方法和hashcode方法,本质上是为了提升get\put等方法的性能

核心方法

put

给HashMap中添加元素,通过hash值定位到元素在table中的位置,然后添加到该位置或者该位置所在的链表

resize

当元素的数量超过设定的阈值(capacity * threshold)的时候,HashMap就会扩容。在HashMap中,table的长度总是为2的n次方,扩容后,table的长度将变成2的(n+1)次方。

元素在桶中的位置 = hashcode & (table.length - 1)

由于table.length是2的整数次方。假定某个元素在当前位置i,在扩容之后,元素的位置只有两种情况:

  • i
  • i + capacity

因此在resize的时候,针对table所在位置的每个元素,我们可以将其拆分为两个指针,分别对应上述两种情况。

如果不适用上述方式做resize,hashmap就必须有额外的空间来做扩缩容

entrySet

entrySet主要是作用是:给调用方提供1个视图,访问HashMap的内部元素。代码如下图所示:

    public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

可以到看到,改代码实际上只是new了1个EntrySet对象。EntrySet类实现了AbstractSet接口,实现的方法:

int size();

void clear()
  
Iterator<T> iterator()

都是基于HashMap中的字段,因此EntrySet只是给我们提供了一种视图,去访问HashMap中的元素。