博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HashMap实现原理和扩容及高版本优化
阅读量:4256 次
发布时间:2019-05-26

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

HashMap的实现原理面试简单解答

补充面试题:为什么hashcode和equals一般同时重写。

hashmap如何判断出现了碰撞,然后存储在链表中。

hash算法。

这里不剖析源码只简单讲解:

1原理简单解析

数组结构

HashMap内部结构为数组加链表方式,这里可以知道HashMap解决冲突的方法是链地址法,

Node<K,V>[] table,Node节点里包括hash值,key,value,nextNode,HashMap中key值得Hash值得计算

return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)(1.8jdk);

上面公式用到了key的hashCode值,每种类型的key都会重写

hashCode的计算方法,例如String:(重写了hashCode一般需要重写equals)

public int hashCode() {        int h = hash;        final int len = length();        if (h == 0 && len > 0) {            for (int i = 0; i < len; i++) {                h = 31 * h + charAt(i);            }            hash = h;        }        return h;    }
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)计算key的hash值最终会比较小,右移16位之后亦或使最终结果包含了高位和低位特性,碰撞的几率会更小。最终会利用hash值和长度进行求余,确定key的位置。
V putVal(int hash, K key, V value, boolean onlyIfAbsent,                   boolean evict) {    //n 是长度,然后& hash值得到key在数组中的位置        Node
[] tab; Node
p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { }

 链表结构

在putVal函数中如果判断数组中已经存在node,此时调用putTreeVal函数把node放在node的链表中,如果链表过长会转换成红黑树存储

2 HashMap扩容

综合load factor和长度,如果需要扩容会调用resize函数,将原来的长度扩大一倍,根据扩容后的长度建立新的数组,然后把旧的数据放入到新数组中。默认大小为16,负载因子和默认大小都可以设置。

final Node
[] resize() {//旧数组 Node
[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length;//旧长度 int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) {//一般情况扩容走这里 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)//得到新的长度 newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); }//赋值 threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"})//建立新数组 Node
[] newTab = (Node
[])new Node[newCap]; table = newTab;//拷贝旧数组到新数组 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node
e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode
)e).split(this, newTab, j, oldCap); else { // preserve order Node
loHead = null, loTail = null; Node
hiHead = null, hiTail = null; Node
next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }

hash算法中解决冲突的办法有很多,开放地址法,链地址发,再hash法等。

3 优化

/** * 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;

大致意思就是当list链表长度超过8时就用tree树进行存储。

/**     * Replaces all linked nodes in bin at index for given hash unless     * table is too small, in which case resizes instead.     */    final void treeifyBin(Node
[] tab, int hash) { int n, index; Node
e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode
hd = null, tl = null; do { TreeNode
p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
/**         * Forms tree of the nodes linked from this node.         * @return root of tree         */        final void treeify(Node
[] tab) { TreeNode
root = null; for (TreeNode
x = this, next; x != null; x = next) { next = (TreeNode
)x.next; x.left = x.right = null; if (root == null) { x.parent = null; x.red = false; root = x; } else { K k = x.key; int h = x.hash; Class
kc = null; for (TreeNode
p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode
xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; root = balanceInsertion(root, x); break; } } } } moveRootToFront(tab, root); }

上面是优化的具体代码,简单说就是把list转成了红黑树,查找效率更高。

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

你可能感兴趣的文章
【Lintcode】寻找峰值
查看>>
Arduino 串口读写 SD 卡模块
查看>>
图的基本算法--深度优先搜索(dfs) 和 广度优先搜索(bfs)
查看>>
[Linux] Linux内核编译安装过程,及Linux源码目录结构
查看>>
[Linux] c语言变量的存储位置-笔记
查看>>
[Linux] 头文件实质-笔记
查看>>
统一修改iOS中xib颜色值
查看>>
数据湖与数据仓库的新未来:阿里提出湖仓一体架构
查看>>
基于 Flink+Iceberg 构建企业级实时数据湖 | 附 PPT 下载
查看>>
Flink 源码:Checkpoint 元数据详解
查看>>
基于Flink+ClickHouse打造轻量级点击流实时数仓
查看>>
Flink sink schema 字段设计小技巧
查看>>
Flink 使用 union 代替 join 和 cogroup
查看>>
踩坑记 | Flink 天级别窗口中存在的时区问题
查看>>
用了 History Server,妈妈再也不用担心我的 Flink 作业半夜挂了
查看>>
强烈推荐三本 Spark 新书籍
查看>>
ClickHouse 知识讲解
查看>>
ClickHouse 如何玩转时序数据
查看>>
Flink 在腾讯视频的应用实践
查看>>
Flink SQL 1.11 on Zeppelin 平台化实践
查看>>