@boothsun
2018-03-25T22:32:59.000000Z
字数 3839
阅读 1771
面试题
参见原文
1. Java集合框架面试题
2. java面试题------40个Java集合面试问题和答案
集合是Java中的一个非常重要的一个知识点,主要分为List、Set、Map、Queue三大数据结构。它们在Java中的结构关系如下:
存储特点:
数据存储方面
无序,可重复
不允许有重复元素的集合。是否是重复是根据equals方法来判定的。
HashSet
底层是由HashMap实现的,通过对象的HashCode方法与equals方法来保证插入元素的唯一性,无序。
LinkedHashSet
底层数据结构由哈希表和链表组成。哈希表保证元素的唯一性,链表保证元素有序。
TreeSet
基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的Comparator 进行排序,具体取决于使用的构造方法。 元素唯一。
Queue用于模拟"队列"这种数据结构(先进先出 FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。结合生活中常见的排队就会很好理解这个概念
PriorityQueue
PriorityQueue并不是一个比较标准的队列实现,PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序,这点从它的类名也可以看出来。
Deque
Deque接口代表一个"双端队列",双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用。
ArrayDeque
是一个基于数组的双端队列,和ArrayList类似,它们的底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出该数组的容量时,系统会在底层重新分配一个Object[]数组来存储集合元素
所以,一般当集合中的元素需要排序时,用TreeSet。其他情况一般都用HashSet,因为没有默认排序,速度比TreeSet块。
在用迭代器遍历一个集合对象时,如果遍历过程中集合对象中的内容发生了修改(增加、删除、修改),则会抛出ConcurrentModificationException
。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount
变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()
遍历下一个元素之前,都会检测modCount
变量是否为expectedmodCount
值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount
这个条件。如果集合发生变化时修改modCount
值刚好又设置为了expectedmodCount
值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException
。
缺点:基于拷贝内容的优点是避免了ConcurrentModificationException
,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent
包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
HashMap和Hashtable都实现了Map接口,看起来很相似,但它们之间有如下的区别:
对于插入、删除、定位元素频繁的操作,HashMap提供了最好的效率。如果想要按key的排序来遍历,那么TreeMap是不二选择。某些情况下,依赖集合的大小,先向HashMap中添加元素,然后转换为TreeMap再按key的排序进行遍历也许会带来效率上的提高。
ArrayList和Vector都是基于数据实现的,但是ArrayList是线程不安全的,而Vector是线程安全的。因而ArrayList的性能更好,不同考虑同步。
可以转化放入TreeMap里面。
通过把List里面的数据放入HashSet就可以去除重复。
想要对数组内的对象进行排序,只需调用Arrays.sort()方法。想要对list中的对象进行排序,只需调用Collections.sort()方 法。但是,这两种方法要么实现了Comparable接口的sort()方法,对集合进行自然排序,要么实现了Comparator接口中的sort() 方法,能按某些条件(criteria)进行排序。其实Collections内部会调用Array的排序方法,因此除了在排序前Collections 类会将list中的元素先复制到数组中消耗点时间外,它们在排序性能上几乎没有差别。
因为ArrayList的底层是数组实现,并且数组的默认值是10,如果插入10000条要不断的扩容,耗费时间,所以我们调用ArrayList的指定容量的构造器方法ArrayList(int size) 就可以实现不扩容,就提高了性能。
Set容器,它是不允许重复的。
我们可以通过调用Collections.unmodifiableCollection(Collection c)
方法来创建一个只读的集合,然后再将它传入函数中,这样就会确保任何企图修改集合的操作都会引起 UnsupportedOperationException
异常。
可以调用Collections.synchronizedCollection(Collection c)方法,会返回一个同步的集合。