mysql存储hashmap类型数据_HashMap集合(⾼级)
1.HashMap集合简介
HashMap基于哈希表的Map接⼝实现,是以key-value存储形式存在,即主要⽤来存放键值对。HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突(两个对象调⽤的hashCode⽅法计算的哈希码值⼀致导致计算的数组索引值相同)⽽存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较⼤的变化,当链表长度⼤于阈值(或者红⿊树的边界值,默认为 8)并且当前数组的长度⼤于64时,此时此索引位置上的所有数据改为使⽤红⿊树存储。
补充:将链表转换成红⿊树前会判断,即使阈值⼤于8,但是数组长度⼩于64,此时并不会将链表变为红⿊树。⽽是选择进⾏数组扩容。
这样做的⽬的是因为数组⽐较⼩,尽量避开红⿊树结构,这种情况下变为红⿊树结构,反⽽会降低效率,因为红⿊树需要进⾏左旋,右旋,变⾊这些操作来保持平衡 。同时数组长度⼩于64时,搜索时间相对要快些。所以综上所述为了提⾼性能和减少搜索时间,底层在阈值⼤于8并且数组长度⼤于64时,链表才转换为红⿊树。具体可以参考 treeifyBin⽅法。
当然虽然增了红⿊树作为底层数据结构,结构变得复杂了,但是阈值⼤于8并且数组长度⼤于64时,链表转换为红⿊树时,效率也变的更⾼效。
⼩结:
特点:
1.存取⽆序的
2.键和值位置都可以是null,但是键位置只能是⼀个null
3.键位置是唯⼀的,底层的数据结构控制键的
4.jdk1.8前数据结构是:链表 + 数组 jdk1.8之后是 : 链表 + 数组 + 红⿊树
5.阈值(边界值) > 8 并且数组长度⼤于64,才将链表转换为红⿊树,变为红⿊树的⽬的是为了⾼效的查询。
2.HashMap集合底层的数据结构
2.1数据结构概念
数据结构是计算机存储、组织数据的⽅式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。通常情况下,精⼼选择的数据结构可以带来更⾼的运⾏或者存储效率。数据结构往往同⾼效的检索算法和索引技术有关。
数据结构:就是存储数据的⼀种⽅式。ArrayList LinkedList
在JDK1.8 之前 HashMap 由 数组+链表数据结构组成的。
在JDK1.8 之后 HashMap 由 数组+链表 +红⿊树数据结构组成的。
2.2HashMap底层的数据结构存储数据的过程
存储过程如下所⽰:
使⽤的代码:
public classDemo01 {public static voidmain(String[] args) {
HashMap map = new HashMap<>();
map.put("刘德华", 53);
map.put("柳岩", 35);
map.put("张学友", 55);
map.put("郭富城", 52);
map.put("黎明", 51);
map.put("林青霞", 55);
map.put("刘德华", 50);
}
}
网页可视化编辑2.⾯试题:当两个对象的hashCode相等时会怎么样?
会产⽣哈希碰撞,此时会调⽤对象的equals⽅法⽐较key值,若key值内容相同则替换旧的value.不然连接到链表后⾯,链表长度超过阈值8就转换为红⿊树存储。
3.⾯试题:何时发⽣哈希碰撞和什么是哈希碰撞,如何解决哈希碰撞?
只要两个元素的key计算的哈希码值相同就会发⽣哈希碰撞。jdk8前使⽤链表解决哈希碰撞。jdk8之后使⽤链表+红⿊树解决哈希碰撞。
4.⾯试题:如果两个键的hashcode相同,如何存储键值对?
hashcode相同,通过equals⽐较内容是否相同。
相同:则新的value覆盖之前的value
不相同:则将新的键值对添加到哈希表中
5.在不断的添加数据的过程中,会涉及到扩容问题,当超出临界值(且要存放的位置⾮空)时,扩容。默认的扩容⽅式:扩容为原来容量的2倍,并将原有的数据复制过来。
但是这样的话问题来了,传统hashMap的缺点,1.8为什么引⼊红⿊树?这样结构的话不是更⿇烦了吗,为何阈值⼤于8换成红⿊树?
JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有⼤量的元素都存放到同⼀个桶中时,这个桶下有⼀条长长的链表,这个时候 HashMap 就相当于⼀个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引⼊了 红⿊树(查时间复杂度为 O(logn))来优化这个问题。 当链表长度很⼩的时候,即使遍历,速度也⾮常快,但是当链表长度不断变长,肯定会对查询性能有⼀定的影响,所以才需要转成树。
⾄于为什么阈值是8,我想,去源码中寻答案应该是最可靠的途径。 下⾯我们在分析源码的时候会介绍。
7.总结:
上述我们⼤概阐述了HashMap底层存储数据的⽅式。为了⽅便⼤家更好的理解,我们结合⼀个存储流程图来进⼀步说明⼀下:(jdk8存储过程)
说明:
1.size表⽰ HashMap中K-V的实时数量 , 注意这个不等于数组的长度 。
2.threshold( 临界值) =capacity(容量) * loadFactor( 加载因⼦ )。这个值是当前已占⽤数组长度的最⼤值。size超过这个临界值就重新resize(扩容),扩容后的 HashMap 容量是之前容量的两倍 。
3.HashMap继承关系
HashMap继承关系如下图所⽰:
说明:
Cloneable 空接⼝,表⽰可以克隆。 创建并返回HashMap对象的⼀个副本。
Serializable 序列化接⼝。属于标记性接⼝。HashMap对象可以被序列化和反序列化。mysql面试题集合
AbstractMap ⽗类提供了Map实现接⼝。以最⼤限度地减少实现此接⼝所需的⼯作。
补充:通过上述继承关系我们发现⼀个很奇怪的现象, 就是HashMap已经继承了AbstractMap⽽AbstractMap类实现了Map接⼝,那为什么HashMap还要在实现Map接⼝呢?同样在ArrayList中LinkedList中都是这种结构。
据 java 集合框架的创始⼈Josh Bloch描述,这样的写法是⼀个失误。在java集合框架中,类似这样的写法很多,最开始写java集合框架的时候,他认为这样写,在某些地⽅可能是有价值
的,直到他意识到错了。显然的,JDK的维护者,后来不认为这个⼩⼩的失误值得去修改,所以就这样存在下来了。
4.HashMap集合类的成员
1.序列化版本号
private static final long serialVersionUID = 362498820763181265L;
2.集合的初始化容量( 必须是⼆的n次幂)
//默认的初始容量是16 -- 1<<4相当于1*2的4次⽅---1*16
strusts框架学习一static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
问题: 为什么必须是2的n次幂?如果输⼊值不是2的幂⽐如10会怎么样?
HashMap构造⽅法还可以指定集合的初始化容量⼤⼩:
HashMap(int initialCapacity) 构造⼀个带指定初始容量和默认加载因⼦ (0.75) 的空 HashMap。
javascript:printall根据上述讲解我们已经知道,当向HashMap中添加⼀个元素的时候,需要根据key的hash值,去确定其在数组中的具体位置。 HashMap 为了存取⾼效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度⼤致相同,这个实现就在把数据存到哪个链表中的算法。
这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算(这点上述已经讲解)。所以源码中做了优化,使⽤ hash& (length-1),⽽实际上hash%length等于hash&(length-1)的前提是length是2的n次幂。
为什么这样能均匀分布减少碰撞呢?2的n次⽅实际就是1后⾯n个0,2的n次⽅-1 实际就是n个1;
举例:说明:按位与运算:相同的⼆进制数位上,都是1的时候,结果为1,否则为零。
例如长度为8时候,3&(8-1)=3 2&(8-1)=2,不同位置上,不碰撞;
例如长度length为8时候,8是2的3次幂。⼆进制是:1000length-1⼆进制运算:1000
- 1
---------------------
111如下所⽰:
hash&(length-1)3 &(8 - 1)=3
00000011 3hash& 00000111 7 length-1
---------------------
00000011-----》3数组下标
hash&(length-1)2 & (8 - 1) = 2
00000010 2hash& 00000111 7 length-1
---------------------
00000010-----》2数组下标
说明:上述计算结果是不同位置上,不碰撞;
concatenatex dax2的n次幂不碰撞
例如长度为9时候,3&(9-1)=0 2&(9-1)=0,都在0上,碰撞了;
例如长度length为9时候,9不是2的n次幂。⼆进制是:00001001length-1⼆进制运算:1001
- 1
简述存储过程的分类---------------------
1000如下所⽰:
hash&(length-1)3 &(9 - 1)=0
00000011 3hash& 00001000 8 length-1
---------------------
00000000-----》0数组下标
hash&(length-1)2 & (9 - 1) = 2
00000010 2hash& 00001000 8 length-1
---------------------
00000000-----》0数组下标
说明:上述计算结果都在0上,碰撞了;
⾮2的n次幂碰撞
注意: 当然如果不考虑效率直接求余即可(就不需要要求长度必须是2的n次⽅了)
⼩结:
1.由上⾯可以看出,当我们根据key的hash确定其在数组的位置时,如果n为2的幂次⽅,可以保证数据的均匀插⼊,如果n不是2的幂次⽅,可能数组的⼀些位置永远不会插⼊数据,浪费数组的空间,加⼤hash冲突。
2.另⼀⽅⾯,⼀般我们可能会想通过 % 求余来确定位置,这样也可以,只不过性能不如 & 运算。⽽且当n是2的幂次⽅时:hash & (length - 1) == hash % length
3.因此,HashMap 容量为2次幂的原因,就是为了数据的的均匀分布,减少hash冲突,毕竟hash冲突越⼤,代表数组中⼀个链的长度越⼤,这样的话会降低hashmap的性能
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论