Java核心技术 第十三章 集合接口

集合接口

队列有两种实现方式,一种是使用循环数组;另一种使用链表。

Queue<Customer> expresslane = new CircularArrayQueue<>(100) ;

expresslane.add(new Customer(“Harry));

Queue<Customer> expresslane = new LinkedListQueue<>(100) ;

expresslane.add(new Customer(“Harry));

循环数组比链表更高效,所以一般选用循环数组。循环数组是一个有界集合,即容量有限。如果程序中要收集的对象数量没有上限,就最好使用链表来实现。

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

Java类库中,集合类基本接口是Collection接口。这个接口有两个基本方法:

public interface Collection<E>

{

  boolean add(E element) ;

  Iterator<E> iterator() ;

  ...

}

add:添加元素,集合发生变化,返回true,集合没有变化,返回false

iterator方法用于返回一个实现了Iterator接口的对象。这个迭代器对象一次访问集合中的元素。

public interface Iterator<E>

{

  E next() ;

  boolean hasNext() ;

  void remove() ;

}

调用next()到达集合末尾,next()方法抛出NoSuchElementException异常。

Collection<String> c = ... ;

Iterator<String> iter = c.iterator ;

while(iter.hasNext())

{

  String element = iter.next() ;

  do something with element

}

Java SE 5.0起,可以用更简便的方式:

for(String element : c)

{

  do something with element

}

编译器将其翻译为带有迭代器的循环。

for each”循环可以与任何实现了Iterable接口的对象一起工作,这个接口只包含一种方法:

public interface Iterable<E>

{

  Iterator<E> iterator() ;

}

Collection接口扩展了Iterable接口。

ArrayList按索引从0开始访问,HashSet按某种随机次序出现。

删除元素

Iterator接口的remove方法将会删除上次调用next()方法时返回的元素。

Iterator<String> it = c.iterator() ;

it.next() ;

it.remove() ;

必须先调用next越过元素,才能调用remove删除元素

泛型使用方法

int size()

boolean isEmpty()

boolean contains(Object obj)

boolean containsAll(Collection<?> c)

boolean equals(Object other)

boolean addAll(Collection<? extends E> from)

boolean remove(Object obj)

boolean removeAll(Collection<?> c)

void clear()

boolean retainAll(Collection<?> c)

Object[] toArray()

<T> T[] toArray(T[] arrayToFill)

Java类库中提供了一个类AbstractCollection,它将基础方法sizeiterator抽象化了,但是在此提供了例行方法。子类可以直接使用或重写提供的理性方法,如contains方法。

具体集合

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

链表

Java中所有链表都是双向链接的。

链表与泛型集合之间有一个重要的区别,链表是一个有序集合。

LinkedList.add方法将对象添加到链表的尾部。

Iterator接口中没有add方法。集合类库提供了子接口ListIterator,其中包含add方法。

interface ListIterator<E> extends Iterator<E>

{

  void add(E element) ;

  ...

}

Collection.add不同,这个方法不返回boolean类型的值。它假定添加操作总会改变链表。另外,ListIterator接口有两个方法,可以用来反向遍历链表。

E previous()

boolean hasPrevious()

next一样,previous方法返回越过的对象。

LinkedList类的listIterator方法返回一个实现了ListIterator接口的迭代器对象。

ListIterator<String> iter = staff.listIterator() ;

iter.next() ;

iter.add(“Juliet”) ;

添加到迭代器前面,刚生成的iter调用add添加到表头。

如果某个集合修改集合时,另一个迭代器对其进行遍历,一定会出现混乱的情况。(会抛出异常)

为避免混乱,应遵循下述规则:可以根据需要给容器附加许多迭代器,但这些迭代器只能读取列表。另外,再单独附加一个既能读,又能写的迭代器。

链表不支持快速地随机访问,因此get(int i)方法比较低效。索引从0开始。

列表元素较少,就完全可以使用ArrayListLinkedList可以尽可能的减少在列表中间插入或删除元素所付出的代价。

int nextIndex()

返回下一次调用next返回的元素索引

int previousIndex()

数组列表

ArrayList也实现了List接口,ArrayList封装了一个动态再分配的对象数组。

ArrayList封装了一个动态再分配的对象数组,可以使用getset进行随机访问。

散列表

可以快速找到元素,缺点是无法控制元素出现的次序。

散列表为每一个对象计算一个整数,称为散列码。

散列码是由对象的实例域产生的一个整数。

如果自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)trueab必须具有相同的散列码。

Java中,散列表用链表数组实现。每个列表被称为桶。要想查找表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引。

没有被占,直接插入,被占,发生散列冲突,需要用新对象与桶中的所有对象进行比较,查看这个对象是否已经存在。

如果想更多的控制性能,需要制定一个初始的桶数。

如果,运行过程中发现桶数过低,需要重新散列。装填因子决定何时对散列表进行再散列。

Java集合类库提供了一个HashSet类,它实现了基于散列表的集。可以用add方法添加元素。contains方法已经被重新定义,用来快速查看某个元素是否已经出现在集中。它只查找桶中的元素。

树集

TreeSet类与散列集十分类似,不过,它比散列集有所改进。树集是一个有序集合。可以以任意顺序插入到集合中。在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。

TreeSet的排序是用树结构完成的,目前用的是红黑树。

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

对象的比较

在默认情况下,树集假定插入的元素实现了Comparable接口。这个接口定义了一个方法:

public interface Comparable<T>

{

  int compareTo(T other) ;

}

如果ab相等,调用a.compareTo(b)返回0,如果排序后a位于b之前,则返回负值,如果a位于b之后,则返回正值。具体返回什么值不重要,关键是和0的关系。

如果要插入自定义对象,就必须通过实现Comparable接口自定义排列顺序。在Object中没有compareTo的默认实现。

使用Comparable接口有一定局限性,对于给定的类,只能实现接口一次,因此只能有一种排序方式。

通过将Comparator对象传递给TreeSet构造器来告诉树集使用不同的比较方法。里面有带有两个显示参数的compare方法。

public interface Comparable

{

  int compare(T a, T b) ;

}

class ItemComparator implements Comparator

{

  public int compare(Item a , Item b)

  {

    String descrA = a.getDescription() ;

    String descrB = b.getDescription() ;

    return descrA.compareTo(descrB) ;

  }

}

ItemComparator comp = new ItemComparator() ;

SortedSet<Item> sortByDescription = new TreeSet(comp) ;

可以用匿名内部类:

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) ;

  } 

}) ;

树的排序必须是全序,也就是说任意两个元素必须是可比的。

队列与双端队列

队列可以在队尾添加一个元素,在队头删除一个。

有两个端头的队列是双端队列,可以在头部和尾部同时添加或删除元素。

不支持在队列中间添加元素。

Java SE 6 引入Deque接口,并由ArrayDequeLinkedList类实现。

优先级队列

优先级队列中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。无论何时调用remove方法,总会得到当前优先级队列中的最小元素。然而,优先级并没有对所有元素进行排序。

优先级队列使用堆结构,堆是一个可以自我调整的二叉树,对树执行添加和删除操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。

TreeSet一样,既可以保存实现了Comparable接口的对象,也可以保存在构造器中提供比较器的对象。

映射表

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

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

与集一样,散列稍微快点,如果不需要按照排序顺序访问,就最好选择散列。

Map<String, Employee> staff = new HashMao<>() ;

向映射表中添加对象,必须提供一个键。

如果在映射表没有与给定键对应的信息,get将返回null

键必须是唯一的。如果对同一个键两次调用put方法,第二个值就会取代第一个值。实际上,put将返回这个键参数存储的上一个值。

remove方法用于删除给定键对应的元素。

映射表本身不是集合。然而可以获得映射表的视图,这是一组实现了Collection 接口的对象,或者它的子接口的视图。

3个视图,分别是:键集、值集合(不是集)和键/值对集。

下列方法将返回这3个视图:

Set<K>  keySet()

Collection<K>  values()

Set<Map.Entry<K, V>>  entrySet()

keySet不是HashSet,也不是TreeSet,而是实现了Set接口的某个其他类的对象。Set接口扩展了Collection接口。因此,可以与使用任何集合一样使用keySet

      for (Map.Entry<String, Employee> entry : staff.entrySet())

      {

         String key = entry.getKey();

         Employee value = entry.getValue();

         System.out.println("key=" + key + ", value=" + value);

      }

调用remove方法,实际从映射表中删除了键以及对应的值。但是不能将元素添加到键集的视图中。条目集也有这样的限制。所以不能调用add方法。

专用集与映射表类

1.弱散列映射表

WeakHashMap

使垃圾回收机制回收不在使用的键对应的键值对。

2.链接散列集和链接映射表

Java SE 1.4 增加了LinkedHashSetLinkedHashMap,用来记住插入元素的顺序。

条目会被并到双向链表中。

3.标识散列映射表

IdentityHashMap

键的散列值不是用hashCode算的,而是用System.IdentityHashCode算的。

对对象进行比较时,使用==,而不是equals方法。

也就是说,不同的键对象,即使内容相同,也被视为是不同的对象。

集合框架

Java集合类库构成了集合类的框架。它为集合的实现定义了大量的接口和抽象类。

Java SE 4 引入了一个标记接口RandomAccess。这个接口没有方法,用于检测是否支持高效的随机访问。

ArrayListVector 实现了RandomAccess接口

视图与包装器

通过使用视图可以获得其他的实现了集合接口和映射表接口的对象。

视图并非创建新集。

keySet方法返回一个实现Set接口的类对象,这个类的方法对原映射表进行操作。这种集合称为视图。

1.轻量级集包装器

ArrayList类的静态方法asList,返回一个包装了普通Java数组的List包装器。

Card[] cardDeck = new Card[52] ;

...

List<Card> cardList = Arrays.asList(CardDeck) ;

返回的对象不是ArrayList,而是一个视图。带有访问底层数组的getset方法。改变数组大小的所有方法都会抛出异常。

Java SE 5.0 asList实现了一个具有可变参数的方法。

List<String> names = Arrays.asList(“Amy”, “Bob”, “Carl”) ;

返回一个实现了List接口的不可修改对象。

Collections类包含很多实用方法,这些方法的参数和返回值都是集合。

2.子范围

可以为很多集合建立子范围视图。

List group2 = staff.subList(10, 20) ;

可以将任何操作应用于子范围,并且能够自动地范影整个列表的情况。

group2.clear() ;

3.不可修改视图

Collections还有几个方法,用于产生集合的不可修改视图。这些视图对现有集合增加了一个运行时的检查。如果发现视图对集合进行修改,就抛出一个异常,同时这个集合将保持未修改的状态。

List<String> staff = new LinkedList<>() ;

...

lookAt(Collections.unmodifiableList(staff)) ;

Collections.unmodifiableList返回的是一个实现了List接口的类对象,只能访问List中的方法。

4.同步视图

类库的设计者使用视图机制来确保常规集合的线程安全,而不是实现线程安全的集合类。

Map<String, Employee> map = Collections.sysnchronizedMap(new HashMap<String, Employee>) ;

现在就可以多线程访问map对象了。

5.检查视图

ArrayList<String> strings = new ArrayList<>() ;

ArrayList rawList = strings ; //only warning

rawList.add(new Date()) ;

使用检查视图

List<String> safeStrings = Collections.checkedList(Strings, String.class) ;

ArrayList rawList = safeStrings ;

rawList.add(new date()) ; //抛出异常

被检查视图受限于虚拟机可以运行的运行时检查。

6.关于受限操作的说明

通常,视图有一些局限性,即可能只可以读、无法改变大小、只支持删除而不支持插入,这些与映射表的键视图情况相同。如果视图进行不恰当的操作,受限制的视图就会抛出一个UnSupportedOperationException

批操作

两个集的共有元素

Set<String> result = new HashSet<>() ;

result.retainAll(b) ;

批操作应用于视图

Map<String , Employee> staffMap = ... ;

Set<String> terminatedIDs = ... ;

stafMap.keySet().removeAll(terminatedIds) ;

批操作限制于子列表和子集

relocated.addAll(staff.subList(0,10)) ;

staff.subList(0, 10).clear() ;

集合与数组之间的转换

数组转换为集合

String[] values = ... ;

HashSet<String> staff = new HashSet<>(Arrays.asList(values)) ;

集合转换为数组

Object[] values = staff.toArray() ;

String[] values = (String[]) staff.toArray() ; //ERROR

toArray方法返回的数组是一个Object[]数组,无法改变类型。

要使用另外的方式:

String[] values = staff.toArray(new String[0]) ;

String[] values = staff.toArray(new String[sraff.size()]) ;

算法

泛型集合有一个很大的优点,即算法只需要实现一次。

public static  <T extends Comparable> T max(Collection<T> c)

排序与混排

List<String> staff = new LinkedList<>() ;

fill collection

Collections.sort(staff) ;

使用比较器

Collections.sort(items, itemComparator) ;

逆序排列(compareTo结果)

Collections.sort(staff, Collections.reverseOrder())

同样

Collections.sort(staff, Collections.reverseOrder(itemComparator))

Java排序机制:

将所有元素转入一个数组,并使用一种归并排序的变体对数组继续排序,然后将排序后的序列复制回列表。

归并排序比快排稍慢,但是稳定。

集合不需要实现所有可选方法,所以接受集合参数的方法必须描述什么时候可以安全的将集合传递给算法。

传递给排序算法,列表必须是可修改的,但不必是可以改变的。

1) 如果列表支持set方法,则是可修改的

2) 如果列表支持addremove方法,则是可改变大小的

Collections类有一个算法shuffle,功能是随机混排列表中的元素。

二分查找

数组有序,可以使用二分查找查找元素。

CollectionsbinarySearch方法实现了这个算法。(针对集合,包括数组)

i = Collections.binarySearch(c, element) ; //实现Comparable接口

i = Collections.binarySearch(c, element, comparator) ;

i>=0,则表示匹配的对象的索引。i<0,表示没有匹配的对象。可以利用返回值计算element应该插入的位置:

insertPoint = -i - 1 ;

只有采用随机访问,二分查找才有意义,如果提供一个列表,它将自动变为线性查找。

简单算法

Collections中包含了几个简单且很有用的算法,见P611

编写自己的算法

如果编写自己的算法,应尽量使用接口,而不是具体的实现。

遗留的集合

HashTable

HashMap作用一致,HashTable的方法是同步的。

属性映射表

property map

l 键与值都是字符串

l 表可以保存到一个文件中,也可以从文件中加载

l 使用一个默认的辅助表

实现属性映射表的Java平台类称为Properties

由于Stack类扩展为Vector类,所以除了pushpop外,拥有不应该属于栈的insertremove方法,即可以在任何地方进行插入或删除操作,而不仅仅在栈顶。

位集

JavaBitSet类用于存放一个位序列。由于位集将位包装在字节里,所以,使用位集要比使用Boolean对象的ArrayList更加高效。

BitSet类提供了一个便于读取、设置或清除各个位的接口。

原文地址:https://www.cnblogs.com/chwy/p/5712497.html