Java集合--07HashMap

HsahMap基础

Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。

根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。

为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { }

(01)是一个散列表,它存储的内容是键值对(key-value)映射。
(02)继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
(03)实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的

    HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。

    容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量,默认初始容量是16,必须是2的幂。加载因子是哈希表判断是否需要自增容量的因子,也是哈希表达到多大容量的衡量尺度。当哈希表中的当前数量超出了加载因子与初始容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
    通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。

备注:默认的初始容量是16,必须是2的幂的原因。容量主要是用于计算key所在的索引,下面是源码中返回索引值的方法。

// 返回索引值
    // h & (length-1)保证返回值的小于length
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

    h是通过K的hashCode最终计算出来的哈希值,并不是hashCode本身,而是在hashCode之上又经过一层运算的hash值,length是目前容量。当容量一定是2^n时,h & (length - 1) == h % length,它俩是等价不等效的,位运算效率非常高,实际开发中,很多的数值运算以及逻辑判断都可以转换成位运算。这个等式实际上可以推理出来,2^n转换成二进制就是1+n个0,减1之后就是0+n个1,如16 -> 10000,15 -> 01111,那根据&位运算的规则,都为1(真)时,才为1,那0≤运算后的结果≤15,假设h <= 15,那么运算后的结果就是h本身,h >15,运算后的结果就是最后三位二进制做&运算后的值,最终,就是%运算后的余数。总之就是当是2的幂的情况下,位运算效率高

HashMap数据结构

执行put方法后,最终HashMap的存储结构会有三种情况:

(01)key的索引位置没有元素,直接进行插入。

(02)key的索引位置存在元素,且hashCode相同,则替换原来的值。

(03)key的索引位置存在元素,但hashCode不相同,则在索引处建立单链表,进行插入。

HashMap就是一个散列表,它是通过“拉链法解决哈希冲突每一个Entry本质上是一个单向链表

    // 将“key-value”添加到HashMap中
    public V put(K key, V value) {
        // 若“key为null”,则将该键值对添加到table[0]中。
        if (key == null)
            return putForNullKey(value);
        // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
  • HashMap通过键的hashCode来快速的存取元素。
  • HashMap中的key-value都是存储在Entry数组中的。

备注:createEntry和addEntry的区别是:
    (01) addEntry()一般用在 新增Entry可能导致“HashMap的实际容量”超过“阈值”的情况下。例如,我们新建一个HashMap,然后不断通过put()向HashMap中添加元素;put()是通过addEntry()新增Entry的。在这种情况下,我们不知道何时“HashMap的实际容量”会超过“阈值”;因此,需要调用addEntry()
   (02) createEntry() 一般用在 新增Entry不会导致“HashMap的实际容量”超过“阈值”的情况下。例如,我们调用HashMap“带有Map”的构造函数,它绘将Map的全部元素添加到HashMap中;但在添加之前,我们已经计算好“HashMap的容量和阈值”。也就是,可以确定“即使将Map中的全部元素添加到HashMap中,都不会超过HashMap的阈值”。 此时,调用createEntry()即可。

HashMap遍历方式

(01)根据entrySet()获取HashMap的“键值对”的Set集合。通过Iterator迭代器遍历“第一步”得到的集合。

// 假设map是HashMap对象
// map中的key是String类型,value是Integer类型
Integer integ = null;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
    Map.Entry entry = (Map.Entry)iter.next();
    // 获取key
    key = (String)entry.getKey();
        // 获取value
    integ = (Integer)entry.getValue();
}

(02)根据keySet()获取HashMap的“键”的Set集合。通过Iterator迭代器遍历“第一步”得到的集合。

// 假设map是HashMap对象
// map中的key是String类型,value是Integer类型
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
        // 获取key
    key = (String)iter.next();
        // 根据key,获取value
    integ = (Integer)map.get(key);
}

(03)根据value()获取HashMap的“值”的集合。通过Iterator迭代器遍历“第一步”得到的集合。

// 假设map是HashMap对象
// map中的key是String类型,value是Integer类型
Integer value = null;
Collection c = map.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

猜你喜欢

转载自blog.csdn.net/u013263066/article/details/86747359
今日推荐