Java核心技术卷一基础技术-第13章-集合-读书笔记

第13章 集合

本章内容:

* 集合接口

* 具体的集合

* 集合框架

* 算法

* 遗留的集合

13.1 集合接口

  1. Enumeration接口提供了一种用于访问任意容器中各个元素的抽象机制。

13.1.1 将集合的接口与实现分离

  1. Java集合类库将接口(interface)与实现(implementation)分离。

  2. 队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以查找队列中元素的个数。当需要收集对象,并按照“先进先出”的规则检索对象时就应该使用队列。

  3. 队列通常有两种实现方式:一种是使用循环数组;另一种是使用链表。
    每一个实现都可以通过一个实现了Queue接口的类表示。

  4. 如果需要一个循环数组队列,就可以使用ArrayDeque类。如果需要一个链表队列,就直接使用LinkedList类,这个类实现了Queue接口。
  5. 可以使用接口类型存放集合的引用。
  6. 循环数组要比链表更高效,因此多数人优先选择循环数组。然而,通常这样做也需要付出一定的代价。
  7. 循环数组是一个有界集合,即容量有限。如果程序中要收集的对象数量没有上限,就最好使用链表来实现。
  8. 在研究API文档时,会发现另外一组名字以Abstract开头的类,例如,AbstractQueue。这些类是为类库实现者设计的。如果想要实现自己的队列类,会发现扩展AbstractQueue类要比实现Queue接口中的所有方法轻松得多。

13.1.2 Java类库中的集合接口和迭代器接口

  1. 在Java类库中,集合类的基本接口是Collection接口,这个接口有两个基本方法:
    public interface Collection<E>
    {
     boolean add(E element);
     Iterator<E> iterator();
     ...
    }
    
    add方法用于向集合中添加元素。如果添加元素确实改变了集合就返回true,如果集合没有发生变化就返回false。集合中不允许有重复的对象。
    iterator方法用于返回一个实现了Iterator接口的对象,可以使用这个迭代器对象依次访问集合中的元素。
  2. Iterator接口包含3个方法:
    public interface Iterator<E>
    {
     E next();
     boolean hasNext();
     void remove();
    }
    
    通过反复调用next方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末尾,next方法将抛出一个NoSuchElementException。因此,需要在调用next之前调用hasNext方法。如果迭代器对象还有多个供访问的元素,这个方法就返回true。如果想要查看集合中的所有元素,就请求一个迭代器,并在hasNext返回true时反复地调用next方法。
  3. 编译器简单地将“for each”循环翻译为带有迭代器的循环。
  4. “for each”循环可以与任何实现了Iterable接口的对象一起工作,这个接口只包含一个方法:
    public interface Iterable<E>
    {
     Iterator<E> iterator();
    }
    
  5. Collection接口扩展了Iterable接口。因此,对于标准类库中的任何集合都可以使用“for each”循环。
  6. 元素被访问的顺序取决于集合类型,如果对ArrayList进行迭代,迭代器将从索引0开始,没迭代一次,索引值加1。然而,如果访问HashSet中的元素,每个元素将会按照某种随机的次序出现。虽然可以确定在迭代过程中能够遍历到集合中的所有元素,但却无法预知元素被访问的次序。
  7. Iterator接口的remove方法将会删除上次调用next方法时返回的元素。
  8. 对next方法和remove方法的调用具有互相依赖性。如果调用remove之前没有调用next将是不合法的。如果这样做,将会抛出一个IllegalStateException异常。
  9. 实现Collection接口的集合类需要提供很多的例行方法,为了能够让实现者更容易地实现这个接口,Java类库提供了一个类AbstractCollection,它将基础方法size和iterator抽象化了。
  10. java.util.Collection< E > 1.2
    • Iterator< E > iterator()
      返回一个用于访问集合中每个元素的迭代器。
    • int size()
      返回当前存储在几个中的元素个数。
    • boolean isEmpty()
      如果集合中没有元素,返回true。
    • boolean contains(Object obj)
      如果集合中包含了一个与obj相等的对象,返回true。
    • boolean containsAll(Collection<?> other)
      如果这个集合包含other集合中的所有元素,返回true。
    • boolean add(Object element)
      将一个元素添加到集合中,如果由于这个调用改变了集合,返回true。
    • boolean addAll(Collection<? extends E> other)
      将other集合中的所有元素添加到这个集合。如果由于这个调用改变了集合,返回true。
    • boolean remove(Object obj)
      从这个集合中删除等于obj的对象。如果有匹配的对象被删除,返回true。
    • boolean removeAll(Collection<?> other)
      从这个集合中删除other集合中存在的所有元素。如果由于这个调用改变了集合,返回true。
    • void clear()
      从这个集合中删除所有的元素。
    • boolean retainAll(Collection<?> other)
      从这个集合中删除所有与other集合中的元素不同的元素。如果由于这个调用改变了集合,返回true。
    • Object[] toArray()
      返回这个集合的对象数组。
    • < T > T[] toArray(T[] arrayToFill)
      返回这个集合的对象数组。如果arrayToFill足够大,就将集合中的元素填入这个数组中。剩余空间填充null;否则,分配一个新数组,其成员类型与arrayToFill的成员类型相同,其长度等于集合的大小,并添入集合元素。
  11. java.util.Iterator< E > 1.2
    • boolean hasNext()
      如果存在可访问的元素,返回true。
    • E next()
      返回将要访问的下一个对象。如果已经到达了集合的尾部,将抛出一个NoSuchElementException。
    • void remove()
      删除上次访问的对象。这个方法必须紧跟在访问一个元素之后执行。如果上次访问之后,集合已经发生了变化,这个方法将抛出一个IllegalStateException。

13.2 具体的集合

  1. Java库中的具体集合
集合类型描述
ArrayList 一种可以动态增长和缩减的索引序列
LinkedList 一种可以在任何位置进行高效地插入和删除操作的有序序列
ArrayDeque 一种用循环数组实现的双端队列
HashSet 一种没有重复元素的无序集合
TreeSet 一种有序集
EnumSet 一种包含枚举类型的集
LinkedHashSet 一种可以记住元素插入次序的集
PriorityQueue 一种允许高效删除最小元素的集合
HashMap 一种存储键/值关联的数据结构
TreeMap 一种键值有序排列的映射表
EnumMap 一种键值属于枚举类型的映射表
LinkedHashMap 一种可以记住键/值项添加次序的映射表
WeakHashMap 一种其值无用武之地后可以被垃圾回收器回收的映射表
IdentityHashMap 一种用==而不是用equals比较键值的映射表

除了以Map结尾的类之外,其他类都实现了Collection接口。而以Map结尾的类实现了Map接口。

13.2.1 链表

  1. 数组和数组列表都有一个重大的缺陷,就是从数组的中间位置删除一个元素要付出很大的代码,其原因是数组中处于被删除元素之后的所有元素都要向数组的前端移动。在数组中间的位置上插入一个元素也是如此。

  2. 在Java程序设计语言中,所有链表实际上都是双向链接的(doubly linked)。

  3. 链表与泛型集合之间有一个重要的区别。链表是一个有序集合(ordered collection),每个对象的位置十分重要。LinkedList.add方法将对象添加到链表的尾部。但是,尝尝需要将元素添加到链表的中间。由于迭代器是描述集合中位置的,所以这种依赖于位置的add方法将由迭代器负责。只有对自然有序的集合使用迭代器添加元素才有实际意义。因此,在Iterator接口中就没有add方法。相反地,集合类库提供了子接口ListIterator,其中包含了add方法。

  4. LinkedList的listIterator方法返回一个实现了ListIterator接口的迭代器对象。
  5. Add方法在迭代器位置之前添加一个新对象。
  6. set方法用一个新元素取代调用next或previus方法返回的上一个元素。
  7. 链表只负责耿总对列表的结构性修改,例如,添加元素、删除元素。set操作不被视为结构性修改。
  8. 链表不支持快速地随机访问。尽管如此,LinkedList类还是提供了一个用来访问某个特定元素的get方法:
    LinkedList<String> list = ...;
    String obj = list.get(n);
    
    这个方法的效率不高。如果发现自己正在使用这个方法,说明有可能对于所要解决的问题使用了错误的数据结构。
    绝对不应该使用这种让人误解的随机访问方法来遍历链表。下面这段代码的效率极低:
    for (int i = 0; i<list.size();i++)
     do something with list.get(i);
    
    每次查找一个元素都要从列表的头部重新开始搜索。LinkedList对象根本不做任何缓存位置信息的操作。
  9. get方法做了微小的优化;如果索引大于size()/2就从列表尾端开始搜索元素。
  10. 列表迭代器接口还有一个方法,可以告之当前位置的索引。实际上,从概念上讲,由于Java迭代器指向两个元素之间的位置,所以可以同时产生两个索引:nextIndex方法返回下一次调用next方法时返回元素的整数索引;previousIndex方法返回下一次调用previous方法时返回元素的整数索引。当然,这个索引只比nextIndex返回的索引值小1。这两个方法的效率非常高,这是因为迭代器保持着当前位置的计数值。最后需要说一下,如果一个整数索引n,list.listIterator(n)将返回一个迭代器,这个迭代器指向索引为n的元素前面的位置。也就是说,调用next与调用list.get(n)会产生同一个元素,知识获得这个迭代器的效率比较低。
  11. 为什么要优先使用链表呢?使用链表的唯一理由是尽可能地减少在列表中间插入或删除元素所付出的代价。如果列表只有少数几个元素,就完全可以使用ArrayList。
  12. 避免使用以整数索引表示链表中位置的所有方法。如果需要对几个进行随机访问,就使用数组或ArrayList,而不要使用链表。
  13. java.util.List< E > 1.2
    • ListIterator< E > listIterator()
      返回一个列表迭代器,以便用来访问列表中的元素。
    • ListIterator< E > listIterator(int index)
      返回一个列表迭代器,以便用来访问列表中的元素,这个元素是第一次调用next返回的给定索引的元素。
    • void add(int i,E element)
      在给定位置添加一个元素。
    • void addAll(int i,Collection<? extends E> elements)
      将某个集合中的所有元素添加到给定位置。
    • E remove(int i)
      删除给定位置的元素并返回这个元素。
    • E get(int i)
      获取给定位置的元素。
    • E set(int i,E element)
      用新元素取代给定位置的元素,并返回原来那个元素。
    • int indexOf(Object element)
      返回与给定元素相等的元素在列表中第一次出现的位置,如果没有这样的元素将返回-1。
    • int lastIndexOf(Object element)
      返回与给定元素相等的元素在列表中最后一次出现的位置,如果没有这样的元素将返回-1。
  14. java.util.ListIterator 1.2
    • void add(E newElement)
      在当前位置前添加一个元素。
    • void set(E newElement)
      用新元素取代next或previous上次访问的元素。如果在next或previous上次调用之后列表结构被修改了,将抛出一个IllegalStateException异常。
    • boolean hasPrevious()
      当反向迭代列表是,还有可供访问的元素,返回true。
    • E previous()
      返回前一个对象。如果已经到达了列表的头部,就抛出一个NoSuchElementException异常。
    • int nextIndex()
      返回下一次调用next方法时将返回的元素索引。
    • int previousIndex()
      返回下一次调用previous方法时返回的元素索引。
  15. java.util.LinkedList< E > 1.2
    • LinkedList()
      构造一个空链表。
    • LinkedList<collection<? extends="" e=""> elements>
      构造一个链表,并将集合中所有的元素添加到这个链表中。
    • void addFirst(E element)
    • void addLast(E element)
      将某个元素添加到列表的头部或尾部。
    • E getFirst()
    • E getLast()
      返回列表头部或尾部的元素。
    • E removeFirst()
    • E removeLast()
      删除并返回列表头部或尾部的元素。

13.2.2 数组列表

  1. List接口用于描述一个有序集合,并且集合中每个元素的位置十分重要。有两种访问元素的协议:一种是用迭代器。另一种是用get和set方法随机地访问每个元素。后者不适用于链表,但对数组却很有用。集合类库提供了一种大家熟悉的ArrayList类,这个类也实现了List接口。ArrayList封装了一个动态再分配的对象数组。

  2. 在需要动态数组时,可能会使用Vector类。为什么要用ArrayList取代Vector呢?原因很简单:Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象。但是,如果由一个线程访问Vector,代码要在同步操作上耗费大量的时间。这种情况还是很常见的。而ArrayList方法不是同步的,因此,建议在不需要同步时使用ArrayLits,而不要使用Vector。

13.2.3 散列集

  1. 可以快速地查找所需要的对象的常见的数据结构就是散列表(hash table)。散列表为每个对象计算一个整数,称为散列码(hash code)。散列码是由对象的实例域产生的一个整数。更准确的说,具有不用数据域的对象将产生不同的散列码。

  2. 如果自定义类,就要负责实现这个类的hashCode方法。注意,自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)俄日true,a与b必须具有相同的散列码。

  3. 在Java中,散列表用链表数组实现。每个列表被称为桶(bucket)。要想查找表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引。如果在这个桶中没有其他元素,此时将元素直接插入到桶中就可以了。当然,有时候会遇到桶被占满的情况,这也是不可避免的。这种现象被称为散列冲突(hash colision)。这时,需要用新对象与桶中的所有对象进行比较,查看这个对象是否已经存在。如果散列码是合理且随机分布的,桶的数目也足够大,需要比较的次数就会很少。

  4. 如果想更多地控制散列表的运行性能,就要指定一个初始的桶数。桶数是指用于收集具有相同散列值的桶的数目。如果要插入到散列表中的元素太多,就会增加冲突的可能性,降低运行性能。
  5. 如果大致知道最终会有多少个元素要插入到散列表中,就可以设置桶数。通常,将桶数设置为预计元素个数的75%~150%。有些研究人员认为:尽管还没有确凿的证据,但最好将桶数设置为一个素数,以防键的集聚。标准类库使用的桶数是2的幂,默认值为16(为表大小提供的任何值都将被自动地转换为2的下一个幂)。
  6. 如果散列表太满,就需要再散列(rehashed)。如果要对散列表再散列,就需要创建一个桶数更多的表,并将所有元素插入到这个新表中,然后丢弃原来的表。装填因子(load factor)决定何时对散列表进行再散列。例如,如果装填因子为0.75(默认值),而表中超过75%的位置已经填入元素,这个表就会用双倍的桶数自动地进行再散列。对于大多数应用程序来说,填装因子为0.75是比较合理的。
  7. 散列表可以用于实现几个重要的数据结构。其中最简单的是set类型。set是没有重复元素的元素集合。set的add方法首先在集中查找要添加的对象,如果不存在,就将这个对象添加进去。
  8. Java集合类库提供了一个HashSet类,它实现了基于散列表的集。可以用add方法添加元素。contains方法已经被重新定义,用来快速地查看是否某个元素已经出现在集中。它只在某个桶中查找元素,而不必查看集合中的所有元素。
  9. 散列集迭代器将以此访问所有的桶。由于散列将元素分散在表的各个位置上,所以访问它们的顺序几乎是随机的。只有不关心集合中元素的顺序时才应该使用HashSet。
  10. java.util.HashSet< E > 1.2
    • HashSet()
      构造一个空散列表。
    • HashSet(Collection<? extends E> elements)
      构造一个散列表,并将集合中的所有元素添加到这个散列集中。
    • HashSet(int initialCapacity)
      构造一个空的具有指定容量(桶数)的散列集。
    • HashSet(int initialCapacity,float loadFactor)
      构造一个具有指定容量和装填因子(一个0.0~1.0之间的数值,确定散列表填充的百分比,当大于这个百分比时,散列表进行再散列)的空散列集。
  11. java.lang.Object 1.0
    • int hashCode()
      返回这个对象的散列码。散列码可以是任何整数,包括正数或负数。equals和hashCode的定义必须兼容,即如果x.equals(y)为true,x.hashCode()必须等于y.hashCode()。

13.2.4 树集

  1. 树集是一个有序集合(sorted collection)。可以以任意顺序将元素插入到集合中。在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。

  2. 排序是用树结构完成的(当前实现使用的是红黑树(red-black tree))每次将一个元素添加到树中时,都被放置在正确的排序位置上。因此,迭代器总是以排好序的顺序访问每个元素。

  3. 将一个元素添加到树中要比添加到散列表中慢,但是,与将元素添加到数组或链表的正确位置上相比还是快很多的。如果树中包含n个元素,查找新元素的正确位置平均需要log2n次比较。

  4. 将一个元素添加到TreeSet中要比添加到HashSet中慢。
  5. 将元素添加到散列集和树集
    文档单词总数不同的单词个数HashSetTreeSet
    Alice in Wonderland 28195 5909 5秒 7秒
    The Count of Monte Cirsto 466300 37545 75秒 98秒
  6. java.util.TreeSet 1.2
    • TreeSet()
      构造一个空树集。
    • TreeSet(Collection<? exxtends E> elements)
      构造一个树集,并将集合中的所有元素添加到树集中。

13.2.5 对象的比较

  1. TreeSet如何知道希望元素怎样排列呢?在默认情况下,树集假定插入的元素实现了Comparable接口。这个接口定义了一个方法:
    public interface Comparable<T>
    {
     int compreTo(T other);
    }
    
    如果a与b相等,调用a.compareTo(b)一定返回0;如果排序后a位于b之前,则返回负值;如果a位于b之后,则返回正值。具体返回什么值并不重要,关键是符号(>0,0或<0)。有些标准的Java平台类实现了Comparable接口,例如,String类。这个类的compareTo方法依据字典序(有时称为字典序)对于字符串进行比较。如果要插入自定义的对象,就必须通过实现Comparable接口自定义排序顺序。在Object类中,没有提供任何compareTo接口的默认实现。
  2. 使用Comparable接口定义排列排序显然有其局限性。对于一个给定的类,只能够实现这个接口一次。可以通过将Comparator对象传递给TreeSet构造器来告诉树集使用不用的比较方法。Comparator接口声明了一个带有两个显式参数的compare方法:

    public interface Comparator<T>
    {
     int compare(T a,T b);
    }
    
  3. 从Java SE 6起,TreeSet类实现了NavigableSet接口。这个接口增加了几个便于定位元素以及反向遍历的方法。

  4. java.lang.Comparale 1.2
    • int compareTo(T other)
      将这个对象(this)与另一个对象(other)进行比较,如果this位于other之前则返回负值;如果两个对象在排列顺序中处于相同的位置则返回0;如果this位于other之后则返回正值。
  5. java.util.Comparator 1.2
    • int compare(T a,T b)
      将两个对象进行比较,如果a位于b之前则返回负值;如果两个对象在排列顺序中处于相同的位置则返回0;如果a位于b之后则返回正值。
  6. java.util.SortedSet 1.2
    • Comparator<? super E> comparator()
      返回用于对元素进行排序的比较器。如果元素用Comparable接口的compareTo方法进行比较则返回null。
    • E first()
    • E last()
      返回有序集中的最小元素或最大元素。
  7. java.util.NavigableSet 6
    • E higher(E value)
    • E lower(E value)
      返回大于value的最小元素或小于value的最大元素,如果没有这样的元素则返回null。
    • E ceiling(E value)
    • E floor(E value)
      返回大于等于value的最小元素或小于等于value的最大元素,如果没有这样的元素则返回null。
    • E pollLast
      删除并返回这个集中的最大元素或做小元素,这个集为空时返回null。
    • Iterator descendingIterator()
      返回一个按照递减顺序遍历集中元素的迭代器。
  8. java.util.TreeSet 1.2
    • TreeSet()
      构造一个用于排列Comparable对象的树集。
    • TreeSet(Comparator<? super E> c)
      构造一个树集,并使用指定的比较器对其中的元素进行排序。
    • TreeSet(SortedSet<? extends E> elements)
      构造一个树集,将有序集中的所有元素添加到这个树集中,并使用与给定集相同的元素比较器。

13.2.6 队列与双端队列

  1. 队列可以让人们有效地在尾部添加一个元素,在头部删除一个元素。有两个端头的队列,即双端队列,可以让人们有效地再头部和尾部同事添加或删除元素。不支持在队列中间添加元素。在Java SE 6中引入了Deque接口,并由ArrayDeque和LinkedList类实现。这两个类都提供了双端队列,并且在必要时可以增加队列的长度。

  2. java.util.Queue 5.0

    • boolean add(E element)
    • boolean offer(E element)
      如果队列没有满,将给定的元素添加到这个双端队列的尾部并返回true。如果队列满了,第一个方法将抛出一个IllegalStatelException,而第二个方法返回false。
    • E remove()
    • E poll()
      加入队列不空,删除并返回这个队列头部的元素。如果队列是空的,第一个方法抛出NoSuchElmentException,而第二个方法返回null。
    • E element()
    • E peek()
      如果队列不空,返回这个队列头部的元素,但不删除。如果队列空,第一个方法将抛出一个NoSuchElementException,而第二个方法返回null。
  3. java.util.Deque< E > 6

    • void addFirst(E element)
    • void addLast(E element)
    • boolean offerFirst(E element)
    • boolean offerLast(E element)
      将给定的对象添加到双端队列的头部或尾部。如果队列满了,前面两个方法将抛出一个IllegalStateException,而后面两个方法返回false。
    • E removeFirst()
    • E removeLast()
    • E pollFirst()
    • E pollLast()
      如果队列不空,删除并返回队列头部的元素。如果队列为空,前面两个方法将抛出一个NoSuchElementException,而后面两个方法返回null。
    • E getFirst()
    • E getLast()
    • E peekFirst()
    • E peekLast()
      如果队列非空,返回队列头部的元素,但不删除。如果队列空,前面两个方法将抛出一个NoSuchElementException,而后面两个方法返回null。
  4. java.util.ArrayDeque< E > 6

    • ArrayDeque()
    • ArrayDeque(int initialCapacity)
      用初始容量16或给定的初始容量构造一个无限双端队列。

13.2.7 优先级队列

  1. 优先级队列(priority queue)中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。也就是说,无论何时调用remove方法,总会获得当前优先级队列中最小的元素。然而,优先级队列并没有对所有的元素进行排序。如果用迭代的方式处理这些元素,并不需要对它们进行排序。优先级队列使用了一个优雅且高效地数据结构,称为堆(heap)。堆是一个可以自我调整的二叉树,对树执行添加(add)和删除(remore)操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。

  2. 与TreeSet一样,一个优先级队列既可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供比较器的对象。

  3. java.util.PriorityQueue 5.0

    • PriorityQueue()
    • PriorityQueue(int initialCapacity)
      构造一个用于存放Comparable对象的优先级队列。
    • PriorityQueue(int initialCapacity,Comparator<? super E> c)
      构造一个优先级队列,并用指定的比较器对元素进行排序。

13.2.8 映射表

  1. 映射表用来存放键/值对。如果提供了键,就能够查找到值。

  2. Java类库为映射表提供了两个通用的实现:HashMap和TreeMap。这两个类都实现了Map接口。

  3. 散列映射表对键进行散列,树映射表用键的整体顺序对元素进行排序,并将其组织成搜索树。散列或比较函数只能作用于键。与键关联的值不能进行散列或比较。

  4. 应该选择散列映射表还是树映射表呢?与集一样,散列稍微快一些,如果不需要按照排列顺序访问键,就最好选择散列。
  5. 键必须是唯一的。不能对同一个键存放两个值。如果对同一个键两次调用put方法,第二个值就会取代第一个值。实际上,put将返回用这个键参数存储的上一个值。
  6. remove方法用于从映射表中删除给定键对应的元素。size方法用于返回映射表中的元素数。
  7. 集合框架并没有将映射表本身视为一个集合(其他的数据结果框架则将映射表视为对(pairs)的集合,或者视为用键作为索引的值的集合)。然而,可以获得映射表的视图,这是一组实现了Collection接口对象,或者它的子接口的视图。
  8. keySet既不是HashSet,也不是TreeSet,而是实现了Set接口的某个其他类的对象。Set接口扩展了Collection接口。因此,可以与使用任何集合一样使用keySey。
  9. 如果调用迭代器的remove方法,实际上就从映射表中删除了键以及对应的值。但是,不能将元素添加到键集的视图中。如果只添加键而不添加值是毫无意义的。如果视图调用add方法,将会抛出一个UnsupportedOperationException异常。条目集视图也有同样的限制,不过,从概念上讲,添加新的键/值对是有意义的。
  10. java.util.Map< K,V > 1.2
    • V get(Object key)
      获取与键对应的值;返回与键对应的对象,如果在映射表中没有这个对象则返回null。键可以为null。
    • V put(K key,V value)
      将键与对应的值关系插入到映射表中。如果这个键已经存在,新的对象将取代与这个键对应的就对象。这个方法将返回键对应的旧值。如果这个键以前没有出现过则返回null。键可以为null,但值不能为null。
    • void putAll(Map<? extends K,?extends V> entries)
      将给定映射表中的所有条目添加到这个映射表中。
    • boolean containsKey(Object key)
      如果在映射表中已经有这个键,返回true。
    • boolean containsValue(Objecy value)
      如果应设变中已经有这个值,返回true。
    • Set< Map.Entry< K,V >> entrySet()
      返回Map.Entry对象的集视图,即映射表中的键/值对。可以从这个集中删除元素,同时也从映射表中删除了它们。但是,不能添加任何元素。
    • Set< K > keySet()
      返回映射表中所有键的集视图。可以从这个集中删除元素,同时也从映射表中删除了它们。但是,不能添加任何元素。
    • Collection< V > values()
      返回映射表中所有值的集合视图。可以从这个集中删除元素,同时也从映射表中删除了它们。但是,不能添加任何元素。
  11. java.util.Map.Entry< K,V > 1.2
    • K getKey()
    • V getValue()
      返回这个条目的键或值。
    • V getValue(V newValue)
      设置在映射表中与新值对应的值,并返回旧值。
  12. java.util.HashMap< K,V > 1.2
    • HashMap()
    • HashMap(int initialCapacity)
    • HashMap(int initialCapacity,float loadFactor)
      用给定的容量和装填因子构造一个空散列映射表(装填因子是一个0.0~1.0之间的数值。这个数值决定散列表填充的百分比。一旦到了这个比例,就要将其再散列到更大的表中)。默认的装填因子是0.75。
  13. java.util.TreeMap< K,V > 1.2
    • TreeMap(Comparator<? super K> c)
      构造一个树映射表,并使用一个指定的比较器对键进行排序。
    • TreeMap<map<? extends="" k,?="" v=""> entries>
      构造一个树映射表,并将某个映射表中的所有条目添加到树映射表中。
    • TreeMap(SortedMap<? extends K,? extends V> entries)
      构造一个树映射表,将某个有序映射表中的所有条目添加到树映射表中,并使用与给定的有序映射表相同的比较器。
  14. java.util.SortedMap< K,V > 1.2
    • Comparator<? super K> comparator()
      返回对键进行排序的比较器。如果键是用Comparable接口的compareTo方法进行比较的,返回null。
    • K firstKey()
    • K lastKey()
      返回映射表中最小元素和最大元素。

13.2.9 专用集合映射表类

  1. 弱散列映射表
    设计WeakHashMap类是为了解决一个有趣的问题。如果有一个值,对应的键已经不在使用了,将会出现什么情况呢?假定对某个键的最后一次引用已经消亡,不会有任何途径引用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以,这个键/值对无法从映射表中删除。为什么垃圾回收期不能够删除它呢?难道删除无用的对象不是垃圾回收器的工作吗?
    垃圾回收器耿总活动的对象。只要映射表对象是活动的,其中的所有桶也是活动的,它们不能被回收。因此,需要由程序负责从长期存活的映射表中删除那些无用的值。或者使用WeakHashMap完成这件事情。当对键的唯一引用来自散列表条目时,这一数据结构将与垃圾回收器协同工作一起删除键/值对。
    WeakHashMap使用弱引用(weak references)保存键。WeakReference对象将引用保存到另外一个对象中,在这里,就是散列表键。对于这种类型的对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象已经没有他人引用了,就将其回收。然而,如果某个对象只能有WeakReference引用,垃圾回收器仍然回收它,但要将引用这个对象的弱引用放入队列中。WeakHashMap将周期性地检查队列,以便找出新添加的弱引用。一个弱引用进入队列意味着这个键不再被他人使用,并且已经被收集起来。于是,WeakHashMap将删除对应的条目。

  2. 链接散列集和链接映射集
    Java SE 1.4增加了两个类:LInkedHashSet和LinkedHashMap,用来记住插入元素想的顺序。这样就可以避免在散列表中的项从表面上看是随机排列的。当条目插入到表中时,就会并入到双向链表中。
    链接散列映射表将用访问顺序,而不是插入顺序,对映射表条目进行迭代。每次调用get或put,受到影响的条目将从当前的位置删除,并放到条目链表的尾部(只有条目在链表中的位置会受影响,而散列表中的桶不会受影响。一个条目总位于与键散列码对应的桶中)。
    访问顺序对于实现高速缓存的“最近最少使用”原色十分重要。

  3. 枚举集与映射集
    EnumSet是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现。如果对应的值在集中,则对应的位被置为1。
    EnumSet类没有公共的构造器。可以使用静态工厂方构造这个集。
    可以使用Set接口的常用方法来修改EnumSet。
    EnumMap是一个键类型为枚举类型的映射表。它可以直接且高效地用一个值数组实现。在使用时,需要在构造器中指定键类型。

  4. 标识散列映射表
    Java SE 1.4还为另外一个特殊目的增加了另一个类IdentityHashMap。在这个类中,键的散列值不是用hashCode函数计算的,而是用System,identityHashCode方法计算的。这是Object.hashCode方法根据对象的内存地址来计算散列码时所使用的方法。而且,在对两个对象进行比较时,IdentityHashMap类使用==,而不使用equals。
    也就是说,不同的键对象,即使内容相同,也被视为是不同的对象。在实现对象遍历算法(如对象序列化)时,这个类非常有用,可以用来跟踪每个对象的遍历状况。
  5. java.util.WeakHashMap< K,V > 1.2

    • WeakHashMap()
    • WeakHashMao(int initialCapacity)
    • WeakHashMap(int initialCapacity,float loadFactor)
      用给定的容量和填充因子构造一个空的散列映射表。
  6. java.util.LinkedHashSet 1.4

    • LinkedHashSet()
    • LinkedHashSet(int initialCapacity)
    • LinkedHashSet(int initialCapacity,float loadFactor)
      用给定的容量和填充因子构造一个空链接散列集。
  7. java.util.LinkedHashMap< K,V > 1.4

    • LinkedHashMap()
    • LinkedHashMap(int initialCapacity)
    • LinkedHashMap(int initialCapacity,float loadFactor)
    • LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)
      用给定的容量、填充因子和顺组构造一个空的链接散列映射表。
    • protected boolean removeEldestEntry(Map.Entry< K,V > eldest)
      如果想删除eldest元素,并同时返回true,就应该覆盖这个方法。eldest参数是预期要删除的条目。这个方法将在条目添加到映射表中之后调用。其默认的实现将返回false。即在默认情况下,旧元素没有被删除。然而,可以重新定义这个方法,以便有选择地返回true。
  8. java.util.EnumSet< E extends Enum< E >> 5.0

    • static < E extends Enum< E >> EnumSet< E > allOf(Class< E > enumType)
      返回一个包含给定枚举类型的所有值的集。
    • static < E extends Enum< E >> EnumSet< E > noneOf(Class< E > enumType)
      返回一个空集,并有足够的保存给定的枚举类型所有的值。
    • static < E extends Enum< E > > EnumSet< E > range(E from,E to)
      返回一个包含from~to之间的所有值(包括两个边界的元素)的集。
    • static < E extends Enum< E > > EnumSet< E > of(E value)
    • static < E extends Enum< E > > EnumSet< E > off(E value,E… value)
      返回包括给定值的集。
  9. java.util.EnumMap< K extends Enum< K >,V > 5.0

    • EnumMap(Class< K > keyType)
      构造一个键为给定类型的空映射集。
  10. java.util.IdentityHashMap< K,V > 1.4

    • IdentityHashMap()
    • IdentityHashMap(int expectedMaxSize)
      构造一个空的标识散列映射集,其容量是大于1.5*expectedMaxSize的2的最小次幂(expectedMaxSize的默认值是21)。
  11. java.lang.System 1.0

    • static int identityHashCode(Object obj) 1.1
      返回Object.hashCode计算出来的相同散列码(根据对象的内存地址产生),即使obj所属的类已经重新定义了hashCode方法也是如此。

13.3 集合框架

  1. 框架(framework)是一个类的集,它奠定了创建高级功能的基础。框架包含很多超类,这些超类拥有非常有用的功能、策略和机制。框架使用者创建的子类可以扩展超类的功能,而不必重新创建这些基本的机制。

  2. Java集合类库构成了集合类的框架,它为集合的实现者定义了大量的接口和抽象类,并且对其中的某些机制给予了描述。如果想要实现用于多种集合类型的泛型算法,或者是想增加新的集合类型,了解一些框架的知识是很有帮助的。

  3. 集合有两个基本的接口:Collection和Map。可以使用下列方法向集合中插入元素:

    boolean add(E element)
    

    但是,由于映射表保存的是键/值对,所以可以使用put方法进行插入。

    V put(K key,V value)
    

    要想从集合中读取某个元素,就需要使用迭代器访问它们。然而,也可以用get方法从映射表读取值:

    V get(K key)
    
  4. List是一个有序集合(ordered collection)。元素可以添加到容器中某个特定的位置。将对象放置在某个位置上可以采用两种方式:使用整数索引或使用列表迭代器。List接口定义了几个用于随机访问的方法:
    void add(int index,E element)
    E get(int index)
    void remove(int index)
    
  5. List接口在提供这些随机访问方法时,并不管它们对某种特定的实现是否高效。为了避免执行成本较高的随机访问操作,Java SE 1.4引入了一个标记接口RandomAccess。这个接口没有任何方法,但可以用来检测一个特定的集合是否支持高效的随机访问:

    if (c instanceof RandomAccess)
    {
     use random access algorithm
    }
    else
    {
     use sequential access algorithm
    }
    

    ArrayList类和Vector类都实现了RandomAccess接口。

  6. ListIterator接口定义了一个方法,用于将一个元素添加到迭代器所处位置的前面:

    void add(E element)
    

    要想获取和删除给定位置的元素,值需要调用Iterator接口中的next方法和remove方法即可。

  7. Set接口与Collection接口是一样的,只是其方法的行为有着更加严谨的定义。集的add方法拒绝添加重复的元素。集的equals方法定义两个集相等的条件是他们包含相同的元素但顺序不必相同。hashCode方法定义应该保证具有相同元素的集将会得到相同的散列码。
  8. 既然方法签名是相同的,为什么还要建立一个独立的接口呢?从概念上讲,并不是所有集合都是集。建立set接口后,可以让程序员编写仅接受集的方法。
  9. SortedSet和SortedMap接口暴露了用于排序的比较器对象,并且定义的方法可以获得集合的子集视图。
  10. Java SE 6引入了接口NavigableSet和NavigableMap,其中包含了几个用于在有序集和映射表中查找和遍历的方法(从理论上讲,这几个方法已经包含在SortedSet和SortedMap的接口中)。TreeSet和TreeMap类实现了这几个接口。
  11. 集合接口有大量的方法,这些方法可以通过更基本的方法加以实现。抽象类提供了许多这样的例行实现:
    AbstractCollection
    AbstractList
    AbstractSequentialList
    AbstractSet
    AbstractQueue
    AbstractMap
    如果实现了自己的集合类,就可能要扩展上面某个类,以便可以选择例行操作的实现。
  12. Java类库支持下面几种具体类:
    LinkedList
    ArrayList
    ArrayDeque
    HashSet
    TreeSet
    PriorityQueue
    HashMap
    TreeMap
    还有许多Java第一版“遗留”下来的容器,在集合框架出现就有了,它们是:
    Vector
    Stack
    Hashtable
    Properties
    这些类已经被集成到集合框架中。

13.3.1 视图与包装器

  1. 通过使用视图(views)可以获得其他的实现了集合接口和映射表接口的对象。映射表类的keySet方法就是一个这样的实例。初看起来,好像这个方法创建了一个新集,并将映射表中的所有键都填进去,然后返回这个集。但是,情况并非如此。取而代之的是:keySet方法返回一个实现Set接口的类对象,这个类的方法对原映射表进行操作。这种集合称为视图。

  2. 轻量级集包装器
    Arrays类的静态方法asList将返回一个包装了普通Java数组的List包装器。这个方法可以将数组传递给一个期望得到列表或几个变元的方法。返回的对象不是ArrayList。
    它是一个视图对象,带有访问底层数组的get和set方法。改变数组大小的所有方法(例如,与迭代器有关的add和remove方法)都会抛出一个Unsupported OperationException异常。
    从Java SE 5.0开始,asList方法声明为一个具有可变数量参数的方法。除了可以传递一个数组之外,还可以将各个元素直接传递给这个方法。

  3. Collections类包含很多实用方法,这些方法的参数和返回值都是集合。不要将它与Collection接口混淆起来。
  4. 如果调用下列方法Collections.singleton(anObject)则将返回一个视图对象。这个对象实现了Set接口(与产生List的ncopies方法不同)。返回的对象实现了一个不可修改的单元素集,而不需要付出建立数据结构的开销。singletonList方法与singletomMap方法类似。

  5. 子范围
    可以为很多集合建立子范围(subrange)视图。可以使用subList方法来获得一个列表的子范围视图。
    可以将任何操作应用于子范围,并且能够自动地反映整个列表的情况。
    对于有序集和映射集,可以使用排序顺序而不是元素位置建立子范围。SortedSet接口声明了3个方法:

    SortedSet< E > subSet(E from,E to)
    SortedSet< E > headSet(E to)
    SortedSet< E > tailSet(E from)
    

    这些方法将返回大于等于from且小于to的所有元素子集。有序映射表也有类似的方法:

    SortedMap< K,V > subMap(K from,K to)
    SortedMap< K,V > headMap(K to)
    SortedMap< K,V > tailMap(K from)
    

    返回映射表视图,该映射表包含键落在指定范围内的所有元素。
    Java SE 6引入的NavigableSet接口赋予自返回操作更多的控制能力。可以指定是否包括边界:

    NavigableSet< E > subSet(E from, boolean fromInclusive,E to,boolean toInclusive)
    NavigableSet< E > headSet(E to,boolean toInclusive)
    Navigable< E > tailSet(E from,boolean fromInclusive)
    
  6. 不可修改的视图
    Collections还有几个方法,用于产生集合的不可修改视图(unmodifiable views)。这些视图对现有集合曾姐了一个运行时的检查。如果发现视图对集合进行修改,就抛出一个异常,同时这个集合将保持未修改的状态。
    可以使用下面6中方法获得不可修改视图:

    Collections.unmodifiableCollection
    Collections.unmodifiableList
    Collections.unmodifianleSet
    Collections.unmodifiableSortedSet
    Collections.unmodifiableMap
    Collections.unmodifiableSortedMap
    

    每个方法都定义于一个接口。例如,Collections.unmodifiableList与ArrayList、LInkedList或者任何实现了List接口的其他类一起协同工作。
    lookAt方法可以调用List接口中的所有方法,而不只是访问器。但是所有的更改器方法(例如,add)已经被重新定位为抛出一个UnsupportedOperationException异常,而不是将调用传递给底层集合。
    不可修改视图并不是集合本身不可修改。仍然可以通过集合的原始引用对集合进行修改。并且仍然可以让集合的元素调用更改器方法。
    由于视图只是包装了接口而不是实际的集合对象,所以只能访问接口中定义的方法。例如,LinkedList类有一些非常方便的方法,addFirst和addLast,它们都不是List接口的方法,不能通过不可修改视图进行访问。
    unmodifiableCollection方法将返回一个集合,它的equals方法不调用底层集合的queals方法。相反,它继承了Object类的equals方法,这个方法只是检测两个对象是否是同一个对象。如果将集或列表转换成集合,就再也无法检测其内容是否相同了。视图就是以这种方式运行的,因为内容是否相等的检测在分层结构的这一层上没有定义妥当。然而,unmodifiableSet类和unmodifiableList类却使用底层集合的queals方法和hashCode方法。

  7. 同步视图
    如果由多个线程访问集合,就必须确保集不会被意外地破坏。类库的设计者使用视图机制来确保常规集合的线程安全,而不是实现线程安全的集合类。例如,Collections类的静态synchronizedMap方法可以将任何一个隐射表转换成具有同步访问方法的Map。现在,就可以由多线程访问map对象了。像get和put这类方法都是串行操作的,即在另一个线程调用另一个方法之前,刚才的方法调用必须彻底完成。

  8. 检查视图
    Java SE 5.0增加了一组“检查”视图,用来对泛型类型发生问题时提供调试支持。检查视图可以探测到将错误类型的元素私自带到泛型集合中的问题。下面定义了一个安全列表:
    List<String> safeStrings = Collections.checkedList(strings,String.class);视图的add方法将检测插入的对象是否属于给定的类,如果不属于给定的类,就立即抛出一个ClassCastException。这样做的好处是错误可以在正确的位置得以报告。
    被检查视图受限于虚拟机可以运行的运行时检查。
  9. 关于可选操作的说明
    通常,视图有一些局限性,即可能只可以读、无法改变大小、只支持删除而不支持插入,这些与映射表的键视图情况相同。如果试图进行不恰当的操作,受限制的视图就会抛出一个UnsupportedOperationException。
    在集合和迭代器接口的API文档中,许多方法描述为“可选操作”。
    是否应该将“可选”方法这一技术扩展到用户的设计中呢?不应该。集合类库的设计者必须解决一组特别严格且又相互冲突的需求。用户希望类库应该易于学习、使用方便,彻底泛型化,面向通用性,同时又与手写算法一样高效。要同时达到所有目标的要求,或者尽量兼顾所有目标完全是不可能的。但是,在自己的编程问题中,很少遇到这样极端的局限性。应该能够找到一种不必依靠极端衡量“可选的”接口操作来解决这类问题的方案。

  10. java.util.Collections 1.2

    • static < E > Collection unmodifiableCollection(Collection< E > c)
    • static < E > List unmodifiableList(List< E > c)
    • static < E > Set unmodifiableSet(Set< E > c)
    • static < E > Set unmodifiableSet(Set< E > c)
    • static < E > SortedSet unmodifiableSortedSet(SortedSet< E > c)
    • static < K,V > Map unmodifiableMap(Map< K,V > c)
    • static < K,V > SortedMap unmodifiableSortedMap(SortedMap< K,V > c)
      构造一个集合视图,其更改器方法将抛出一个UnsupportedOperationException。
    • static < E > Collection< E > synchronizedCollection(Collection< E > c)
    • static < E > List synchronizedList(List< E > c)
    • static < E > Set synchronizedSet(Set< E > c)
    • static < E > SortedSet synchronizedSortedSet(SortedSet< E > c)
    • static < K,V > Map< K,V > synchronizedMap(Map< E > c)
    • static < K,V > SortedMap< K,V > synchronizedSortedMap(SortedMap< E > c)
      构造一个集合视图,其方法都是同步的。
      • static < E > Collection checkedCollection(Collection< E > c,Class< E > elementType)
    • static < E > List checkedList(List< E > c,Class< E > elementType)
    • static < E > Set checkedSet(Set< E > c,Class< E > elementType)
    • static < E > SortedSet checkedSortedSet(SortedSet< E > c,Class< E > elementType)
    • static < K,V > Map checkedMap(Map< K,V > c,Class< K > keyType Type,Class< V > valueType)
    • static < K,V > SortedMap checkedSortedMap(SortedMap< K,V > c,Class< K > keyType Type,Class< V > valueType)
      构造一个集合视图。如果插入一个错误类型的元素,将抛出一个ClassCastException。
    • static < E > List< E > nCopies(int n,E value)
    • static < E > Set< E > singleton(E value)
      构造一个对象视图,它既可以作为一个拥有n个相同元素的不可修改列表,又可以作为一个拥有单个元素的集。
  11. java.util.Arrays 1.2
    • static < E > List< E > asList(E… array)
      返回一个数组元素的列表视图,这个数组是可修改的,但其大小不可变。
  12. java.util.List< E > 1.2

    • List< E > subList(int firstIncluded,int firstExcluded)
      返回给定位置范围内的所有元素的列表视图。
  13. java.util.SortedSet< E > 1.2

    • SortedSet< E > subSet(E firstInclued,E firstExcluded)
    • SortedSet< E > headSet(E firstExcluded)
    • SortedSet< E > tailSet(E firstInclued)
      返回给定范围内的元素视图。
  14. java.uril.NavigableSet< E > 6

    • NavigableSet< E > subSet(E from, boolean fromIncluded, E to,boolean toIncluded)
    • NavigableSet< E > headSet(E to,boolean toIncluded)
    • NavigableSet< E > tailSet(E from, boolean fromIncluded)
      返回给定返回内的元素视图。boolean 标志确定视图是否包含边界。
  15. java.util.SortedMap< K,V > 1.2

    • SortedMap< K,V > subMap(K firstIncluded,K firstExcluded)
    • SortedMap< K,V > headMap(K firstExcluded)
    • SortedMap< K,V > tailMap(K firstIncluded)
      返回在给定范围内的键条目的映射表视图。
  16. java.util.NavigableMap< K,V > 6

    • NavigableMap< K,V > subMap(K from,boolean fromIncluded,K to,boolean toIncluded)
    • NavigableMap< K,V > headMap(K from,boolean fromIncluded)
    • NavigableMap< K,V > tailMap(K to,boolean toIncluded)
      返回在给定范围内的键条目的映射表视图。boolean标志决定视图是否包含边界。

13.3.2 批操作

  1. 可以使用类库中的批操作(bulk operation)避免频繁地使用迭代器。

  2. 通过使用一个子范围视图,可以将批处理限制于子列表和子集的操作上。

13.3.3 集合与数组之间的转换

  1. 如果有一个数组需要转换为集合。Arrays.asList的包装器就可以实现这个目的。

  2. 由toArray方法返回的数组是一个Object[]数组,无法改变其类型。相反,必须使用另外一种toArray方法,并将其设计为所希望的元素类型且长度为0 的数组。随后返回的数组将与所创建的数组一样。

  3. 为什么不直接将一个Class对象传递给toArray方法。其原因是这个方法具有“双重职责”,不仅要填充已有的数组(如果足够长),还要创建一个新数组。

13.4 算法

13.4.1 排序与混排

  1. Collections类中的sort方法可以对实现了List接口的集合进行排序。这个方法假定列表元素实现了Comparable接口。如果想采用其他方式对列表进行排序,可以将Compaator对象作为第二个参数传递给sort方法。

  2. 如果想按照降序对列表进行排序,可以使用一种非常方便的静态方法Collections.reverseOrder()。这个方法将返回一个比较器,比较器则返回b.compareTo(a)。这个方法将根据元素类型的compareTo方法给定排序顺序。

  3. 集合类库中使用的归并算法比快速排序要慢一些,快速排序是通用排序算法的传统选择。但是,归并排序有一个主要的优点:稳定,即不需要交换相同的元素。

  4. 列表必须是可修改的,但不必是可以改变大小的。下面是有关的术语定义:

    • 如果列表支持set方法,则是可修改的。
    • 如果列表支持add和remove方法,则是可改变大小的。
  5. Collections类有一个算法shuffle,其功能与排序刚好相反,即随机地混排列表中元素的顺序。如果提供的列表没有实现RandomAccess接口,shuffle方法将元素复制到数组中,然后打乱数组元素的顺序,最后再将打乱顺序后的元素复制回列表。
  6. java.util.Collections 1.2
    • static < T extends Comparable<? super T> > void sort(List< T > elemets)
    • static < T > void sort(List< T > elemets, Comparator<? super T> c)
      使用稳定的排序算法,对列表中的元素进行排序。这个算法的时间复杂度是O(n logn),其中n为列表的长度。
    • static void shuffle(List<?> elements)
    • static void shuffle(List<?> elements,Random r)
      随机地打乱列表中的元素。这个算法的时间复杂度是O(n a(n)),n是列表的长度,a(n)是访问元素的平均时间。
    • static < T > Comparator< T > reverseOrder()
      返回一个比较器,它用与Comparable接口的compareTo方法规定的顺序的逆序对元素进行排序。
    • static < T > Comparator< T > reverseOrder(Comparator< T > comp)
      返回一个比较器,它用与comp给定的顺序的逆序对元素进行排序。

13.4.2 二分查找

  1. Collections类的binarySearch方法实现了二分查找算法。如果binarySearch方法返回的数值大于等于0,则表示匹配对象的索引。如果但会负值,则表示没有匹配的元素,但是,可以利用返回值计算应该将element插入到集合的哪个位置,以保证集合的有序性。插入的位置是(-i-1)。

  2. binarySearch方法检查列表参数是否实现了RandomAccess接口。如果实现了这个接口,这个方法将采用二分查找;否则,将采用线性查找。

  3. java.util.Collections 1.2

    • static < T extends Comparable< ? super T >> int binarySearch(List< T > elements, T key)
    • static < T > int binarySearch(List< T > elements, T key,Comparator< ? super T > c)
      从有序列表中搜索一个键,如果元素扩展了AbstractSequentialList类,则采用线性查找,否则将采用二分查找。这个方法的时间负责度为O(a(n)logn),n是列表的长度,a(n)是访问一个元素的平均时间。这个方法将返回这个键在列表中的索引,如果在列表中不存在这个键将返回负值i。在这种情况下,应该将这个键插入到列表索引-i-1的位置,以保持列表的有序性。

13.4.3 简单算法

  1. java.util.Collections 1.2
    • static < T extends Comparable<? super T>> T min(Collection< T > elements)
    • static < T extends Comparable<? super T>> T max(Collection< T > elements)
    • static < T > min(Collection< T > elements,Comparator<? super T> c)
    • static < T > max(Collection< T > elements,Comparator<? super T> c)
      返回集合中最小的活最大的元素(为清楚起见,参数的边界被简化了)。
    • static < T > void copy(List< ? super T > to,List< T > from)
      将原列表中的所有元素复制到目标列表的相应位置上。目标列表的长度至少与原列表一样。
    • static < T > void fill(List<? super T> to,List< T > from)
      将列表中所有位置设置为相同的值。
    • static < T > boolean addAll(Collection<? super T> c,T… values) 5.0
      将所有的值添加到集合中。如果集合改变了,则返回true。
    • static < T > boolean replaceAll(List< T > l,T oldValue,T newValue) 1.4
      用newValue取代所有值为oldValue的元素。
    • static int indexOfSubList(List<?> l,List<?> s) 1.4
    • static int lastIndexOfSubList(List<?> l,List<?> s) 1.4
      返回l中第一个或最后一个等于s子列表的索引。如果l中不存在等于s的子列表,则返回-1。例如,l为[s,t,a,r],s为[t,a,r],另个方法都将返回索引1。
    • static void swap(List<?> l,int i,int j) 1.4
      交换给定偏移量的两个元素。
    • static void reverse(List<?> l)
      逆置列表中元素的顺序。例如,逆置列表[t,a,r]后将得到列表[r,a,t]。这个方法的时间复杂度为O(n),n为列表的长度。
    • static void rotate(List<?> l,int d) 1.4
      旋转列表中的元素,将索引i的条目移动到位置(i+d)%l.size()。例如,将列表[t,a,r]旋转移2个位置后得到[a,r,t]。这个方法的时间复杂度为O(n),n为列表的长度。
    • static int frequency(Collection<?> c,Object o) 5.0
      返回c中与对象o相同的元素个数。
    • boolean disjoint(Collection<?> cl,Collection<?> c2) 5.0
      如果两个几个没有相同的元素,则返回true。

13.4.4 编写自己的算法

  1. 如果编写自己的算法(实际上,是以集合作为参数的任何方法),应该尽可能地使用接口,而不要使用具体的实现。

13.5 遗留的集合

13.5.1 Hashtable类

  1. Hashtable类与HashMap类的作用一样,实际上,它们拥有相同的接口。与Vector类的方法一样。Hashtable的方法也是同步的。如果对同步性或与遗留代码的兼容性没有任何要求,就应该使用HashMap。

13.5.2 枚举

  1. 遗留集合使用Enumeration接口对元素序列进行遍历。Enumeration接口有两个方法,即hasMoreElements和nextEments。这两个方法与Iterator接口的hasNext方法next方法十分类似。

  2. 静态方法Collections.enumeration将产生一个枚举对象,枚举集合中的元素。

  3. java.util.Enumeration< E > 1.0

    • boolean hasMoreElements()
      如果还有更多的元素可以查看,则返回true。
    • E nextElement()
      返回被检测的下一个元素。如果hasMoreElements()返回false,则不要调用这个方法。
  4. java.util.Hashtable< K,V > 1.0

    • Enumeration< K > keys()
      返回一个遍历散列表中键的枚举对象。
    • Enumeration< V > elements()
      返回一个遍历散列表中元素的枚举对象。
  5. java.util.Vector< E > 1.0

    • Enumeration< E > elements()
      返回遍历向量中元素的枚举对象。

13.5.3 属性映射表

  1. 属性映射表(property map)是一个类型非常特殊的映射表结构。它有下面3个特性:

    • 键与值都是字符串。
    • 表可以保存到一个文件中,也可以从文件中加载。
    • 使用一个默认的辅助表。
      实现属性映射表的Java平台类称为Properties。
      属性映射表通常用于程序的特殊配置选项。
  2. java.util.Properties 1.0

    • Properties()
      创建一个空的属性映射表。
    • Properties(Properties defaults)
      创建一个带有一组默认值的空的属性映射表。
    • String getProperty(String key)
      获得属性的对应关系;返回与键对应的字符串。如果在映射表中不存在,返回默认表中与这个键对应的字符串。
    • String getProperty(String key, String defaultValue)
      获得在键没有找到时具有的默认值属性;它将返回与键对应的字符串,如果在映射表中不存在,就返回默认的字符串。
    • void load(InputStream in)
      从InputStream加载属性映射表。
    • void store(OutputStream out,String commentString)
      把属性映射表存储到OutputStream。

13.5.4 栈

  1. 从1.0版开始,标准库中就包含了Stack类,其中有push方法和pop方法。但是,Stack类扩展了Vector类,从理论角度看,Vector类并不太令人满意,它可以让栈使用不属于栈操作的insert和remove方法,即可以在任何方法进行插入或删除操作,而不仅仅是在栈顶。

  2. java.util.Stack< E > 1.0

    • E push(E item)
      将item压入栈并返回item。
    • E pop()
      弹出并返回栈顶的item。如果栈为空,请不要调用这个方法。
    • E peek()
      返回栈顶元素,但不弹出。如果栈为空,请不要调用这个方法。

13.5.5 位集

  1. Java平台的BieSet类用于存放一个位序列(它不是数学上的集,称为位向量或位数组更为合适)。如果需要高效地存放位序列(例如,标志)就可以使用位集。由于位集将位包装在字节里,所以,使用位集要比使用Boolean对象的ArrayList更加高效。

  2. BitSet类提供了一个便于读取、设置或清除各个位的接口。使用这个接口可以避免屏蔽和其他麻烦的位操作。如果将这些位存储在int或long变量中就必须进行这些繁琐的操作。

  3. java.util.BitSet 1.0

    • BitSet(int initialCapacity)
      创建一个位集。
    • int length()
      返回位集的“逻辑长度”,即1加上位集的最高设置位的索引。
    • boolean get(int bit)
      获得一个位。
    • void set(int bit)
      设置一个位。
    • void clear(int bit)
      清除一个位。
    • void add(BitSet set)
      这个位集与另一个位集进行逻辑“AND”。
    • void or(BitSet set)
      这个位集与另一个位集进行逻辑“OR”。
    • void xor(BitSet set)
      这个位集与灵羽阁位集进行逻辑“XOR”。
    • void andNot(BitSet set)
      清除这个位集中对应另一个位集中设置的所有位。
posted on 2018-11-08 20:39  zhangmiao14  阅读(227)  评论(0编辑  收藏  举报