HashMap源码解读

介绍了ArrayList和LinkedList,就两者而言,反应的是两种思想

1> ArrayList底层是以数组构成的,查询和在不扩容的情况下,顺序添加元素很快,插入和删除较慢

2> LinkedList底层是双向链表实现的,查询需要从header节点向前或向后遍历,插入和删除元素快

是否存在一个集合具备上面两个的优点,就是HashMap

HashMap是一种key-value形式存储的数据结构

HashMap的关注点

是否允许为空key和value都允许为空
是否允许重复key重复会覆盖,value允许重复
是否有序无序,遍历得到的顺序基本上不可能是put的顺序
是否线程安全非线程安全

HashMap的数据结构

java中,最基本的结构就是两种,一个是数组,另一个是模拟指针(引用),HashMap实际上一个链表散列的数据结构,就是数组和链表的结合体


从上图可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表,新建一个HashMap的时候,就会初始化一个数组.

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{
    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;
 static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

从源码可以看出,hashmap的底层是一个Entry数组,每一个entry存储了key-vlaue 和下一个entry的引用,是个链表结构

HashMap的存储

public V put(K key, V value) {
		//如果key是null,将这个entry放在table[0]的位置
        if (key == null)
            return putForNullKey(value);
		//根据key的hashcode计算hash值
        int hash = hash(key.hashCode());
		//根据key的hash值计算出在数组中的索引位置
        int i = indexFor(hash, table.length);
		//遍历entry[]索引的链表,找到hash值和key值相同的节点,覆盖value,返回
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
		//否则,就记录下修改次数,在这个索引位置加上一个节点
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

整个过程:

根据key计算他的hash值,找到在数组中的位置(即下标),如果该数组改位置上已经有元素,就会已链表的形式存储,新加入的放在前面,原来的放后面,如果改位置上没有元素,就会创建一个元素放在改位置。

下面详细的介绍下,每个方法是怎么执行的

第一步: 如果key是null,从table[0]这个索引,找到链表进行遍历,如果找到节点的key为null,就将value替换原来的value,

如果table[0]处的节点为空,就创建节点放到哪里,后面检查size+1是否超过容量*加载因子的值,超过的话按2倍大小扩容。

private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);//每次调用put方法重写节点的value,都会调用
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
 void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

第二步:计算hash值,找到索引位置,遍历索引处的链表,找到相同的key,进行替换。

static int hash(int h) {
        // 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);
    }
static int indexFor(int h, int length) {
        return h & (length-1);
    }

在hashmap中要找到某个元素,需要根据key的hash值求得在数组中索引位置使用hash算法求的这个位置的时候,检查这个位置上的链表是否存在相同的key,存在的话,替换value,否则,就是在这个索引位置新增元素。

在数组索引位置处新增节点

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {//扩容的代码,暂且不看
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

对于createEntry方法

如果在某个索引位置处没有元素,执行put("111","111")就创建一个节点e1,此时next = null,table[2]=e1

假设如果执行put节点e2,也放在这个索引位置,此时next=table[2],就是e1,然后有table[2] = e2 

结论:在一个索引位置,每新增一个元素,就会将table[index]指定它,新增的这个元素的next执行原来table[index]的元素

,形成了单向链表

为什么HashMap的长度是2的n次方

hashmap默认长度是16,其他情况取的是大于设定容量的2的n次方的最小值

当长度是2的n次方时候,能够减少元素得到的数组下标在同一个位置的概率,减少碰撞。

h & (table.length-1)             hash                  table.length-1        
4 & (15-1)                       0100                    1110              = 0100
5 & (15-1)                       0101                    1110              = 0100

4 & (16-1)                       0100                    1111              = 0100
5 & (16-1)                       0101                    1111              = 0101

和15-1运算的后果是0001,0011,0111,0101,1001,1101这几个位置都无法存放元素,因为运算的结果根本就不会算到这个索引上,不仅浪费了空间,还增加了元素碰撞的效率, 降低了查询效率

长度是2的n次方长度,(length-1)每个位都是1,因为hash值是均匀分布的,不同key算到的结果相同的概率很小,碰撞的机会少,效率更高

读取

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

有了前面的介绍,后面看起来就比较简单了,如果是null,从table[0]找到key为null的节点,否则返回null

key不为null,计算hash值找到在数组中的下表,从这个位置链表找到hash和key相同的节点,返回value,否则返回null

归纳起来说,HashMap在底层将key-value当做一个整体进行处理,这个整体是一个Entry对象。HashMap底层采用一个Entry[]数组保存所有的key-value对,当需要存储Entry对象时,根据hash算法决定其在数组中的存储位置,在根据equals方法找到链表上的存储位置;当需要取出一个entry时候,也是根据hash算法找到数组上的位置,equals方法找到节点,取出entry

HashMap的扩容

void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

threshold = int(capacity*loadFactor)

当数组中元素的个数大于等于threshold(容量*loadfactor),就会按照原来容量的2倍进行扩容

hashMap的性能参数

hashMap包括下面几个构造器

new HashMap() 初始容量16,loadfactor(加载因子)为0.75

new HashMap(int capacity) 初始容量为大于capacity的2的n次方的最小值,loadfactor为0.75

new HashMap(int capacity,float loadFactor)

loadFactor:数组中entry元素的个数除以总容量,加载因子如果过大,对空间利用率高,但是查询效率低,反之,加载因子小,浪费空间.

Fast-fail 机制

 transient volatile int modCount;
 final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
	    current = e;
            return e;
        }

在多线程环境下如果使用迭代器迭代,存在另一个线程修改了数据结构(put,remove),就会导致当前线程迭代器出现modCount!=exceptedModCount,并抛出异常。迭代器就会快速失败。

HashMap和HashTable的区别

HashTable和HashMap是一组相似的键值对集合,主要的区别

1. HashTable是线程安全的,通过synchronized锁保证线程安全,HashMap则是线程不安全的

2. HashTable不允许key为null,HashMap允许key为null

public synchronized V put(K key, V value) {
	// Make sure the value is not null
	if (value == null) {
	    throw new NullPointerException();
	}

	// Makes sure the key is not already in the hashtable.
	Entry tab[] = table;
	int hash = key.hashCode();
	int index = (hash & 0x7FFFFFFF) % tab.length;
	for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
	    if ((e.hash == hash) && e.key.equals(key)) {
		V old = e.value;
		e.value = value;
		return old;
	    }
	}

3. HashTable和HashMap的hash算法不同

参考博客:http://www.cnblogs.com/xrq730/p/5030920.html



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值