集合框架图
ArrayList实现原理
对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
调整数组容量
ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现:
数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
Fail-Fast机制
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。具体请参看下面HashMap的实现原理,也实现了Fail-Fast机制。
总结
- ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素。
- 底层使用数组实现,顺序存储。
- 该集合是可变长度数组,数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量增长是其容量的1.5倍,这种操作的代价很高。
- 采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
详细实现原理博客链接:http://zhangshixi.iteye.com/blog/674856
LinkedList实现原理要点概括
LinkedList数据结构
LinkedList是List和Deque接口的双向链表的实现。实现了所有可选列表操作,并允许包括null值。
实现随机访问
|
|
该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。 源码中先将index与长度size的一半比较,如果index
Fail-Fast机制
LinkedList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
总结
- LinkedList是List接口的双向链表非同步实现,并允许包括null在内的所有元素。
- 底层的数据结构是基于双向链表的,该数据结构我们称为节点,顺序存储。
- 双向链表节点对应的类Node的实例,Node中包含成员变量:previous,next,item。其中,previous是该节点的上一个节点,next是该节点的下一个节点,item是该节点所包含的值。
4.索引查找时通过与中间值比较遍历前半段或后半段从而节省时间。
详细实现原理博客链接:http://www.cnblogs.com/CherishFX/p/4734490.html
Vector实现原理
和ArrayList不同,Vector中的操作是线程安全的,但速度慢,在每个方法前都加上了锁(即synchronized关键字)。
- Vector实际上是通过一个数组去保存数据的。当我们构造Vecotr时;若使用默认构造函数,则Vector的默认容量大小是10。
- 当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 >0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。
- Vector的克隆函数,即是将全部元素克隆到一个数组中。
详细实现原理博客链接:http://www.cnblogs.com/skywang12345/p/3308833.html
CopyOnWriteArrayList实现原理
与 Vector一样,CopyOnWriteArrayList也可以认为是ArrayList的线程安全版,不同之处在于当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWriteArrayList进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWriteArrayList是一种读写分离的思想,读和写不同的容器。
这种机制让CopyOnWriteArrayList可以对读操作不加锁,只对写的有关操作加锁(即方法前加synchronized关键字)这就使CopyOnWriteArrayList的读效率远高于Vector。
CopyOnWriteArrayList的理念比较类似读写分离,适合读多写少的多线程场景。但要注意,CopyOnWriteArrayList只能保证数据的最终一致性,并不能保证数据的实时一致性,执行读操作时是有可能会读到失效的数据的。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
与Vector区别:
- 二者均是线程安全的。
- CopyOnWriteArrayList不能保证读的实时线程安全,CopyOnWriteArrayList读性能远高于Vector。CopyOnWriteArrayList占用更多的内存空间
详细实现原理博客链接:http://www.cnblogs.com/dolphin0520/p/3938914.html
HashMap实现原理
HashMap的数据结构
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结构,但是在jdk1.8里加入了红黑树的实现,当链表的长度大于8时,转换为红黑树的结构。如图所示:
在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表上。
有时两个key会定位到相同的位置,表示发生了Hash碰撞。当然Hash算法计算结果越分散均匀,Hash碰撞的概率就越小,map的存取效率就会越高。
HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组,明显它是一个Node的数组。Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。
结合负载因子的定义公式可知,threshold就是在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。在HashMap中,哈希桶数组table的长度length大小必须为2的n次方
确定哈希桶数组索引位置
|
|
这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算。
HashMap的put方法实现
put函数大致的思路为:
- 对key的hashCode()做hash,然后再计算index;
- 如果没碰撞直接放到bucket里;
- 如果碰撞了,以链表的形式存在buckets后;
- 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
- 如果节点已经存在就替换old value(保证key的唯一性)
- 如果bucket满了(超过load factor*current capacity),就要resize。
HashMap的get方法实现
思路如下:
- bucket里的第一个节点,直接命中;
- 如果有冲突,则通过key.equals(k)去查找对应的entry
若为树,则在树中通过key.equals(k)查找,O(logn);
若为链表,则在链表中通过key.equals(k)查找,O(n)。
Fail-Fast机制
java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。
这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。
在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:
注意到modCount声明为volatile,保证线程之间修改的可见性。
迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
总结
- HashMap是基于哈希表的Map接口的非同步实现,允许使用null值和null键,但不保证映射的顺序。
- 底层使用数组实现,数组中每一项是个链表,即数组和链表的结合体。
- HashMap在底层将key-value当成一个整体进行处理。HashMap底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置,当链表的长度大于8时,转换为红黑树的结构;当需要取出一个Entry时,也会根据key的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
- HashMap进行数组扩容,两倍扩容。需要重新计算扩容后每个元素在数组中的位置,很耗性能。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历
- 采用了Fail-Fast机制,通过一个modCount值记录修改次数,对HashMap内容的修改都将增加这个值。迭代器初始化过程中会将这个值赋给迭代器的expectedModCount,在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map,马上抛出异常
详细实现原理博客链接:http://blog.csdn.net/richard_jason/article/details/53887222
Hashtable实现原理
总结:
- Hashtable是基于哈希表的Map接口的同步实现,不允许使用null值和null键
- 底层使用数组实现,数组中每一项是个单链表,即数组和链表的结合体
- Hashtable在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。Hashtable底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据key的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
- synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,即对每个方法操作都加上了synchronized关键字
详细实现原理博客链接:http://blog.csdn.net/zheng0518/article/details/42199477
ConcurrentHashMap实现原理
ConcurrentHashMap数据结构
ConcurrentHashMap相比HashMap而言,是多线程安全的,其底层数据与HashMap的数据结构相同,数据结构如下:
ConcurrentHashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树是为了提高查找效率。
ConcurrentHashMap的put方法实现
put函数底层调用了putVal进行数据的插入,对于putVal函数的流程大体如下。
- 判断存储的key、value是否为空,若为空,则抛出异常,否则,进入步骤2
- 计算key的hash值,随后进入无限循环,该无限循环可以确保成功插入数据,若table表为空或者长度为0,则初始化table表,否则,进入步骤3
- 根据key的hash值取出table表中的结点元素,若取出的结点为空(该桶为空),则使用CAS将key、value、hash值生成的结点放入桶中。否则,进入步骤4
- 若该结点的的hash值为MOVED(-1),则对该桶中的结点进行转移,否则,进入步骤⑤
- 对桶中的第一个结点(即table表中的结点)进行加锁,对该桶进行遍历,桶中的结点的hash值与key值与给定的hash值和key值相等,则根据标识选择是否进行更新操作(用给定的value值替换该结点的value值),若遍历完桶仍没有找到hash值与key值和指定的hash值与key值相等的结点,则直接新生一个结点并赋值为之前最后一个结点的下一个结点。进入步骤6
- 若binCount值达到红黑树转化的阈值(默认为8),则将桶中的结构转化为红黑树存储,最后,增加binCount的值。
同步性能
ConcurrentHashMap的性能相比HashMap的线程安全同步集合和Hashtable而言,性能都要高出不少。原因是经过Collections封装的线程安全的HashMap和Hashtable都是对整个结构加锁,而ConcurrentHashMap是对每一个桶单独进行锁操作,不同的桶之间的操作不会相互影响,可以并发执行。因此,其速度会快很多。
详细实现原理博客链接:http://www.cnblogs.com/leesf456/p/5453341.html
HashSet实现原理
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。无序存储且不能重复(因为内部主要是由HashMap的key值来存储,key不能重复)
对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成, HashSet的源代码如下:
详细实现原理博客链接:http://zhangshixi.iteye.com/blog/673143
LinkedHashMap实现原理
LinkedHashMap数据结构:
LinkedHashMap会将元素串起来,形成一个双链表结构。可以看到,其结构在HashMap结构上增加了链表结构。数据结构为(数组 + 单链表 + 红黑树 + 双链表),图中的标号是结点插入的顺序。
LinkedHashMap继承了HashMap,所以HashMap的一些方法或者属性也会被继承;同时也实现了Map结构
在HashMap.Node基础上增加了前后两个指针域,注意,HashMap.Node中的next域也存在。
LinkedHashMap(int)型构造函数:
LinkedHashMap的核心就是存在存储顺序(accessOrder = false)和访问顺序(accessOrder = true),主要是由双向链表来维护顺序,由上面的结果图可看出。
若访问顺序为true,且访问的对象不是尾结点,则下面的图展示了访问前和访问后的状态,假设访问的结点为结点3,从图中可以看到,结点3链接到了尾结点后面,从而实现访问顺序(最近最少使用):
详细实现原理博客链接:http://www.cnblogs.com/leesf456/p/5248868.html
LinkedHashSet实现原理
HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序(由于LinkedHashSet底层使用LinkedHashMap来保存所有元素)。
对于LinkedHashSet而言,它继承与HashSet、又基于LinkedHashMap来实现的。LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承与HashSet,其所有的方法操作上又与HashSet相同
详细实现原理博客链接:http://zhangshixi.iteye.com/blog/673319
TreeMap实现原理
TreeMap 是基于红黑树的实现,是有序的,它根据 key 的自然顺序排序(如果是基本类型,那就是自然顺序,由小到大。如果是对象,那就是比较哈希值,由小到大),或者根据指定的比较器(comparetor)进行排序,具体取决于构造方法。
TreeMap 不是同步的,因为排序调用比较方法,所以不支持 null 键,但是可以 null 值。
与LinkedHashMap区别:
- TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
- LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现.
- TreeMap 的性能略微低于 HashMap。
详细实现原理博客链接:http://blog.csdn.net/chenssy/article/details/26668941
TreeSet实现原理
与HashSet完全类似,TreeSet里面绝大部分方法都市直接调用TreeMap方法来实现的。
与TreeMap的区别:
相同点:
- TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是排好序的。
- TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()和Collections.synchronizedList()来实现同步
- 运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。
不同点:
- 最主要的区别就是TreeSet和TreeMap非别实现Set和Map接口
- TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)
- TreeSet中不能有重复对象,而TreeMap中可以存在
详细实现原理博客链接:http://blog.csdn.net/speedme/article/details/22661671
Collection和Collections区别
java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。