Java标准库中的集合类,用以数据结构(Data Structures)
9.1 Java集合框架 9.1.1 将集合的接口与实现分离Java集合类库将接口(interface)与实现(implementation)分离。
例如队伍接口的最简形式可能类似下面这样:
public interface Queue
这个接口没有说明队伍是如何实现的,每一个实现都可以通过一个实现了Queue接口的类表示
public class CircularArrayQueue
9.1.2 Collection接口实际上,Java标准类库中没有名为CircularArrayQueue和linkedListQueue的类,这里只是做一个简单的说明,解释一下集合接口和实现在概念上的不同。如果需要一个循环数组队列,就可以使用ArrayDeque类,如果需要的是一个链表队列,就可以直接使用linkedList类,这个类实现了Queue接口
在API文档中,有另外一组名字以Abstract开头的类,例如AbstractQueue,这些类是为类库实现者而设计的。如果要实现自己的队伍类,会发现扩展AbstractQueue类要比实现Queue接口中的方法轻松的多。
Java类库中,集合类的基本接口是Collection接口,这个接口有两个基本方法:
public inteface Collection
add方法用于想集合中添加元素iterator方法用于返回一个实现Iterator接口的对象,可以使用这个迭代器对象依次访问集合中的元素 9.1.3 迭代器
Iterator接口包含4个方法:
public interface Iterator
如果想要查看及恶化中的所有元素,就请求一个迭代器,并在hasNext返回true时反复调用next方法
Collection
或者是使用for each循环
for(String eleemnt : c){ // do something with element}
在Java SE 8中,甚至不用写循环,可以调用forEachRemaining方法并提供一个lambda表达式(它会处理一个元素)
iterator.forEachRemaining(element -> do something with element);
Iterator接口的remove方法将会删除上次调用next方法时返回的元素,注意在调用remove之前没有调用next将是不合法的
9.1.4 泛型实用方法Collection和Iterator都是泛型接口,可以编写操作任何集合类型的实用方法
9.1.5 集合框架中的接口Java集合框架为不同类型的集合定义了大量接口,如下流程图所示
9.2 具体的集合Java库中的具体集合,除了以Map结尾的类之外,其他类都实现了Collection接口,而以Map结尾的类实现了Map接口
在Java程序设计语言中,所有链表实际上都是**双向链接(doubly linked)**的——即每个结点都存放着指向前驱结点的引用。如下代码是Java中使用linkedList添加3个元素之后再将第2个元素删除的代码
List
如果多次调用add方法,将按照提供的次序将元素添加到链表中。他们被依次添加到迭代器当前位置之前
9.2.2数组列表ArrayList封装了一个动态再分配的对象数组,可以使用get和set方法随机地访问每个元素
9.2.3 散列集散列集为每个对象计算一个整数,成为散列码(hash code)
散列码的计算只与散列的对象状态有关,而与散列表中的其他对象无关在Java中,散列表用链表数组实现
package chap9.set;import java.util.*;public class SetTest { public static void main(String[] args) { Set
TreeSet是一个有序集合(sorted collection)。可以以任意顺序将元素插入到集合中
SortedSet
排序是用树结构完成的(当前实现使用的是红黑树(red-black tree)
要使用树集,必须能够比较元素。这些元素必须实现Comparable接口,或者构造集时必须提供一个Comparator
treeSet/TreeSetTest.java
package chap9.treeSet;import java.util.*;public class TreeSetTest { public static void main(String[] args){ SortedSet
treeSet/Item.java
package chap9.treeSet;import java.util.*;public class Item implements Comparable
队列可以让人们有效地在尾部添加一个元素,在头部删除一个元素
双端队列,可以让人们有效地在头部和尾部同时添加或删除元素
9.2.6 优先级队伍优先级队列(priority queue)中的元素可以按照任意的顺序插人,却总是按照排序的顺序进行检索
优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)
priorityQueue/PriorityQueueTest.java
package chap9.priorityQueue;import java.util.*;import java.time.*;public class PriotityQueueTest { public static void main(String[] args) { PriorityQueue
映射是知道某些键的信息,并想要查找与之对应的元素,用来存放键/值对
9.3.1 基本映射操作Java类库为映射提供了两个通用的实现
HashMapTreeMap
如果不按照排列顺序访问键,最后选择散列-HashMap
键必须是唯一的,对同一键调用两次put方法,第二个值就会取代第一个值
map/MapTest.java
package chap9.map;import java.util.*;public class MapTest { public static void main(String[] args) { Map
处理映射的一个难点就是更新映射项,例如统计单次出现的频度
counts.put(word, counts.get(word) + 1);
但是如果单词第一次出现,就会出现NullPointerException异常,一个简单的补救措施是
counts.put(word, counts.getOrDefault(word, 0) + 1);
另一种方法是调用putIfAbsent方法。只有当键原先不存在时才会放入一个值0
counts.putIfAbsent(word, 0);counts.put(word, counts.get(word) + 1);
9.3.3 映射视图可以通过即可一样使用映射的keySet
Set
如果想要同时查看键和值,可以通过枚举条目来避免查找值
for(Map.Entry
泛型集合接口有一个很大的优点,即算法只需要实现一次。Java类库中的算法虽然没有C++类库的丰富,但是也包含了基本的排序、二分查找等实用算法
9.5.1 排序与混排Collections类中的sort方法可以对实现了List接口的集合进行排序
List
这个方法假定列表元素实现了Comparable接口。如果想采用其他方式对列表进行排序,可以使用List接口的sort方法并传入一个Comparator对象
staff.sort(Comparator.comparingDouble(Employee::getSalary));
sort方法的实现是直接将所有元素转入一个数组,对数组进行排序,然后,再将排序后的序列复制回列表。
集合类库中使用的排序算法比快速排序要慢一些,快速排序是通用排序算法的传统选择。但是,归并排序有一个主要的优点:稳定,即不需要交换相同的元素。
Collections类有一个算法shuffle,其功能与排序刚好相反,即随机地混排列表中元素的顺序。例如:
ArrayList
shuffle/ShuffleTest.java
package chap9.shuffle;import java.util.*;public class ShuffleTest { public static void main(String[] args) { List
Collections类的binarySearch方法实现了这个算法。注意,集合必须是排好序的,否则算法将返回错误的答案。如果集合没有采用Comparable接口的compareTo方法进行排序,
i = Collections.binarySearch(c, element);i = Collections.binarySearch(c, element, comparator);
9.5.3 简单算法在Collections类中包含了几个简单且很有用的算法,例如查找集合中最大元素的示例
9.5.4 批操作// 删除所有coll2中元素coll1.removeAll(coll2);// 删除所有未在coll2中出现的元素coll1.retainAll(coll2);// 从一个子列表中选出前10个元素relocated.addAll(staff.subList(0, 10))
9.5.5 集合与数组的转换
将数组转换为集合
String[] values = ...;HashSet
从集合中得到数组
Objects[] values = staff.toArray();
但是toArray方法返回的数组是一个Object[]数组,不能改变它的类型,实际上,必须使用toArray方法的一个变体形式
// 提供一个所需类型并且长度为0的数组String[] values = staff.toArray(new String[0]);// 或者构造一个指定大小的数组staff.toArray(new String[staff.size()]);
9.5.6 编写自己的算法如果编写自己的算法(实际上,是以集合作为参数的任何方法),应该尽可能地使用接口,而不要使用具体的实现
9.6 遗留的集合 9.6.1 Hashtable类Hashtable类与HashMap类的作用一样,实际上,它们拥有相同的接口。与Vector类的方法一样。Hashtable的方法也是同步的。如果对同步性或与遗留代码的兼容性没有任何要求,就应该使用HashMap。如果需要并发访问,则要使用ConcurrentHashMap
9.6.2 枚举遗留集合使用Enumeration接口对元素序列进行遍历,Enumeration接口有两个方法,即hasMoreElements和nextElement
Enumeration
属性映射是一个类型非常特殊的映射结构,有以下三个特性
键与值都是字符串表可以保存到一个文件中,也可以从文件中加载使用一个默认的辅助表
9.6.4 栈实现属性映射的Java平台类成为Properties
属性映射通常用于程序的特殊配置选项
Stack是从Java 1.0版开始就包含在标准类库中的,以下是Stack的几个常用的方法
Stack
BitSet类提供了一个便于读取、设置或清除各个位的接口
// 获取第i位状态,处于"开",返回true,否则,返回falsebucketOfBits.get(i);// 将第i位置为"开"bucketOfBits.set(i);// 将第i位置为"关"bucketOfBits.clear(i);