Java核心技术 卷1-总结-12

  • 具体的集合
  • 链表
  • 数组列表


具体的集合

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

集合类型

描述

ArrayList

一种可以动态增长和缩减的索引序列

LinkedList

一种可以在任何位置进行高效地插入和删除操作的有序序列

ArrayDeque

一种用循环数组实现的双端队列

HashSet

一种没有重复元素的无序集合

TreeSet

一种有序集

EnumSet

一种包含枚举类型值的集

LinkedHashSet

一种可以记住元素插入次序的集

PriorityQueue

一种允许高效删除最小元素的集合

HashMap

一种存储键/值关联的数据结构

TreeMap

一种键值有序排列的映射表

EnumMap

一种键值属于枚举类型的映射表

LinkedHashMap

一种可以记住键/值项添加次序的映射表

WeakHashMap

一种其值无用武之地后可以被垃圾回收器回收的映射表

IdentityHashMap

一种用==而不是用equals比较键值的映射表

java 核心技术第一卷目录_java

链表

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

java 核心技术第一卷目录_java_02

链表(linked list)解决了这个问题。 数组在连续的存储位置上存放对象引用,链表却将每个对象存放在独立的结点中。每个结点还存放着序列中下一个结点的引用。在Java中,所有链表实际上都是双向链接的—即每个结点还存放着指向前驱结点的引用。

java 核心技术第一卷目录_java_03


从链表中间删除一个元素只需要更新被删除元素附近的链接。

java 核心技术第一卷目录_java 核心技术第一卷目录_04

Java集合类库提供了一个类LinkedList,在下面的代码示例中,先添加3个元素,然后再将第2个元素删除:

List<String> staff = new LinkedList<>();//LinkedList implements List 
staff.add("Amy");
staff.add("Bob");
staff.add("Carl");
Iterator iter = staff.iterator();
String first = iter.next();//visit first element 
String second = iter.next();//visit second element 
iter.remove();// remove last visited element

链表与泛型集合之间有一个重要的区别。链表是一个有序集合(ordered collection)。 LinkedList.add方法将对象添加到链表的尾部。但是,常常需要将元素添加到链表的中间。由于迭代器是描述集合中位置的,所以这种依赖于位置的add方法将由迭代器负责。只有对自然有序的集合使用迭代器添加元素才有实际意义。例如,集合(set)类型中的元素完全无序。因此,在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();

Add方法在迭代器位置之前添加一个新对象。例如,下面的代码将越过链表中的第一个元素,并在第二个元素之前添加"Juliet"

List<String> staff = new LinkedList<>();
staff.add("Amy");
staff.add("Bob");
staff.add("Carl");
ListIterator<String> iter = staff.listIterator();
iter.next();// skip past first element 
iter.add("Juliet");

如果多次调用add方法,将按照提供的次序把元素添加到链表中。它们被依次添加到迭代器当前位置之前。

当用一个刚刚由Iterator方法返回,并且指向链表表头的迭代器调用add操作时,新添加的元素将变成列表的新表头。当迭代器越过链表的最后一个元素时(即hasNext返回false),添加的元素将变成列表的新表尾。如果链表有n个元素,有n+1个位置可以添加新元素。

LinkedListset方法用一个新元素取代调用nextprevious方法返回的上一个元素。例如,下面的代码将用一个新值取代链表的第一个元素:

ListIterator<String> iter = list.listIterator();
String oldValue = iter.next();// returns first element 
iter.set(newValue);// sets first element to newValue

如果在某个迭代器修改集合时,另一个迭代器对其进行遍历,一定会出现混乱的状况。例如,一个迭代器指向另一个迭代器刚刚删除的元素前面,现在这个迭代器就是无效的,并且不应该再使用。 链表迭代器的设计使它能够检测到这种修改。如果迭代器发现它的集合被另一个迭代器修改了,或是被该集合自身的方法修改了,就会抛出一个ConcurrentModificationException异常。例如下面这段代码:

List<String> list = . . . ;
ListIterator<String> iter1= list.listIterator();
ListIterator<String> iter2= list.listIterator();
iter1.next();
iter1.remove();
iter2.next();// throws ConcurrentModi ficationException

iter2检测出这个链表被从外部修改了,所以对iter2.next的调用抛出了一个ConcurrentModificationException异常。

为了避免发生并发修改的异常,需要遵循下述规则:可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的迭代器。

有一种简单的方法可以检测到并发修改的问题。集合可以跟踪改写操作(诸如添加或删除元素)的次数。每个迭代器都维护一个独立的计数值。在每个迭代器方法的开始处检查自己改写操作的计数值是否与集合的改写操作计数值一致。如果不一致,抛出一个ConcurrentModificationException异常。

Collection接口中声明了许多用于对链表操作的有用方法。其中大部分方法都是在LinkedList类的超类AbstractCollection中实现的。例如,toString方法调用了所有元素的toString,并产生了一个很长的格式为[A,B,C]的字符串。可以使用contains方法检测某个元素是否出现在链表中。例如,如果链表中包含一个等于“Harry”的字符串,调用staff.contains(“Harry”)后将会返回true

链表不支持快速地随机访问。如果要查看链表中第n个元素,就必须从头开始,越过n-1个元素。没有捷径可走。 鉴于这个原因,在程序需要采用整数索引访问元素时,通常不选用链表。尽管如此,LinkedList类还是提供了一个用来访问某个特定元素的get方法:

LinkedList<String> list = ...;
String obj = list.get(n);

注意:get方法做了微小的优化:如果索引大于size()/2就从列表尾端开始搜索元素。

列表迭代器接口还有一个方法,可以告之当前位置的索引。 实际上,由于Java迭代器指向两个元素之间的位置,所以可以同时产生两个索引:nextIndex方法返回下一次调用next方法时返回元素的整数索引;previousIndex方法返回下一次调用previous方法时返回元素的整数索引。这个索引只比 nextlndex返回的索引值小1。这两个方法的效率非常高,这是因为迭代器保持着当前位置的计数值。

使用链表的唯一理由是尽可能地减少在列表中间插入或删除元素所付出的代价。如果列表只有少数几个元素,就完全可以使用ArrayList 如果需要对集合进行随机访问,就使用数组或ArrayList,而不要使用链表。

数组列表

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

在需要动态数组时,可能会使用Vector类。因为Vector类的所有方法都是同步的。但是,如果由一个线程访问Vector,代码要在同步操作上耗费大量的时间。ArrayList方法不是同步的,因此,在不需要同步时使用ArrayList,而不要使用Vector