博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从源码看Android常用的数据结构 ( 七, SDK28下的HashMap )
阅读量:6008 次
发布时间:2019-06-20

本文共 11815 字,大约阅读时间需要 39 分钟。

相关衔接

此系列文章放在了我的专栏里, 欢迎查看

Github里有一份Android使用到的小技术, 欢迎查看:

前言

Java1.8 和 Android SDK28 对 HashMap 做了大量优化, 与之前版本的主要区别在于以下两点:

  • hash碰撞超过8时把原链表转化为红黑树, 提高查询效率;
  • resize时对链表节点的重定位算法无需重hash计算, 而是通过判断&操作的高位来判断是放在低位cap还是高位cap;

新版本的HashMap结合了链表和红黑树的优化, 以达到最优的时间空间效率平衡, 这种精雕细琢的精神值得好好学习!

下面详细说明

先看关键参数

/**     * The table, initialized on first use, and resized as     * necessary. When allocated, length is always a power of two.     *      * 数据存储集合, 必要时会resize, 重分配后大小为2的n次方.     * 添加transient关键字以屏蔽默认serializable, 自主实现read/write.     */    transient Node
[] table; /** * Holds cached entrySet(). Note that AbstractMap fields are used * for keySet() and values(). * * 缓存entrySet. */ transient Set
> entrySet; /** * The number of key-value mappings contained in this map. * * map内实际存储的元素数目 */ transient int size; /** * The number of times this HashMap has been structurally modified * Structural modifications are those that change the number of mappings in * the HashMap or otherwise modify its internal structure (e.g., * rehash). This field is used to make iterators on Collection-views of * the HashMap fail-fast. (See ConcurrentModificationException). * * HashMap结构修改的次数. 结构修改是指元素数量改变或者以其他方式修改其内部 * 结构等操作(例如rehash). * 执行结构修改时modCount++, 在执行遍历等非原子操作前缓存modCount, 遍历过程 * 中或之后如果modCount与原缓存值不同, 说明出现了并发修改, 抛出异常 */ transient int modCount; /** * The next size value at which to resize (capacity * load factor). * 阈值, 触发resize的临界容量, 初始化时为最小的超过容量的2的指数, 后续更改为 * newCap * loadFactor */ int threshold; /** * The load factor for the hash table. * 加载因子, 用于判断threshold */ final float loadFactor;复制代码

核心方法Get/Put/Remove

GET方法

public V get(Object key) {        Node
e; return (e = getNode(hash(key), key)) == null ? null : e.value; } /** * 计算key的hash值, 使用hashcode的高16位异或低16位. 看注释的意思是可能会发生碰撞, * 但是考虑到碰撞的元素使用较高效率的tree来存储, 所以hash算法采用了较轻量的方式. */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } /** * Implements Map.get and related methods * * @param hash hash for key * @param key the key * @return the node, or null if none */ final Node
getNode(int hash, Object key) { Node
[] tab; Node
first, e; int n; K k; // 如果(n-1)&hash位置存在元素 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 如果hash相等, key相等, 返回第一个 if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 不是第一个的话遍历链表或者树 if ((e = first.next) != null) { // 查询树 if (first instanceof TreeNode) return ((TreeNode
) 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; }复制代码

Put相关

public V put(K key, V value) {        return putVal(hash(key), key, value, false, true);    }        /**     * Implements Map.put and related methods     *     * @param hash         hash for key     * @param key          the key     * @param value        the value to put     * @param onlyIfAbsent if true, don't change existing value     * @param evict        if false, the table is in creation mode.     * @return previous value, or null if none     */    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {        Node
[] tab; Node
parent; int tabLength, position; // 如果还未初始化, 调用resize来初始化一下, resize见下方方法 if ((tab = table) == null || (tabLength = tab.length) == 0) tabLength = (tab = resize()).length; // 如果该位置未存有元素, 第一位新建node存入 if ((parent = tab[position = (tabLength - 1) & hash]) == null) tab[position] = newNode(hash, key, value, null); // 如果该位置已有元素, 分为三种情况: else { Node
exitMapping; K k; // 该位置头节点就是对应节点 if (parent.hash == hash && ((k = parent.key) == key || (key != null && key.equals(k)))) exitMapping = parent; // 如果是树, 去树内查找 else if (parent instanceof TreeNode) exitMapping = ((TreeNode
) parent).putTreeVal(this, tab, hash, key, value); // 链表 else { for (int binCount = 0; ; ++binCount) { // 如果直到链表末尾也没找到已存在的点, 则直接插入到末尾, 退出循环 if ((exitMapping = parent.next) == null) { parent.next = newNode(hash, key, value, null); // 如果碰撞次数超过了转化为tree的阈值(8), 转化为红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 找到了对应节点 if (exitMapping.hash == hash && ((k = exitMapping.key) == key || (key != null && key.equals(k)))) break; parent = exitMapping; } } // 如果已存在节点, 在这里做value替换; 不存在对应节点的情况在前面for循环中已经插入了新节点 if (exitMapping != null) { // existing mapping for key V oldValue = exitMapping.value; // 如果允许替换值, 替换 if (!onlyIfAbsent || oldValue == null) exitMapping.value = value; // 用于linkedHashMap afterNodeAccess(exitMapping); return oldValue; } } // 发生了结构化修改 ++modCount; // 如果大小超过了tab阈值, resize为double if (++size > threshold) resize(); // 用于linkedHashMap afterNodeInsertion(evict); return null; } /** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * 某元素只可能保持在当前位置或者2次方位置 * * @return the table */ final Node
[] resize() { Node
[] oldTab = table; // 旧表的容量 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 旧表的阈值 int oldThr = threshold; int newCap, newThr = 0; // 如果有旧值, 不是初始化 if (oldCap > 0) { // 如果旧容量超过了2的30次方, 则阈值*2即int的最大值, 也就是2的31次方, 也不重新分配bin了, resize直接完成 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 新容量首先变为两倍, 如果旧容量超过了默认容量16, 新阈值double else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // 以下两种情况均是初始化 else if (oldThr > 0) { // 旧容量为0, 阈值不为0, 就把新的容量被设置为旧阈值 newCap = oldThr; } else { // 容量阈值均使用默认值 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 上面设置好了新的容量, 如果新阈值还是0的话(主要是针对上面三种情况的第二种), 设置为newCap * 重载因子loadFactor if (newThr == 0) { float ft = (float) newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE); } threshold = newThr; // 通过上面的处理, 新的容量和阈值已经配置好了, 下面的逻辑是把旧表里的节点放入新表中 Node
[] newTab = (Node
[]) new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node
header; // 某位置的链表头节点 if ((header = oldTab[j]) != null) { oldTab[j] = null; // 如果只有头节点, 就重hash放入新表中 if (header.next == null) { newTab[header.hash & (newCap - 1)] = header; } // 树形结构的修剪 else if (header instanceof TreeNode) { // 把树切为低位和高位两部分, 如果某一部分size小于6了, 转化为链表 ((TreeNode
) header).split(this, newTab, j, oldCap); } // 链表节点切分(!精髓!), (header.hash & oldCap) == 0 的节点放在前oldCap位, 其余的放在后半部分 // 原来的hash计算是 header.hash & (oldCap- 1), doubleSize后计算 header.hash & oldCap, // // 因为oldCap肯定是2的幂次, 所以只有一个高位1, 而计算 hash&(oldCap-1)时忽略了header.hash的最高位, 所以这一堆链表节点才会在这一个链表里 // 而header.hash & oldCap的结果反应的就是hash值的对应高位是否为1, 如果为1则插入高位, 为0说明本来就该在低位 else { // 放在新table低位的链表头尾 Node
loHead = null, loTail = null; // 放在新table高位的链表头尾 Node
hiHead = null, hiTail = null; Node
next; do { next = header.next; // 无需重hash的精髓!! if ((header.hash & oldCap) == 0) { if (loTail == null) loHead = header; else loTail.next = header; loTail = header; } else { if (hiTail == null) hiHead = header; else hiTail.next = header; hiTail = header; } } while ((header = next) != null); // 低尾节点不为null, 把低头节点放在新table的低位 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 高尾节点不为null, 把高头节点放在新table的高位 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } /** * 把链表转化为树 */ final void treeifyBin(Node
[] tab, int hash) { int tabLength, index; Node
e; // 遍历node // 如果tab太小, 直接调用resize, 可能就不用转化为tree了 if (tab == null || (tabLength = tab.length) < MIN_TREEIFY_CAPACITY) { resize(); } // 正常tableSize, 需要转化. 首先找到对应位置 else if ((e = tab[index = (tabLength - 1) & hash]) != null) { TreeNode
header = null, tail = null; do { // 依次转化为TreeNode TreeNode
p = replacementTreeNode(e, null); if (tail == null) header = p; else { p.prev = tail; tail.next = p; } tail = p; } while ((e = e.next) != null); if ((tab[index] = header) != null) // 格式化treeNode, 令其满足红黑树规则 header.treeify(tab); } } 复制代码

Remove相关

/**     * Removes the mapping for the specified key from this map if present.     *     * @param key key whose mapping is to be removed from the map     * @return the previous value associated with key, or     * null if there was no mapping for key.     * (A null return can also indicate that the map     * previously associated null with key.)     */    public V remove(Object key) {        Node
e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } /** * Implements Map.remove and related methods * * @param hash hash for key * @param key the key * @param value the value to match if matchValue, else ignored * @param matchValue if true only remove if value is equal * @param movable if false do not move other nodes while removing * @return the node, or null if none */ final Node
removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node
[] tab; Node
header; // 对应位置的头节点 int n, index; // 如果有该hash对应的节点 if ((tab = table) != null && (n = tab.length) > 0 && (header = tab[index = (n - 1) & hash]) != null) { // 待删除的点 Node
node = null; // 用于遍历的node Node
e; K k; V v; // 恰好是第一位 if (header.hash == hash && ((k = header.key) == key || (key != null && key.equals(k)))) node = header; else if ((e = header.next) != null) { // 树结构查找 if (header instanceof TreeNode) node = ((TreeNode
) header).getTreeNode(hash, key); else { // 链表遍历 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } header = e; } while ((e = e.next) != null); } } // 找到了这个点 if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { // 调用红黑树的remove if (node instanceof TreeNode) ((TreeNode
) node).removeTreeNode(this, tab, movable); // 以下两种情况均是链表前移 else if (node == header) tab[index] = node.next; else header.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }复制代码

转载地址:http://nivmx.baihongyu.com/

你可能感兴趣的文章
2018新版驾照驾照psd模板下载
查看>>
【矢量图控件教程】矢量图控件VectorDraw 常见问题整理大全(一)
查看>>
文件系统、服务、防火墙、SELINUX——安全四大金刚
查看>>
RabbitMQ如何保证队列里的消息99.99%被消费?
查看>>
Lync Server 2010的部署系列_第五章 准备 Active Directory 域服务
查看>>
java基本数据类型及运算符小结
查看>>
第一周博客作业
查看>>
Python strip lstrip rstrip使用方法
查看>>
Linux开发工具_1_gcc入门(上)
查看>>
在这里安家了
查看>>
ERP项目更应授人以渔
查看>>
我的友情链接
查看>>
thinkpython2
查看>>
JDK、JRE和JVM的关系
查看>>
String、StringBuffer和StringBuilder的区别
查看>>
【原创】ObjectARX中的代理对象
查看>>
.net中验证码的几种常用方法
查看>>
解决OracleDBConsoleorcl不能启动
查看>>
.net DLL程序集中打包另一个DLL
查看>>
我的友情链接
查看>>