hashmap底层数据结构(hashmap和hashtable的底层数据结构)
本篇文章给大家谈谈hashmap底层数据结构,以及hashmap和hashtable的底层数据结构对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
JDK1.8中的HashMap底层数据结构的图解
hashmap是java开发最常用的一种数据模型,hashmap属于map接口的一种实现。以key-value的这种形式存储数据,其中key是不允许重复的但是允许为空,value是可以重复或为空的。其中key只能使用基本数据类型(int,double...)的封装类(interger,double。。。。)。
JDK1.7中HashMap的底层是由数组+单向链表这两种数据结构组合而成的,而在JDK1.8中HashMap是由数组+单向链表+红黑树三种数据结构组合而成的。
初始化有固定的大小长度,有顺序的下标(下标从罩衫0开始),图1只是示例。
由每个节点组成,每个节点包含一个data(存储数据)和指向下一个节点地址的next,如图二。
(物答腔1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数举老目的黑节点。
介绍完三种基本结构后,我们再来看看hashmap的数据结构图,如图4。
图5是我截自JDK1.8的HashMap底层源码。
一图了解ConcurrentHashMap底层原理
1、ConcurrentHashMap底层数据结构是一个数组table
2、table数组上挂着单向链表或红黑树码巧伍
3、new ConcurrentHashMap();如果没有指定长度的话,默认是16,并且数组长度必须是2的n次幂,若自定义初始化的长度不是2的n次幂,那么在初始化数组时,会吧数组长度设置为大于自定义长度的最近的2的n次幂。(如:自定义长度为7,那么实际初始化数组后的长度为8)
4、底层是使用synchronized作为同步锁,并且锁的粒度是数组的具体索引位(有些人称之为分段锁)。
5、Node节点,hash0,当hash冲迟或突时,会形成一个单向链表挂在数组上。
6、ForwardindNode,继承至Node,其hash=-1,表示当前索引位宽镇的数据已经被迁移到新数组上了
7、ReservationNode,继承至Node,其hash=-3,表示当前索引位被占用了(compute方法)
8、TreenBin,继承至Node,其hash=-2,代表当前索引下挂着一颗红黑树
9、lockState为ConcurrentHashMap底层自己实现的基于cas的读写锁,锁粒度是具体的某颗树。当向红黑树进行增,删操作时,首先会先上sync同步锁,然后自平衡的时候会上lockState写锁,当读红黑树的时候,会上lockState读锁,然后判断此时是否有线程正持有写锁,或是否有线程正在等待获取写锁,若有,则读线程会直接去读双向链表,而不是去读红黑树。
10、TreeNode,hash0,为红黑树具体节点。TreeBin的root代表红黑树的根节点,first代表双向链表的第一个节点
11、双向链表:构建红黑树时还维护了一个双向链表,其目的为:(1)当并发写读同一颗红黑树的时候,读线程判断到有线程正持有写锁,那么会跑去读取双向链表,避免因为并发写读导致读线程等待或阻塞。(2)当扩容的时候,会去遍历双向链表,重新计算节点hash,根据新的hash判断放在新数组的高位还是地位
1、触发扩容的规则:
1)添加完元素后,若判断到当前容器元素个数达到了扩容的阈值(数组长度*0.75),则会触发扩容
2)添加完元素后,所在的单向链表长度=8,则会转为红黑树,不过在转红黑树之前会判断数组长度是否小于64,若小于64则会触发扩容
2、table为扩容前的数组,nextTable为扩容中创建的新数组,迁移数据完毕后需要将nextTable赋值给table
3、扩容后的数组是元素组长度的2倍
4、ln,hn分别表示高位和低位的链表(高链,低链)。即,扩容时,会用nh==0来判断元素应该放在新数组的i位置还是i+n位置。n:元素组长度;h:元素hash值;i:元素所在的原数组索引位;。这样就会出现有些元素会被挂在低位,有些元素会被挂在高位,从而达到打散元素的目的。
5、红黑树扩容时,会遍历双向链表,并且计算nh==0来判断元素放在低位(lo)还是高位(ho),确定完元素的去处之后,会判断分别判断两个双向链表(lo,ho)的长度是否大于6,若大于6则将还是以一颗红黑树的结构挂在数组上,若=6的话,则转为单向链表,挂在数组上
[img]HashMap底层实现原理剖析
Hashmap是一种非常常用的、应用广泛的数据类型,最近研究到相关的内容,就正好复习一下并者御。网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论。
1.HashMap的数据结构:在java 中 数据结构,最基本 也就两种 一种数组 一种模拟指针。所有的数据结构都可以用这两个基本结构来构造的,hashmap也不例外。Hashmap实际上是一个数组和链表的结合体绝岩。数组的默认长度为16,
2.hashMap源码解析
static final int DEFAULT_INITIAL_CAPACITY = 1 4; // 初始化容量大小
static final int MAXIMUM_CAPACITY = 1 30; ///容器最大值
static final float DEFAULT_LOAD_FACTOR = 0.75f; //加载影子
static final Entry[] EMPTY_TABLE = {}; //null 的hashMap
transient Entry[] table = (Entry[]) EMPTY_TABLE;///动态扩大容器时使用的一个hashMap
transient int size;//当前数据量
int threshold;//扩大容器时的大小 为 capacity * load factor
final float loadFactor;//使用率阀值,默认为:DEFAULT_LOAD_FACTOR
存取元素 :调用put方法
public V put(K key, V value) {
//判断当前table 为Null 第一次Put
if (table == EMPTY_TABLE) {
inflateTable(threshold); //初始化容器的大小
}
if (key == null)
return putForNullKey(value); //判断当前key 为null 将Null key添加到数组的第一个位置
int hash = hash(key); //将当前key进嫌掘行hash 详情见下方
int i = indexFor(hash, table.length); //调用完hash算法后,详情见下方
for (Entry e = table[i]; e != null; e = e.next) { //循环判断当前数组下标为Entry的实体 将当前key相同的替换为最新的值
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); //如果key都不同 则添加Entry.详情见下方
return null;
}
hashMap的hash算法剖析
final int hash(Object k) {
int h = hashSeed;
if (0 != h k instanceof String) { //判断当前k是否为string 和
return sun.misc.Hashing.stringHash32((String) k); //使用stringHash32算法得出key 的hash值
}
h ^= k.hashCode(); //调用key的hashCode 得出值 后使用"或"运算符
h ^= (h 20) ^ (h 12);
return h ^ (h 7) ^ (h 4);
前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的 元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的效率。
一个十进制数32768(二进制1000 0000 0000 0000),经过上述公式运算之后的结果是35080(二进制1000 1001 0000 1000)。看出来了吗?或许这样还看不出什么,再举个数字61440(二进制1111 0000 0000 0000),运算结果是65263(二进制1111 1110 1110 1111),现在应该很明显了,它的目的是让“1”变的均匀一点,散列的本意就是要尽量均匀分布。使用上述算法后 "1"就变得很均匀了。
我们用table[index]表示已经找到的元素需要存储的位置。先判断该位置上有没有元素(这个元素是HashMap内部定义的一个类Entity, 基本结构它包含三个类,key,value和指向下一个Entity的next),没有的话就创建一个Entity对象,在 table[index]位置上插入,这样插入结束;如果有的话,通过链表的遍历方式去逐个遍历,看看有没有已经存在的key,有的话用新的value替 换老的value;如果没有,则在table[index]插入该Entity,把原来在table[index]位置上的Entity赋值给新的 Entity的next,这样插入结束
}
indexFor 返回当前数组下标 ,
static int indexFor(int h, int length) {
return h (length-1);
}
那么得到key 之后的hash如何得到数组下标呢 ?把h与HashMap的承载量(HashMap的默认承载量length是16,可以自动变长。在构造HashMap的时候也可以指定一个长 度。这个承载量就是上图所描述的数组的长度。)进行逻辑与运算,即 h (length-1),这样得到的结果就是一个比length小的正数,我们把这个值叫做index。其实这个index就是索引将要插入的值在数组中的 位置。第2步那个算法的意义就是希望能够得出均匀的index,这是HashTable的改进,HashTable中的算法只是把key的 hashcode与length相除取余,即hash % length,这样有可能会造成index分布不均匀。
首先来解释一下为什么数组大小为2的幂时hashmap访问的性能最高?
看下图,左边两组是数组长度为16(2的4次方),右边两组是数组长度为15。两组的hashcode均为8和9,但是很明显,当它们和1110“与”的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hashcode的值会与14(1110)进行“与”,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
void addEntry(int hash, K key, V value, int bucketIndex) {
//// 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小
if ((size = threshold) (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//// 设置“bucketIndex”位置的元素为“新Entry”,// 设置“e”为“新Entry的下一个节点”
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//将当前key 和value添加到Entry[]中
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry e = table[bucketIndex]; //将第一个就得table 复制个新的entry
table[bucketIndex] = new Entry(hash, key, value, e); //将当前新的Entry 复制个table[bucketIndex] 旧的table[bucketIndex] 和新的table[buckIndex]之间用next关联。第一个键值对A进来,通过计算其key的hash得到的index=0,记做:table[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做: B.next = A ,table[0] = B,如果又进来C,index也等于0,那么 C.next = B ,table[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起
size++; //容量加1
}
以上就是HashMap添加元素时的过程解析
那么如何get元素呢?
public V get(Object key) {
if (key == null) return getForNullKey(); //当前key是否为null 如果为null值返回table[0]这个value
Entry entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final EntrygetEntry(Object key) {
if (size == 0) { return null; } //判断容量是否大于0
int hash = (key == null) ? 0 : hash(key); //对当前key 进行hash hash后得到一个值
for (Entry e = table[indexFor(hash, table.length)]; //获取当前Entry 循环遍历
e != null;
e = e.next) {
Object k;
if (e.hash == hash
((k = e.key) == key || (key != null key.equals(k))))
return e;
}
return null;
}
扩展问题:
1.当前我们的hashMap中越来越大的之后,"碰撞"就越来越明显,那么如何解决碰撞呢?扩容!
当hashmap中的元素个数超过数组大小capti*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 1000, 也就是说为了让0.75 * size 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了的问题,也避免了resize的问题
HashMap的两种遍历方式
第一种
Map map = newHashMap();
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}
效率高,以后一定要使用此种方式!
第二种
Map map = newHashMap();
Iterator iter = map.keySet().iterator();
while(iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
效率低,以后尽量少使用!
归纳
简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,
也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
hashmap底层实现原理
hashmap底层原理是HashMap基于hashing原理,通过put和get方法储存和获取对象。
当将键值对传递给put方法时,它调用键对象的hashCode方法来计算hashcode,然后找到bucket位置来储存值对象。
当获取对象时,通过键对象的equals方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。HashMap在每个链表节点中储存键值对对象。
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,森喊迅并允许使用null值和null键。
此类不保证映射的顺序,特别是它不保证该顺序恒久不变。在java编程语言渗冲中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。
HashMap实际上是一个“此此链表散列”的数据结构,即数组和链表的结合体。
关于hashmap底层数据结构和hashmap和hashtable的底层数据结构的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。