13 集合
13.1 集合接口
随着Java se 1.2的问世,设计人员感觉到是推出一组功能完善的数据结构的时候了。
13.1.1 将集合的接口与实现分离
队列接口,可以在队伍尾部添加元素,在队列头部删除元素,可以查找队列中元素的个数。当需要收集对象,并按照“先进先出”的规则检索对象时就应该使用队列。
一个队列的最小形式:
interface Queue<E> { void add(E element); E remove(); int size(); }
队列通常有两种实现方式:一种是使用循环数组;另外一种是使用链表。
每一个实现都可以通过一个实现了Queue接口的类表示。
class CircularArrayQueue<E> implements Queue<E> class LinkedListQueue<E> implements Queue<E>
Queue<Customer> expressLane = new CircularArrayQueue<>(100);
Queue<Customer> expressLane = new LinkedListQueue<>(100);
循环数组要比链表更高效。但是,循环数组是一个有界集合,即容量有限。如果要收集的对象数量没有上限,那就最好用链表来实现。
13.1.2 Java类库中的集合接口和迭代器接口
在Java类库中,集合类的基本接口是Collection接口。这个接口有两个基本方法:
public interface Collection<E> { boolean add(E element); Iterator<E> iterator(); }
1,迭代器
Iterator接口包含3个方法:
public interface Iterator<E> { E next(); boolean haseNext(); void remove(); }
"for each"循环可以与任何实现了Iterable接口的对象一起工作,Collection接口继承了Iterable接口,所以对任何标准类库当中的集合都可以使用“for each”循环。
元素被访问的顺序取决于集合类型。
Java类库中迭代器查找一个元素的唯一方法就是调用next,而在执行查找过程中,迭代器的位置随之向前移动。因此,应该将Java迭代器认为是位于两个元素之间。
2,删除元素
Iterable接口的remove方法将会删除上次调用next方法时返回的元素。next方法和remove方法的调用具有互相依赖性。
3,泛型实用方法
Colletion接口声明了许多有用的方法,所有的实现类都必须提供这些方法。
int size(); boolean isEmpty(); boolean contains(Object obj); boolean equals(Object obj); ...
为了使实现者更容易实现这个接口,Java 类库提供了一个类AbstractColleticon。
13.2 具体的集合
Map类实现了Map接口,而不是Collection接口。
ArrayList, LinkedList, ArrayDeque, HashSet, TreeSet, PriorityQueue, HashMap, TreeMap, EnumMap, WeakHashMap, EnumMap,
13.2.1 链表
数组和数组列表有一个重大的缺陷,就是从数组中间位置删除一个元素要付出很大的代价。链表,解决了这个问题。
在Java 中,所有的链表实际上是双向链接的。
链表与泛型集合之间有一个重要的区别,链表是一个有序集合,每个对象的位置十分重要。
使用链表的唯一理由是尽可能减少在列表中间插入或删除元素所付出的代价。
import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; public class LinkedListTest { public static void main(String[] args) { List<String> a = new LinkedList<>(); a.add("Amy"); a.add("Carl"); a.add("Erica"); List<String> b = new LinkedList<>(); b.add("Bob"); b.add("Doug"); b.add("France"); b.add("Gloria"); ListIterator<String> aIter = a.listIterator(); Iterator<String> bIter = b.iterator(); while (bIter.hasNext()) { if (aIter.hasNext()) aIter.next(); aIter.add(bIter.next()); } System.out.println(a); bIter= b.iterator(); while (bIter.hasNext()) { bIter.next(); if (bIter.hasNext()) { bIter.next(); bIter.remove(); } } System.out.println(b); a.removeAll(b); System.out.println(a); } }
13.2.2 数组列表
数组列表实现了List接口,用get和set方法随机访问每个元素。
13.2.3 散列集
如果不在意元素出现的次序,可以有几种能够快速查找元素的数据结构。缺点是无法控制元素出现的次序。
Java 中,散列表用链表数组实现,每个列表被称为桶。要想查找表中元素的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引。
如果想更多地控制散列表的运行性能,就要制定一个原始的桶数。如果散列表太满,就要再散列。装填因子决定何时对散列表进行再散列。
散列表可以实现几个重要的数据结构。其中最简单的是set类型。set是没有重复元素的元素集合。set的add方法首先在set里面查找要添加的元素,如果没有,就把这个对象添加进去。
Java集合类库提供了一个HashSet类,它实现了基于散列表的集。
import java.util.HashSet; import java.util.Iterator; import java.util.Scanner; import java.util.Set; public class SetTest { public static void main(String[] args) { Set<String> words = new HashSet<>(); long totalTime = 0; Scanner in = new Scanner(System.in); while (in.hasNext()) { String word = in.next(); long callTime = System.currentTimeMillis(); words.add(word); callTime = System.currentTimeMillis() - callTime; totalTime += callTime; } Iterator<String> iter = words.iterator(); for (int i = 0; i <= 20 && iter.hasNext(); i++) { System.out.println(iter.next()); } System.out.println(". . ."); System.out.println(words.size() + " distinct words. " + totalTime + ", milliseconds"); } }
13.2.4 树集
TreeSet是一个有序集。在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。
将一个元素添加到树里面要比添加到散列表中慢,但是,比起添加到数组或者链表中来说,还是快很多。比添加到HashSet中要慢。
13.2.5 对象的比较
如果需要对一个类的对象进行排序,而这个类的创建者又没有费心实现Comparable接口,在这种情况下,可以通过将Comparator对象传递给TreeSet构造器来告诉树集使用不同的比较方法。
import java.util.*; public class TreeSetTest { public static void main(String[] args) { SortedSet<Item> parts = new TreeSet<>(); parts.add(new Item("Toaster", 1234)); parts.add(new Item("Widget", 4562)); parts.add(new Item("Modem", 9912)); System.out.println(parts); SortedSet<Item> sortByDescription = new TreeSet<>(new Comparator<Item>() { public int compare(Item a, Item b) { String descrA = a.getDescription(); String descrB = b.getDescription(); return descrA.compareTo(descrB); } }); sortByDescription.addAll(parts); System.out.println(sortByDescription); } }
13.2.6 队列与双端队列
Java se6中引入了Deque接口,并有ArrayDeque和LinkedList类实现。
13.2.7 优先级队列
优先级队列中的元素可以按照任意顺序插入,却总是按照排序的顺序进行检索。也就是,无论何时调用remove,总是删掉当前优先级队列中最小的元素。然而,优先级队列并没有对所有的元素进行排序。而是采用了一个优雅而且高效的数据结构,称为堆heap。堆是一个可以自我调整的二叉树,对树进行添加add和删除remove操作,可以让最小的元素移动到根,而不必花费时间进行排序。
优先级队的典型实例是任务调度。
import java.util.*; public class PriorityQueueTest { public static void main(String[] args) { PriorityQueue<GregorianCalendar> pq = new PriorityQueue<>(); pq.add(new GregorianCalendar(1906, Calendar.DECEMBER, 9)); // G. Hopper pq.add(new GregorianCalendar(1815, Calendar.DECEMBER, 10)); // A. Lovelace pq.add(new GregorianCalendar(1903, Calendar.DECEMBER, 3)); // J. von Neumann pq.add(new GregorianCalendar(1910, Calendar.JUNE, 22)); // K. Zuse System.out.println("Iterating over elements..."); for (GregorianCalendar date : pq) System.out.println(date.get(Calendar.YEAR)); System.out.println("Removing elements..."); while (!pq.isEmpty()) System.out.println(pq.remove().get(Calendar.YEAR)); } }
13.2.8 映射表
映射表用来存放键/值对。如果提供了键,就能找到值。
Java类库为映射表提供了两个通用的实现,HashMap和TreeMap。这两个类都实现了Map接口。
散列映射表对键进行散列。树映射表用键的整体顺序对元素进行排序。如果不需要按照排列顺序访问键,就最好选择散列映射表。
键必须是唯一的,不能对同一个键存放两个值。
remove方法,size方法。
集合框架并没有把映射表本身视为一个集合。然后,可以获得映射表的视图。有三个视图,分别是键集,值集,和键/值集。
import java.util.*; public class MapTest { public static void main(String[] args) { Map<String, Employee> staff = new HashMap<>(); staff.put("144-25-5464", new Employee("Amy Lee")); staff.put("567-24-2546", new Employee("Harry Hacker")); staff.put("157-62-7935", new Employee("Gary Cooper")); staff.put("456-62-5527", new Employee("Francesca Cruz")); // print all entries System.out.println(staff); // remove an entry staff.remove("567-24-2546"); // replace an entry staff.put("456-62-5527", new Employee("Francesca Miller")); // look up a value System.out.println(staff.get("157-62-7935")); // iterate through all entries for (Map.Entry<String, Employee> entry : staff.entrySet()) { String key = entry.getKey(); Employee value = entry.getValue(); System.out.println("key=" + key + ", value=" + value); } } }
13.2.9 专用集与映射表类
1,弱散列映射表
2,链接散列集和链接映射表
3,枚举集与映射表
4,标识散列映射表
不同的键对象,即使内容相同,也被视为不同的对象。
13.3 集合框架
框架framework是一个类的集,它奠定了创建高级功能的基础。
Java集合类库构成了集合类的框架,它为集合类的实现者定义了大量的接口与抽象类。
集合有基本的接口,Collection和Map。
boolean add(E element); V put(K key, V value); V get(K key);
List是一个有序集合。
ListIterator接口定义了一个方法,用于讲一个元素添加到迭代器所处位置的前面。
Set接口的add方法拒绝添加重复的元素。
SortedSet和SortedMap接口暴露了用于排序的比较器对象。
13.3.1 视图与包装器
1,轻量级集包装器
Arrays.asList();
2,子范围
subSet, headSet, tailSet; subMap, headMap, tailMap.
3,不可修改的视图
Collections.unmodifiableColletion
Collections.unmodifiableList
...
4,同步视图
Collections类的静态synchronizedMap方法可以将任何一个映射表转换成具有同步访问方法的Map。
5,检查视图
6,关于可选操作的说明
13.3.2 批操作
Set<String> result = new HashSet<>(a); result.retainAll(b);
13.3.3 集合与数组之间的转换
Arrays.asList();
toArray();
13.4 算法
泛型集合接口有一个很大的优点,即算法只需要实现一次。
13.4.1 排序与混排
Colletions.sort();
Colletions.shuffle();
13.4.2 二分查找
Colletions.binarySearch(c, element, comparator);
13.4.3 简单算法
13.4.4 编写自己的算法
13.5 遗留的集合
13.5.1 Hashtable类
13.5.2 枚举
13.5.3 属性映射表
13.5.4 栈
13.5.5 位集

浙公网安备 33010602011771号