源码分析之-容器类-HashMap
来源:互联网 发布:青铜器 软件 编辑:程序博客网 时间:2024/05/09 08:17
一、HashMap基本原理
HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,依次来解决Hash冲突的问题,因为HashMap是按照Key的hash值来计算Entry在HashMap中存储的位置的,如果hash值相同,而key内容不相等,就用链表来解决这种hash冲突。
二、HashMap源码分析
本文以JDK7源码分析一下HashMap的关键代码。
private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; }
Integer.highestOneBit方法的作用是二进制位除最左面为1的位之外全部置为0的值。roundUpToPowerOf2方法的作用是取比给定值稍大的2的次幂。至于这里为什么要取2的次幂,原因如下:(1)浪费空间(2)减低查询效率 举例: 当数组长度为15时,添加数组时h & (length-1)计算成为hash&14(0x1110),那么最后一位永远是0,从而造成table数组中 1(0x0001),3(0x0011),5(0x0101),7(0x0111),9(0x1001),11(0x1011)等位置永远不可以存放数据,从而造成空间浪费;更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,单个桶里保存的数据多,从而减慢了查询的效率。
/** * Initialize the hashing mask value. We defer initialization until we * really need it. */ final boolean initHashSeedAsNeeded(int capacity) { boolean currentAltHashing = hashSeed != 0; boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; }
用指定初始容量和指定加载因子构造一个新的空哈希表。其中initHashSeedAsNeeded方法用于初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决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); }
因为length为2的指数倍,所以length-1所对应的二进制位都为1,然后在与hashCode(key)做与运算,即可得到[0,length)内的索引,但是这里有个问题,如果hashCode(key)的大于length的值,而且hashCode(key)的二进制位的低位变化不大,那么冲突就会很多。造成冲突的原因关键在于16限制了只能用低位来计算,高位直接舍弃了,所以我们需要额外的哈希函数而不只是简单的对象的hashCode方法了。具体来说,就是HashMap中hash函数干的事了。
/** * 返回hashcode=h的索引位置. */ static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); } /** * 根据key查找entry,从代码可以看出key=null是合法的 */ final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); 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 != null && key.equals(k)))) return e; } return null; }
/** * */ public V put(K key, V value) { //判断数组table是否为空 if (table == EMPTY_TABLE) { inflateTable(threshold); } //如果key为空调用putForNullKey方法添加键为null的entry if (key == null) return putForNullKey(value); //得到key的hash值 int hash = hash(key); //根据上面得到的hash值得到应该放到table中的位置 int i = indexFor(hash, table.length); //遍历i位置的链表 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //判断i位置是否存在相同的key的entry,存在则用新值替换老的值 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; //如果走到这里,说明map中不存在键为key的entry addEntry(hash, key, value, i); return null; }
void addEntry(int hash, K key, V value, int bucketIndex) { // 如果当前table的大小大于阈值且当前位置的桶有数据,则对map进行扩容到原来的2倍 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); //扩容后重新计算key的hash值和应放置的位置 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //根据传入的参数创建一个新的entry放置在位置bucketIndex处 createEntry(hash, key, value, bucketIndex); }
void createEntry(int hash, K key, V value, int bucketIndex) { //先取出位置bucketIndex处的entry,这里是位置bucketIndex处链表的头结点 Entry<K,V> e = table[bucketIndex]; //把新建的entry放置到位置bucketIndex处作为该处链表的头结点,并把此处原来的entry设置为 //新entry的后继节点,也就是说每插入一个新值,都会放到链表的最前端。 table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
/** * 插null值,从代码可以看出,null值全部被插到第一个桶里面,而且只能插入一个key为null的数据 * 后面key为null的数据不会被插入 */ 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); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; }
阅读全文
1 0
- 源码分析之-容器类-HashMap
- 源码分析之HashMap
- java 容器类研究一:HashMap源码原理分析
- Java集合类之HashMap源码分析
- HashMap 类源码分析
- java源码分析之HashMap
- Java源码分析之HashMap
- java源码分析之HashMap
- Java源码分析之HashMap
- Map之HashMap源码分析
- JAVA之HashMap源码分析
- jdk源码分析之HashMap
- jdk源码分析之HashMap
- Java源码分析之HashMap
- java源码分析之HashMap
- Map之hashmap源码分析
- JAVA源码分析之HashMap
- [Java容器]HashMap实现原理和源码分析
- python enumerate
- 全选全不选批量删除
- linux命令:文件搜索(locate、whereis、which、find、grep)
- Android加载WebView避免页面空白
- 智能照明灯具认证相关标准介绍
- 源码分析之-容器类-HashMap
- Sharding-JDBC使用问题记录
- python加密->RC4.py
- FPGA电源供电芯片的设计篇
- iaas、paas、saas的云计算服务模式简介
- jsf dataTable修改一行数据的方法
- python初体验
- LED Tube(LED 灯管)常见欧规安规认证测试要求
- oracle的开启和关闭