java集合(下)——集合框架与算法详解
框架是指一个类的集,在集中有很多超类和接口,这些超类中实现了很多高级的机制、功能和策略。框架的使用者可以创建子类来实现和扩展超类,而不用来重新创建这些基本的机制。在日常工作中,我们用到的技术基本都是框架,我们去使用那些包,去调用那些函数时都会用到这种框架的思想。在集合(一)中分析完集合的数据结构,今天我们就一起来继续讨论一下集合的框架。
(一)集合数据结构回顾
基本 | 类型 | 实现接口 | 说明 |
---|---|---|---|
List | 链表LinkedList | Deque,List,Queue | 通过存放前后结点的引用,实现双向链表 |
数组列表ArrayList | List,RandomAccess | 数据传入动态数组中,自动扩充数组大小 | |
Set | 散列集HashSet | Set | 哈希法存储数据,无序但查找时效率高 |
树集TreeSet | NavigableSet,Set,SortedSet | 按照一定方法排序,输出有顺序的集合 | |
Queue | 优先级队列PriorityQueue | Queue | 按照堆排序的方法排序的队列树 |
双端队列ArrayDeque | Deque, Queue | 可以在两端添加和删除,不能操作中间的队列 | |
Map | 散列表HashMap | Map | 用哈希法存放的,键值映射的表 |
树表TreeMap | Map,NavigableMap,SortedMap | 将键按照一定方法排序的表 | |
注:前六种(不包括Map)都实现了Collection和Iterator接口,因为篇幅限制没有写出。 |
(二)视图
在集合类库中,我们已经用了非常大的比例来构建实现类的接口,那么这些接口有什么用呢?如果我们直接把接口中方法写在实现类中不也起到一样的效果么?实际情况远非我们想的这么简单,有很多复杂的东西需要我们通过这些接口来操作。比如,我们把某些类的公有接口类型的对象称为视图对象。这些对象的类型并不是具体的某个实现类,而是一些实现类的公有接口。视图有很多作用,主要的有以下这几点:
1. 视图是轻量级的集包装器
我们可以通过方法,将普通数组包装成类型为List的视图对象。这个视图对象的类型是List,这听上去可能有些不可思议,因为List是一个接口,并不能实例化。但这就是视图对象的优势,这个视图对象可以通过一定的方法被赋予到任何实现了List接口的集合类中,比如常见的ArrayList、LinkedList等等,从而达到我们将一个普通类包装成集合类的目的。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
这就是视图的一大优点,它是一个轻量级的数组包装器,将数组包装成集合类库中的类对象。同样的,我们可以把那些键值对、需要存放到Set中的数据…都通过视图包装起来传入相应的类中。所以,我们把这个视图的功能叫做轻量级的集包装器。
2.通过视图分离出类中的子范围
可以为很多集合建立子范围视图,这些子范围是原有集合的一个引用,如果修改了这些子范围会影响到原有集合。如果不想有影响,我们需要用其他类似addAll的方法来做复制。在列表中,分例子范围的方法很简单。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
除此之外,在其他的有序集和映射表中,我们可以使用排序顺序而不是元素位置来创建子范围。这些方法在SortedSet和SortedMap中有声明。
SortedSet<E> headSet(E toElement)
返回此 set 的部分视图,其元素严格小于 toElement。SortedSet<E> subSet(E fromElement, E toElement)
返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。SortedSet<E> tailSet(E fromElement)
返回此 set 的部分视图,其元素大于等于 fromElement。
上面的三个方法是在SortedSet中声明的方法,我们在TreeSet中是可用的。同理,如果把方法中所有的Set换成Map,我们就可以在TreeMap中使用类似的这三个方法,要注意的是,Map中所有的操作都是用键来进行的,而不是值。
3.不可修改的安全视图
在现实生活中,如果你把你的集合对象传入到你同事的方法中,结果发现他错误的把你的对象修改了,这一定会让你恼火不已。那么,我们如何去解决这类问题,让我们的数据结构更加安全呢?在视图中,我们可以用不可修改视图来给我们的对象上锁,如果发现试图对集合进行修改,就抛出一个异常,同时将这个集合还原回没修改的状态。
在Collections类中,为我们提供了这么几种获得不可修改视图的方法:
unmodifiableCollection : 返回指定 collection 的不可修改视图(下同)。
unmodifiableList
unmodifiableMap
unmodifiableSet
unmodifiableSortedMap
unmodifiableSortedSet
上面这些方法非常简单清晰,我们来看一个具体的例子,看这些方法是如何工作的。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
不可修改并不是指集合对象再也不能修改,而是当它在套用Collections的静态方法时是不能被修改的。但是这样有个隐含的问题,因为使用了静态方法后,他的类型变成了视图类型,这样导致一些更加细节的方法不能被使用。比如,一个ArrrayList对象在通过Collections.unmodifiableList( )包装后,类型变为List,有一部分ArrayList特有的方法将不能被使用,这是需要注意的一点。
4.同步视图
多线程在我们的日常工作中十分常见,如果由多个线程访问集合,就必须确保集合不会被意外的破坏。在这里也不想过多讨论多线程的问题。在集合类库中,设计者使用了视图机制来保证常规集合的线程安全。比如Collecions类的静态方法synchronizedMap就可以将作为参数传入的Map实现类的对象变为线程安全的Map。
- 1
相似的方法还有一大堆,和第三点中的不可修改视图是类似的,在这里就不多讨论了。
5.检查视图
有的时候,我们在使用类似ArrayList等数据结构时并不使用泛型,从而会导致一些难以发现的类型错误。这个时候,如果我们使用了视图机制,用一种方法来不断地检查我们的代码就可以直接判断出错误的原因。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
视图是个好机制,它为我们提供了很多方便快捷的转换,安全等方面的操作,让我们对整个集合类库的构架有了个初步的理解。那么谈完了宏观的概念,我们来看一下实际操作中集合框架的一些细节的特性和机制。
(三)批操作
集合就是许许多多的数据在一起构成的。在实际应用中,处理这些海量的数据让我们非常的头痛,而且一旦需要操作的复杂一些,几遍for循环的时空复杂度直接以次方形式增长,这是我们绝对不想看到的。为了方便集合类中数据的操作,集合框架提供类一种叫做批操作的概念。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
这是一个比较简单的例子,它省略了for循环,而是采用这种批操作的概念来完成交集的查找。但是方法毕竟有限,我们能处理的问题还是在少数,如果真的想广泛的运用批操作,那么结合视图机制是必须的。因为视图机制可以在一定程度上,可以非常简单的进行类型的转换,通过子视图等方法在复杂度较低的情况下达到自己的目的。
(四)集合类的类型转换
1.集合与数组之间的转换
由于java平台API中的大部分内容都是在集合框架创建之前设计的,所以,有的时候的确需要在传统的数组和现代的集合之间进行转换。我们可以通过asList方法和toArray方法来解决这个问题。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
2.Map和Set之间的转换
集合框架并没有把Map作为一个集合来看待,然而,我们可以获得映射表的视图,这个视图是一组实现了Collection接口的对象。大家都知道,Collection和Map是两个不相干的接口,如今Map的视图是Collection的对象,无疑是更加方便了我们的操作。Map接口为我们提供了三个方法,这三个方法返回三个不同的视图:
Set<Map.Entry<K,V>> entrySet()
返回此映射中包含的映射关系的 Set 视图。
Set<K> keySet()
返回此映射中包含的键的 Set 视图。
Collection<V> values()
返回此映射中包含的值的 Collection 视图。
这其中值得注意的是Map.Entry,很显然这是一个静态内部接口,这个接口中有几个最简单的方法:
K getKey()
返回与此项对应的键。
V getValue()
返回与此项对应的值。
V setValue(V value)
用指定的值替换与此项对应的值。
看完了这些方法,我们也就知道如何去将Map转型为其他,或者如何用V来获取K。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
(五)算法与Collections类
一提到算法大家想到的可能是c语言中各式各样的排序,查找,二分等算法。但是在java集合类库中,并不需要这么麻烦。我们在Collections这个类中实现了几乎所有的简单算法,为了让程序员不会因为每次要实用算法时,都要自己编写一遍而感到苦恼。Collections这个类在上面也多次出现过了,这个类全部都是静态方法,而且它也确实只是在做一些包装性质的工作。这个类和视图密不可分。我们就来看一下Collections中的方法。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
(六)总结
两篇集合的文章,从前到后系统的总结了一遍java集合类库的林林总总,感觉学习了很多。尤其是java这种框架的概念,框架把原本分散的一些有关联的东西整合起来,并且给了很多实用的方法,整体上给人一种有血有肉很饱满的感觉。我们在日常使用框架的时候也很有必要去研究一下框架是如何搭建起来的,不仅可以让我们更加熟练的运用,而且能学到设计者的一些优秀的思想。