【Java集合源码剖析】LinkedList源码剖析
来源:互联网 发布:淘宝客服电话是多少 编辑:程序博客网 时间:2024/06/01 09:41
推荐一篇分析LinkedList的博客,非常不错:传送门
LinkedList是基于双向链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈、队列和双端队列来使用。
LinkedList同样是非线程安全的,只在单线程下适合使用。
LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。
源码如下。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ // 链表尺寸 transient int size = 0; /** * 头结点 * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; /** * 头结点 * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last; /** * Constructs an empty list. */ public LinkedList() { } /** * 链接新元素到头部 */ 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; else f.prev = newNode; // 尺寸增加 size++; modCount++; } /** * 链接新元素到尾部 */ 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++; } /** * 在succ结点前插入元素e */ void linkBefore(E e, Node<E> succ) { // 记录succ结点的前驱 final Node<E> pred = succ.prev; // 生成新结点,并链接到succ结点的前驱和succ结点本身。 final Node<E> newNode = new Node<>(pred, e, succ); // 更新succ的直接前驱结点为新结点 succ.prev = newNode; // 如果记录的succ结点的前驱为null,说明插入的位置位于头部,则让头指针指向新结点,否则更新其后驱为新结点 if (pred == null) first = newNode; else pred.next = newNode; // 尺寸增加 size++; modCount++; } /** * 断开头部f结点的链接, */ private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else // 非空表,则置空新的头结点元素的前驱 next.prev = null; // 减少尺寸 size--; modCount++; return element; } /** * 断开尾部l结点的链接 */ private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; } /** * 断开结点x,并返回其item */ 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; } /** * 获取头结点元素 */ 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; } /** * 移除头结点元素 */ 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); } /** * 添加元素到头结点 */ public void addFirst(E e) { linkFirst(e); } /** * 添加元素到尾结点 */ public void addLast(E e) { linkLast(e); } /** * 添加指定的元素到链表的尾部. * 和addLast()方法行为一致. */ public boolean add(E e) { linkLast(e); return true; } /** * 获取指定位置的元素 */ public E get(int index) { checkElementIndex(index); return node(index).item; } /** * 更新指定位置的元素 */ public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; } /** * 插入新元素到指定位置 */ public void add(int index, E element) { // 检查插入位置是否合法 checkPositionIndex(index); // 如果是尾部,则直接链接到尾部,否则先查找到指定为的元素,然后在其前链接元素。 if (index == size) linkLast(element); else linkBefore(element, node(index)); } /** * 获取指定索引的元素 */ Node<E> node(int 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; } } 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; } } // 返回LinkedList的Object[]数组 public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; return result; } // 返回LinkedList的模板数组。所谓模板数组,即可以将T设为任意的数据类型 public <T> T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; if (a.length > size) a[size] = null; return a; } private static final long serialVersionUID = 876323262645176354L; // Positional Access Operations 随机访问位置的方法 // set 设置index位置对应的节点的值为element // get 返回LinkedList指定位置的元素 // remove 删除index位置的节点 // Search Operations 搜索方法 // indexOf // lastIndexOf // Queue operations. 队列方法 // peek 返回第一个节点 // element 返回第一个节点 // pool 删除并返回第一个节点 // remove 删除并返回第一个节点 // offer 添加指定元素到队列尾部 // Deque operations 双端队列方法 // offerFirst 添加元素到队列头部 // offerLast 添加元素到队列尾部 // peekFirst 返回队头元素 // peekLast 返回队尾元素 // pollFirst 返回并移除队头元素 // pollLast 返回并移除队尾元素 // push 添加元素到链表头部 // pop 移除链表头部的数据 // 从LinkedList开始向后查找,删除第一个值为元素(o)的节点 // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点 // removeFirstOccurrence // 从LinkedList末尾向前查找,删除第一个值为元素(o)的节点 // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点 // removeLastOccurrence // 返回“index到末尾的全部节点”对应的ListIterator对象(List迭代器) // listIterator // List迭代器 // class ListItr}
关于LinkedList的源码,给出几点比较重要的总结:
1, 从源码中可以看出,LinkedList的实现是基于双向链表的,始终有头指针指向第一个结点,有尾指针指向最后一个结点。
2,注意两个不同的构造方法。无参构造方法中没有做任何事情,包含Collection的构造方法,将Collection中的数据加入到链表的尾部后面。
3,在查找和删除某元素时,源码中都划分为该元素为null和不为null两种情况来处理,LinkedList中允许元素为null。
4,LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
5,源码中node(int index)方法,该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果index<size/2
,就只从位置0往后遍历到位置index处,而如果index>size/2
,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。
6,LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。
7,在源码中提供了栈,队列,双端队列的操作方法,因此可以作为栈和双端队列使用。
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- Java LinkedList源码剖析
- Java LinkedList简介 源码剖析
- 【Java深入】LinkedList源码剖析
- 【源码】LinkedList源码剖析
- Java集合源码剖析
- 【Java1.7.5集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】ArrayList源码剖析
- 【Java集合源码剖析】Vector源码剖析
- swift教程【入门】 swift 函数之旅
- 我的linux菜鸟之路3
- C语言表驱动法编程实践
- LeetCode.Problem2 Add two numbers
- 几种常见的CSS列布局
- 【Java集合源码剖析】LinkedList源码剖析
- 用JS判断两个数字的大小
- 调皮的MFC(2)
- CardView解决5.0以下出现padding问题
- javascript Date() 浏览器兼容问题解决
- MySql 显示表字段及注释等信息
- jQuery中attr和prop的区别
- 一个带微博按钮的jquery返回顶部效果代码
- C# 数组拆分