`
zhaohaolin
  • 浏览: 1003972 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

关于集合框架的思考【转】

    博客分类:
  • JAVA
阅读更多

jungleford如是说

    对于Java集合框架(Java Collections Framework,JCF),Java玩家大概都不会陌生,在C++里面相似的概念是标准模板库(Standard Template Library,STL),主要是对一些数据结构和相关算法的封装。前段时间在J2SE版看到一个关于Java集合框架的问题,当时re了一下,简单解释了一些的概念,考虑到这是一个Java初学者将会经常接触的工具,所以有了以下的一些文字。主要是参考了IBM developerWorks上的一篇教程,它可能解释得更加清晰,这里算是浓缩了一下吧,真正的来龙去脉可以看看JDK文档里的“The Collections Framework”,说明更为详细。

问题的源头

集合:对象的容器与数据结构
    回忆一下我们在程序设计里头可能会面对一些什么,无非是两类:基本类型和复合类型,后者常见的组织方式就是类。和基本类型不同,类对象通常是需要以动态方式分配的,譬如在内存的堆空间里new一个对象,这个我们一写OO的程序就必然会用到。同时我们面对的不仅仅是单个的基本类型或对象,对多个这样的数据我们通常采用的组织方式是什么?不错,是数组,这是伴随程序设计的一个古老概念。数组的优点显而易见,像根据下标检索元素这样的操作不费吹灰之力,但缺点也很明显:空间固定而不能动态增长(像Java这样的强类型语言对数组越界是及其敏感的),插入或删除元素比较费劲。因此数组不是解决一切集合问题的方便工具。我们可能需要一些新的工具,研究这些工具常常就是研究数据结构,特别的,数组本身就是一种线性有序的数据结构。
    数据结构的数学基础是集合论,为什么这么说呢?上面倒??衷谖颐且?芯康牟皇堑ジ龅幕?纠嘈突蚨韵螅?喔龆韵蟮恼?宀痪褪羌?下穑看?O的角度上看,集合也是一种对象,但它是一种特殊的对象:对象的容器(注意,我们这里没有继续讨论基本类型的集合,因为基本类型和存储分配方式与对象有着本质的差别)。集合论的一个根本问题就是:给定一个元素,集合必须能够回答该元素是或者不是属于这个集合。还有一个问题也很重要,就是:如果元素是属于一个集合,那该元素在集合中的地位应该是唯一的,或者说它是唯一确定的。当然还有其它问题,譬如查找、遍历、排序等等,这和具体的集合类型相关,后面将会讲到。

无序集、有序集、映射
    谈到集合的类型,我们在高中所学的集合概念是其中的一种,叫做“无序集”,也就是说集合的各个元素都是平等的,没有先后的区别,于是在无序集当中就决不允许出现一模一样的元素,否则当取到这个元素的时候就不知道应该取哪一个,这就违反了上面的“唯一确定”原则。
    等到我们上了大学,开始知道了另一种集合类型,叫做“有序集”(或者叫“线性表”,区别于以后碰到的像“树”,“图”这样的非线性的数据结构),如果是计算机专业的,大概学过离散数学当中的“代数结构”,那你就更清楚的知道,“有序集”其实是一种“二元关系”,确切的说是“偏序关系”,它是可以包含相同元素的,因为两个的相同元素的“序号”可以不同,这样根据“序号”仍可以“唯一确定”一个元素,数组就是一种有序集,有序集的另一个特点就是任意两个元素可以确定他们的顺序。
    无序集,有序集,难道还有第三种可能?呵呵,它还是出现在我们的高中代数课本里,叫做“映射”。映射也是集合?其实自从康托尔以来,集合论就认为“万物皆集合”(但也就是这个断言导致了集合论以后的尴尬境地,有兴趣可以看看罗素或哥德尔的一些结论,或google“集合论 悖论”)。映射其实是一种“元素对”的集合,就像f(a)=b, f(c)=d, ...等效于集合(无序集){(a, b), (c, d), ...},在“映射”中可以看作是(原象, 象)的集合,换一种说法就是(关键字key, 值value)的集合。所以我们可以在笛卡儿正交坐标平面上画出漂亮的函数图像,因为在集合论看来,函数(映射)就是二维平面上的一个个点,明白了?这样一来上面的“有序集”也好理解了,偏序关系a>b>c>d>...(不知道“偏序关系”就把它们看作是数组x[1]=a, x[2]=b, x[3]=c, x[4]=d ...好了)等效于无序集{(1, a), (2, b), (3, c), (4, d), ...},于是乎,所有的集合都等效于无序集!所以高中只教了我们一种集合,呵呵……

JCF的全家福

    好啦好啦,这些我们都知道,又不是在上数学课,说了这么多废话,怎么还没扯到正题上来?JCF的影子我还没看见呢!列位看官别急,这就给您道来。其实上面的概念对理解JCF非常重要。
    JCF是个颇有点规模的家族,看看它的类层次关系图就知道了,下面这张图(图1)摘自著名的Thinking in Java:


                                       图1 JCF层次结构

    哇,这么多接口和类,真有点让人无从下手的感觉。其实我们真正需要记住的只是这么一个超超easy的结构(图2):


                                       图2

    这张图看起来舒服多了吧?但它又能说明什么问题呢?它怎么就能够把握整个JCF呢?我们把Collection接口置于最顶上,意思是想说:Collection其实是整个JCF家族中的“祖宗”,几乎所有的JCF成员都源自该接口,或者和它有密切的关系,Collection提供关于集合的一些通用操作的接口,包括插入(add()方法)、删除(remove()方法)、判断一个元素是不是其成员(contains()方法)、遍历(iterator()方法)等等。注意了,前面的“废话”在这里将得到体现:Set接口体现的是“无序集”的概念,它是不允许有重复元素出现的;List接口代表“有序集”;而Map接口则是“映射”(在早期的Java版本中并不叫Map,我们在后面会看到),其实Map.Entry接口就是代表一个“元素对”我们可以通过Map的entrySet()方法得到这样一个由“元素对”组成的Set对象。我们注意到Set和List都是从“祖宗”Collection派生的,而Map不是,毕竟对一对元素的操作与对单个元素的操作还是有区别的,但是如果你仔细对照一下Collection和Map的源代码,以及它们的直接后代AbstractCollection和AbstractMap的源代码,你将会发现很多相似的地方,所以我们仍然可以把Map看成是和Collection有着血缘关系的接口,而和Set,List一起处于并列的位置。
    有了“无序集”,“有序集”和“映射”,我们就可以定义各种各样的抽象数据结构了,譬如图1所示的向量,链表,堆栈,哈希表,平衡二叉树等。但我们需要记住的,仅仅是图2,置于其它的成员,在用到的时候查一下API手册不就行了?不过一般初学者还是比较容易用到一些类,像Vector、ArrayList、HashMap。
    可能有的概念您还不是太了解,譬如什么叫“历史集合”,Hashtable、HashMap、TreeMap三者之间有什么区别和联系,怎样实现对一个特定集合的快速遍历、元素查找或者排序,没关系,我们在下面将逐一进行研究。

细节考虑:目标与效率

    有了JCF的层次还不够,重要的是对集合所容纳的对象的具体操作,以前我们学数据结构的时候可能老师总会让你计算一个算法的时间复杂度,可能你会对这个O(f(n))很不耐烦,但事实上算法效率是一个重要的因素。

1. 侧重点:遍历 vs. 查找
    对集合的有两个主要的应用:我需要知道集合有哪些元素;根据条件找到一个特定的元素。在算法上通常称为“遍历”和“查找”。不要以为我们生活中不常用哦!譬如CCTV的“幸运52”里面,李咏让参赛者报出一款PDA的准确价位,他会怎么做?“2000”“高了”“1000”“低了”“1500”“低了”……直到答对为止。可能有很多人都会选择这个策略,无论他是不是计算机专业出身的,也不知道他是不是了解“数据结构”和“折半查找”,更不用说他是不是知道还有比在无初始代价下O(log n)的时间复杂度更快的算法了,但我们经常会自然而然地用这样的方法,这和一个人的行业无关,除非这个人的rp超强,呵呵……
    又讲了一堆题外话了,遍历和修改似乎是一对矛盾,一个可以高效率插入删除元素的数据结构通常遍历的性能并不是最优。于是JCF在这里根据用户的目标实现了两种定制的数据结构:哈希表(包括HashSet和HashMap)和平衡二叉树(包括TreeSet和TreeMap)。由于可排序性是一种独特的要求,所以引入了SortedSet和SortedMap,它们分别是AbstractSet和AbstractMap的子接口,而TreeSet和TreeMap又分别是他们的一种实现。熟悉数据结构的人可能比较了解,哈希表在进行插入、删除、查找这样的操作是很快的,其时间复杂度是常数级O(1);平衡二叉树虽然插入、删除操作比较麻烦(需要O(log n)的代价),但进行遍历和排序却很快。选择完全在于用户的侧重点,但由于类型转换的方便性,通常我们用哈希表构造一个集合以后,再把它转换成相应的树集进行遍历,以获得较好的效果。

Set set1 = new HashSet(); 
set1.add(elem1);// 通过插入元素构造集合 
set1.add(elem2); 
set1.add(elem3); 
Set set2 = new TreeSet(set); 
Iterator all = set2.iterator(); 
while (all.hasNext()) 
{// 遍历集合 
all.next(); 
... 

 
2. 历史实现 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不是。不过不要因为Hashtable是“老前辈”而瞧不起它哦,它的一个著名的子类Properties我们可是经常会用到的。

3. 同步 vs. 不同步
    从上面的描述中我们似乎可以得出这么一个印象:历史实现好像都是同步的,但新实现中却没有。需要同步操作的理由是,可能存在多个线程对同一个集合进行操作的情况:譬如一个线程正在对某集合进行遍历,但与此同时,另一个线程又在对该集合进行插入或删除,那么第一个线程的遍历结果将是不可预测的,对于同步集合,它将会抛出一个ConcurrentModificationException异常,JCF把这种机制成为“fail-fast”。我们对比一下Vector和ArrayList的源代码就可以发现Vector的很多方法都是有synchronized关键字修饰的,但ArrayList没有。

4. 容易遗忘的工具: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()。

5. 泛型
    目前我们了解的JCF的一个重要特征是:所有加入到集合当中的对象都将在表面上失去它们自己的特性,而看上去仅仅只是一个Object对象而已,除非你把它强制类型转换成它们原来的对象。这一点很自然,集合嘛,对象的容器,它容纳的是各种各样的对象,而不仅仅是某种特定类型的对象。J2SE 5.0出现以后,JCF开始引入泛型的特性,譬如我们经常碰到这样的应用,就是把集合转换成特定的数组,虽然Collection有toArray()的方法,但可惜的是,这个数组的所有元素都是Object类型的,我们通常的做法是用一个for循环把数组的每个元素都进行强制类型转换,虽然可行,但看上去很笨拙,如果有了泛型,我们就可以预先指定要得到的类型,然后一次toArray就可以得到我们期望的数组,里面的元素全部都是指定类型了。惭愧的是,我对5.0还不是太了解,具体可以参考J2SE 5.0的JCF文档。

小结

    我这里走马观花一样把JCF的一些主要概念罗嗦了一下,Java的老手们可能比较厌烦,新手们可能更觉得像回顾了一下高中的数学课和大学的数据结构,呵呵。这只是一个小小的例子,可见基础知识对现实当中的应用还是蛮有指导意义的。大师们看数学,觉得那是很唯美很艺术的一样东西,西方一直都把数学区别于其它自然科学,而认为它更靠近于哲学,像我等这样整天还在为找工作烦得要死的俗人还是没法入道啊,sigh……

分享到:
评论

相关推荐

    一个Java框架引发的思考:语言、框架、范式转换和软件生产力

    标题中的“一个Java框架引发的思考:语言、框架、范式转换和软件生产力”提示了本文将探讨一个特定的Java框架,并由此引申出关于编程语言、开发框架、编程范式以及它们如何影响软件开发效率的主题。从描述中提供的...

    使用建议 资源类型:Java面经文档、技术要点或面试编程题 难度:中等 覆盖范围:Java基础知识、面向对象、集合框架、

    3. **集合框架**:Java集合框架提供了多种数据结构,如List(ArrayList、LinkedList)、Set(HashSet、TreeSet)和Map(HashMap、TreeMap)。理解它们之间的区别、特性和应用场景,以及如何高效地操作这些数据结构。...

    Java程序设计教程,电子教案,实例源程序,思考练习参考答案

    5. **集合框架**:Java集合框架是存储和管理对象的重要工具,包括List、Set、Map接口及其实现类如ArrayList、LinkedList、HashSet、HashMap等。 6. **IO流**:Java的输入输出流系统允许程序读写文件、网络数据。...

    如何像计算机科学家一样的思考

    5. **Java编程**:深入学习Java语言的特性,如异常处理、垃圾回收、IO流、集合框架等。 6. **算法实现**:用Java实现常见的数据结构和算法,提升编程技巧。 7. **问题解决策略**:教授如何有效地调试代码,追踪和...

    java 编程入门思考

    引言 1. 前提 2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 ...1.7.1 集合与继承器 ...1.7.3 集合库与方便使用...附录E 关于垃圾收集的一些话 附录F 推荐读物

    java基础知识思考题+答案(个人整理)

    - **java.util**:提供了各种实用工具类,如集合框架、日期处理、随机数生成等。 - **java.awt**:用于构建图形用户界面(GUI)的类库。 - **java.net**:包含与网络相关的类,用于实现网络通信等功能。 - **java....

    论我学java之小小思维

    通过以上分析可以看出,“论我学java之小小思维”不仅涉及了Java语言的基本概念,还涵盖了高级主题,如Servlet、集合框架以及数据源管理等。这些知识点对于深入理解和掌握Java编程语言至关重要。希望上述内容能够...

    心智转换、创新发展.pptx

    【心智模式】是个人对世界的理解方式,是我们的思考习惯、认知框架和信念体系的集合。它塑造了我们如何看待自己、他人、组织和社会,影响着我们的决策和行动。心智模式往往是无形的,但却强有力地制约着我们的思维...

    关于C#转换二进制所引起的一些思考

    在源码中,`Buffer`类创建了一个新的数组,并使用`ICollection<T>`接口(如果源是集合类型)的`Count`属性来确定数组长度,然后通过`Buffer`类的内部方法复制元素。 总结,C#中的二进制转换涉及多个层面,包括字符...

    Java复习---思考题

    - `java.util`:包含各种实用工具类,如集合框架、日期时间、随机数等。 - `java.io`:提供输入输出流处理,支持文件操作和网络通信。 - `java.awt`:图形用户界面(GUI)类库,用于创建窗口和控件。 - `java...

    Thinking In Java

    这本书不仅深入浅出地介绍了Java语言的核心概念,还涵盖了面向对象设计原则、泛型、异常处理、集合框架、多线程、网络编程等高级主题,对于初学者和有经验的开发者而言,都具有极高的价值。 ### 重要性与独特之处 ...

    <好书>java解惑(java puzzlers),过来挑战吧

    6. 集合框架:Java集合框架包含了大量的类和接口,如List、Set、Map等。书中可能讲解它们之间的区别,以及如何选择合适的数据结构,如ArrayList与LinkedList,HashMap与TreeMap的区别。 7. Java API使用:书中还...

    通信工程课后思考题.docx

    通信工程是一门涉及信息传输与处理的学科,其核心...以上是对通信工程课后思考题中关键知识点的详细解析,涵盖了信号类型、数字通信系统、随机过程、信道模型等多个重要领域,这些内容构成了通信工程的基础理论框架。

    《Java程序设计》翻转课堂实践.zip

    3. **数组与集合**:掌握一维和多维数组的使用,学习ArrayList、LinkedList、HashSet、HashMap等集合框架类的使用。 4. **异常处理**:学习Java中的异常处理机制,包括try-catch-finally语句块和throw、throws...

    某市装备制造业向现代产业集群发展的思考概论.doc

    【衢州市装备制造业向现代产业集群发展的思考概论】 本文探讨了衢州市装备制造业向现代产业集群发展的议题,旨在分析现状、揭示问题并提出解决方案。装备制造业作为国家的战略性产业,对于推动国民经济各行业的技术...

    多线程集合及IO面试

    - `Collection`是Java集合框架中的根接口,它定义了集合类的基本操作。 - `Collections`是Java的一个工具类,提供了一系列静态方法来辅助集合的操作,如排序、查找等。 7. **List、Set、Map与Collection的关系**...

    清华大学 JAVA 试题.rar

    通过解决这些试题,你可以系统地复习Java的基础概念、语法特性、面向对象编程、异常处理、集合框架、多线程、IO流、网络编程以及Java标准库的使用等核心知识点。 1. **基础概念与语法**:试题会涵盖变量、数据类型...

    java集合,多线程,序列化等基础练习源码

    Java集合框架是Java API的重要组成部分,它提供了一组高级数据结构,如List(列表)、Set(集合)和Map(映射)。ArrayList和LinkedList是两种常见的List实现,前者在随机访问上更高效,后者则在插入和删除操作中...

    JAVA 考试竞赛题--有一定的难度

    6. **集合框架**:ArrayList、LinkedList、HashSet、HashMap等是Java集合框架的核心类。熟练使用它们进行数据存储、操作和检索是必不可少的技能。 7. **IO流**:Java的IO流用于读写文件和处理输入输出,包括字节流...

Global site tag (gtag.js) - Google Analytics