Java里的Map集合(一)HashMap

笔者最近发现java里面的Map集合比较有意思,而且经常被拿来面试用,所以这里写几篇关于Map集合的博客与大家讨论

首先是一张Map集合的框架图(这张图网上引用的)

接下来的几篇博客,我会从比较重要的HashMap、Hashtable、TreeMap、ConcurrentHashMap等集合常见的Map集合来介绍

·HashMap

        ·基本概念

        HashMap是一个很常见的Map,它是根据键的HashCode值进行存储数据,根据键可以直接获取它的值。这里一些Map常见的方法就不介绍了,HashMap实现了Map接口,继承AbstractMap。Map定义键值映射的规则,而AbstractMap提供了Map接口的骨干实现。

        ·两个重要的参数

        在HasMap中有两个重要的参数,初始容量和加载因子,这两个参数是影响HashMap性能的重要参数,其中容量表示的是哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前的一种衡量的尺度,加载因子表示的是散列表的装填程度大小。这两个概念是HashMap中的比较重要的概念,后面会重点介绍。

        ·HashMap的结构(jdk1.7的)

 

        其中每个链表都可以看成一个桶。插入元素的时候,首先将键传入一个哈希函数,函数通过散列的方式告知元素属于哪个桶,然后在相应的链表头插入元素。查找和删除元素的时候,用同样的方式先找到元素的桶,然后遍历相应的链表,直到发现我们想要的元素。

        下面通过源码来介绍一下,put和get方法

public V put(K key, V value) {
        //当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因
        if (key == null)
            return putForNullKey(value);

        //计算key的hash值

        int hash = hash(key.hashCode());                  ------(1)
        //计算key hash 值在 table 数组中的位置
        int i = indexFor(hash, table.length);             ------(2)
        //从i出开始迭代 e,找到 key 保存的位置
        for (Entry e = table[i]; e != null; e = e.next) {
            Object k;
            //判断该条链上是否有hash值相同的(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;     //返回旧值
            }
        }
        //修改次数增加1
        modCount++;
        //将key、value添加至i位置处
        addEntry(hash, key, value, i);
        return null;
    }

        可以看出先判断key是否为空,若为空则直接调用putForNullKey方法

        若不为空,则先计算key的hash值,然后通过hash值搜索table数组中的索引位置,如果table数组在该位置有元素,则通过比较是否存在相同的key,若存在则覆盖原来key的value,否则将该元素保存在链头(最先保存的元素放在链尾)。若table在该处没有元素,则直接保存。这里又引入了两个新的方法indexFor方法和hash方法。

indexFor方法

里面有一条语句    h&(length-1)

hash方法

final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

        首先来解释一下indexFor方法,这个方法很有意思,它的作用其实是为了均匀分布table数据和充分利用空间

这里引入一张图片

 

       可以看出来当length长度为15时,有很多的结果值是一样的,也就是他们会产生碰撞,相反我们想一下,当length长度为16的时候,就不会减少这个碰撞的几率,15就是1111,做&运算的时候,值总是与原来hash值相同,这样就会使得table数组中的数据分布的均匀,查询速度也较快。

        ·加载因子问题

        随着HashMap中的元素越来越多,产生碰撞几率就会越来越大,势必会影响HashMap的速度,所以当桶的数量=table数组的长度*加载因子的时候,就会扩容,这里扩容长度是*2。扩容会引来一些问题,这样会重新计算这些数据在新的table数组中的位置,并复制处理,所以使用hash容器时尽量预估自己的数据量来设置初始值。这里其实细节有很多,可以去csdn上看看其他文章,有介绍的很详细的。

       这里又有人会问了,为什么加载因子是0.75。。。emmm笔者认为应该从空间和时间上综合考虑,才采用的默认因子为0.75吧

        ·jdk1.8中的HashMap

        上面我们说jdk1.7中的HashMap采用一个Entry数组来存储数据,用key的hashcode取模来决定回放到哪个数组里,然后如果hashcode相同,或者hashcode取模后的结果相同,那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。所以可以说查询数据的时间复杂度为O(1+n)

        而1.8中的HashMap使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构,如果同一个格子里的key不超过8个,使用链表结构存储;如果超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。所以查询的时间复杂度为O(1+logn)比1.7的效率有了一定的提升

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值