Java中集合框架的总结
在学习这块知识的时候觉得内容很多,还有很多比较底层的东西需要理解,当时就觉得头都大了,这几天静下心来,翻了翻书,也查了一些资料,对这块的知识体系,也有了自己的一个框架,所以对此做以总结,此文主要从以下几个方面进行阐述:
1.为什么要使用集合框架?
数组是Java提供的随机访问对象序列的最有效方法,访问元素较快,缺点就是数组的大小自创建之后就固定了,所以集合框架的出现就是为了解决数组定长的问题。
2.什么是集合框架?
集合框架是一个用来代表和操纵集合的统一架构,Java集合框架主要包括两种类型的容器,Collection 和Map,下面对这两种容器作以详述:
Collection集合:保存单个元素的父接口:下图为Collection及其子类的关系图:
在这里,主要对Collection接口子类中的List接口和Set接口作主要介绍:
(1)List接口:
List接口是一个有序的Collection,使用List接口能够精确的控制每个元素插入的位置,能够通过索引来访问List中的元素,并且允许元素可以重复。在List有三个重要的子类:ArrayList、LinkedList、Vector
三者的异同:
a. 相同之处:ArrayList 、LinkList、Vector都实现了List接口;
b.不同之处:
ArrayList:JDK1.2出现,采用懒加载策略,扩容时都是当前长度的1.5倍,底层数组实现,线程不安全,查找效率高;
LinkedList:JDK1.2出现,底层使用双向循环链表实现,查询慢,增删快,线程不安全,不存在容量不足的情况,允许有null(空元素);
Vector:JDK1.0出现,底层使用数组实现,查询块,增删慢,线程安全效率低,容量不足时扩增为当前容量的2倍。
(2)Set接口:不允许数据重复,实质是穿了马甲的Map
Set接口下有两个重要的子类:HashSet、TreeSet
对比HashSet 和TreeSet:
a.HashSet:不保证集合元素中的顺序,允许包含值为null的元素,但最多只能一个;
HashSet中元素判重的依据:hashCode()与equals()方法:hashCode()相等的元素equals()不一定相等,equals()相等的元素hashCode()不一定相等。
b.TreeSet:保证集合中的元素有序,可以实现排序等功能;
TreeSet有序,序指的是什么?
----要使用TreeSet,必须满足以下两个条件之一:
i)作为TreeSet集合的类,实现Comparable接口(内部接口)
ii)通过构造方法传入Comparator接口对象(外部排序)----更加灵活,采用策略模式,可以修改排序的规则;
(3)LIst和Set的区别:
a.Set接口实例存储的时无需的,不可重复的数据,List接口实例存储的是有序的,可以重复的元素;
b.Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变;
c.List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度,查找元素效率高,插入删除效率低。
Map:保存一对对象的接口(键值对),下图为Map与其子类的关系图:
在这里主要对Map接口下的Hashmap、Treemap、HashTable、ConcurrentHashMap做以介绍:
a.HashMap:元素无序,不可重复,但可以为null(最多只允许一个),底层采用数组和链表结合的数据结构,线程不安全,查找效率高,容量一直为2的n次方(即每次扩充都为原来的2倍);
b.TreeMap:底层采用二叉树实现,线程不安全;
c.HashTable:元素无序,不可重复,不可以为null,底层采用数组和链表结合的数据结构,线程安全,查找效率高,2倍扩容
d.HashTable的优化:
----ConcurrentHashMap
a.JDK1.7实现:【基于分段锁Segment来实现,每个Segment都是ReentrantLock的子类】
i)将哈希表拆分为16个Segment,每个Segment下又是一个小的哈希表;
ii)关于锁:将原先整表的一把锁细粒度化为每个Segment的一把锁,并且不同Segment之间互不干扰,每个Segment实际上都是ReentrantLock的子类
iii)扩容机制:Segment初始化之后就无法扩容(默认初始化为16),这里的扩容实际上是Segment对应的每个小的哈希表,并且不同Segment之间的扩容完全隔离。
b.JDK1.8实现:
i)结构:与JDK1.8的HashMap如出一辙,也是哈希表+红黑树的底层结构,原先的Segment仍然保留,但是没有实际意,仅仅用作序列化
ii) 关于锁:将原先锁一片再次细粒度化为只锁桶中的头结点【使用CAS+同步代码块(synchronized)】
c.JDK1.7 与JDK1.8的对比:
i)结构上:JDK1.7 是基于分段锁的Segment,JDK1.8 是哈希表+红黑树;
ii)锁的使用:JDK1.7使用Reentrant将Segment上锁,JDK1.8使用CAS+synchronized代码块。
fail-fast:Java.Util.concurrentModificationException(集合并发可修改异常)
如何产生?
----在迭代遍历集合的时候同时修改结合结构(通过add()、remove())
产生原因:在集合里边有 modCount(用于存储当前修改集合次数的值)!=expectedModCount(用来取出当前集合修改次数的值),当取得集合迭代器遍历的时候,会先比较这两个值,如果不相等立即抛出异常。
为什么抛出此异常?
----尽量保证在多线程的场景下,数据不会产生脏读(即如果有线程并发修改集合结构时,相同遍历该集合的线程发出一个异常,通知遍历集合的线程 集合数据已经被修改)。
HashMap底层原理实现: