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给它的执行权,会继续尝试