遍历 vs. 查找
遍历和修改似乎是一对矛盾,一个可以高效率插入删除元素的数据结构通常遍历的性能并不是最优。于是
JCF在这里根据用户的目标实现了两种定制的数据结构:
哈希表(包括HashSet和HashMap)和平衡二叉树(包括TreeSet和
TreeMap)。
由于可排序性是一种独特的要求,所以引入了SortedSet和SortedMap,它们分别是AbstractSet和
AbstractMap的子接口,而TreeSet和TreeMap又分别是他们的一种实现。熟悉数据结构的人可能比较了解,哈希表在进行插入、删除、查
找这样的操作是很快的,其时间复杂度是常数级O(1);平衡二叉树虽然插入、删除操作比较麻烦(需要O(log
n)的代价),但进行遍历和排序却很快。选择完全在于用户的侧重点,但由于类型转换的方便性,通常我们用哈希表构造一个集合以后,再把它转换成相应的树集
进行遍历,以获得较好的效果。
历史实现 vs. 新实现
历史实现(Legacy
Implementations)是JCF的一个术语,准确的意义不是很清楚,但大致可以认为在Java 2(JDK
1.2)出现以前的老版本中JCF的一个雏形框架。在Java
2以后,JCF才开始完善健壮起来,新实现中出现了一些新的类用于替代老版本中的成员,但由于种种原因,老版本中很多类都代表了传统数据结构的精髓部分,
以及一些安全原因,所以仍然被我们使用着。
Enumeration vs. Iterator
Enumeration是一个传统的集合遍历工具,在新的JCF中使用的是Iterator,Iterator同样具有遍历功能,还包含一个remove()方法来删除当前得到的元素。
Dictionary vs. Map
Dictionary
是一个现在已经被标记为deprecated的类,实现了老版本中的映射功能,现在已经完全被Map取代。它们的区别是:Dictionary中key和
value不能为null,但Map却允许空的关键字和值,这一点直接影响到它们的后代:Hashtable和HashMap。
Vector vs. ArrayList
Vector
和ArrayList是数组在JCF中的体现,还记得前面讲过的数组的缺点么?Vector和ArrayList就是一种可以动态增长的数组。
Vector是历史实现,它和ArrayList的主要区别在于,Vector是同步集合(或者说是线程安全的),但ArrayList并不是同步的,由
于同步需要花一定的代价,所以ArrayList看起来要比Vector的存取访问效率更高。关于同步我们下面还将要谈到。
Hashtable vs. HashMap
Hashtable
是Dictionary的子类,属于历史实现,而HashMap是Map的子类,是新实现。它们的区别除了上面所说的key和value是否可以为空之
外,也有同步的差别,Hashtable是同步的,但HashMap不是。HashMap的一个经典的例子就是JSP的内置对象session。不过不要
因为Hashtable是“老前辈”而瞧不起它哦,它的一个著名的子类Properties我们可是经常会用到的。
同步 vs. 不同步
从上面的描述中我们似乎可以得出这么一个印象:历史实现好像都是同步的,但新实现中却
没有。需要同步操作的理由是,可能存在多个线程对同一个集合进行操作的情况:譬如一个线程正在对某集合进行遍历,但与此同时,另一个线程又在对该集合进行
插入或删除,那么第一个线程的遍历结果将是不可预测的,对于同步集合,它将会抛出一个ConcurrentModificationException异常,JCF把这种机制成为“fail-fast”。我们对比一下Vector和ArrayList的源代码就可以发现Vector的很多方法都是有synchronized关键字修饰的,但ArrayList没有。
容易遗忘的工具:Collections和Arrays
在图1中右下角落里有两个类叫做Collections(注意,不是
Collection!)和Arrays,这是JCF里面功能强大的工具,但初学者往往会忽视。按JCF文档的说法,这两个类提供了封装器实现
(Wrapper Implementations)、数据结构算法和数组相关的应用。
想必大家不会忘记上面谈到的“折半查找”、“排序”等经典算法吧,Collections类提供了丰富的静态方法帮助我们轻松完成这些在数据结构课上烦人的工作:
binarySearch:折半查找。
sort:排序,这里是一种类似于快速排序的方法,效率仍然是O(n * log n),但却是一种稳定的排序方法。
reverse:将线性表进行逆序操作,这个可是从前数据结构的经典考题哦!
rotate:以某个元素为轴心将线性表“旋转”——哇,这个功能太酷了!
swap:交换一个线性表中两个元素的位置。
……
Collections还有一个重要功能就是“封装器”(Wrapper),它提供了一些方法可以把一个集合转换成一个特殊的集合:
unmodifiableXXX:转换成只读集合,这里XXX代表六种基本集合接口:Collection、List、Map、Set、SortedMap和SortedSet。如果你对只读集合进行插入删除操作,将会抛出UnsupportedOperationException异常。
synchronizedXXX:转换成同步集合。
singleton:创建一个仅有一个元素的集合,这里singleton生成的是单元素Set,singletonList和singletonMap分别生成单元素的List和Map。
空集:由Collections的静态属性EMPTY_SET、EMPTY_LIST和EMPTY_MAP表示。
此外,我们知道把集合转换成对象数组可以用Collection的toArray()方法,我们也可以方便地把一个对象数组转换成一个线性表(可不要告诉我你是一个一个地add哦):Arrays.asList()。
泛型
目前我们了解的JCF的一个重要特征是:所有加入到集合当中的对象都将在表面上失去它
们自己的特性,而看上去仅仅只是一个Object对象而已,除非你把它强制类型转换成它们原来的对象。这一点很自然,集合嘛,对象的容器,它容纳的是各种
各样的对象,而不仅仅是某种特定类型的对象。J2SE
5.0出现以后,JCF开始引入泛型的特性,譬如我们经常碰到这样的应用,就是把集合转换成特定的数组,虽然Collection有toArray()的
方法,但可惜的是,这个数组的所有元素都是Object类型的,我们通常的做法是用一个for循环把数组的每个元素都进行强制类型转换,虽然可行,但看上
去很笨拙,如果有了泛型,我们就可以预先指定要得到的类型,然后一次toArray就可以得到我们期望的数组,里面的元素全部都是指定类型了。惭愧的是,
我对5.0还不是太了解,具体可以参考J2SE 5.0的JCF文档。
posted on 2009-08-14 15:18
suker 阅读(162)
评论(0) 编辑 收藏