您的位置:首页 > 其它

List接口实现类-ArrayList、Vector、LinkedList集合深入学习以及源码解析

2015-06-07 16:46 411 查看
学习List接口实现类 ArrayList Vector LinkedList

List接口的实现类中最常用最重要的就是这三个:ArrayList、Vector、LinkedList。

JDK中这三个类的定义:

1、ArrayList<E>:

public class ArrayList<E> extends AbstractList<E>

implements List<E>, RandomAccess, Cloneable, java.io.Serializable

{

private static final long serialVersionUID = 8683452581122892189L;

/**

* The array buffer into which the elements of the ArrayList are stored.

* The capacity of the ArrayList is the length of this array buffer.

*/

private transient Object[] elementData;

/**

* The size of the ArrayList (the number of elements it contains).

*

* @serial

*/

private int size;

2、 Vector<E>:

public class Vector<E>extends AbstractList<E>

implements List<E>, RandomAccess, Cloneable, java.io.Serializable

{

/**

* The array buffer into which the components of the vector are

* stored. The capacity of the vector is the length of this array buffer,

* and is at least large enough to contain all the vector's elements.

*

* <p>Any array elements following the last element in the Vector are null.

*

* @serial

*/

protected Object[] elementData;

3、LinkedList<E>:

public class LinkedList<E>extends AbstractSequentialList<E>

implements List<E>, Deque<E>, Cloneable, java.io.Serializable

{ // 在序列化这个对象的时候这个变量不会被这样序列化

transient int size = 0;

/**

* Pointer to first node.

* Invariant: (first == null && last == null) ||

* (first.prev == null && first.item != null)

*/

transient Node<E> first;

/**

* Pointer to last node.

* Invariant: (first == null && last == null) ||

* (last.next == null && last.item != null)

*/

transient Node<E> last;

4、从这三个类定义就可以看出一些信息:

(1)、三个都直接实现了AbstractList这个抽象类

(2)、ArrayList和Vector都实现了RandomAccess接口,而LinkedList没有,这是什么意思呢?

在JDK 中,RandomAccess接口是一个空接口,所以它没有实际意义,就是一个标记,

标记这个类支持快速随机访问,所以,arrayList和 vector是支持随机访问的,但是LinkedList不支持持

(3)、serializbale接口表明他们都支持序列化。

5、下面详细说说List的这三个实现:

(1)、 查看实现源码会发现这三个里面,ArrayList和Vector使用了数组的实现,相当于封装了对数组的操作。这也正是他们能够支持快速随机访问的原因,多说一句,JDK中所有基于数组实现的数据结构都能够支持快速随机访问。

ArrayList和Vector的实现上几乎都使用了相同的算法,他们的主要区别就是ArrayList没有对任何一个方法做同步处理,所以不是线程安全的;而Vector中大部分方法都做了线程同步所以是线程安全的。

(2)、LinkedList使用的是双向循环链表的数据结构。由于是基于链表的,所以无法法实现随机访问的,只能顺序访问,这也正是它没有实现RandomAccess接口的原因。

(3)、由于ArrayList、Vector和LinkedList所采用的数据结构不同,注定他们适用的是完全不同的场景。

通过阅读这几个类的源码,我们可以看到他们实现的不同。ArrayList和Vector基本一样,我们就用ArrayList和LinkedList做对比。

在末尾增加一个元素

6、ArrayList中的add方法实现如下:

/**

* Appends the specified element to the end of this list.

*将元素添加到list的最后

* @param e element to be appended to this list

* @return <tt>true</tt> (as specified by {@link Collection#add})

*/

public boolean add(E e) {

// 判断容量


ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

这个方法做两件事情,首先确保数组空间足够大,然后在数组末尾增加元素并且通过后++使得完成size+1

从这个代码可以看出,如果数组空间足够大,那么只是数组的add操作就是O(1)的性能,非常高效。

7、在看看ensureCapacityInternal这个方法的实现:

private void ensureCapacityInternal(int minCapacity) {

modCount++;

// overflow-conscious code,如果空间不够则扩容也就是重新创建一个Object[]对象

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

8、grow(int minCapacity)方法创建数组并将原来的数据拷贝到新数组中

/**

* Increases the capacity to ensure that it can hold at least the

* number of elements specified by the minimum capacity argument.

*

* @param minCapacity the desired minimum capacity

*/

private void grow(int minCapacity) {

// overflow-conscious code

int oldCapacity = elementData.length;

// 使用移位运算,提高效率

int newCapacity = oldCapacity + (oldCapacity >> 1);

if (newCapacity - minCapacity < 0)

newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE > 0)

newCapacity = hugeCapacity(minCapacity);

// minCapacity is usually close to size, so this is a win:

elementData = Arrays.copyOf(elementData, newCapacity);

}

可以看出,如果数组空间不够,那么这个方法就会做数组扩容和数组复制操作,看上面,JDK利用移位运算符进行扩容计算,>>1右移一位表示除2,所以newCapacity就是扩容为原来的1.5倍。

9、这里的代码都是JDK1.7中的实现,JDK1.7对1.6的很多代码做了优化,比如上面这段扩容代码,在JDK1.6中上面的是直接除2,显然,移位运算要更高效。

10、在看看LinkedList中的add方法:

(1)、add(E e):

/**

* Appends the specified element to the end of this list.

*

* <p>This method is equivalent to {@link #addLast}.

*

* @param e element to be appended to this list

* @return {@code true} (as specified by {@link Collection#add})

*/

public boolean add(E e) {

linkLast(e);

return true;

}

(2)、linkLast(E e) :

/**

* Links e as last element.

*/

void linkLast(E e) {

final Node<E> l = last;

final Node<E> newNode = new Node<>(l, e, null);

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

(3)、内部类:

private static class Node<E> {

// 当前节点

E item;

// 当前节点的后节点

Node<E> next;

// 当前节点的前节点

Node<E> prev;

// 构造函数

Node(Node<E> prev, E element, Node<E> next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

从这段add代码可以看出,LinkedList由于使用了链表,所以不需要进行扩容,直接把元素加到链表最后,把新元素的前驱指向之前的last元素,并把last元素指向新元素就可以了。这也是一个O(1)的性能。

在任意位置插入元素

11、ArrayList中的实现如下:

(1)、 add(int index, E element)

public void add(int index, E element) {

rangeCheckForAdd(index);

// 判断容量

ensureCapacityInternal(size + 1); // Increments modCount!!

System.arraycopy(elementData, index, elementData, index + 1,

size - index);

elementData[index] = element;

size++;

}

(2)、ensureCapacityInternal(int minCapacity)

private void ensureCapacityInternal(int minCapacity) {

modCount++;

// overflow-conscious code,如果数组长度不够则进行扩容

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

(3)、grow(int minCapacity)


/**

* Increases the capacity to ensure that it can hold at least the

* number of elements specified by the minimum capacity argument.

*

* @param minCapacity the desired minimum capacity

*/

private void grow(int minCapacity) {

// overflow-conscious code

int oldCapacity = elementData.length;

int newCapacity = oldCapacity + (oldCapacity >> 1);

if (newCapacity - minCapacity < 0)

newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE > 0)

newCapacity = hugeCapacity(minCapacity);

// minCapacity is usually close to size, so this is a win:

elementData = Arrays.copyOf(elementData, newCapacity);

}

这段代码,首先先检查数组容量,容量不够先扩容,然后把index之后的数组往后挪一个,最后在index位置放上新元素。由于数组是一块连续内存空间,所以在任意位置插入,都会导致这个其后数组后挪一位的情况,需要做一次数组复制操作,很明显,如果有大量的随机插入,那么这个数组复制操作开销会很大,而且插入的越靠前,数组复制开销越大。

12、LinkedList中的实现:

(1)、add(int index, E element)

/**

* Inserts the specified element at the specified position in this list.

* Shifts the element currently at that position (if any) and any

* subsequent elements to the right (adds one to their indices).

*

* @param index index at which the specified element is to be inserted

* @param element element to be inserted

* @throws IndexOutOfBoundsException {@inheritDoc}

*/

public void add(int index, E element) {

checkPositionIndex(index);

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

(2)、linkBefore(E e, Node<E> succ)

/**

* Inserts element e before non-null Node succ.

*/

void linkBefore(E e, Node<E> succ) {

// assert succ != null;

final Node<E> pred = succ.prev;

final Node<E> newNode = new Node<>(pred, e, succ);

succ.prev = newNode;

if (pred == null)

first = newNode;

else

pred.next = newNode;

size++;

modCount++;

}

(3)、 Node<E> node(int index)

/**

* Returns the (non-null) Node at the specified element index.

*/

Node<E> node(int index) {

// assert isElementIndex(index);

if (index < (size >> 1)) {

Node<E> x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node<E> x = last;

for (int i = size - 1; i > index; i--)

x = x.prev;

return x;

}

}

这段代码,取到原先index处节点的前驱,变成新节点的前驱,同时把原先index变成新节点的后驱,这样就完成了新节点的插入。这个就是链表的优势,不存在数据复制操作,性能和在最后插入是一样的。

小结:

从上面的源码剖析可以看出这三种List实现的一些典型适用场景,如果经常对数组做随机插入操作,特别是插入的比较靠前,那么LinkedList的性能优势就非常明显,而如果都只是末尾插入,则ArrayList更占据优势,如果需要线程安全,则使用Vector或者创建线程安全的ArrayList。

在使用基于数组实现的ArrayList 和Vector 时我们要指定初始容量,因为我们在源码中也看到了,在添加时首先要进行容量的判断,如果容量不够则要创建新数组,还要将原来数组中的数据复制到新数组中,这个过程会减低效率并且会浪费资源。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: