CoreJava第9章 集合
一、Java集合框架
1.1 集合接口与实现分离
在Java集合类库中,将接口与实现进行了分离。只有在构造集合对象时,才会使用具体的类。可以使用接口类型存放集合引用。便于一种接口有多个实现,只需对程序一个地方进行修改,即调用构造器的地方。
1.2 Collection接口
Java中集合类的基本接口时Collection
接口。其中包括:
1 | public interface Collection<E> { |
add
方法用于添加元素,iterator
方法用于返回一个实现了Iterator
接口的对象,可使用迭代器来访问集合的元素。
1.3 迭代器
1 | public interface Iterator<E> { |
通过反复调用next方法,可以逐个访问集合中的元素。
for-each
语法在底层转换为带有迭代器的循环。可以处理任何实现了迭代器接口的对象
也可以不使用循环,调用forEachRemaining
方法并提供lambda表达式。
1 | iterator.forEachRemianing(element -> do something with element); |
1.4 泛型实用方法
类库的设计者认为应该预留一些对所有集合通用的方法,所有的实现类都应该提供这些方法
1 | /*返回一个用于访问集合中各个元素的迭代器。*/ |
若实现了Collection接口的类都要提供这么多方法自然会十分繁琐。为了让实现者更容易地实现,Java类库提供了一个类AbstractCollection
,它保持基础方法size
和iterator
仍为抽象方法,但是为实现者实现了其他方法。
这样,具体集合类就可以扩展抽象集合类。由更具体的类实现iterator
方法,contains
等方法已由超类提供。子类也可以重写这些方法。
对于Iterator
接口:
1 | /*如果存在另一个可访问的对象,返回true*/ |
二、集合框架中的接口
集合框架中的接口图:

有两个基本接口:Collection
和Map
。
集合框架中的类图:

三、具体集合
集合类型 | 描述 |
---|---|
ArrayList |
可以动态增长和缩减的一个索引序列 |
LinkedList |
可以在任何位置高效插入和删除的一个有序序列 |
ArrayDeque |
实现为循环数组的一个双端队列 |
HashSet |
没有重复元素的一个无序集合 |
TreeSet |
一个有续集 |
EnumSet |
一个包含枚举类型值的集 |
LinkedHashSet |
一个可以记住元素插入次序的集 |
PriorityQueue |
允许高效删除最小/大元素的一个集合 |
HashMap |
存储键/值关联的一个数据结构 |
TreeMap |
键有序的一个映射 |
EnumMap |
键属于枚举类型的一个映射 |
LinkedHastMap |
可以记录键/值项添加次序的一个映射 |
WeakHashMap |
值不会在别处使用时就可以被垃圾回收的一个映射 |
IdentityHashMap |
用==而不是equals比较键的一个映射 |
3.1 链表(LinkedList)
数组基于连续的存储位置存放对象,而链表将每个对象单独的链接。
链表是一个有序集合,每个对象位置十分重要。add方法将对象添加到链表尾部。
3.2 数组列表(ArrayList)
访问元素的协议:
通过迭代器;
通过get和set方法随机访问元素
3.3 散列集(HashSet)
散列表可以用于快速地查找对象。Java中散列表由链表数组实现。每个列表称为桶(bucket)。要想查找元素,先求出散列码,然后与桶总数取余,即求出了桶的索引。
如果碰到桶中已有元素,则称碰到哈希冲突。需要看新对象是否在桶中已存在。在Java8中,桶满时会从链表变为平衡二叉树。
一般会将桶数设置为预计元素总数的75%~150%。最好将其设置为一个素数,以防止键的聚集。标准类库使用的都是2的幂。
如果散列表太满,则需要再散列,创建一个桶数更多的表,并将所有元素差人到新表中,再丢弃原表。
一般而言,装填因子(load factor)可以确定何时对散列表进行再散列,如默认装填因子为0.75,这时会进行再散列。
散列表可以用于实现集类型。集是没有重复元素的类型。HashSet是实现了散列表的集,可以快速查找某个元素是否已经在集中,只需查看一个桶中的元素而不必查找所有元素。
3.4 树集(TreeSet)
TreeSet类与散列集十分类似,但比起散列集有所改进。树集是一个有序集合。可以以任意顺序将元素插入到集合中。
TreeSet是基于红黑树完成,每次添加,都会将其放置在正确的排序位置上。
但是添加到树中比添加到散列表中慢。要使用树集,必须能够比较元素,元素必须实现Comparable接口,或者构造树集时必须提供比较器Comparator。
TreeSet:
1 | TreeSet(); |
构造树集,并增加一个集合或有序集中的所有元素。
SortedSet:
1 | /* |
NaviableSet:
1 | /*返回大于value的最小元素或小于value的最大元素*/ |
3.5 队列与双端队列
队列允许在头、尾高效删除、添加元素。双端队列(deuqe)允许在头部和尾部都高效的删除和添加元素。
Java6开始引入了Deuqe接口,ArrayDeuqe和LinkedList都实现了接口。
Queue:
1 | /*如果队列没有满,将给定元素添加到这个队列的队尾并返回true。若满,第一个抛出异常,第二个则返回false*/ |
Deuqe:
1 | /*含义同上*/ |
ArrayDeuqe:
1 | ArrayDeuqe(); |
3.6 优先队列
优先队列中的元素可以按任意顺序插入,但会按照有序的顺序进行检索。无论何时调用remove方法,都会获得当前优先队列中最小的元素。不过优先队列并没有对所有元素进行排序。使用了一种数据结构,称为堆。堆是一个自组织的二叉树。其添加,删除操作都是最小元素移动到根。
PriorityQueue:
1 | PriorityQueue(); |
四、 映射
映射(map)用来存储键/值对。
4.1 基本映射操作
Java类库提供了两个映射基本实现:HashMap和TreeMap。散列映射对键进行散列,树映射根据键的顺序将元素组织为搜索树。但散列和比较函数只作用于键,不做用于值。
而要检索一个映射,也必须使用键(get),如果没有出现映射中的键,将返回null值。
4.2 更新映射条数
1 | counts.put(word, counts.get(word) + 1);//worse |
或先调用其他方法
1 | counts.putIfAbsent(word, 0); |
Map在Java8中新增的默认方法:
1 | /* |
4.3 映射视图
集合框架不认为映射本身是一个集合。不过可以得到映射的视图。
键集、值集合(不是一个集)以及键值对集。
在Map中分别做了如下实现。
1 | /* |
4.4 弱散列映射
WeakHashMap使用弱引用保存键。垃圾回收器不会马上将没有引用的键值回收。
4.5 链接散列表与映射
LinkedHashSet和LinkedHashMap类会记住插入元素项的顺序,避免了散列表中项看起来是随机的。在表中插入元素时,会加入到双向链表中。
4.6 枚举集与映射
EnumSet是一个枚举类型元素集的实现。由于枚举类型只有有限个实例,所以内部使用位序列实现。如果对应的值在集中,则对应位被置为1。
EnumSet没有构造器。要使用静态工厂方法来构造这个集:
EnumMap是一个键类型位枚举类型的映射。可以直接高效地实现为一个值数组。需要在构造器中声明键类型。
4.7 标识散列映射
类IdentityHashMap中键的散列值不使用hashCode函数计算,使用System.identityHashCode方法计算。这是Object.hashCode根据对象的内存地址计算散列码时所使用的方法。而且在进行两个对象比较时,IdentityHashMap类使用==,而不使用equals。
不同键对象即使内容相同,也被视为不同对象。在实现对象遍历算法时,可以标识哪些对象已经被遍历过。
五、 视图与包装器
视图即映射中的部分数据的集合,可以在视图中操纵原映射,这种集合称为视图。
5.1 小集合
Java9中引入部分静态方法,可以生成给定元素的集或列表,以及给定键、值对的映射。
5.2 子范围
可以为集合建立子范围视图。对子范围的操作会反映到元集合。
SortedMap接口和NaviaableSet接口分别有一些对应方法。
5.3 不可修改视图
Collections.unmodifiableXxx方法返回不可修改的视图。
并不是集合不可改,而是对视图修改,会抛出异常。仍可以对集合引用进行修改。
5.4 同步视图
多线程访问集合,要确保集合不会被破坏。类库设计者在设计时通过视图机制确保常规集合时线程安全的,但没有实现线程安全的集合。
5.5 检查型视图
用于对泛型类型可能出现的问题提供调试支持。
5.6 关于可选操作的说明
视图通常只读、无法改变大学或只支持删除而不支持插入。如执行不恰当操作可能抛出异常。
1 | /*List接口、Set接口*/ |
六、 算法
6.1 泛型算法
泛型集合的优点在于算法只需实现一次。
6.2 排序与混排
Collections类中sort方法实现了对List接口的集合进行排序。shuffle方法随机地混排列表中的元素顺序。
6.3 二分查找
元素类型需要实现RandomAccess接口,否则会退化为线性查找。
6.4 简单算法
Collections类
1 | //返回集合中最小的或最大的元素 |
Collection和List接口中新默认方法
1 | default boolean removeIf(Predicate<? super E> filter); |
6.5 批操作
removeAll、retainAll、addAll等系列方法。
6.6 集合与数组的转换
List.of包装器可以将数组转为集合。
toArray方法将集合转为数组。
6.7 编写算法
七、 遗留的集合

7.1 HashTable
经典的HashTable与HashMap作用一样,接口也基本相同。与Vector类一样,HashTable类中方法也是同步的。如果对遗留代码的兼容性没有要求,应该使用HashMap。需要并发访问应该使用ConcurrentHashMap。
7.2 枚举
遗留的集合使用Enumeration接口遍历元素序列。
其中包含hasMoreElements和nextElement类似于Iterator接口的hasNext和next。
1 | boolean hasMoreElements(); |
Collections:
1 | static <T> Enumeration<T> enumeratrion<Collection<E> c); |
7.3 属性映射
属性映射(property map)是一个特殊的映射结构。
- 键与值都是字符串。
- 可以保存到文件或从文件加载。
- 有一个二级表存放默认值。
实现属性映射的类称为Properties。常用于指定程序的配置选项。
1 | var settings = new Properties(); |
也可以使用store方法将属性映射保存到一个文件中。
Properties类有两种提供默认值的方式。第一种是查找一个字符串的值,可以指定一个默认值,当键不存在时会自动使用默认值。或设置一个二级属性映射。
7.4 栈
Stack类:扩展自Vector类
1 | E push(E item);//入栈 |
7.5 位集
BitSet存储一个位序列(不是数学意义上的集,是位向量或位数组)。如果需要高效地存储位序列(例如,标志),就可以使用位集。由于位集将位包装在字节里,所以使用位集要比使用Boolean的ArrayList高效许多。
常用埃拉托色尼筛选法测试编译器性能。在最近的版本中,Java编译器性能的优化已经战胜了G++编译器。
C++:

Java:
