@w1992wishes
2018-05-10T01:34:40.000000Z
字数 9426
阅读 1163
JAVA_java容器 源码分析
本篇结构:
ArrayList和LinkedList都是很常用的容器,它们适合不同的场景。
对于随机访问和设置频繁的场景,应该选用ArrayList,因为ArrayList是基于一个动态数组的容器;而对于插入和删除频繁的场景,应该选用LinkedList,ArrayList插入和删除都需要复制数组,会消耗一定的性能,LinkedList则没有这样的顾虑。
下面就来深入源码来看看它是怎么实现的。
与ArrayList不同,LinkedList是基于链表实现的。
何为链表?
链表是一种基本的线性数据结构,其和数组同为线性,但是数组是内存的物理存储上呈线性,逻辑上也是线性;而链表只是在逻辑上呈线性。在链表的每一个存储单元中不仅存储有当前的元素,还有下一个存储单元的地址,这样的可以通过地址将所有的存储单元连接在一起。
链表有单向,循环和双向之分。

LinkedList使用的是一个双向链表。
链表中的每个元素都是一个Node对象,其定义在LinkedList内部,Node中有三个属性,分别是:当前节点值、下一个节点引用、上一个节点引用。
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;}}
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;
ArrayList有三个重要的属性:
同ArrayList相比,LinkedList多了addFirst,addLast,getFirst,getLast,removeFirst,removeLast等实现了Deque接口的相关方法(LinkedList即实现了List接口也实现了Deque接口)。
Deque又继承Queue接口,这里简单说下Queue中remove/poll、 add/offer、 element/peek的区别。
1.offer,add区别:
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。
add() 方法抛出一个 new IllegalStateException("Queue full") 异常,offer 则返回 false。
2.poll,remove区别:
remove() 和 poll() 方法都是从队列中删除第一个元素。
空集合调用remove() 方法时抛出 NoSuchElementException 异常, poll() 方法则返回 null。
3.peek,element区别:
element() 和 peek() 用于在队列的头部查询元素。
与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。
对于LinkedList,其offer方法不过是对add方法的调用,同add没有区别。
LinkedList提供了两个构造方法,一个无参构造方法,一个接收 Collection 的构造方法。
public LinkedList() {}public LinkedList(Collection<? extends E> c) {this();addAll(c);}
无参构造什么也没做,只是创建了 LinkedList 对象,有参构造则调用了 addAll 方法。
前面提过,LinkedList即实现了List接口,又实现了Deque接口,所以LinkedList既可以添加将元素添加到尾部,也可以将元素添加到指定索引位置,还可以添加添加整个集合;另外既可以在头部添加,又可以在尾部添加。
有addFirst(E e)、addLast(E e)、add(E e)、add(int index, E element)、addAll(Collection c)、addAll(int index, Collection c)、offer(E e)、offerFirst(E e)、offerLast(E e)等添加元素的方法。

1.先看List接口的添加操作
add(E e)
add(E e) 调用 linkLast 将元素插入链表尾部。
public boolean add(E e) {linkLast(e);return true;}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;// 链表不为空,将该结点作为原链表尾部的后继节点elsel.next = newNode;// 长度增加size++;modCount++;}
add(int index, E element)
add(int index, E element)在指定位置添加元素。它分三步:
linkBefore 方法又调用了 node(index) node(index) 方法返回 index 处的节点,然后 linkBefore 将往index插入新节点,并更新相应节点的 prev,next 属性。
public void add(int index, E element) {// 检查索引是否处于[0-size]之间checkPositionIndex(index);// 索引等于size,相当于插在尾部if (index == size)linkLast(element);// 否则插在链表中间elselinkBefore(element, node(index));}// 返回index处的节点Node<E> node(int index) {// assert isElementIndex(index);// 为提升遍历效率,如果index小于size/2,就顺序往后遍历if (index < (size >> 1)) {Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;// 如果index大于size/2,就逆向往前遍历} else {Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}}// 在链表中间插入节点void linkBefore(E e, Node<E> succ) {// assert succ != null;// 定义一个节点元素保存succ的prev引用,也就是index-1处的节点final Node<E> pred = succ.prev;// 根据新增的元素创建新节点,原index处的节点succ变为index+1处的节点final Node<E> newNode = new Node<>(pred, e, succ);// 修改succ的上一个节点为插入的新节点succ.prev = newNode;// 如果pred是null 表明succ第一个节点if (pred == null)// 成员属性first指向新节点first = newNode;// 节点前元素(index-1)的next属性指向新节点elsepred.next = newNode;size++;modCount++;}
addAll方法
addAll有两个重载方法,一个参数的方法表示将集合元素添加到链表尾部,而两个参数的方法指定了开始插入的位置。
// 将集合插入到链表尾部,即开始索引位置为sizepublic boolean addAll(Collection<? extends E> c) {return addAll(size, c);}// 将集合从指定位置开始插入public boolean addAll(int index, Collection<? extends E> c) {// 1.检查索引是否越界,index >= 0 && index <= sizecheckPositionIndex(index);// 2.将集合转化为数组,如果是空数组,直接返回falseObject[] a = c.toArray();int numNew = a.length;if (numNew == 0)return false;// 3.获取插入位置index处前驱节点和后继节点(要注意,该后继节点是所有元素插入完成后的后继节点)Node<E> pred, succ;// 3-1.如果从尾部插入,前驱节点为last,后继节点为nullif (index == size) {succ = null;pred = last;// 3-2.否则通过node(index)得到后继节点,进而拿到前驱节点} else {succ = node(index);pred = succ.prev;}// 4.遍历数组逐个插入for (Object o : a) {@SuppressWarnings("unchecked")E e = (E) o;// 4-1.创建新节点,前驱节点前面已经获取,后继节点为null(因为还未插入)Node<E> newNode = new Node<>(pred, e, null);if (pred == null)first = newNode;else// 4-2.在这里更新新插入节点的后继节点pred.next = newNode;pred = newNode;}// 如果插入位置在尾部,重置last节点if (succ == null) {last = pred;// 否则,将插入的链表与先前链表连接起来} else {pred.next = succ;succ.prev = pred;}size += numNew;modCount++;return true;}
2.接着看Deque接口的添加操作
addFirst(E e)
addFirst(E e)将元素添加到链表头部
public void addFirst(E e) {linkFirst(e);}private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);first = newNode;if (f == null)last = newNode;elsef.prev = newNode;size++;modCount++;}
addLast(E e)
addLast(E e) 将元素添加到链表尾部
public void addLast(E e) {linkLast(e);}
offer(E e)
offer(E e)方法用于将数据添加到链表尾部,其内部调用了add(E e)方法,可见 LinkedList 的 add (其 add 方法应该归属于 List 接口)和 offer 方法没有区别,并不是 Queue 接口中 add 满了就抛错,offer 返回false。
public boolean offer(E e) {return add(e);}
offerFirst(E e) & offerLast(E e)
public boolean offerFirst(E e) {addFirst(e);return true;}public boolean offerLast(E e) {addLast(e);return true;}
Deque接口的几个添加方法相对简单,就不多说。
1.List接口检索操作
get(int index)
get(int index) 获取索引index处得元素
public E get(int index) {// index >= 0 && index < sizecheckElementIndex(index);// 获取 index 的 node 节点的 itemreturn node(index).item;}
indexOf(Object o)
indexOf(Object o) 返回第一个匹配的节点所在的索引
public int indexOf(Object o) {int index = 0;// 为nullif (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null)return index;index++;}// 不为null} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item))return index;index++;}}return -1;}
contains(Object o)
contains(Object o)方法检查对象o是否存在于链表中,调用indexOf(o)方法判断,不等于-1代表存在。
public boolean contains(Object o) {return indexOf(o) != -1;}
2.Deque接口检索操作
getFirst() & getLast()
获取头节点元素 & 尾节点元素
public E getFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return f.item;}public E getLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return l.item;}
peek() & element()
element()方法的内部就是使用getFirst()实现的,在链表为空时,抛出NoSuchElementException,peek直接返回null。
public E peek() {final Node<E> f = first;return (f == null) ? null : f.item;}public E element() {return getFirst();}
1.List接口删除操作
remove(int index)
remove(int index)删除 index 索引处的节点
public E remove(int index) {// 1.index >= 0 && index < sizecheckElementIndex(index);// 2.unlinkreturn unlink(node(index));}E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;// 得到后继节点final Node<E> prev = x.prev;// 得到前驱节点// 将待删除的节点同前驱节点断开,并将前驱节点与后继节点关联if (prev == null) {first = next;} else {prev.next = next;x.prev = null;}// 将待删除的节点同后继节点断开,并将后继节点与前驱节点关联if (next == null) {last = prev;} else {next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;}
remove(Object o)
remove(Object o)删除指定对象
public boolean remove(Object o) {if (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;}
2.Deque接口删除操作
removeFirst() & removeLast()
public E removeFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return unlinkFirst(f);}public E removeLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return unlinkLast(l);}
poll() & remove() & pop()
remove()和pop()内部调用了removeFirst()方法,而removeFirst()在链表为空时将抛出NoSuchElementException。
public E poll() {final Node<E> f = first;return (f == null) ? null : unlinkFirst(f);}public E remove() {return removeFirst();}public E pop() {return removeFirst();}
iterator()
iterator()调用listIterator(),listIterator()调用其有参重载listIterator(final int index),这里看到可以从指定索引处开始迭代。
public Iterator<E> iterator() {return listIterator();}public ListIterator<E> listIterator() {return listIterator(0);}public ListIterator<E> listIterator(final int index) {rangeCheckForAdd(index);return new ListItr(index);}
ListItr是AbstractList中的一个私有内部类,运用起来是先判断 hasPrevious() ,如果true,则 previous(), 或者 hasNext(),如果true,则 next()。hasNext()和next()定义在其父类Itr中。
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 < 0)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 {int i = cursor;AbstractList.this.add(i, e);lastRet = -1;cursor = i + 1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}}
ArrayList和LinkedList的区别:
LinkedList是基于双向链表的数据结构,下面还是同ArrayList一起分析:
ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方:
总体来说:
当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;
当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。