Java HashMap源码简析
来源:互联网 发布:javascript 书 编辑:程序博客网 时间:2024/06/07 04:54
Java HashMap的核心是数组加链表结构,数组主要用于存储和快速寻址,链表主要用户解决冲突。当然这是概念上的,源码体现的更加详细和丰满,下面是一些源码关键点:
成员变量:
** * 默认初始容量必须是2的幂(性能问题,后面会涉及) */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * 最大容量 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 默认负载参数 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; //Entry类型数组 transient Entry[] table;
Entry类:
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; ……}next是实现链表的关键,HashMap里的链表是个单链表,后插入的在链表头,先插入的在链表尾。
get方法:
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; }注意到get方法是通过indexFor方法来寻找数组的索引号,indexFor的参数是数组长度和对key的hashCode再次hash的结果,找到数组索引号后遍历链表寻找元素。
hash方法:
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); }
indexFor方法:
static int indexFor(int h, int length) { return h & (length-1); }根据key定位数组的索引号方法很多,比如我们都很熟悉的取模方法(数组长度模key的hashCode)。源码的做法是对key的hashCode再次进行哈希,然后与数组长度-1相与。
为什么要对key的hashCode再次哈希?源码里的javadoc的解释是避免过差的key的hashCode方法。我认为跟indexFor采用的跟数组长度-1相与来寻址有关,试想如果数组长度16(数组长度-1的二进制表示为:1111,1111),但是我的key为(1111,1111,1111),(1110,1111,1111),(1101,1111,1111),是不是三个key都映射到同一个数组索引号了?再次hash的原因大概如此,更详细原理可以参考:http://www.365doit.com/all/news/hashmapdeep.html。
那么为什么不用取模方法用相与呢?因为相与操作比取模性能更好。那么为什么是跟数组长度-1相与呢?因为在设置数组长度为2的幂的情况下,用数组长度-1相与可以减少冲突。更详细的可以参考:http://www.iteye.com/topic/539465
put方法:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); 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; }put方法源码现实很清晰的说明了一个事情:插入的key value pair如果key重复了,新的value将覆盖老的value。如果key不重复,那么说明冲突了,方法addEntry把元素加入到hashMap,下面看看addEntry的源码:
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
取出原来数组里面已经有的Entry对象,作为参数构造一个新的Entry,放回数组。这个Entry的构造函数式怎么样的?
Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }真相大白,它把原来的Entry作为新插入Entry的next引用。即在冲突的情况下,新元素在链表头,老元素在链表尾。
0 0
- Java HashMap源码简析
- java源码研究-HashMap
- Java源码之HashMap
- Java HashMap 源码分析
- 【Java源码】HashMap类
- java HashMap源码分析
- Java HashMap 核心源码
- Java HashMap 源码解析
- Java HashMap 源码解析
- Java HashMap 源码解析
- Java源码之HashMap
- Java源码分析:HashMap
- JAVA源码学习-HashMap
- Java-HashMap源码分析
- Java:HashMap源码解析
- java HashMap源码剖析
- Java源码解析-hashmap
- [Java]HashMap源码分析
- 三级联动的省市县下拉菜单
- inflate中的参数
- Linux20-shell编程基础、数值计算、字符串处理
- C++代码实现快速排序
- Runloop学习总结
- Java HashMap源码简析
- C语言读取文本文档里面的字符and 和只读一行;
- 我电脑上python相关软件
- UVa:10494 - If We Were a Child Again
- c++ part3
- UVA - 152 - Tree's a Crowd
- Android-动画小结
- Spring——容器,注入(配置和注解两种方式)
- struts2的ognl与valuestack