首页 > 代码库 > Java集合框架梳理(含经典面试题)

Java集合框架梳理(含经典面试题)

Java Collections Framework是Java提供的对集合进行定义,操作,和管理的包含一组接口,类的体系结构。

1. 整体框架

Java容器类库一共有两种主要类型:Collection和Map。层次结构如下: 

蓝色椭圆框为接口类(不可实例化),黑色矩形框为实现类或子类。

技术分享

java.util.Collection [I]
+--java.util.List [I]
   +--java.util.ArrayList [C]
   +--java.util.LinkedList [C]
   +--java.util.Vector [C]
      +--java.util.Stack [C]
+--java.util.Set [I]
   +--java.util.HashSet [C]
   +--java.util.SortedSet [I]
      +--java.util.TreeSet [C]
 
java.util.Map [I]
+--java.util.SortedMap [I]
   +--java.util.TreeMap [C]
+--java.util.Hashtable [C]
+--java.util.HashMap [C]
+--java.util.LinkedHashMap [C]
+--java.util.WeakHashMap [C]
 
[I]:接口
[C]:类
 
2. Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object的集合,这些Object被称作Collection的元素。子类如下:
  • List:有序,元素可重复的集合。因为用户能够使用索引来访问List中的元素。
    • ArrayList:底层的数据结构使用的是数组,实现了可变大小的数组。非同步的(unsynchronized)。擅长随机访问,查询快、插入删除移动元素较慢。方法:size(),isEmpty(),get(),set(),add()。
    • LinkedList:底层使用的链表数据结构。非同步。增删移元素快,随机访问(查询)较差。提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
    • Vector:类似ArrayList,但是是同步的(synchronized),当一个Iterator被创建且正在被使用,另一个线程改变了Vector的状态时,调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
      • Stack:继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push()和pop(),还有peek()得到栈顶的元素,empty()测试堆栈是否为空,search()检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
  • Set:无序,元素不可重复的集合,因为没有索引。Set最多有一个null元素。
    • HashSet:底层数据结构是哈希表。非同步。保证元素的唯一性:hashCode和equals()。适合存取和查找。
      • LinkedHashSet:链表+哈希表。需维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时(遍历)将有很好的性能(链表适合进行遍历)。
    • TreeSet:底层数据结构是二叉树。有序,用二叉树排序。保证元素的唯一性:compareTo()。
  • Queue:先进先出的容器。新元素插入(offer)到队列尾部,访问元素(poll)操作会返回队列头部的元素,不允许随机访问队列中的元素。
    • PriorityQueue:优先级队列,保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序。
 
3. Map接口
保存具有"映射关系"的数据。Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value。key和value都可以是任何引用类型的数据。Map的key不允许重复。
  • HashTable:同步。不允许空。
  • HashMap:非同步。允许null,即null value和null key(最多一个)。
  • WeekHashMap:一种改进的HashMap,对key实行“弱引用”:如果一个key不再被外部所引用,那么该元素可以被GC回收。
  • TreeMap:有序。
 

4. 主要实现类对比

4.1 Vector和ArrayList

相同点:都实现了List接口,元素有序可重复;都基于数组的数据结构实现,都允许直接序号索引元素,所以随机性较好,适合用于查询,但是增删移动数据比较慢(用LinkedList),;

不同点:Vector是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的;Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差;在集合中使用数据量比较大的数据,用Vector有一定的优势。

 

4.2ArrayList和LinkedList
相同点:都实现了List接口,元素有序可重复;

不同点:具体实现:ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构;效率:对于随机访问get和set,ArrayList优于LinkedList,但对于新增和删除操作add和remove,LinedList比较占优势。因为LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,而ArrayList每插入一条数据,要移动插入点及之后的所有数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList。 

 

4.3 HashMap与TreeMap
都实现了Map接口,是一种键值对的映射关系。 

HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。

使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
两个map中的元素一样,但顺序不一样,导致hashCode()不一样。
同样做测试:
在HashMap中,同样的值的map,顺序不同,equals时,false;
而在TreeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。

 

4.4 HashTable与HashMap
都实现了Map接口,是一种键值对的映射关系。采用的hash/rehash算法类似,性能差异不大。

HashMap不是同步的,是线程不安全的;允许一个null键和多个null值。HashTable是同步的,线程安全;不允许有null的键或值。

HashTable有contains()方法,在HashMap中只有containsKey()和containsValue()来检测哈希表中是否有某个key或者value,有则返回true。

 

总结:
    实现List接口的ArrayList、LinkedList、Vector等有序,和实现了SortedXX的TreeXX有序:TreeSet、TreeMap;其他通通无序。
    在除需要排序时使用TreeSet,TreeMap外,都应使用HashSet,HashMap,因为他们的效率更高。
    如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。   
    同步的类:Vector、HashTable
    如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
    要特别注意对哈希表的操作,作为key的对象要正确复写equals()和hashCode()方法。
    容器类仅能持有对象引用(指向对象的指针),而不是将对象信息copy一份至数列某位置。一旦将对象置入容器内,便损失了该对象的型别信息。
    尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
 
注意:
1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。
2、Set和Collection拥有一模一样的接口。
3、List,可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)
4、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue。
5、Map用 put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value。
      HashMap会利用对象的hashCode来快速找到key。
6、Map中元素,可以将key序列、value序列单独抽取出来。
使用keySet()抽取key序列,将map中的所有keys生成一个Set。
使用values()抽取value序列,将map中的所有values生成一个Collection。
为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。
 
5. 面试题集合

 1、什么是Java集合API

  Java集合框架API是用来表示和操作集合的统一框架,它包含接口、实现类、以及帮助程序员完成一些编程的算法。简言之,API在上层完成以下几件事:编程更加省力,提高城程序速度和代码质量;非关联的API提高互操作性;节省学习使用新API成本;节省设计新API的时间;鼓励、促进软件重用。

  具体来说,有6个集合接口,最基本的是Collection接口,由三个接口Set、List、SortedSet继承,另外两个接口是Map、SortedMap,这两个接口不继承Collection,表示映射而不是真正的集合。

  2、什么是Iterator

  一些集合类提供了内容遍历的功能,通过java.util.Iterator接口。这些接口允许遍历对象的集合。依次操作每个元素对象。当使用 Iterators时,在获得Iterator的时候包含一个集合快照。通常在遍历一个Iterator的时候不建议修改集合本省。

  3、Iterator与ListIterator有什么区别?

  Iterator:只能正向遍历集合,适用于获取移除元素。ListIerator:继承Iterator,可以双向列表的遍历,同样支持元素的修改。

  4、什么是HaspMap和Map?

  Map是接口,Java 集合框架中一部分,用于存储键值对,HashMap是用哈希算法实现Map的类。

  5、HashMap与HashTable有什么区别?对比Hashtable VS HashMap

  两者都是用key-value方式获取数据。Hashtable是原始集合类之一(也称作遗留类)。HashMap作为新集合框架的一部分在Java2的1.2版本中加入。它们之间有一下区别:

  ● HashMap和Hashtable大致是等同的,除了非同步和空值(HashMap允许null值作为key和value,而Hashtable不可以)。

  ● HashMap没法保证映射的顺序一直不变,但是作为HashMap的子类LinkedHashMap,如果想要预知的顺序迭代(默认按照插入顺序),你可以很轻易的置换为HashMap,如果使用Hashtable就没那么容易了。

  ● HashMap不是同步的,而Hashtable是同步的。

  ● 迭代HashMap采用快速失败机制,而Hashtable不是,所以这是设计的考虑点。

  6、在Hashtable上下文中同步是什么意思?

  同步意味着在一个时间点只能有一个线程可以修改哈希表,任何线程在执行hashtable的更新操作前需要获取对象锁,其他线程等待锁的释放。

  7、什么叫做快速失败特性

  从高级别层次来说快速失败是一个系统或软件对于其故障做出的响应。一个快速失败系统设计用来即时报告可能会导致失败的任何故障情况,它通常用来停止正常的操作而不是尝试继续做可能有缺陷的工作。当有问题发生时,快速失败系统即时可见地发错错误告警。在Java中,快速失败与iterators有关。如果一个iterator在集合对象上创建了,其它线程欲“结构化”的修改该集合对象,并发修改异常 (ConcurrentModificationException) 抛出。

  8、怎样使Hashmap同步?

  HashMap可以通过Map m = Collections.synchronizedMap(hashMap)来达到同步的效果。

  9、什么时候使用Hashtable,什么时候使用HashMap

  基本的不同点是Hashtable同步HashMap不是的,所以无论什么时候有多个线程访问相同实例的可能时,就应该使用Hashtable,反之使用HashMap。非线程安全的数据结构能带来更好的性能。

  如果在将来有一种可能—你需要按顺序获得键值对的方案时,HashMap是一个很好的选择,因为有HashMap的一个子类 LinkedHashMap。所以如果你想可预测的按顺序迭代(默认按插入的顺序),你可以很方便用LinkedHashMap替换HashMap。反观要是使用的Hashtable就没那么简单了。同时如果有多个线程访问HashMap,Collections.synchronizedMap()可以代替,总的来说HashMap更灵活。

  10、为什么Vector类认为是废弃的或者是非官方地不推荐使用?或者说为什么我们应该一直使用ArrayList而不是Vector

  你应该使用ArrayList而不是Vector是因为默认情况下你是非同步访问的,Vector同步了每个方法,你几乎从不要那样做,通常有想要同步的是整个操作序列。同步单个的操作也不安全(如果你迭代一个Vector,你还是要加锁,以避免其它线程在同一时刻改变集合).而且效率更慢。当然同样有锁的开销即使你不需要,这是个很糟糕的方法在默认情况下同步访问。你可以一直使用Collections.sychronizedList来装饰一个集合。

  事实上Vector结合了“可变数组”的集合和同步每个操作的实现。这是另外一个设计上的缺陷。Vector还有些遗留的方法在枚举和元素获取的方法,这些方法不同于List接口,如果这些方法在代码中程序员更趋向于想用它。尽管枚举速度更快,但是他们不能检查如果集合在迭代的时候修改了,这样将导致问题。尽管以上诸多原因,Oracle也从没宣称过要废弃Vector。

 
参考链接:
http://blog.sina.com.cn/s/blog_a345a8960101k9vx.html
http://www.cnblogs.com/leeplogs/p/5891861.html
http://blog.csdn.net/zsm653983/article/details/7562324
 
 

Java集合框架梳理(含经典面试题)