java集合框架小结

来源:互联网 发布:禁用445端口 编辑:程序博客网 时间:2024/05/23 01:17
/**
 *博客内容仅作学习交流之用,详细内容参见更多网络资源,欢迎大家交流探讨!
 */

    参考了一些资料,对Java集合框架总一个小结。

1 问题导入

    当我们根本不知道自己到底需要多少数量的对象,甚至不知道它们的准确类型。为了满足编程的需要,我们要求能在任何时候、任何地点创建任意数量的对象。为了解决这个非常关键的问题,Java提供了容纳对象(或者对象的句柄)的多种方式。其中内建的类型是数组,此外,Java的工具(实用程序)库提供了一些“集合类”(亦称作“容器类/container”,“容器”一词在抽象Windows工具包/AWT中已得到使用)。

——Eckel Bruce,陈吴鹏译 Thinking in Java. 4th ed. 2007: 机械工业出版社.



2 集合框架图及分类

2.1 集合框架

java常用集合框架图

fig1  java常用集合框架图

java集合框架图(1)

fig2  java集合框架图(1)

java集合框架图(2)

fig3  java集合框架图(2)

简要java集合框架图

fig4  简要java集合框架图


    Collection
    ├List
    │├LinkedList
    │├ArrayList
    │└Vector
    │ └Stack
    └Set
    Map
    ├Hashtable
    ├HashMap
    └WeakHashMap

2.2 集合容器分类

     1.内置容器:数组
     2.list容器:Vetor,Stack,ArrayList,LinkedList,
     CopyOnWriteArrayList(1.5),AttributeList(1.5),RoleList(1.5),RoleUnresolvedList(1.5),
     ConcurrentLinkedQueue(1.5),ArrayBlockingQueue(1.5),LinkedBlockingQueue(1.5),
     PriorityQueue(1.5),PriorityBlockingQueue(1.5),SynchronousQueue(1.5)
     3.set容器:HashSet(1.2),LinkedHashSet(1.4),TreeSet(1.2),
     CopyOnWriteArraySet(1.5),EnumSet(1.5),JobStateReasons。
     4.map容器:Hashtable,HashMap(1.2),TreeMap(1.2),LinkedHashMap(1.4),WeakHashMap(1.2),
     IdentityHashMap(1.4),ConcurrentMap(1.5),concurrentHashMap(1.5)。

     注意:Vector,Stack,Hashtable是Java1.2前的容器。
     虽然在Java2之前,Java是没有完整的集合框架的。它只有一些简单的可以自扩展的容器类。但是在Java2后他们还是被融入到了集合框架的,不过只是历史遗留而已。它们和1.2前应该还是有些变化的,虽然本质没什么变化。
    Set接口继承于Collection,但不允许重复,使用自己内部的一个排列机制。
    List接口继承Collection,允许重复,以元素安插的次序来放置元素,不会重新排列。
    Map接口是一组成对的键-值对象,即所持有的是key-value pairs。Map中不能有重复的key。拥有自己的内部排列机制。

2.3 集合容器对比
    Collection是集合接口
    |————Set子接口:无序,不允许重复。
    |————List子接口:有序,可以有重复元素。

    区别:Collections是集合类

    Set和List对比:
    Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
    List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

    其中,List的特点是:有顺序而可以重复 。Set的特点是:无顺序而不可重复 。
    什么是重复?
    不是说容器中有两个格子装了同一个对象,重复是指:a.equals(b) == true; 所以,当我们自定义对象作为容器的储存元素时,我们必须重写 java.lang.Object的equals方法,Object的默认equals只有当比较的参数是本身时才返回true,明显不是我们想要的,所以必须重写,而且,如果重写equals就必须重写hashCode()方法,因为,当我们的对象作为字典Map的键key时,我们的定位是同过 hashCode方法,这样来提高效率。注:两个对象equals若为TRUE,hashCode一定返回同样的int;但是,如果Equals返回 FALSE,hashCode不一定不同。

    Set和List具体子类:
    Set
     |————HashSet:以哈希表的形式存放元素,插入删除速度很快。

    List
     |————ArrayList:动态数组
     |————LinkedList:链表、队列、堆栈。

    Array和java.util.Vector
    Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。

    Java2的集合框架,抽其核心,主要有三类:List(包括List,Queue,BlockingQueue)、Set和Map。List和Set继承了Collection,而Map则独成一体。
    初看上去可能会对Map独成一体感到不解,它为什么不也继承Collection呢?但是这种设计是合理的。
    Map提供了通过Key对Map中存储的Value进行访问,也就是说它操作的都是成对的对象元素,比如put()和get()方法,而这是一个Set或List 所不就具备的。当然在需要时,你可以由keySet()方法或values()方法从一个Map中得到键的Set集或值的Collection集。
    集合框架中还有两个很实用的公用类:Collections和Arrays。Collections提供了对一个Collection容器进行诸如排序、复制、查找和填充等一些非常有用的List方法,需要实现的接口:Comparable接口,当两个对象涉及比较操作时,使用这个接口的compareTo方法;Iterator接口,涉及遍历时使用。Arrays则是对一个数组进行类似的操作。

集合对比

table1  集合对比

集合分类

table2  集合分类

2.4 Fail-fast的机制
    Collection接口提供了一组操作成批对象的方法。(它只是个接口),它提供了基本操作如添加、删除。它也支持查询操作如是否为空isEmpty()方法等。
    为了支持对Collection进行独立操作,Java的集合框架给出了一个Iterator,它使得你可以泛型操作一个Collection,而不需知道这个 Collection的具体实现类型是什么。它的功能与Java1中的Enumeration类似,只是更易掌握和使用,功能也更强大。
    在建立集合框架时,Sun的开发团队考虑到需要提供一些灵活的接口,用来操作成批的元素,又为了设计的简便,就把那些对集合进行可选操作的方法与基本方法放到了一起。因为一个接口的实现者必须提供对接口中定义的所有方法的实现,这就需要一种途径让调用者知道它正在调用 的可选方法当前不支持。最后开发团队选择使用一种信号,也即抛出一种不支持操作例外(UnsupportedOperationException),如果你在使用一个Collection中遇到一个上述的例外,那就意味着你的操作失败,比如你对一个只读Collection添加一个元素时,你就会得到一个不支持操作例外。
    在你实现一个集合接口时,你可以很容易的在你不想让用户使用的方法中抛出UnsupportOperationException来告诉使用者这个方法当前没有实现,UnsupportOperationException是RuntimeException的一个扩展。
    另外Java2的容器类库还有一种Fail fast的机制。比如你正在用一个Iterator遍历一个容器中的对象,这时另外一个线程或进程对那个容器进行了修改,那么再用next()方法时可能会有灾难性的后果,而这是你不愿看到的,这时就会引发一个ConcurrentModificationException例外。
    这就是快速失败(fail-fast)。


3 各接口及部分实现类介绍

    Java中的容器,接口都是由一些接口,抽象类及它们的实现类所组成。而它们全部封装在java.util包中。

3.1 Collection接口
    大多数的集合都实现了此接口,它基本方法是add(没有get()方法,实现类中可能有如Arrylist),添加一对象。添加成功则返回true ,否则返回false。这是与Map不同的地方。还有一些常用的方法如iterator(),size(),toArray()(注:toArray()是返回一对象----object数组,而Arrays----也是java.util下的一个类,有一个asList方法它们通常认为是各集合之间转换的桥梁)等等!具体用法可以参考API文档。

3.2 Map(映射)接口
    Map没有继承Collection接口,Map提供key到value的映射。集合中的每一个元素都包含一对键对象和值对象,每个key只能映射一个value,集合中没有重复的键对象,值对象可以重复。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。它的有些实现类能对集合中的键对象进行排序。与Collection截然不同的是,它其中所存取的是一些值与名相对应的数据。也就是一个Key对应一个Value的方式来存储。所以它就有与之对应的一些方法如:put (K key, V value)等等,更多可以参考API文档。

    Hashtable类 
    Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
    Hashtable 通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
    Hashtable是同步的。 

    HashMap类 
    HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回 Collection),其迭代子操作时间开销和HashMap 的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

    WeakHashMap类 
    WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。

    关于对HashMap的加深理解
     HashMap是由键值对组成的,关于HashMap有二点要注意:1. 它的键只能是一个Object对象。 2. 当二个HashMap用equals方法比较时,实际的比较是它的Key,而与Value无关。
     HashMap的主要特点是其底层的物理存放与查找用到了hash函数相关的原理。根据java窗口的查找原理,查找最快的应该是由数组经过工具类Arrays的Arrays.sort方法排序后,再用此工具类的Arrays.binarySearch方法进行查找。对于HashMap的数据查找就是用这个原理实现的,另外由于数组的致命缺点就是它是定长的,而HashMap却是可以动态增加,所以查找过程其实不是将Key本身放在一个Object[]的数组中,而是将与Key有密切相关的信息做为索引Object[]数组的下标,然后根据此下标去Object[]数组中查找数据,这个所谓密切相关的信息就是通过Key.hashCode()函数所产生的数字。可想而知,当HashMap中的Key很多时,各Key所产生的hashCode肯定会有重合的现象发生,为了防止此情况发生,所以根据这个索引在数组中得到的对象并不是最终要查找的数据,查到的其实是一个list列表,在这列表中列出了由于HashMap中的Key通过散列后具有相同hashCode的全部对像。可以想像得到,这个列表中的对像应该是相当少的。对于对Object[]数据下下标定位后,就得到了这个列表,接下来equals函数粉墨登场了,若能返回true则表示此对象已经存在,这时HashMap会用新的值覆盖旧值,若不存在则会做添加操作了。
     在HashMap的初始化中,会涉及到二个比较重要的值,也是影响其性能的二个重要值:Object[]的长度(v1)、Object[]中实际已经存放了多少object对象(v2)。在我们初始化HashMap时会有:HashMap(int initialCapacity, float loadFactor) 这个方法,initialCapacity表示object[]的初始化长度,loadFactor表示允许此在Object[]存放数据的百分比(loadFactor=v2/v1),系统默认的是0.75(也就是可以存放占object[]数组3/4的数据)。当HashMap里的数据不断增加时,它会自动地按数量级扩展Object[]的长度(应该尽量阻止Object[]的自动增加,这样不但消费资源对于以后的查找、插入操作也不利)。
     HashMap结论:对于Key一定要实现hashCode() and equals方法,且尽量要让hashCode散布得均匀。这样才能充分利用Object[]数组,不然,会导致Object[]得不到充分利用,而在Object[index]具体对应的对象list列表中存放很多Key对像,而在list中进行查找操作是比较耗时的。

3.3 List(列表)接口
    List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置,集合中的对象按索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,这类似于Java的数组。
    除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。

    实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。

    LinkedList类 
    LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
    注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
    List list = Collections.synchronizedList(new LinkedList(...));

    ArrayList类 
    ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
    size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
    每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并 没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
    和LinkedList一样,ArrayList也是非同步的(unsynchronized)。

    一般情况下使用这两个就可以了,因为非同步,所以效率比较高。

    Vector类 
    Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的 Iterator是同一接口,但是,因为Vector是同步的,当一个 Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例 如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该 异常。

    Stack 类 
    Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方 法,还有 peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

3.4 Set(集)接口
    Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素,它的有些实现类能对集合中的对象按特定的方式排序。

    HashSet 类和 TreeSet 类
    “集合框架”支持 Set 接口两种普通的实现:HashSet 和TreeSet。在更多情况下,您会使用 HashSet 存储重复自由的集合。考虑到效率,添加到 HashSet 的对象需要采用恰当分配散列码的方式来实现hashCode() 方法。虽然大多数系统类覆盖了 Object 中缺省的 hashCode()实现,但创建您自己的要添加到 HashSet 的类时,别忘了覆盖 hashCode()。当您要从集合中以有序的方式抽取元素时,TreeSet 实现会有用处。为了能顺利进行,添加到TreeSet 的元素必须是可排序的。 “集合框架”添加对 Comparable 元素的支持,在排序的“可比较的接口”部分中会详细介绍。我们暂且假定一棵树知道如何保持java.lang 包装程序器类元素的有序状态。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。
    为优化 HashSet 空间的使用,您可以调优初始容量和负载因子。TreeSet 不包含调优选项,因为树总是平衡的,保证了插入、删除、查询的性能为log(n)。
    HashSet 和 TreeSet 都实现 Cloneable 接口。

3.5 Iterator(迭代器)接口
    它是一个接口,只有三个方法hasnext(),next(),remove()只有最后一个是可选的,也就是remove()是可选(在实现的时候)。其可选性也意味着它的实现类中,remove方法是可有可无的。例如,若有一个如下的List 实例。
    Arrylist al = new Arrylist();
    Object[] ob = al.toArray();
    List list = Arrays.asList(ob);
    Iterator itor = list.iterator();
    itor.remove();      //Error 
    当调用Ierator itr = list.iterator()方法返回一迭代器的时候,便不支持remove方法,所以当你再使用irt.remove()时程序就是异常!

    使用此迭代器要注意的是remove()方法。它所删除的是指指针(暂这么叫着)上次所移经过的位置(Removes from the underlying collection the last element returned by the iterator (optional operation).)。我个人觉得有点象在JDBC中的ResultSet rs = ....;rs.last();rowsCount=rs.getRow();类似呢。前面所讲的,由于clollection提供了iterator()方法,所以迭代器是很容易实现的!

Java集合框架中更多的接口和实现类可参见API帮助文档或《Java集合容器简介》  作者:hudashi

3.6 常用实现类的一些继承关系 
    Collections,它是Java.util下的一个类。它为我们提供了许多有用的方法,如sort(...),max()等其具体用法可以参考API文档,比如sort(List list);中list内的所有元素都必须实现Comparable接口(All elements in the list must implement the Comparable interface)。
    Arrylist ,它是List接口的实现类,而List则是继承于Collection。
    LinkedList,它也是间接对Colections的实现。用linkedlist的一些方法如addfirst(),removefirst(),addlast()等等可以用来实现如C中的堆栈,链表。(对于频繁使用插入与删除操作使用linkedlist是个不错的选择,对于经常进行索引操作则arrylist较好)。 
    HashSet(散列表),它实现了Set接口,也就意味着它的元素不能有重复值出现。并且在HashSet中没有get()方法,但可以通过iterator()来实现。要注意的是假如要在HasSet中存放一些对象,那么你得重定义hashCode()与equals()二个方法来保不可以存放相同的内容的元素。对于hashcode()所返回的值,hashset用它来计算(通过特定的函数)该对象在内存中的存放位置;后者主要用来判断二个对象的内容是否相等而返回对应的boolen型。
    TreeSet,主要用来对元素进行排序操作,假如要往其中添加对象,则对象得实现Comparable接口。(假如不要对元素排序,则一般可选用HashSet)。
    HashMap,主要特点是存放的一个键值对,一些有用的方法是可返回视图(我觉得可以把它理解为一个集合)如:keyset(),values(),entyset()等。

3.7 集合选用原则
    一般情况下数据结构的选择:
    多查少改选ArrayList
    多改少查选LinkedList
    如果大量数据进行检索选Map, 

    详细:
    如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。 
    如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
    要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
    尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程(面向抽象编程--使变量声明为抽象类型,实例化为具体类,即类在不增加公共方法的情况下也能让各变量顺利调用。具体类包括对抽象类的继承和接口的实现)。

4 Java集合框架问题总结
  Java集合框架是最常被问到的Java面试问题,要理解Java技术强大特性就有必要掌握集合框架。这里有一些实用问题,常在核心Java面试中问到。

  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。



参考:
[1] Eckel Bruce,陈吴鹏译 Thinking in Java. 4th ed. 2007: 机械工业出版社.
[2] 《java集合类图》  作者:liulin_good
[3] 《java集合类详解》  作者:半缘君
[4] 《Java集合容器简介》  作者:hudashi
[5] 《Java容器集合学习心得》  作者:Jkallen
[6] 《Java中的容器详细讲解学习》


/**
 *站在巨人的肩上才能看得更远,一步一个脚印才能走得更远。分享成长,交流进步,转载请注明出处!
 */

0 0