HashMap源码解析
来源:互联网 发布:maya软件视频教程 编辑:程序博客网 时间:2024/05/22 10:39
HashMap根据key的hashcode值存储数据,大多数情况下可以直接定位到值,因而具有很快的访问速度,但遍历顺讯确实不确定的。HashMap最多允许一条记录的key为null,允许多条记录的value为null。同时HashMap是非线程安全的,即任一时刻可以有多个线程同时写HashMap,可能会导致数据不一致。
从结构上讲,HashMap是数组+链表的结合体,每一个键值对都被包装为Entry类。当执行put(key,value)的方法时,通过计算当前key的哈希值来决定该键值对在table数组中的下标i。如果table[ i ]为空,则直接插入;如果不为空,循环遍历table[ i ]的链表,如果有键值对的key与当前key相同,直接覆盖该键值对;如果没有相等的键值对,将当前键值对添加至链表尾部。
1、put实现
public V put(K key, V value) { /* 如果key值为null,调用putForNullKey,不同版本该方法的实现也不同, 1.该方法将从头遍历table数组,将key为null的键值对放在数组中第一个为空的地方。 2.用中间变量标记null 键值对。*/ if (key == null) return putForNullKey(value); //key不为null时,计算当前key的hash值。 int hash = hash(key.hashCode()); //计算该hash值对应的数组下标i int i = indexFor(hash, table.length); /* 遍历table[i]中的Entry链表,如果不存在hash值相同的键值对,直接插入数组, 如果存在hash值相同的键值对,比较两个键值对的key,如果key相同,用当前键值对覆盖此键值对, 如果key不同,直接插入。 */ 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;}
2、hash()、indexFor()实现——确定键值对在table中的索引
我们希望HashMap中的元素位置尽量分布均匀,尽量使得每个位置上的元素数量只有一个,那么当用hash算法求得这个位置时,马上就可以知道对应位置的元素就是目标元素,不用遍历链表,大大优化了查询效率。HashMap定位数组索引位置,直接决定了hash方法的离散性能。
static final int hash(Object key) { //jdk1.8 & jdk1.7 int h; // h = key.hashCode() 为第一步 取hashCode值 // h ^ (h >>> 16) 为第二步 高位参与运算 为了让高低位都充分参与运算 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 return h & (length - 1); //第三步 取模运算 h按位与上length - 1等价于h对length取模,使得元素分布均匀 }
3、resize()实现——table的扩容机制
table的默认初始长度为16,当无法装在更多元素时,就需要扩大数组长度调用resize方法。
由于在进行扩容时,是用更大容量的数组代替之前的小容量数组,并且会对原来的键值对重新计算排序,特别耗性能,所以在创建HashMap的时候最好把大概容量写上,避免进行扩容操作。另外,之所以hashmap不支持多线程操作,是因为resize扩容是在把小容量table的数据转移至大容量数组时,会让数组错位造成调用transfer时造成死循环。
void resize(int newCapacity) { //传入新的容量 Entry[] oldTable = table; //引用扩容前的Entry数组 int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { //扩容前的数组大小如果已经达到最大(2^30)了 threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了 return; } Entry[] newTable = new Entry[newCapacity]; //初始化一个新的Entry数组 transfer(newTable); //!!将数据转移到新的Entry数组里 table = newTable; //HashMap的table属性引用新的Entry数组 threshold = (int) (newCapacity * loadFactor);//修改阈值 }
阅读全文
0 0
- Android源码解析 -- HashMap
- HashMap源码解析
- HashMap源码解析
- HashMap源码解析
- HashMap源码解析
- HashMap源码解析
- HashMap源码解析
- Java HashMap 源码解析
- Java HashMap 源码解析
- Java HashMap 源码解析
- 源码解析HashMap
- Java:HashMap源码解析
- HashMap 源码解析
- HashMap源码解析
- HashMap 源码解析
- Java源码解析-hashmap
- HashMap源码解析
- HashMap源码解析
- 图方法:寻找无向图联通子集的JAVA版本
- item01
- as中git、gitHub相关配置
- Banner的使用
- 深度学习与神经网络学习笔记(五)
- HashMap源码解析
- JAVA四种引用方式-上篇
- FTPClient 删除中文文件
- 项目删除不了问题
- zabbix添加图片等信息到仪表盘的常用选项中
- item02
- maven仓库settings.xml文件配置阿里云下载
- flex-grow、flex-shrink、flex-basis详解、flex:1;详解
- Docker 镜像使用