9.1 Java框架
9.1.1 集合接口与实现分离
Java集合类库将接口与实现分离
9.1.2 Collection接口
在Java类库中,集合类的基本接口是 Collection 接口。这个接口有两个基本方法:
public interface Collection<E>{
boolean add(E element); //添加元素
Iterator<E> iterator(); //返回一个实现了Iterator的对象
...
}
9.1.3 迭代器
9.1.4 泛型实用方法
由于Collection与Iterator都是泛型接口,这意味着你可以编写处理任何集合类型的实用方法
9.2 集合框架中的接口
集合有两个基本接口:Collection和Map
9.3 具体集合
9.3.1 链表(LinkedList)
在Java程序设计语言中,所有链表实际上都是双向链接的——即每个链接还存放着其前驱的引用
链表是一个有序的集合,每个对象的位置十分重要
9.3.2 数组列表(ArrayList)
ArrayList类:封装了一个动态再分配的对象数组
9.3.3 散列集(hashSet)
如果不在意元素的顺序,有几种能够快速查找元素的数据结构。其缺点是无法控制元素出现的次序,这些数据结构将按照对自己最方便的方式组织元素
有一种众所周知的数据结构,可以用于快速地查找对象,这就是散列表。散列表为每个对象计算一个整数,称为散列码
在Java中,散列表用链表数组实现,每个列表被称为桶(bucket)。要想查找表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引
散列表可以用于实现很多重要的数据结构,其中最简单的是集类型。集是没有重复元素的元素集合。Java集合类库提供了一个hashSet类,它实现了基于散列表的集
Set<Integer> set = new HashSet<>();
HashSet<Integer> m = new HashSet<Integer>();
9.3.4 树集(TreeSet)
TreeSet类与散列集十分类似,不过,它比散列集有所改进。树集是一个有序集合。可以以任意顺序将元素插入到集合中,在对集合进行遍历时,值将自动地按照排序后的顺序呈现
排序是用一个树的数据结构完成的,每次将一个元素添加到树中时,都会将其放置在正确的排序位置上。因此,迭代器总是以有序的顺序访问每个元素
9.3.5 队列与双端队列
队列允许你高效地在尾部添加元素,并在头部删除元素
双端队列允许在头部和尾部都高效地添加或删除元素,不支持在队列中间添加元素
Java 6中引入了Deque接口,ArrayDeque和LinkedList类实现了这个接口。这两个类都可以提供双端队列,其大小可以根据需要扩展
9.3.6 优先队列(PriorityQueue)
优先队列中的元素可以按照任意的顺序插入,但会按照有序的顺序进行检索。也就是说,无论何时调用remove方法,总会获得当前优先队列中最小的元素。
不过,优先队列并没有对所有元素进行排序。如果迭代处理这些元素,并不需要对他们排序。优先队列使用了一个精巧且高效的数据结构,称为堆。堆是一个可以自组织的二叉树,其添加和删除操作可以让最小的元素移动到根,而不必花费时间对元素进行排序
与TreeSet一样,优先队列既可以保存实现了Comparable接口的类对象,也可以保存构造器中提供的Comparator对象;与TreeSet中的迭代不同,优先队列的迭代并不是按照有序顺序来访问元素,删除操作却总是删除剩余元素中最小的那个元素
PriorityQueue<Integer> p = new PriorityQueue<Integer>((a,b) -> a-b);
//按照从小到大的顺序排列
PriorityQueue<Integer> p = new PriorityQueue<Integer>((a,b) -> b-a);
//按照从大到小的顺序排列
9.4 映射
通常,我们知道某些关键信息,希望查找与之关联的某些元素。映射(map)数据结构就是为此设计的。
映射用来存放键/值对。如果提供了键,就能够查找到值
9.4.1 基本映射操作
Java类库为映射提供了两个通用的实现:HashMap和TreeMap,这两个类都实现了Map接口
散列映射对键进行散列,树映射根据键的顺序将元素组织为一个搜索树。散列或比较函数只应用于键,与键关联的值不进行散列或比较。
9.4.2 更新映射条目
处理映射的一个难点就是更新映射条目
9.4.3 映射视图
集合框架不认为映射本身是一个集合,不过,可以得到映射的视图——这是实现了Collection接口或某个子接口的对象
9.4.4 弱散列映射
9.4.5 链接散列集与映射
LinkedHashSet和LinkedHashMap类会记住插入元素项的顺序,这样就可以避免散列表中的项看起来是随机的。在表中插入元素项时,就会并入到双向链表中
链表散射映射可以使用访问顺序而不是插入顺序来迭代处理映射条目。每次调用get或put时,受到影响的项将会从当前位置删除,并放到项链表的尾部(只影响项在链表中的位置,而散列表的桶不会受到任何影响)
访问顺序对实现缓存“最近最少使用原则”十分重要
9.4.6 枚举集与映射
EnumSet是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现,如果对应的值在集中,则对应的位被置1
EnumSet类没有公共构造器,要使用静态工厂方法构造这个集。可以使用Set接口的常用方法来修改EnumSet
EnumMap是一个键类型为枚举类型的映射,它可以直接且高效地实现为一个值数组,需要在构造器中指定键类型
9.4.7 标识散列映射
类IdentityHashMap有特殊的用途。在这个类中,键的散列值不是用hashCode函数计算的,而是用System.identityHashCode方法计算的,根据对象的内存地址来计算散列码。
9.5 视图与包装器
可以使用视图获得其他实现了Collection接口或者Map接口的对象
9.5.1 小集合
Java 9 引入了一些静态方法,可以生成给定元素的集或列表,以及给定键 / 值对的映射
9.5.2 子范围
可以为很多集合建立子范围(subrange)视图
可以对子范围应用任何操作,而且操作会自动反映到整个列表。当使用group2.clear()删除整个子范围时,元素会自动地从staff列表清除
9.5.3 不可修改的视图
Collections类还有几个方法,可以生成集合的不可修改视图。这些视图对现有集合增加了一个运行时检查。如果发现试图对视图进行修改,就会抛出一个异常
9.5.4 同步视图
类库的设计者使用视图机制来确保常规集合是线程安全的,而没有实现线程安全的集合类
9.5.5 检查型视图
“检查型”视图用来对泛型类型可能出现的问题提供调试支持
9.6 算法
9.6.1 为什么使用泛型算法
泛型集合接口有一个很大的优点,即算法只需要实现一次
9.6.2 排序与混排
Collections类中的sort方法可以对实现了List接口的集合进行排序
9.6.3 二分查找
Collections类中的binarySearch方法实现了二分查找的算法
使用这个方法的集合必须是有序的,否则算法会返回错误的答案。要想查找某个元素,必须提供集合(该集合实现了List接口)以及要查找的元素。如果集合没有采用Comparable接口的compareTo方法进行排序,那么还要提供一个比较器对象
如果binarySearch方法返回一个非负的值,这表示匹配的对象的索引。如果返回负值,则表示没有匹配的元素,不过可以利用返回值来计算应该将element插入到哪个位置,以保持集合的有序性,插入的位置是 insertionPoint = -i-1
9.6.4 简单算法
Collections类中包含几个简单但很有用的算法,其他算法还包括:将一个列表中的元素复制到另外一个列表中;用一个常量值填充容器;逆置一个列表的元素顺序
9.6.5 批操作
很多操作会“成批”复制或删除元素
9.6.6 集合与数组的转换
9.6.7 编写自己的算法
如果编写自己的算法,应该尽可能的使用接口,而不要使用具体的实现
9.7 遗留的集合
9.7.1 Hashtable类
经典的Hashtable类与HashMap类的作用一样,接口也基本相同;与vector类的方法一样,Hashtable的方法也是同步的
9.7.2 枚举
遗留的集合使用Enumeration接口遍历元素序列
Enumeration接口有两个方法:hasMoreElements和nextElement。这两个方法完全类似于Iterator接口的hasNext方法和next方法
9.7.3 属性映射
属性映射是一个特殊类型的映射结构,它有下面3个特性:
- 键与值都是字符串
- 这个映射可以很容易地保存到文件以及从文件加载
- 有一个二级表存放默认值
实现属性映射的Java平台类名为Properties。属性映射对于指定程序的配置选项很有用
9.7.4 栈
从1.0版本开始,标准类库中就包含了Stack类,其中有push方法和pop方法。但是,Stack类扩展了Vector类,可以使用并非栈操作的insert和remove方法在任何地方插入或删除值,而不是在栈顶