面试积累-集合框架

面试几乎每次都会问到集合框架,所以总结一下吧。
下面是集合框架的继承体系,这里画出来的只是常用的,有些不常用但是面试经常问的后面再说。

集合框架集成体系

1、List和Set的异同

List(列表):
有序(存储元素的顺序与取出元素的顺序一致)
有索引,可以通过索引访问元素
可存在重复数据

Set(集):
不存在重复数据
无序(存入元素的顺序与取出元素的顺序不一定一致)
只能通过迭代器遍历
Set的底层实现是Map,其集合元素存在map的key上,map的value是一个Object常量

问:Set如何判断数据重复?
往Set集合里存入对象的时候,首先判断这个对象与已有对象的hashcode,如果hashcode相同,则在判断equals,如果equals也为true,则不能添加,否则可以。

2、ArrayList/Vector/LinkedList的区别

ArrayList:

底层实现:动态数组(即一个可变大小的数组)
容量:默认初始容量10,可以通过构造函数指定初始容量,
扩容算法 新容量=(旧容量*3/2)+1
特性:查询快,增删慢,线程不安全

问:为什么ArrayList查询快,增删慢?
因为ArrayList底层是动态数组来实现的,是连续的内存,存在索引的概念,可以通过索引来直接查询而不需要比较,查询方式=首地址+(元素长度*索引下标),基于这个位置读取相应的字节数即可,索引查询快;因为是数组结构,增删会带来数据元素的移动,增加,后面的元素都需要往后移动,删除,后面的元素都要往前移动,效率很低。删除的位置越靠前,开销越大。

Vector:
底层实现:动态数组,同ArrayList
容量:默认初始容量为10,默认扩容算法 新容量=旧容量*2,可设置增量因子
特性:线程安全,很多方法被synchronized【悲观锁】修饰,效率低。

LinkedList:
底层实现:双向链表,不需要连续的内存。
特性:增删快,查询慢,线程不安全,存储同样的数据需要的内存开销更大(因为需要存储前驱和后继)

LinkedList不存在线程安全的实现,可以用
Collections.synchronizedList(new LinkedList())来做到线程安全,
原理是同步整个LinkedList,当有线程访问的时候,其他线程只能等待。

3、HashSet/TreeSet的区别

HashSet:
特性:
不能保证元素的排列顺序(根据元素的hashcode值来决定在hashset中的排列)
线程不安全
集合元素可以是null,只能是最多一个

TreeSet:
特性:
确保元素处于排序状态
线程不安全
集合元素不能出现null,排序是通过比较的方式实现的,null值比较会出现空指针异常。
TreeSet中应该放入同一个类型的对象。(判断对象不想等:通过对象的compareTo方法实现,compareTo结果为0则相等,结果为正则obj1大于obj2,否则obj1小于obj2 ,在TreeSet里升序排序)

实现排序:
实现Comparable接口或者实现Comparator接口,两者的区别后续再说。

4、HashMap/HashTable/TreeMap区别
HashMap:
底层实现:
底层是一个数组,数组中的每一个元素是Entry的一个链表。
HashMap.Entry实现自Map.Entry,他里面保存有key,value,hash值以及对下一个Entry的引用,在保存键值对时,根据HashCode值计算存放该键值对的位置,并且把对象放在链表的头部,如果还没有数据,直接把对象放在该数组的位置。
线程不安全。
允许且最多出现一个null key。
效率高。
初始化容量16。

问:为什么HashMap的效率高?
HashMap的底层是链表的数组,当通过key的hashcode确定一个索引,找到指定链表,再根据key的equal来找到对应的kv对,当链表长度很长的时候效率并不会高。因此如果该数组的每一个元素,都只包含一个kv对,那样效率就很高了,只需要根据HashCode确定索引就相当于找到对应的kv对了。当数组的长度为2的幂次方时,不同key算得index相同的概率比较小,也就是在这个数组里分布比较均匀,这样查询的时候有很大的概率是不需要遍历一个链表,相对效率就大大提高了。

注意:
当知道数据的大概数量时,在初始化HashMap时最好在初始化时给定初始化容量,因为HashMap扩容后,需要重新根据HashCode计算kv对存放的位置,开销很大。【当元素数量超过数组大小*loadFactor时,扩容为原来的两倍】

HashTable:
底层实现:同HashMap,基于Dictory
不允许出现null,key
线程安全,效率低。
默认初始容量11,加载因子0.75,扩容算法 新容量=(旧容量)*加载因子*2+1

TreeMap:
底层实现原理:
红黑树
每一个节点保存(key,value,左节点,右节点,父节点,颜色)
所以是有序的。
线程不安全。

关于TreeMap的增删以及红黑树比较复杂,后面另起一篇细说。

猜你喜欢

转载自blog.csdn.net/lovezhaohaimig/article/details/80287697