HashMap实现原理学习
来源:互联网 发布:js 获取特定属性值 编辑:程序博客网 时间:2024/06/03 03:15
HashMap实现原理学习
HashMap源码来自:android-25/java/util/HashMap
一、构造方法
static final int MAXIMUM_CAPACITY = 1 << 30;static final int DEFAULT_INITIAL_CAPACITY = 4;static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);}// 参数默认为 4,0.75fpublic HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // 4 < MAXIMUM_CAPACITY if (initialCapacity > MAXIMUM_CAPACITY) { initialCapacity = MAXIMUM_CAPACITY; } // 4 = DEFAULT_INITIAL_CAPACITY else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) { initialCapacity = DEFAULT_INITIAL_CAPACITY; } if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); threshold = initialCapacity; init();}
ps:
init()为空方法;构造方法中只是做了HashMap数组容量字段的一个简单限制,最大为MAXIMUM_CAPACITY,最小为DEFAULT_INITIAL_CAPACITY
二、添加元素 put(K key, V value)
添加数据时,若出现冲突。
Java是通过 数组+链表 的形式解决冲突。效果如下图所示:
- HashMap中有一个默认长度为16的table数组,当数组的容量达到默认长度的0.75倍时,则扩容两倍;
- 其中table数组的每一项数据结构如下:
static class HashMapEntry<K,V> implements Map.Entry<K,V> { final K key; // key V value; // value HashMapEntry<K,V> next; // 链表的下一项 int hash; // key 的hash值}
下面通过跟中源码查看:
table数组初始化
介绍put(K key, V value)方法前,先简单介绍table数组初始化
// 添加key valuepublic V put(K key, V value) { // 如果table列表为null,则用过inflateTable方法初始化 if (table == EMPTY_TABLE) { inflateTable(threshold); } ... return null;}// 初始化table数组private void inflateTable(int toSize) { // Find a power of 2 >= toSize // 这里计算一个2的n次方的数组容量,默认为2的4次方,为16 int capacity = roundUpToPowerOf2(toSize); // 计算数组容量的0.75倍,超过数组容量0.75倍时,数组需要扩容 float thresholdFloat = capacity * loadFactor; if (thresholdFloat > MAXIMUM_CAPACITY + 1) { thresholdFloat = MAXIMUM_CAPACITY + 1; } // 数组容量的0.75倍 threshold = (int) thresholdFloat; // 初始化数组,默认容量capacity为16 table = new HashMapEntry[capacity];}
ps:
这里默认初始化了一个数组容量为16的table数组,其中关于roundUpToPowerOf2(toSize)为什么为2的n次方的问题,在下边进行介绍
put(K key, V value)
// 添加key valuepublic V put(K key, V value) { // 如果table列表为null,则用过inflateTable方法初始化 if (table == EMPTY_TABLE) { inflateTable(threshold); } // key 为null,则添加key为null的value if (key == null) return putForNullKey(value); // 根据key获取hash值 // Jenkins hash算法,可参考以下链接: // https://en.wikipedia.org/wiki/Jenkins_hash_function int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key); // h & (length-1) 取余太消耗性能,这里通过位运算达到同样的效果 // 获取该key在table 数组的index int i = indexFor(hash, table.length); // 循环table[i]对应的链表 // 如果 hash值相同 && key相同,则替换对应value,并返回老的value值 // 注:这里只是循环table[i]位置的链表,对于table数组未做循环 for (HashMapEntry<K,V> 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; } } modCount++; // 以下两种情况,则需要通过createEntry方法来看了 // hash相同 && key不同 // hash不同 && key不同 addEntry(hash, key, value, i); return null;}
ps:
以上介绍了添加数据时,“如果 hash值相同 && key相同,则替换对应value,并返回老的value值”,但对于“hash相同 && key不同”与“hash不同 && key不同”情况,则需要在createEntry中进行说明
void addEntry(int hash, K key, V value, int bucketIndex) { // 当数组的占用量,达到数组长度的0.75倍时,则需要扩容,扩展后的容量为原容量的2倍 // 数组扩容首先创建一个长度为原数组两倍的数组,然后将老的数组数据赋值给新数组的对应项项目 // 数组扩容的代码,这里不再说明 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0; bucketIndex = indexFor(hash, table.length); } // hash相同 && key不同 // hash不同 && key不同 createEntry(hash, key, value, bucketIndex);}// hash相同 && key不同// hash不同 && key不同 void createEntry(int hash, K key, V value, int bucketIndex) { // 取出table[bucketIndex]数组的原有值,可能为null,可能为HashMapEntry // 若为null,则直接将value放在table[bucketIndex]位置就ok了 // 若不为null,则将新数组放到table[bucketIndex]位置,老数组放到新数据链表的next字段 // hash冲突就是这样解决了,可以看到确实与上图一致,为数组+链表的方式解决冲突 HashMapEntry<K,V> e = table[bucketIndex]; table[bucketIndex] = new HashMapEntry<>(hash, key, value, e); size++;}
ps:
通过createEntry方法,我们看到HashMap中通过数组+链表方式解决了Hash冲突,呼应了上图
roundUpToPowerOf2(toSize)为什么为2的n次方
打个比方:
- 当数组长度为15时,添加数组时
h & (length-1)
计算成为hash&14(0x1110)
,那么最后一位永远是0,从而造成table数组中 1(0x0001),3(0x0011),5(0x0101),7(0x0111),9(0x1001),11(0x1011)等位置永远不可以存放数据,从而造成空间浪费; - 更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率。
ps:
关于 roundUpToPowerOf2(toSize)为什么为2的n次方问题,详细可查看
http://blog.csdn.net/yyh352091626/article/details/60866689?locationNum=4&fps=1
阅读全文
0 0
- HashMap实现原理-学习
- HashMap实现原理学习
- (转)java学习:HashMap的实现原理
- 深入学习java的HashMap实现原理
- 深入学习集合之HashMap实现原理
- Java 学习系列:HashMap 的实现原理
- java学习-[转]HashMap实现原理分析
- 深入学习集合之HashMap实现原理
- 【数据结构】HashMap原理及实现学习总结
- Java集合学习:HashMap实现原理
- Java集合学习:HashMap的实现原理和工作原理
- HashMap的实现原理
- Java HashMap实现原理
- HashMap的实现原理
- hashMap的实现原理
- HashMap的实现原理
- HashMap的实现原理
- Hashmap实现原理
- 收藏的几个容易出错的面试题
- JAVA基础入门
- JSP中图片的设置
- 《Android源码设计模式解析与实战》读书笔记(十二)——观察者模式
- Google 官方推出应用开发架构指南
- HashMap实现原理学习
- 总结自己在spring中遇到的各个注解的含义
- android studio 快捷键
- JAVA中的数据类型和方法重载
- BroadcastReceiver全面解析
- 2017年技术和生活的目标
- 海康威视摄像头忘记密码后如何重置
- 分页计算总页数算法
- 类似支付宝禁止截屏