13.1 集合接口
13.1.1 将集合的接口与实现分离
Java集合类库将接口interface与实现implementations分离。
队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以查找队列中元素的个数。当需要收集对象,并按照先进先出的规则检索对象时就应该使用队列。
一个极简的队列接口:interface Queue<E>{ void add(E element); E remove(); int size(); }
队列通常有两种实现方式:使用循环数组,使用链表。
使用队列时,一旦构建了集合就不需要知道究竟使用了哪种实现。接口本身不能说明哪种实现的效率究竟如何。循环数组要比链表更搞笑,但循环数组是一个有界集合,容量有限。
有一组Abstract开头的类,如果想实现自己的类,扩展Abstract类比实现Queue接口等轻松的多。
13.1.2 Java类库中的集合接口和迭代器接口
集合类的基本接口是Collection接口,有两个基本方法:boolean add(E element); Iterator<E> iterator();。
add用于添加元素,如果添加元素确实改变了集合就返回true。
interator用于返回一个实现了Iterator接口的对象,可以使用这个迭代器对象依次访问集合中的元素。
1. 迭代器,Iterator包含三个方法:E next(); boolean hasNext(); void remove();
通过反复调用next方法,可以逐个访问集合中的每个元素,如果到达集合末尾,next将抛出一个NoSuchElementException。调用next之前调用hasNext方法,如果还有多个供访问的元素就返回true。编译器简单地将foreach循环翻译为带有迭代器的循环。foreach循环可以与任何实现了iterable接口的对象工作。
2. 删除元素
Iterator接口的remove方法将会删除上次调用next方法时返回的元素。如果调用remove之前没有调用next将是不合法的,二者相互依赖。
3. 泛型实用方法
13.2 具体的集合
ArrayList 动态增长和缩减的索引序列
LinkedList 可以在任何位置进行高效插入和删除操作的有序序列
ArrayDeque 循环数组实现的双端队列
HashSet 没有重复元素的无序集合
TreeSet 有序集
EnumSet 包含枚举类型值的集
LinkedHashSet 可以记住元素插入次序的集
PriorityQueue 允许高效删除最小元素的集合
HashMap 存储键值关联的数据结构
TreeMap 键值有序排列的映射表
EnumMap 键值属于枚举类型的映射表
LinkedHashMap 可以记住键值项添加次序的映射表
WeakHashMap 其值无用武之地后可以被GC回收的映射表
IdentityHashMap 用==而不是用equals比较键值的映射表
13.2.1 链表LinkedList
Java中所有链表都是双向链接的,每个节点存放着前驱节点和后继节点的引用。
删除元素只需要对被删除元素附近的节点更新一下即可。
LinkedList.add将对象添加到链表的尾部。
LinkedList的listIterator方法返回一个实现了ListIterator的迭代器对象,这个迭代器对象的add方法在迭代器位置之前添加一个对象。如果链表有n个元素,就有n+1个位置可以添加新元素,这些位置与迭代器的n+1个可能的位置对应。当用一个刚刚由Iterator方法返回,且指向链表表头的迭代器调用add方法时,新添加的元素变成新表头。当迭代器越过最后一个元素时(hasNext返回false),新添加的元素变成表尾。
set方法用一个新元素取代调用next或previous返回的上一个元素。
如果迭代器发现它的集合被另一个迭代器修改了或被该集合自身的方法修改了,抛出ConcurrentModificationException。可以根据需要给容器附加多个迭代器,但这些迭代器只能读取列表,再附加一个既能读又能写的迭代器。
链表不支持快速地随机访问,如果要查看链表第n个元素,只能从头开始,越过n-1个元素。
ListIterator的nextIndex方法返回下一次调用next方法时返回元素的整数索引,这个方法效率很高,因为迭代器保存着当前位置的计数值。
13.2.2 数组列表ArrayList
ArrayList的方法不是同步的,在不需要同步时使用ArrayList,不使用Vector(同步的),效率更高。
13.2.3 散列集HashSet
散列表为每个对象计算一个整数,称为散列码,散列码的计算只与要散列的对象状态有关,与散列表中的其他对象无关。
讲一下散列表的实现机制:
散列表用链表数组实现,每个列表被称为桶bucket。要想查找表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得结果就是保存这个元素的桶的索引,即对象的位置=散列码%桶的总数。如果恰好桶中没有其他元素,此时直接插入桶中即可。如果桶被占(散列冲突),此时需要用新对象与桶中的所有对象进行比较,查看该对象是否已经存在。如果想要更多地控制散列表的运行性能,就要指定一个初始桶数,桶数是指用于收集具有相同散列值的桶的数目。通常将桶数设置为预计元素个数的75%-100%。如果散列表太满,就需要再散列(rehashed),需要创建一个桶数更多的表,并将所有元素插入这个新表中,丢弃原来的表。装填因子(load factor)决定何时对散列表进行再散列,默认为0.75,即表中超过75%的位置已填入元素,这个表就会用双倍的桶数自动进行再散列。
散列表可以用于实现set类型,set是没有重复元素的集合。set的add首先在集合中查找要添加的对象,如果不存在,就将对象添加进去。
13.2.4 树集TreeSet
有序的,对集合进行遍历时,每个值按排序后的顺序呈现,排序是用树结构完成的。每次将一个元素添加到树中时,都被放置在正确的排序位置上。比添加到散列表中慢。
13.2.5 对象的比较
TreeSet假定插入的元素实现了Comparable接口。也可以传递一个Comparator给树集的构造器。
13.2.6 队列Queue与双端队列Deque
Queue和Deque,Deque可以在头部和尾部同时添加或删除元素。
13.2.7 优先级队列PriorityQueue
优先级队列中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。无论何时调用remove方法,总会获得当前优先级队列中最小的元素。与TreeSet一样,优先级队列既可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供的比较器的对象。
使用优先级队列的典型示例是任务调度。每一个任务有一个优先级,任务随机顺序添加到队列中,每当启动一个新的任务时,都将优先级最高的任务从队列中删除。
13.2.8 映射表Map
用来存放键值对,如果提供了键,就能查找到值。两个通用实现:HashMap和TreeMap。
HashMap对键进行散列。TreeMap使用键的整体顺序对元素进行排序,并将其组织成搜索树。散列或比较函数只能作用于键,不能对值进行散列或比较。
HashMap比TreeMap,如果不需要按照排列顺序访问键,最好选择散列。
如果在映射表中没有与给定键对应的信息,get将返回null。
对同一个键调用两次put方法,后一个值会覆盖前一个值,键必须是唯一的,不能对同一个键存放两个值。
remove方法用于从映射表中产出给定键对应的元素。size方法用于返回映射表中的元素数。
键集、值集合、键值对集:keySet(),values(),entrySet()。
13.2.9 专用集与映射表类
1. 弱散列映射表WeakHashMap
从长期存活的映射表中删除那些无用的值。WeakHashMap使用弱引用(weak reference)保存键。WeakFeference将引用保存到另一个对象中,在这里,就是散列表键。通常,如果GC发现某个特定对象已经没有他人引用了,就将其回收。如果某个对象只能由WeakReference引用,GC仍然回收它, 但要将引用这个对象的弱引用放入队列中。WeakHashMap将周期性地检查队列,以便找出新添加的弱引用。一个弱引用进入队列意味着这个键不再被他人使用,并且已经被收集起来,于是WeakHashMap将删除对应的条目。
2. 链接散列集LinkedHashSet和链接映射表LinkedHashMap
可以记住插入元素项的顺序。
LinkedHashMap将用访问顺序,而不是插入顺序,对映射表条目进行迭代。每次调用get或put,受到影响的条目将从当前的位置删除,并放到条目链表的尾部。根据“最近最少使用”原则,实现高速缓存。即将访问频率高的元素放在内存中,访问频率低的元素从数据库中读取。
3. 枚举集EnumSet与枚举映射表EnumMap
EnumSet是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现。如果对应的值在集中,则相应的位被置为1。
EnumSet没有公共的构造器,使用静态工厂方法构造:EnumSet.allOf(); EnumSet.noneOf(); EnumSet.range(); EnumSet.of();
EnumMap是一个键类型为枚举类型的映射表,使用时需要在构造器中指定键类型。
4. 标识散列映射表IdentityHashMap
IdentityHashMap的键的散列值不是用hashCode计算的,而是用System.identityHashCode计算的,在对两个对象进行比较时,IdentityHashMap使用==,而不使用equals。不同的键对象,即使内容相同,也被视为不同的对象。
13.3 集合框架
13.3.1 视图与包装器
13.3.2 批操作
13.3.3 集合与数组之间的转换
13.4 算法
13.4.1 排序与混排
Collections.sort(list);对实现了List接口的集合进行排序,假定列表元素实现了Comparable接口。
Collections.sort(list, comparator);按照comparator提供的规则进行排序。
Collections.sort(list, Collections.reverseOrder());按照降序对列表进行排序。
Collections.sort(list, Collections.reverseOrder(comparator));将逆置comparator的次序。
Collections.shuffle(list);随机地混排列表中元素的顺序。
13.4.2 二分查找
Collections.binarySearch(list, element);集合必须是有序的。
13.4.3 简单算法
Collections.max(list, comparator);
Collections.min(list, comparator);
Collections.copy(toList, fromList);
Collections.fill(list, value);
Collections.addAll(c, values);
Collections.replaceAll(list, oldValue, newValue);.......
13.4.4 编写自己的算法
应该尽可能地使用接口,而不要使用具体的实现。
13.5 遗留的集合
13.5.1 Hashtable
Hashtable类与HashMap类的作用一样。Hashtable的方法是同步的。
13.5.2 枚举Enumeration
Enumeration接口有两个方法hasMoreElements和nextElement。与Iterator接口的hasNext和next类似。
13.5.3 属性映射表Properties
特性:1. 键与值都是字符串。2. 表可以保存到一个文件中,也可以从文件中加载。3. 使用一个默认的辅助表。
通常用于程序的特殊配置选项。
13.5.4 栈Stack
13.5.5 位集BitSet