程序优化之数据结构(Hash的数据结构的选择)

本文详细介绍了HashMap、LinkedHashMap和Hashtable的区别与特性。HashMap是基于数组和链表的数据结构,适合快速访问但线程不安全;LinkedHashMap则额外维护双向链表以保证插入或访问顺序;Hashtable线程安全但效率较低,且不允许null值。在并发场景下,ConcurrentHashMap提供了线程安全的解决方案,JDK1.7采用Segment锁分段,JDK1.8使用Node+CAS+Synchronized实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.介绍下HashMap

(1)HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲 突而存在

(2)性能考虑,HashMap 中的链表出现 越少,性能才会越好

(3)对于想要快速访问数据,不经常有插入和删除元素的时候,选择数组;对于需要经常的插入和删除元素,而对访问元素时的效率没有很高要求的话,选择链表。既有数组查找效率快的优势,又有链表插入和删除元素速度快的特点,并且有动态扩展机制。

(4)首先加载因子是表示hash表的填满程度,当为0.75的时候是在对提高空间利用率和减少查询成本的折中,当大于0.75的时候填满的元素越多,空间利用率越高,但是冲突的概率变大;当小于0.75的时候填满的元素越少,空间利用率越低,但是冲突的概率变小。

(5)1.7的时候底层数据结构是数组+链表;而在1.8的时候变成了数组+链表+红黑树。1.7采用插入方式是头插法,1.8采用的是尾插法。

(6)HashMap是线程不安全。解决的方案:一、使用HashTable效率比较差。二、使用ConcurrentHashMap比较常用的。三、使用Collections.synchronizedMap() 以上三种线程安全。

(7)HashMap一般采用String、Integer 等类作为key、因为这些类底层已经重写了hashcode、equals方法,用的是final修饰类在多线程情况下相对安全。

2.介绍下LinkedHashMap

LinkedHashMap是HashMap的子类,但是内部还有一个双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于双向链表中。LinkedHashMap支持两种顺序插入顺序 、 访问顺序
1.插入顺序:先添加的在前面,后添加的在后面。修改操作不影响顺序
2.访问顺序:所谓访问指的是get/put操作,对一个键执行get/put操作后,其对应的键值对会移动。
保存插入顺序:LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时带参数,按照应用次数排序。
速度慢:在遍历的时候会比HashMap慢,不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

3.HashMap与LinkedHashMap的区别?

HashMap因为index是随机生成的,所以每次put,存放的位置是无序的。(虽然节点类有next,但这个单链表仅是在同一个index的坑位,是串联的^^)

LinkedHashMap通过额外维护一个双向链表,来保证迭代顺序。所以它是有序的。即,它是有办法知道谁先入,谁后入的。当然,为此也增加了时间/空间的开销。

一般HashMap已经足够了,除非你有强需求,需要知道数据顺序,想要了解谁最早插入,才用开销大一些的LinkedHashMap!

4.Hashtable

哈希表(HashTable)又叫做散列表,是根据关键码值(即键值对)而直接访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。

数据结构中,我们对两种数据结构应该会非常熟悉:数组与链表。数组的特点就是查找容易,插入删除困难;而链表的特点就是查找困难,但是插入删除容易。既然两者各有优缺点,那么我们就将两者的有点结合起来,让它查找容易,插入删除也会快起来。哈希表就是讲两者结合起来的产物。

5.HashMap与Hashtable的比较

(1) 继承和实现方式不同

HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。

(2) 线程安全不同

Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。
而HashMap的函数则是非同步的,它不是线程安全的。

(3) 对null值的处理不同

HashMap的key、value都可以为null。
Hashtable的key、value都不可以为null。

(4) 遍历方式不同

HashMap只支持Iterator(迭代器)遍历。此种迭代方式,HashMap是“从前向后”的遍历数组;Hashtabl是“从后往前”的遍历数组;

而Hashtable支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历。

(5) 添加元素时的hash值算法不同

HashMap添加元素时,是使用自定义的哈希算法。

(6)Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。

(7)容量的初始值和增加方式不一样

HashMap默认的“加载因子”是0.75,默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。
Hashtable默认的“加载因子”是0.75,默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”。

6.并发情况下HashMap的安全问题(ConcurrentHashMap

JDK1.7

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据 时,其他段的数据也能被其他线程访问。 在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实 现,结构如下: 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每 个 Segment 守护着一个 HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先 获得对应的 Segment的锁。

JDK1.8
jdk1.8中, 采用 Node + CAS + Synchronized 来保证并发 安全进行实现, synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产 生并发,效率又提升 N 倍。
volatile保证内存可见性,CAS(Compare-And-Swap)算法保证数据的原子性,CAS算法:解决了原子性的问题
*CAS包含了三个操作数:
*内存值V(读主存中的值)
*预估值A(再次读下主存中的值)
*更新值B
*并且仅当V==A时,V=B,否则不做任何操作
*只有一个会成功,cas算法的效率比较高,当这次不成功的时候,不会阻塞,不会放弃cpu给它的执行权,会继续尝试
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值