本文是“最最最多见Java面试题总结”系列第三周的文章。
主要内容:php
- Arraylist 与 LinkedList 异同
- ArrayList 与 Vector 区别
- HashMap的底层实现
- HashMap 和 Hashtable 的区别
- HashMap 的长度为何是2的幂次方
- HashSet 和 HashMap 区别
- ConcurrentHashMap 和 Hashtable 的区别
- ConcurrentHashMap线程安全的具体实现方式/底层具体实现
- 集合框架底层数据结构总结
本文会同步更新在我开源的Java学习指南仓库 Java-Guide (一份涵盖大部分Java程序员所须要掌握的核心知识,正在一步一步慢慢完善,期待您的参与)中,地址:https://github.com/Snailclimb/Java-Guide,欢迎star、issue、pr。html
add(E e)
方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种状况时间复杂度就是O(1)。可是若是要在指定位置 i 插入和删除元素的话(add(int index, E element)
)时间复杂度就为 O(n-i)。由于在进行上述操做的时候集合中第 i 和第 i 个元素以后的(n-i)个元素都要执行向后位/向前移一位的操做。 ② LinkedList 采用链表存储,因此插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。 get(int index)
方法)。双向链表也叫双链表,是链表的一种,它的每一个数据结点中都有两个指针,分别指向直接后继和直接前驱。因此,从双向链表中的任意一个结点开始,均可以很方便地访问它的前驱结点和后继结点。通常咱们都构造双向循环链表,以下图所示,同时下图也是LinkedList 底层使用的是双向循环链表数据结构。java
Vector类的全部方法都是同步的。能够由两个线程安全地访问一个Vector对象、可是一个线程访问Vector的话代码要在同步操做上耗费大量的时间。git
Arraylist不是同步的,因此在不须要保证线程安全时时建议使用Arraylist。程序员
JDK1.8 以前 HashMap 由 数组+链表 组成的(“链表散列” 即数组和链表的结合体),数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(HashMap 采用 “拉链法也就是链地址法” 解决冲突),若是定位到的数组位置不含链表(当前 entry 的 next 指向 null ),那么对于查找,添加等操做很快,仅需一次寻址便可;若是定位到的数组包含链表,对于添加操做,其时间复杂度依然为 O(1),由于最新的 Entry 会插入链表头部,急须要简单改变引用链便可,而对于查找操做来说,此时就须要遍历链表,而后经过 key 对象的 equals 方法逐一比对查找.github
所谓 “拉链法” 就是将链表和数组相结合。也就是说建立一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中便可。
相比于以前的版本, JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减小搜索时间。面试
TreeMap、TreeSet以及JDK1.8以后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,由于二叉查找树在某些状况下会退化成一个线性结构。
推荐阅读:算法
synchronized
修饰。(若是你要保证线程安全的话就使用 ConcurrentHashMap 吧!);为了能让 HashMap 存取高效,尽可能较少碰撞,也就是要尽可能把数据分配均匀,每一个链表/红黑树长度大体相同。这个实现就是把数据存到哪一个链表/红黑树中的算法。数组
这个算法应该如何设计呢?安全
咱们首先可能会想到采用%取余的操做来实现。可是,重点来了:“取余(%)操做中若是除数是2的幂次则等价于与其除数减一的与(&)操做(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 而且 采用二进制位操做 &,相对于%可以提升运算效率,这就解释了 HashMap 的长度为何是2的幂次方。
ConcurrentHashMap 和 Hashtable 的区别主要体如今实现线程安全的方式上不一样。
二者的对比图:
图片来源:http://www.cnblogs.com/chengx...
HashTable:
JDK1.7的ConcurrentHashMap:
JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点
Node: 链表节点):
首先将数据分为一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其余段的数据也能被其余线程访问。
ConcurrentHashMap 是由 Segment 数组结构和 HahEntry 数组结构组成。
Segment 实现了 ReentrantLock,因此 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
static class Segment<K,V> extends ReentrantLock implements Serializable { }
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap相似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每一个 HashEntry 是一个链表结构的元素,每一个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先得到对应的 Segment的锁。
ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构相似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提高N倍。
你若怒放,清风自来。 欢迎关注个人微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号有大量资料,回复关键字“1”你可能看到想要的东西哦!