Map之hashmap源码分析
来源:互联网 发布:python编程求圆的面积 编辑:程序博客网 时间:2024/05/20 14:18
全限定类名:java.util.HashMap
一:HashMap概述
HashMap是实现了KEY-VALUE的非线程安全的数据结构。允许使用 null 值和 null 键。
二:HahsMap的内部是以何种形式存储数据的.
根据每个segment包含元素的具体数量以及MIN_TREEIFY_CAPACITY参数的限定共同决定使用链表还是红黑树。(在1.7以及之前只存在链表结构。关于红黑树的数据结构这边不予以详细介绍)
三:JDK1.8的HashMap与1.7中有何区别
JDK7与JDK8中HashMap实现的最大区别就是对于冲突的处理方法。由JDK7中的链表变为了JDK8中的链表、红黑树并存。
四:哪些优秀的内容本文没有提及的
五:源码分析及实现
1.常量说明
1.1 DEFAULT_INITIAL_CAPACITY 默认初始化容量。容量必须为2的次方。原因在下面hash的时候会解释。默认的hashmap大小为16.
/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
1.2 MAXIMUM_CAPACITY 最大的容量大小2^30。
/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30;
1.3 DEFAULT_LOAD_FACTOR默认resize的因子。0.75,即实际数量超过总数*DEFAULT_LOAD_FACTOR的数量即会发生resize动作。
/** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;
1.4 TREEIFY_THRESHOLD 树化阈值。当单个segment的容量超过阈值时,将链表转化为红黑树。
/** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. */ static final int TREEIFY_THRESHOLD = 8;
1.5 UNTREEIFY_THRESHOLD 链表化阈值。当删除操作后单个segment的容量低于阈值时,将红黑树转化为链表。
/** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6;
1.6 MIN_TREEIFY_CAPACITY 最小树化容量。当桶中的bin被树化时最小的hash表容量,低于该容量时不会树化。
/** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64;
2.关键操作分析
2.1 如何分配到各个segment当中。
一致性算法一般包含mod或者是hash。HashMap是以hash操作作为散列依据。但是又与传统的hash存在着少许的优化。其hash值是key的hashcode与其hashcode右移16位的异或结果。在put方法中,将取出的hash值与当前的hashmap容量-1进行与运算。得到的就是位桶的下标。那么为何需要使用key.hashCode() ^ h>>>16的方式来计算hash值呢。其实从微观的角度来看,这种方法与直接去key的哈希值返回在功能实现上没有差别。但是由于最终获取下表是对二进制数组最后几位的与操作。所以直接取hash值会丢失高位的数据,从而增大冲突引起的可能。由于hash值是32位的二进制数。将高位的16位于低位的16位进行异或操作,即可将高位的信息存储到低位。因此该函数也叫做扰乱函数。目的就是减少冲突出现的可能性。而官方给出的测试报告也验证了这一点。直接使用key的hash算法与扰乱函数的hash算法冲突概率相差10%左右。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {...n = table.lengthi = (n - 1) & hash...}2.2 如何获取下一个需要扩容的大小。即如何获取距离一个数字的最近的2的幂次方。
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }由于map的容量需要维持在2的幂次方,所以需要计算出下一个扩容的大小。具体算法如图所示,int n = cap-1;的目的是为了防止当前的大小正好为2的幂次。
二进制中一个大于0的数字必定有一位为1。假设一个数字为00001******。在进行第一次n|= n>>>1;后结果变为000011*****;即让最高位以及次一位的数字变为1.同理
下一步变为00001111****;即让后两位再变为一。以此类推直到数据变为000011111111.最后执行+1操作即得到了距离给定数字最近的二次幂。
2.3 如何根据key获取value。
源码:public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
核心思想即通过传入的key,调用其hash值对segment的位置进行判断后。根据定位的segment对相应的链表/红黑树进行搜索。判断两个key的.equals()方法为真时,即取得对应的value。2.5 如何根据提交新的值。
源码:public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) //(1) n = (tab = resize()).length;//(2) if ((p = tab[i = (n - 1) & hash]) == null)//(3) tab[i] = newNode(hash, key, value, null);//(4) else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))//(5) e = p;//(6) else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //(7) else { for (int binCount = 0; ; ++binCount) {//(7) if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//(8) treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e);//无用操作,为linkedhashmap服务 return oldValue; } } ++modCount;//(9) if (++size > threshold)//(10) resize(); afterNodeInsertion(evict);//无用操作,为linkedhashmap服务 return null; }先判断当前的table是否为空(1),如果是则对table进行resize操作(2)。否则根据传入的key的hash算法进入到相应的segment(3)。如果segment对应的Node节点为空(4),则创建新节点并赋值。如果不为空则判断当前节点是否为传入节点(5),若是则替换value值(6),否则循环遍历list或者红黑树查询是否存在相同的key(7),若是则替换,否则插入。同时判断树化阈值以及最小树化阈值来判断是否resize或者是树化(8)。将操作计数+1(9)。判断是否需要扩容(10)。本段没有详细描述红黑树如何插入,查找节点。红黑树内容请参照其他文章进行学习。2.6 如何删除一个值
源码public V remove(Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; }final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }删除操作可以参照上面的插入操作。进行反向思路即可。
- Map之HashMap源码分析
- Map之hashmap源码分析
- Java集合框架之Map---HashMap和LinkedHashMap源码分析
- Java集合框架之Map---HashMap和LinkedHashMap源码分析
- 源码分析之HashMap
- Map集合及HashMap源码分析
- java源码分析之HashMap
- Java源码分析之HashMap
- java源码分析之HashMap
- Java源码分析之HashMap
- JAVA之HashMap源码分析
- jdk源码分析之HashMap
- jdk源码分析之HashMap
- Java源码分析之HashMap
- java源码分析之HashMap
- JAVA源码分析之HashMap
- HashSet与HashMap关系之源码分析
- Thinking in Java之HashMap源码分析
- LeetCode 二叉树路径问题 Path SUM(①②③)总结
- 几种工厂模式的区别
- 度度熊的交易计划 最小费用流
- ping命令返回的TTL值判断操作系统
- 安卓开发设置EditView中只能输入数字
- Map之hashmap源码分析
- 剑指OFFER-二叉树中和为某一值的路径
- hdu 6081 度度熊的王国战略
- Junit 学习总结(一)
- 翻转单词顺序列
- 代码干货 | 嵌入式开发C语言位结构体用途详解
- mybatis like查询常用两种写法
- 表单验证
- 正则表达式随机采集页面上面的六位数