这两个星期一直处在面试找工作阶段,发现在面试过程中,对于集合的问题还问的挺频繁的,然后就看了挺多大神的总结啊什么的,然后我想自己来做个总结。
首先是集合这个接口Collection,是在java.util工具包中,然后我们用到的最多的他的子类就是list和set
先说list吧
list又分为ArrayList、LinkedList和Vector
ArrayList
底层是用数组实现的,具体怎么实现,可以去查看他的源代码,
我们都知道集合和数组的最大的区别就是数组的长度是不可变的,但是集合长度是无限制的,这就要考虑到集合的扩容机制。
对于集合的扩容机制我们需要了解两个参数,一个是扩容增量,一个是加载因子:
加载因子的系数小于等于1,意思是当集合中所包含元素的总量超过容量的长度*加载因子时,进行扩容操作,而扩容的大小则根据扩容增量决定。
ArrayList的初始容量为10,加载因子为1,扩容增量为原容量的0.5倍+1;
ArrayList的扩容需要用到一个数组copy的操作,因此如果需要copy的数据量过大,对其的效率来说是很低效的。
Vector
底层操作与ArrayLisst类似,不同点是,ArrayList是线程不安全的,而Vector是线程安全的,他底层的操作的数组是加锁的即都加了synchronized锁。
Vector的初始容量为10,加载因子为1,扩容增量为原容量的1倍
LinkedList
与前两者不同,LinkedList的底层是基于链表实现的,在其源代码中,我们可以看到,LinkedListd中的元素主要是存储在entry中,通过entry里再来操作元素,因此,在其底层中,同时维护了待插入的元素和前置存储单元的引用地址entry和后置存储单元的引用地址entry。LinkedList是一个双向链表,其查询也可以从两个方向查询,LinkedList的缺点就在于寻址,当集合中元素过多时,寻址的时间也就相对较长,LinkedList做插入删除操作时,慢在寻址,快在只需改变前后entry的引用地址。
由此可以得出一些结论,三种list集合中,ArrayList查询速度最快,而LinkedList的插入和删除的效率最高
解释:ArrayList的插入和删除效率取决于集合中元素的多少,取决于数组元素要批量copy的数量;
LikedList的查询也是取决于元素的多少,元素越往前或者越往后,其查询效率也会提高,甚至比ArrayList要快;
list中可能还需要了解一个同步的list就是
CopyOnWriteArrayList(java.util.concurrent包中,为并发而设计的一个同步包,具体想了解一些同步类,可以去这个包里了解一下,看看源码什么的)
这个类我们很少去用,但是面试的时候可能会问到,需要了解一下,用过数据库的人需要知道,所有数据库的驱动Drivers都维护在这个类中,它的底层是一个 Object[] array,在操作过程中都加了分段锁ReentrantLock,其优点就是读写分离,最终一致,实现同步。
Set
与List最大的不同是Set里存放元素是无序的,存放的元素不允许重复
Set我们普遍使用的也是三种:HashSet、LikedHashSet、TreeSet
HashSet
HashSet底层是用HashMap来存储数据,数据存储在HashMap的key中,这也是其为什么不允许重复的原因;
默认初始容量为16(16是2^4,可以提高查询效率,为何要加这段话,具体原因不知道,看了很多文章都加了,我就效仿一下,应该有点作用),加载因子为0.75,扩容增量为原容量的1倍
LinkedHashSet
LinkedHashSet底层是基于LinkedHashMap来实现的,是不是觉得Set的底层都与Map有关,所以我建议先了解Map的底层实现等操作后再来看Set的内容,可能更能理解一些。
LikedHashSet是HashSet的子类,他的底层也是实现链表的数据结构;与LinkedList存储方式类似,想要深入了解的可以去看源码;
TreeSet
TreeSet的底层是基于TreeMap来实现的。
TreeSet的底层是基于二叉树的结构实现的,其对放进来的元素进行排序,排序的方式有两种,一种是自然排序,一种是自定义排序,TreeSet对一些基本数据类型的包装类的对象会有一些默认的排序规则,但是对一些其他自定义的对象的排序规则需要我们自己去重写比较的方法,来按照我们的需要来排序。
排序的两种实现方式:
1.实现comparable接口,实现compareTo方法
2.实现比较器comparator接口,实现compare方法
Map
Map是一个双列集合,其元素都是以键值对(key-value)的方式存在的,并且key是唯一的,不能重复,
我们常用的Map也只有这3种:HashMap、HashTable、TreeMap
HashMap(线程不安全的)
HashMap的底层存储单元也是一个entry<K,V>;
按照hash算法来存取键对象,如果该键不存在则返回null,即在存储元素时,要对其键的值进行一次重写hashcode方法
允许键值都为空,如果在存储的过程中,出现键已经存在的情况,那么后面的值就会覆盖前面的值。
初始容量为16,加载因子为0.75,扩容增量为原来的1倍
HashTable
与HashMap类似,但是HashTable是线程安全的,对所有的方法都加了锁,其不允许键为null,不允许null的value,会导致空指针异常
还有一点与HashMap的区别就是:两者的重写hashcode的方法rehash()实现的操作不同
初始容量为11,加载因子为0.75,扩容增量为原容量的1倍+1
TreeMap
对key值进行排序,实现了SortedMap接口,分为自然排序和客户化排序两种排序方式,具体实现之前在TreeSet中讲了,这里就不多说了
还有一种map集合也是需要了解的
LinkedHashMap
底层可以认为是HashMap+LinkedList,也是基于链表来实现的,需要了解的一点就是:每次访问LinkedHashMap中的元素(get或put),被访问的元素都会被提到entry的最后面。
还要提到一个面试中很容易提到的一个map集合,同样也是线程安全的集合,面试中最喜欢问同步相关的东西了。
ConcurrentHashMap
底层与HashMap相同,在方法上加了分段锁,来实现同步。我了解了一下添加元素的操作,这里讲一下吧:
put:首先是对key进行hash操作,得到其hash值,然后获取对应的segment对象,接着调用Segment对象的put方法完成添加操作,当调用put方法时,首先Lock操作,完成操作后再释放锁,锁用的是ReentrantLock.
要提醒自己,下次多了解一下同步相关概念。。。。。。