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,才将链表转换为红黑树,变为红黑树的目的是为了高效的查询。

326b72d19581007df2b6f7ee73061d17.png

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);

}

}

4475a5788df4dcf48fb23a93f6a7bcbc.png

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存储过程)

979a70fc60ad451f0957d922c86bc507.png

说明:

1.size表示 HashMap中K-V的实时数量 , 注意这个不等于数组的长度 。

2.threshold( 临界值) =capacity(容量) * loadFactor( 加载因子 )。这个值是当前已占用数组长度的最大值。size超过这个临界值就重新resize(扩容),扩容后的 HashMap 容量是之前容量的两倍 。

3.HashMap继承关系

HashMap继承关系如下图所示:

4b000a12af4cc0a8a0db5a33dad8853f.png

说明:

Cloneable 空接口,表示可以克隆。 创建并返回HashMap对象的一个副本。

Serializable 序列化接口。属于标记性接口。HashMap对象可以被序列化和反序列化。

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

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

问题: 为什么必须是2的n次幂?如果输入值不是2的幂比如10会怎么样?

HashMap构造方法还可以指定集合的初始化容量大小:

HashMap(int initialCapacity) 构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。

根据上述讲解我们已经知道,当向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,否则为零。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

例如长度为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数组下标

说明:上述计算结果是不同位置上,不碰撞;

2的n次幂不碰撞

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

例如长度为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的性能

4.如果创建HashMap对象时,输入的数组长度是10,不是2的幂,HashMap通过一通位移运算和或运算得到的肯定是2的幂次数,并且是离那个数最近的数字。

源代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

//创建HashMap集合的对象,指定数组长度是10,不是2的幂

HashMap hashMap = new HashMap(10);public HashMap(int initialCapacity) {//initialCapacity=10

this(initialCapacity, DEFAULT_LOAD_FACTOR);

}public HashMap(int initialCapacity, float loadFactor) {//initialCapacity=10

if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity >MAXIMUM_CAPACITY)

initialCapacity=MAXIMUM_CAPACITY;if (loadFactor <= 0 ||Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor =loadFactor;this.threshold = tableSizeFor(initialCapacity);//initialCapacity=10

}/*** Returns a power of two size for the given target capacity.*/

static final int tableSizeFor(int cap) {//int cap = 10

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;

}

hashMap转换源码

说明:

由此可以看到,当在实例化HashMap实例时,如果给定了initialCapacity(假设是10),由于HashMap的capacity必须都是2的幂,因此这个方法用于找到大于等于initialCapacity(假设是10)的最小的2的幂(initialCapacit

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值