数据结构详细解析

数据结构之表

1. 概述

在类库中,Java语言包含有一些普通的数据结构的实现,通常称为Collection API。

1.1 Collection接口

Collection API位于java.util包中,集合(Collection)的概念在Collection接口中得到了抽象,用来存储一组类型相同的对象。通过查阅API可以发现Collection接口中包括了许多重要的抽象方法,但是查阅API时我们发现Collection接口并不是集合的最顶级的接口:

public interface Collection<E> extends Iterable<E>

Iterable接口中有什么呢,只定义了一个抽象方法:

Iterator<T> iterator() 
    返回一个在一组 T 类型的元素上进行迭代的迭代器。 

为什么要继承Iterable接口呢,我们发现API中说到:

public interface Iterable<T>
    实现这个接口允许对象成为 "foreach" 语句的目标。

这时我们明白了,集合(Collection)不仅仅为了我们方便于存数据,也要方便我们取出数据,因此Collection接口继承了Iterable接口,这样以后不论是任何一个实现了集合的子类,也就同时实现了Iterable接口中的抽象方法,这样对于实现类来讲就可以进行遍历了。因为Collection接口已经继承了Iterable接口,因此我们只需要查阅Collection接口中的抽象方法即可:

boolean add(E e) 
      确保此 collection 包含指定的元素(可选操作)。 
boolean addAll(Collection<? extends E> c) 
      将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。 
void clear() 
      移除此 collection 中的所有元素(可选操作)。 
boolean contains(Object o) 
      如果此 collection 包含指定的元素,则返回 true。 
boolean containsAll(Collection<?> c) 
      如果此 collection 包含指定 collection 中的所有元素,则返回 true。 
boolean equals(Object o) 
      比较此 collection 与指定对象是否相等。 
int hashCode() 
      返回此 collection 的哈希码值。 
boolean isEmpty() 
      如果此 collection 不包含元素,则返回 true。 
Iterator<E> iterator() 
      返回在此 collection 的元素上进行迭代的迭代器。 
boolean remove(Object o) 
      从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。 
boolean removeAll(Collection<?> c) 
      移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。 
boolean retainAll(Collection<?> c) 
      仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。 
int size() 
      返回此 collection 中的元素数。 
Object[] toArray() 
      返回包含此 collection 中所有元素的数组。 
<T> T[] toArray(T[] a) 
      返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同。 

Collection接口之的许多方法所做的工作由它的英文名称可以看出,此处不做过的说明,值得我们注意的是一个方法那就是从Iterable接口继承过来的抽象方法。

1.2Iterator接口

 Iterator<E> iterator() 
      返回在此 collection 的元素上进行迭代的迭代器。 

实现Iterable接口的集合都必须提供一个称为iterator的方法,从上我们可看出,该方法返回一个Iteratoer对象,类型是该集合中所存储的元素类型,Iterator接口和Collection接口一样都在java.util包中,但是Iterator接口并不像Collection接口一样存在许多的方法,只包括三个抽象方法,该接口的思路是,通过调用iterator方法,每个集合都可以穿件并返回给客户一个实现Iterator接口的对象,并且将当前位置的概念在对象的内部存储下来,这样我们就可以通过调用返回的Iterator对象,对集合中的对象进行迭代遍历。

boolean hasNext() 
      如果仍有元素可以迭代,则返回 true。 
E next() 
      返回迭代的下一个元素。 
void remove() 
      从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 

通过反复调用next()方法,可以逐个访问集合中的元素。但是需要我们注意的是,如果到达了集合的末尾,next()方法将抛出一个NoSuchElementException。因此,我们在调用next()方法之前需要先调用hasNext()方法,如果迭代器对象还有多个访问的元素,该方法返回true。

Collection<String> s = ....;
Iterator<String> i = c.iterator();
while(i.hasNext()) {
    String elemennt = i.next();
    do something with element
}

迭代器取代了 Java Collections Framework 中的 Enumeration。迭代器与枚举有两点不同:

1.迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的collection移除元素。

2.方法名称得到了改进。

for each循环可以更加简练的表示同样的循环操作,对比上面的额一段代码如下:

for (String element : collection) {
    do something with element;
}

虽然应用迭代器可以迭代遍历集合中的元素,不过元素被访问顺序却取决于集合类型。不过对于迭代器来讲,最重要的是理解迭代器的原理:

Java集合类库中的迭代器与其它类库中的迭代器在概念上有着重要的区别。在传统的集合类库中,例如C++的标准模板库,迭代器是根据数组索引进行建模的。如果给定这样的一个迭代器,我们查看指定位置上的元素,就像是在查阅数组中的元素一样,只需要根据i++就可以进行查阅数组中的元素,但是对于迭代器来讲却是不同的操作,查找操作与位置更变是紧密的联系在一起的,查找一个元素的唯一方法是调用next()方法,而在执行查找操作的同事,迭代器的位置随之向前移动。因此,Java的迭代器更应该认为是位于两个元素之前,每当调用next()方法是,迭代器就越过下一个元素,并且返回刚才越过的那个元素的引用。
  • 这里有一个有用的类推。可以将Iter.next与InputStream.read看做是等效的。从数据流中读取一个自己,就会自动地消耗掉这个字节。下一次调用read将会小号并返回输入的下一个字节。用同样的方式,反复地调用next就可以读取集合中的所有元素。

了解了迭代器的原理我们就知道了,为什么next方法和remove方法的调用具有相互依赖性。如果调用remove()方法之前没有调用next()方法,那么将会抛出一个IllegalStateException异常。

不过细心的同学或许发现了remove方法在Iterator接口和Collection接口中都存在,那么两者使用哪个更好一些呢:

  • Iterator接口中的remove方法主要的优点在于,Collection接口的remove方法必须首先找到要被删除的元素,如果知道所要删除的项的准确位置,那么删除它的开销很可能小很多,而Iterator接口中的remove方法潜藏着更高的效率。
  • 第二点也是更为重要的一点,直接使用Iterator时,重要的是要记住一个基本法则:如果对正在被迭代的集合进行结构上的改变(例如使用add,remove或者clear方法)时,那么迭代器此时将不再合法(并且在其后使用该迭代器时将会有ConcurrentModificationException异常被抛出)。为了避免迭代器准备给出某一项作为下一项而该项此后或者被删除,或者也许一个新的项正好插在该项的前面这样一些讨厌的情况。情况意味着,只有在需要立即使用一个迭代器的时候,我们才应该获取迭代器。然而,如果迭代器调用了自己的remove方法,那么,这个迭代器仍是合法的。

1.3实现接口

如果实现Collection接口的每一个类都需要提供如此多的例行方法将是一个很烦人的事情,因此为了能够让实现者更容易的实现这个接口。Java类库提供了一个抽象类AbstractCollection,它将基础方法size和Iterator抽象化了,但是实现了接口中其余的方法。此时,一个具体的集合类可以被扩展成AbstractCollection类了。现在要有具体的集合类提供iterator方法,而contains方法已经由AbstractCollection超类提供了,不过如果子类有更加有效的方法实现contains方法,也可由子类提供。

对于类框架而言,这是一种好的设计。集合类的用户可以使用泛型接口中的一组更加丰富的方法,而实际的数据结构实现者并没有需要实现所有方法的负担。

2. List接口及实现类

对于Collection而言有很多的子接口,针对不同的集合添加了各种相应的方法。

2.1 List接口

public interface List<E> extends Collection<E>

API中对List接口详细的描述,如下:

  • 有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
  • 与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。难免有人希望通过在用户尝试插入重复元素时抛出运行时异常的方法来禁止重复的列表,但我们希望这种用法越少越好。
  • List 接口在 iterator、add、remove、equals 和 hashCode 方法的协定上加了一些其他约定,超过了 Collection 接口中指定的约定。为方便起见,这里也包括了其他继承方法的声明。
  • List 接口提供了 4 种对列表元素进行定位(索引)访问方法。列表(像 Java 数组一样)是基于 0 的。注意,这些操作可能在和某些实现(例如 LinkedList 类)的索引值成比例的时间内执行。因此,如果调用者不知道实现,那么在列表元素上迭代通常优于用索引遍历列表。
  • List 接口提供了特殊的迭代器,称为 ListIterator,除了允许 Iterator 接口提供的正常操作外,该迭代器还允许元素插入和替换,以及双向访问。还提供了一个方法来获取从列表中指定位置开始的列表迭代器。
  • List 接口提供了两种搜索指定对象的方法。从性能的观点来看,应该小心使用这些方法。在很多实现中,它们将执行高开销的线性搜索。
  • List 接口提供了两种在列表的任意位置高效插入和移除多个元素的方法。

注意:尽管列表允许把自身作为元素包含在内,但建议要特别小心:在这样的列表上,equals 和 hashCode 方法不再是定义良好的。

某些列表实现对列表可能包含的元素有限制。例如,某些实现禁止 null 元素,而某些实现则对元素的类型有限制。试图添加不合格的元素会抛出未经检查的异常,通常是 NullPointerException 或 ClassCastException。试图查询不合格的元素是否存在可能会抛出异常,也可能简单地返回 false;某些实现会采用前一种行为,而某些则采用后者。概括地说,试图对不合格元素执行操作时,如果完成该操作后不会导致在列表中插入不合格的元素,则该操作可能抛出一个异常,也可能成功,这取决于实现的选择。此接口的规范中将这样的异常标记为“可选”。

由API中给出的描述我们可以知道,List接口中的元素可以重复,并且可以按照插入顺序进行保留,可以通过索引进行查找元素,而且List接口中提供了一种特殊的迭代器ListIterator用于进行迭代遍历,并且List接口在 iterator、add、remove、equals 和 hashCode 方法的协定上加了一些其他约定,超过了Collection 接口中指定的约定。

boolean add(E e) 
      向列表的尾部添加指定的元素(可选操作)。 
void add(int index, E element) 
      在列表的指定位置插入指定元素(可选操作)。 
boolean addAll(Collection<? extends E> c) 
      添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序(可选操作)。 
boolean addAll(int index, Collection<? extends E> c) 
      将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)。 
void clear() 
      从列表中移除所有元素(可选操作)。 
boolean contains(Object o) 
      如果列表包含指定的元素,则返回 true。 
boolean containsAll(Collection<?> c) 
      如果列表包含指定 collection 的所有元素,则返回 true。 
boolean equals(Object o) 
      比较指定的对象与列表是否相等。 
E get(int index) 
      返回列表中指定位置的元素。 
int hashCode() 
      返回列表的哈希码值。 
int indexOf(Object o) 
      返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。 
boolean isEmpty() 
      如果列表不包含元素,则返回 true。 
Iterator<E> iterator() 
      返回按适当顺序在列表的元素上进行迭代的迭代器。 
int lastIndexOf(Object o) 
      返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。 
ListIterator<E> listIterator() 
      返回此列表元素的列表迭代器(按适当顺序)。 
ListIterator<E> listIterator(int index) 
      返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。 
E remove(int index) 
      移除列表中指定位置的元素(可选操作)。 
boolean remove(Object o) 
      从此列表中移除第一次出现的指定元素(如果存在)(可选操作)。 
boolean removeAll(Collection<?> c) 
      从列表中移除指定 collection 中包含的其所有元素(可选操作)。 
boolean retainAll(Collection<?> c) 
      仅在列表中保留指定 collection 中所包含的元素(可选操作)。 
E set(int index, E element) 
      用指定元素替换列表中指定位置的元素(可选操作)。 
int size() 
      返回列表中的元素数。 
List<E> subList(int fromIndex, int toIndex) 
      返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。 
Object[] toArray() 
      返回按适当顺序包含列表中的所有元素的数组(从第一个元素到最后一个元素)。 
<T> T[] toArray(T[] a) 
      返回按适当顺序(从第一个元素到最后一个元素)包含列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 

相对于Collection接口List接口增加了一些方法,方便于今后实现类的一些操作,仿照Collection的查阅方式我们发现有一个叫做AbstractList的抽象类,对它的描述如下:

public abstract class AbstractList<E> 
                extends AbstractCollection<E>
                implements List<E>

- 此类提供 List 接口的骨干实现,以最大限度地减少实现“随机访问”数据存储(如数组)支持的该接口所需的工作。对于连续的访问数据(如链表),应优先使用 AbstractSequentialList,而不是此类。
- 要实现不可修改的列表,编程人员只需扩展此类,并提供 get(int) 和 size() 方法的实现。
- 要实现可修改的列表,编程人员必须另外重写 set(int, E) 方法(否则将抛出UnsupportedOperationException)。如果列表为可变大小,则编程人员必须另外重写 add(int, E) 和 remove(int) 方法。
- 按照 Collection 接口规范中的建议,编程人员通常应该提供一个 void(无参数)和 collection 构造方法。
- 与其他抽象 collection 实现不同,编程人员不必 提供迭代器实现;迭代器和列表迭代器由此类在以下“随机访问”方法上实现:get(int)、set(int, E)、add(int, E) 和 remove(int)。
- 此类中每个非抽象方法的文档详细描述了其实现。如果要实现的 collection 允许更有效的实现,则可以重写所有这些方法。

跟AbstractCollection所描述的一样,AbstractList实现了部分的方法,

API中对AbstractList描述如下:

  • 此类提供 List 接口的骨干实现,以最大限度地减少实现“随机访问”数据存储(如数组)支持的该接口所需的工作。对于连续的访问数据(如链表),应优先使用 AbstractSequentialList,而不是此类。 要实现不可修改的列表,编程人员只需扩展此类,并提供 get(int) 和 size() 方法的实现。要实现可修改的列表,编程人员必须另外重写 set(int, E) 方法(否则将抛出 UnsupportedOperationException)。如果列表为可变大小,则编程人员必须另外重写 add(int, E) 和 remove(int) 方法。 按照 Collection 接口规范中的建议,编程人员通常应该提供一个 void(无参数)和 collection 构造方法。
    与其他抽象 collection 实现不同,编程人员不必 提供迭代器实现;迭代器和列表迭代器由此类在以下“随机访问”方法上实现:get(int)、set(int, E)、add(int, E) 和 remove(int)。 此类中每个非抽象方法的文档详细描述了其实现。如果要实现的 collection 允许更有效的实现,则可以重写所有这些方法。

下面终于来到了今天的重点AbstractList的实现类也是值得我们好好分析和学习的ArrayList类

2.2 ArrayList和Vector

public class ArrayList<E> extends AbstractList<E>
                          implements List<E>, 
                                     RandomAccess, 
                                     Cloneable, 
                                     Serializable

关于ArrayList我们可以发现它是继承自AbstactList抽象类,但是实现了List, RandomAccess, Cloneable, Serializable接口。

  • List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)
  • size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间 运行,也就是说,添加 n 个元素需要 O(n) 时间。其他所有操作都以线性时间运行(大体上讲)。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低。
  • 每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。
  • 在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
  • 注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:
    List list = Collections.synchronizedList(new ArrayList(...));
  • 此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
  • 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。

ArrayList相当于数据结构中表的数组实现,但是与传统的数组不同的是,ArrayList是一种大小可变的数组,运行时判断大小就行,而不同于以往普通的数组需要在编译器确定数组中元素的数目,以防止运行时出现数组下标越界。我们可以看到API中关于AbstractList的实现类还有一个Vector类:

  • ArrayList是最常用的List实现类,内部是通过数组进行实现的,它允许对元素进行快速随机的访问。数组的缺点是每个元素之间不可以有间隔,当数组大小不满足是需要增加存储能力,这是就需要开辟一块新的空间,然后将现在数组中的元素复制到新的存储空间中。当从ArrayList中间进行删除和插入新的数据元素是,由于ArrayList是由数组进行实现的,除了在数组的尾部进行删除和插入操作以外,其余情况都需要移动数据进行额外的时间开销。因此,它适合进行碎金的查找和遍历,但是不适合进行插入和删除操作,并且此类事不同的,因此线程不安全。
  • Vector和ArrayList一样,也是通过数组进行实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时读写而引起的不一致性,但实现同步需要很高的花费,因此访问它比访问ArrayList要慢。

因此ArrayList与Vector之间的主要区别如下:

1.ArrayList在内存不够的时候默认是扩展50% + 1,但是Vector是默认的扩展一倍。

2.Vector提供indexOf(obj,start)接口,ArrayList则没有。

3.Vector属于线程安全级别的,但是大多情况下不使用Vector,因为线程安全需要更大的系统开销。

ArrayList常见操作如下:

boolean add(E e) 
      将指定的元素添加到此列表的尾部。 
void add(int index, E element) 
      将指定的元素插入此列表中的指定位置。 
boolean addAll(Collection<? extends E> c) 
      按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。 
boolean addAll(int index, Collection<? extends E> c) 
      从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。 
void clear() 
      移除此列表中的所有元素。 
Object clone() 
      返回此 ArrayList 实例的浅表副本。 
boolean contains(Object o) 
      如果此列表中包含指定的元素,则返回 true。 
void ensureCapacity(int minCapacity) 
      如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。 
E get(int index) 
      返回此列表中指定位置上的元素。 
int indexOf(Object o) 
      返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。 
boolean isEmpty() 
      如果此列表中没有元素,则返回 true 
int lastIndexOf(Object o) 
      返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。 
E remove(int index) 
      移除此列表中指定位置上的元素。 
boolean remove(Object o) 
      移除此列表中首次出现的指定元素(如果存在)。 
protected  void removeRange(int fromIndex, int toIndex) 
      移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。 
E set(int index, E element) 
      用指定的元素替代此列表中指定位置上的元素。 
int size() 
      返回此列表中的元素数。 
Object[] toArray() 
      按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。 
<T> T[] toArray(T[] a) 
      按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 
void trimToSize() 
      将此 ArrayList 实例的容量调整为列表的当前大小。 

Vecto与之相似在此不再列出。在此我们发现ArrayList为什么没有实现Iterator接口,原因是ArrayList底层是使用数组,可以使用数组下表进行索引,不需要迭代器进行迭代遍历。现在已经找到ArrayList类了,但是与LinkedList却没有发现,如果细心地话,我们会发现在AbstractList抽象类之下还存在一个抽象类AbstractSequentiaList,关于此抽象类API介绍如下:

public abstract class AbstractSequentialList<E> extends AbstractList<E>

- 此类提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作。对于随机访问数据(如数组),应该优先使用 AbstractList,而不是先使用此类。
- 从某种意义上说,此类与在列表的列表迭代器上实现“随机访问”方法(get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index))的 AbstractList 类相对立,而不是其他关系。
- 要实现一个列表,程序员只需要扩展此类,并提供 listIterator 和 size 方法的实现即可。对于不可修改的列表,程序员只需要实现列表迭代器的 hasNext、next、hasPrevious、previous 和 index 方法即可。
- 对于可修改的列表,程序员应该再另外实现列表迭代器的 set 方法。对于可变大小的列表,程序员应该再另外实现列表迭代器的 remove 和 add 方法。

直接查阅LinkedList的API:

public class LinkedList<E> extends AbstractSequentialList<E>
                           implements List<E>, 
                           Deque<E>, 
                           Cloneable, 
                           Serializable

- List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
- 此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
- 所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。
- 注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:
List list = Collections.synchronizedList(new LinkedList(...));
- 此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

以下是LinkedList中的API

boolean add(E e) 
      将指定元素添加到此列表的结尾。 
void add(int index, E element) 
      在此列表中指定的位置插入指定的元素。 
boolean addAll(Collection<? extends E> c) 
      添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。 
boolean addAll(int index, Collection<? extends E> c) 
      将指定 collection 中的所有元素从指定位置开始插入此列表。 
void addFirst(E e) 
      将指定元素插入此列表的开头。 
void addLast(E e) 
      将指定元素添加到此列表的结尾。 
void clear() 
      从此列表中移除所有元素。 
Object clone() 
      返回此 LinkedList 的浅表副本。 
boolean contains(Object o) 
      如果此列表包含指定元素,则返回 true。 
Iterator<E> descendingIterator() 
      返回以逆向顺序在此双端队列的元素上进行迭代的迭代器。 
E element() 
      获取但不移除此列表的头(第一个元素)。 
E get(int index) 
      返回此列表中指定位置处的元素。 
E getFirst() 
      返回此列表的第一个元素。 
E getLast() 
      返回此列表的最后一个元素。 
int indexOf(Object o) 
      返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。 
int lastIndexOf(Object o) 
      返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。 
ListIterator<E> listIterator(int index) 
      返回此列表中的元素的列表迭代器(按适当顺序),从列表中指定位置开始。 
boolean offer(E e) 
      将指定元素添加到此列表的末尾(最后一个元素)。 
boolean offerFirst(E e) 
      在此列表的开头插入指定的元素。 
boolean offerLast(E e) 
      在此列表末尾插入指定的元素。 
E peek() 
      获取但不移除此列表的头(第一个元素)。 
E peekFirst() 
      获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。 
E peekLast() 
      获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。 
E poll() 
      获取并移除此列表的头(第一个元素) 
E pollFirst() 
      获取并移除此列表的第一个元素;如果此列表为空,则返回 null。 
E pollLast() 
      获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。 
E pop() 
      从此列表所表示的堆栈处弹出一个元素。 
void push(E e) 
      将元素推入此列表所表示的堆栈。 
E remove() 
      获取并移除此列表的头(第一个元素)。 
E remove(int index) 
      移除此列表中指定位置处的元素。 
boolean remove(Object o) 
      从此列表中移除首次出现的指定元素(如果存在)。 
E removeFirst() 
      移除并返回此列表的第一个元素。 
boolean removeFirstOccurrence(Object o) 
      从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。 
E removeLast() 
      移除并返回此列表的最后一个元素。 
boolean removeLastOccurrence(Object o) 
      从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。 
E set(int index, E element) 
      将此列表中指定位置的元素替换为指定的元素。 
int size() 
      返回此列表的元素数。 
Object[] toArray() 
      返回以适当顺序(从第一个元素到最后一个元素)包含此列表中所有元素的数组。 
<T> T[] toArray(T[] a) 
      返回以适当顺序(从第一个元素到最后一个元素)包含此列表中所有元素的数组;返回数组的运行时类型为指定数组的类型。 

需要我们注意的是:

  • LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢,另外,还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当做堆栈,队列和双向队列使用。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构:⼋⼤数据结构分析 数据结构分类 数据结构是指相互之间存在着⼀种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成 。 常⽤的数据结构有:数组,栈,链表,队列,树,图,堆,散列表等,如图所⽰: 线性表和⾮线性表 ⼀、线性表 常见的线性表有:数组、队列、栈、链表 线性表是最基本、最简单、也是最常⽤的⼀种数据结构。线性表(linear list)是数据结构的⼀种,线性表就是数据排列成⼀条先⼀样的结 构,每个线性表上的数据最多只有前和后两个⽅向。⼀个线性表是n个具有相同特性的数据元素的有限序列。 特点: 1. 集合中必存在唯⼀的⼀个"第⼀元素"。 2. 集合中必存在唯⼀的⼀个 "最后元素" 。 3. 除最后⼀个元素之外,均有唯⼀的后继(后件)。 4. 除第⼀个元素之外,均有唯⼀的前驱(前件)。 顺序表⾥⾯元素的地址是连续的;链表⾥⾯节点的地址不是连续的,是通过指针连起来的。 1.数组 数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进⾏访问,数组下标从0开始。例 如下⾯这段代码就是将数组的第⼀个元素赋值为 1。 int[] data = new int[100];data[0] = 1; 优点: 1、按照索引查询元素速度快 2、按照索引遍历数组⽅便 缺点: 1、数组的⼤⼩固定后就⽆法扩容了 2、数组只能存储⼀种类型的数据 3、添加,删除的操作慢,因为要移动其他的元素。 适⽤场景: 频繁查询,对存储空间要求不⼤,很少增加和删除的情况。 2.栈 栈是⼀种特殊的线性表,仅能在线性表的⼀端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈 顶放⼊元素的操作叫⼊栈,取出元素叫出栈。 栈的结构就像⼀个集装箱,越先放进去的东西越晚才能拿出来,所以,栈常应⽤于实现递归功能⽅⾯的场景,例如斐波那契数列。 3.队列 队列与栈⼀样,也是⼀种线性表,不同的是,队列可以在⼀端添加元素,在另⼀端取出元素,也就是:先进先出。从⼀端放⼊元素的操作称 为⼊队,取出元素为出队,⽰例图如下: 使⽤场景:因为队列先进先出的特点,在多线程阻塞队列管理中⾮常适 ⽤。 4.链表 链表是物理存储单元上⾮连续的、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,⼀个是存 储元素的数据域 (内存空间),另⼀个是指向下⼀个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表, 循环链表等。 链表的优点: 1. 链表是很常⽤的⼀种数据结构,不需要初始化容量,可以任意加减元素; 2. 添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快; 缺点: 1. 因为含有⼤量的指针域,占⽤空间较⼤; 2. 查找元素需要遍历链表来查找,⾮常耗时。 适⽤场景: 数据量较⼩,需要频繁增加,删除操作的场景 ⼆、⾮线性表 常见的⾮线性表有:树、图、堆 ⾮线性表中数据并不是简单的前后关系。 1.树 树是⼀种数据结构,它是由n(n>=1)个有限节点组成⼀个具有层次关系的集合。把它叫做 "树" 是因为它看起来像⼀棵倒挂的树,也就 是说它是根朝上,⽽叶朝下的。它具有以下的特点: 每个节点有零个或多个⼦节点; 没有⽗节点的节点称为根节点; 每⼀个⾮根节点有且只有⼀个⽗节点; 除了根节点外,每个⼦节点可以分为多个不相交的⼦树; 在⽇常的应⽤中,我们讨论和⽤的更多的是树的其中⼀种结构,就是⼆叉树。 ⼆叉树是树的特殊⼀种,具有如下特点: 每个结点最多有两颗⼦树,结点的度最⼤为2。 左⼦树和右⼦树是有顺序的,次序不能颠倒。 即使某结点只有⼀个⼦树,也要区分左右⼦树。 ⼆叉树是⼀种⽐较有⽤的折中⽅案,它添加,删除元素都很快,并且在查找⽅⾯也有很多的算法优化,所以,⼆叉树既有链表的好处,也有 数组的好处,是两者的优化⽅案,在处理⼤批量的动态数据⽅⾯⾮常有⽤。 2.散列表 散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进⾏访问的数据结构,通过key和value来映射到集合中的⼀个位置,这样就可 以很快找到集合中的对应元素。 记录的存储位置=f(key) 这⾥的对应关系 f 成为散列函数,⼜称为哈希 (hash函数),⽽散列表就是把Key通过哈希函数转换成⼀个整型数字,然后就将该数字对数 组长度进⾏取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间⾥,这块连续存储空间称为散列表或哈希表 (Hash table),这种存储空间可以充分利⽤数组的查找优势来查找元素,所以查找的速度很快。 散列数据结构的性能取决于以下三个因素: 哈希函数 哈希表的⼤⼩ 碰撞处理⽅法 哈希表在应⽤中也是⽐较常见的,就如Java中有些集合类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值