您的位置:首页 > 编程语言 > Java开发

Java集合---List

2015-08-15 21:36 477 查看

List接口

List接口,成为有序的Collection也就是序列。该接口可以对列表中的每一个元素的插入位置进行精确的控制,同时用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。例如List接口定义如下方法,实现元素指定位置的插入和访问。

add(int index, E element)

get(int index)

AbstractList 抽象类

List 接口的骨干实现,以最大限度地减少实现“随机访问”数据存储(如数组)支持的该接口所需的工作。

List具体实现类

Vector

类声明如下

public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable


构造方法如下

Constructor 1
public Vector() {
this(10);
}


Constructor 2
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}


Constructor 3
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}


Constructor 4
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}


Constructor 1:创建一个容量为 10,增量为 0 的 Vector 容器,容量和增量的概念接下来会介绍。

Constructor 2:创建一个容量为形参 initialCapacity,增量为 0 的 Vector 容器。

Constructor 3:创建一个容量为形参 initialCapacity,增量为 capacityIncrement的Vector 容器。

Constructor 4:创建一个容量为集合 c 长度,增量为 0 的 Vector 容器。

上面四个构造方法中使用到了Vector定义的三个成员变量:

1 protected Object[] elementData;


Object类型的数组,Vector底层是数组来实现的,所有的元素都在 elementData 数组中存放

2 protected int elementCount;


elementCount 属性用来记录Vector容器中的元素个数,也就是数组 elementData 中元素的个数,而不是 elementData 数组的长度,下面是Vector size()方法的定义,直接返回elementCount 的值。

public synchronized int size() {
return elementCount;
}


3 protected int capacityIncrement;


capacityIncrement 属性记录容器的增量,即容器自动扩展的时候一次性扩展的大小。

接下来看一下 Vector 类元素增加、删除、访问和迭代实现的具体细节

1 增加元素

public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}


1 synchronized:Vector在多线程环境下提供了同步机制。

2 add 方法并没有进行null判断,允许null元素。

modCount++:modCount 属性在 AbstractList 中定义

protected transient int modCount = 0;


modCount 是用来记录容器被结构性修改的次数,所谓结构性修改是对容器进行了元素增加和删除等操作,下面是Vector修改和获取元素的代码都没有进行 modCount++ 的操作。

public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return (E) elementData[index];
}
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
Object oldValue = elementData[index];
elementData[index] = element;
return (E)oldValue;
}


那设置这个属性的作用是什么呢?是为了在多线程环境下以一种叫做“快速失败”的方式通知程序迭代遍历容器失败。下面是调用 Vector 容器的 iterator()方法返回的迭代器对象的具体类。

private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}


此类通过 expectedModCount 保存了一个 Vector 结构化修改的次数,而在迭代过程中会不断的调用 checkForComodification 方法,一旦发现容器被结构化修改就会抛出ConcurrentModificationException 异常。可以联想,当线程 t1 正在迭代遍历 Vector 容器时,线程 t2 结构性修改了容器,那么线程 t1 将立刻抛出异常,这也是所谓的“快速失败”。

接着上面讲 Vector add 方法的第二行代码:

ensureCapacityHelper(elementCount + 1);


在往容器中添加元素之间需要确保容器有足够的空间来存储元素,即 elementData 数组还有空间存储元素,ensureCapacityHelper 方法代码如下:

private void ensureCapacityHelper(int minCapacity) {
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object[] oldData = elementData;
int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement)
: (oldCapacity * 2);
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
}


elementCount + 1 代表容器存放下当前元素需要的最小空间,如果最小空间尚且大于数组的长度,那么必须进行动态扩容,扩容的方法如上所示。

接着 Vector add 方法的第三行代码

elementData[elementCount++] = e;


这个代码完成了元素的添加。

2 删除元素

public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
Object oldValue = elementData[index];
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--elementCount] = null;
return (E)oldValue;
}


此处关键的操作,是 System.arraycopy 方法,此方法是一个 native 方法,主要完成数组元素的移动,即从 index+1 开始,每个元素往前移动一位。

elementData[--elementCount] = null;


因为index后面的元素都往前移动了一位,所以需要将最后一位制空,elementCount 属性值减一。

3 访问元素

public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return (E)elementData[index];
}


代码简单易懂,此处不再介绍。

4 迭代元素

Vector 没有重写 iterator 方法,直接继承 AbstractList 类的iterator方法,定义如下:

public Iterator<E> iterator() {
return new Itr();
}


内部类 Itr 定义如下所示

private class Itr implements Iterator<E> {

int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}

public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();

try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}


Iterator remove 操作不会引起 当前线程迭代遍历失败,因为删除元素之后重新设置了 expectedModCount 的值。

expectedModCount = modCount;


next 和 hasNext 方法简单易懂。

3 Vector迭代遍历,按照加入元素的顺序输出所有元素。

4 Vector加入元素时并没有进行排序。

List<Integer> vector = new Vector<Integer>();
vector.add(5);
vector.add(4);
vector.add(3);
vector.add(2);
vector.add(1);
for(Integer i: vector){
System.out.println(i);
}


所以代码输出结果为:

5

4

3

2

1

Vector#总结

提供同步机制

底层数组实现

可以加入null元素

元素加入时没有进行排序

元素可以重复加入

Stack

类声明如下

public class Stack<E> extends Vector<E>


Stack通过五个操作对Vector进行扩展,允许将向量视为堆栈

empty() 测试堆栈是否为空

peek() 查看堆栈顶部的对象,但不从堆栈中移除它

pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象

push(E item) 把项压入堆栈顶部

search(Object o) 返回对象在堆栈中的位置,以 1 为基数

Stack构造函数

public Stack() {
}


只有一个空构造函数

因为 Stack 继承自 Vector ,所以兼备Vector所有的特性,下面我们看一下 Stack 扩展的 5 中基本操作。

1 push(E item) 将元素推入栈顶

public E push(E item) {
addElement(item);
return item;
}
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}


push 方法调用 Vector 中定义的方法将当前元素添加到Statck容器中数组的末尾,也就是栈结构对应的栈顶。

2 peek() 查看堆栈顶部的对象,但不从堆栈中移除它

public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}

public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return (E) elementData[index];
}


peek 方法调用 Vector 中定义的 elementAt 方法返Statck容器中数组最后一个元素,即栈顶元素。

3 pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象

public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}


pop 方法调用 Vector 方法中定义的 removeElementAt 方法删除并返回Statck容器中数组的最后一个元素,即栈顶元素。

4 empty() 测试堆栈是否为空

public boolean empty() {
return size() == 0;
}


5 search(Object o) 返回对象在堆栈中的位置,以 1 为基数

public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}


从栈顶处开始检索,即栈顶元素的返回 1,次栈顶元素返回 2 。

Stack<Integer> stack = new
10764
Stack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
System.out.println(stack.search(5));
System.out.println(stack.search(1));


1

5

Stack总结

继承自 Vector

提供同步机制

扩展了 push、pop、 empty、peek、search 五个方法

ArrayList

类声明如下

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


构造方法

Constructor 1
public ArrayList() {
this(10);
}


Constructor 2
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "
+ initialCapacity);
this.elementData = new Object[initialCapacity];
}


Constructor 3
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}


构造方法与 Vector 差不多,这里不再赘述。

ArrayList 成员属性

1 private transient Object[] elementData;


Object 类型的数组,ArrayList 底层是数组来实现的,所有的元素都在 elementData 数组中存放.

2 private int size;


size 属性用来记录 ArrayList 容器中的元素个数,也就是数组 elementData 中元素的个数,而不是 elementData 数组的长度。

接下来看一下 ArrayList 类元素增加、删除、访问和迭代实现的具体细节

1 增加元素

public boolean add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e;
return true;
}


这跟 Vector 的 add 方法基本上一致,添加元素之前要确保空间够用,然后将元素加入容器,size++。

1 no synchronized:ArrayList 不是多线程安全的。

2 add 方法并没有进行null判断,允许null元素。

2 删除元素

public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}


跟 Vector 中定义的删除方法基本一致,首先是越界检查,然后数组元素移位。

3 访问元素

public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}


代码简单明了,不再赘述。

4 迭代元素

Arraylist 没有重写 iterator 方法,同样使用 AbstractList 定义的方法返回容器迭代器,跟上述的 Vector 完全一样。

ArrayList 总结

不提供同步机制

底层数组实现

可以加入null元素

元素加入时没有进行排序

元素可以重复加入

LinkedList

LinkedList 与 ArrayList 一样实现 List 接口,只是 ArrayList 是 List接 口的大小可变数组的实现,LinkedList 是 List 接口链表的实现。基于链表实现的方式使得LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList逊色些。

AbstractSequentialList提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现List接口的复杂度。

除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

与栈结构相关的方法

public void push(E e);
public E pop();
public E peek();


LinkedList 实现了 Deque 接口,Deque 接口继承自 Queue 接口,LinkedList中与单端对列相关的此方法

boolean add(E e);
E poll();
E peek();


LinkedList与双端队列相关的方法

双端队列:即在队列两端都可以 insert 和 remove:insertLast、insertFirst,removeLast、removeFirst 含有栈和队列的功能,如去掉 insertLast、removeLast,那就跟栈一样了;如去掉insertLast、removeFirst,那就跟队列一样了。

public void addFirst(E e) ;
public void addLast(E e);
public E removeFirst();
public E removeLast();


1 LinkedList 提供了栈、队列及双端队列数据结构的实现。

LinkedList

类声明

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


LinedList 构造方法

Constructor1

public LinkedList() {
header.next = header.previous = header;
}


Constructor2

public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}


在介绍构造方法之前首先看一下 LinkedList 的成员属性

private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;


size 属性记录 LinkedList 容器的尺寸

header 属性是一个 LinkedList 内部类 Entry 类型,接下来看一下 Entry 类的定义

private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}


element 是元素的值

next 是当前元素后继的引用

previous 是当前元素前驱的引用

接下来继续探讨 LinkedList 的构造方法

Constructor2 中显示调用 Constructor1 方法,Constructor1 执行了这样一条语句

header.next = header.previous = header;


初始化成员属性 header,为下面添加元素做准备



接下来看一下 LinkedList 类元素增加、删除、访问和迭代实现的具体细节

1 增加元素

public boolean add(E e) {
addBefore(e, header);
return true;
}

private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}


Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);


创建一个新的 元素节点。



接下来的几行代码完成新元素的添加

newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;


为了更清楚的展示,在向 LinkedList 中添加一个元素 B



2 LinkedList 底层使用循环双链表实现。

3 元素添加时没有进行排序。

4 元素添加时没有提供同步机制。

5 没有限制 null 元素的添加。

2 删除元素

public E remove() {
return removeFirst();
}
public E removeFirst() {
return remove(header.next);
}
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();

E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}


删除操作,简单明了,只不过是在循环双链表上进行元素前驱和后继引用的设置,但要明确一点,header 并不是容器中的有效元素,因为其 element 为 null。它只是一个辅助元素。

3 访问操作

public E get(int index) {
return entry(index).element;
}
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
Entry<E> e = header;
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}


get 方法调用 entry 方法来定位目标元素在LinkedList中的位置,因为 LinkedList 底层双循环链表实现的,所以可以根据 index 参数的大小从前往后检索、或者从后往前检索。

4 迭代操作

LinkedList 没有重写 iterator 方法 ,继承 AbstractSequentialList 中定义的 iterator 方法,下面看一下 AbstractSequentialList 中的 iterator 方法的定义

public Iterator<E> iterator() {
return listIterator();
}
public ListIterator<E> listIterator(final int index) {
if (index<0 || index>size())
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}


iterator 方法调用 listIterator 方法,最后生成一个 ListItr 类型的迭代器,下面是 ListItr 类型的定义

private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
}

public boolean hasPrevious() {
return cursor != 0;
}

public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}

public int nextIndex() {
return cursor;
}

public int previousIndex() {
return cursor - 1;
}

public void set(E e) {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();

try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

public void add(E e) {
checkForComodification();

try {
AbstractList.this.add(cursor++, e);
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}


ListItr 扩展了如下方法

public boolean hasPrevious();
public E previous();
public int nextIndex();
public int previousIndex();
public void set(E e);
public void add(E e);


这里我们不在讨论上述 六种方法,只关心与迭代紧密相关的 hashNext和next方法,但是在 ListItr 类中没有发现这两个方法。因为 ListItr 继承自 Itr(在 AbstractList 中定义) 类,所以 hashNext 和 next 方法跟上面讲述 Vector 迭代时一致。

LinkedList 总结

底层是循环双联表实现

没有提供同步机制

允许 null 元素

加入元素时没有进行排序

直接继承自 AbstractSequentialList

元素可以重复加入

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java