文章目录
探索 LinkedList 原理
1. LinkedList 集合底层数据结构
-
LinkedList底层维护了一个双向链表。
-
详细如下源码
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; } }
-
Node节点类是 LinkedList的一个静态内部类,它有三个成员变量: item,next,prev;其中每个节点都有一个指针指向前一个结点和后一个结点,这就是双向链表的结点;
- 结构图如下
- 结构图如下
-
扩展
- 有双向链表就有单向链表:
- 单向链表特点:一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。
-
模拟双向链表示例代码如下:
public class LinkListDemo1 { public static void main(String[] args) { NodeDemo java = new NodeDemo("java"); NodeDemo phyton = new NodeDemo("phyton"); NodeDemo php = new NodeDemo("php"); //连接以上三个节点 java -> phyton -> php java.next = phyton; phyton.next = php; php.prev = phyton; phyton.prev = java; //初始化头节点:让first引用指向node1, NodeDemo first = java; //初始化尾节点:让last引用指向node3, NodeDemo last = php; System.out.println("=======从头遍历到尾部=========="); while (true){ if(first == null) break; System.out.println(first); first = first.next; } System.out.println("=======从尾部遍历到头部=========="); while (true){ if(last == null) break; System.out.println(last); last = last.prev; } } static class NodeDemo { public Object item; public NodeDemo next; public NodeDemo prev; public NodeDemo(Object item) { this.item = item; } @Override public String toString() { return "NodeDemo{" + "item=" + item + '}'; } }}
- 输出结果如下
=======从头遍历到尾部========== NodeDemo{item=java} NodeDemo{item=phyton} NodeDemo{item=php} =======从尾部遍历到头部========== NodeDemo{item=php} NodeDemo{item=phyton} NodeDemo{item=java}
- 模拟向双向链中添加一条数据代码如下:
public class LinkListDemo1 { public static void main(String[] args) { NodeDemo java = new NodeDemo("java"); NodeDemo phyton = new NodeDemo("phyton"); NodeDemo php = new NodeDemo("php"); //连接以上三个节点 java -> phyton -> php java.next = phyton; phyton.next = php; php.prev = phyton; phyton.prev = java; //初始化头节点:让first引用指向node1, NodeDemo first = java; //初始化尾节点:让last引用指向node3, NodeDemo last = php; // java - phyton - php //演示链表的添加对象/数据 //要求,是在phyton---直接插入一个对象 Android //1.先创建一个Node 节点,name 就是 Android NodeDemo android = new NodeDemo("Android"); android.next = php; android.prev = phyton; php.prev = android; phyton.next = android; System.out.println("=======从头遍历到尾部=========="); while (true){ if(first == null) break; System.out.println(first); first = first.next; } System.out.println("=======从尾部遍历到头部=========="); while (true){ if(last == null) break; System.out.println(last); last = last.prev; } } static class NodeDemo { public Object item; public NodeDemo next; public NodeDemo prev; public NodeDemo(Object item) { this.item = item; } @Override public String toString() { return "NodeDemo{" + "item=" + item + '}'; } }}
-
输出结果如下
=======从头遍历到尾部========== NodeDemo{item=java} NodeDemo{item=phyton} NodeDemo{item=Android} NodeDemo{item=php} =======从尾部遍历到头部========== NodeDemo{item=php} NodeDemo{item=Android} NodeDemo{item=phyton} NodeDemo{item=java}
1.1 LinkedList集合介绍
- LinkedList 链表
- 是一种物理存储单元上非连续、非顺序 的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
- 由一系列结点组成。结点可以在运行时动态生成。每个结点包括两部分;一是存储数据元素的数据域,二是存储下一个结点地址的指针域。
- 由于不必须按顺序存储,所以链表的插入时候的复杂度可以达到O(1);
1.2 LinkedList的优点缺点
- 优点:
- 插入数据快,不需要预先知道数据大小;
- 可以充分的利用计算机的内存空间;
- 缺点:
- 随机读取速度慢,寻址花时间;
- 空间开销比较大,因为增加了结点的指针域;
2. LinkedList继承关系
- 源码结构图如下
3. LinkedList继承体系源码分析
- LinkedList 继承体系
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{……}
- ArrayList 继承体系
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{……}
- 值得注意的是,LinkedList 相比ArrayList ,它没有实现RandomAccess 随机访问接口,LinkedList 实现了Deque双向队列的接口,最终继承的是Queue; 实现所有可选列表操作,并允许所有元素(包括null )。
4. 源码分析
4.1 构造方法
-
API
LinkedList() 构造一个空列表。 LinkedList(Collection<? extends E> c) 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
-
1、LinkedList() 无参构造源码简单,如下
public LinkedList() {}
-
2、LinkedList(Collection<? extends E> c)
-
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
- 源码如下
// 初始化集合元素数量 size 大小为0 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; public LinkedList(Collection<? extends E> c) { this();//调用无参构造 addAll(c); } public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } public boolean addAll(int index, Collection<? extends E> c) { //校验 索引是否处于在 0-size之间 checkPositionIndex(index); // 集合元素赋值给a数组; Object[] a = c.toArray(); // 初始化集合的长度 int numNew = a.length; if (numNew == 0) return false; //定义两个节点 pred succ,目的是用来存储下一个元素的和上一个元素的指向; // 前置节点 后置节点 Node<E> pred, succ; // 初次添加数据 index size 都为0 // 判断是否为链表尾部,如果是,则在尾部追加数据 // 最后一个节点的后置节点一定为null ,前置节点是最后一个元素 if (index == size) { succ = null; pred = last; } else { //如果不是尾部,则为中间,通过node(index)方法如出节点,作为后置节点 succ = node(index); // index 节点的前置作为前置节点 pred = succ.prev; } //链表核心添加方法 for (Object o : a) { //o 向上转型 @SuppressWarnings("unchecked") E e = (E) o; // 创建一个节点 前置节点为pred(初始为null),当前节点数据为e, 后置节点为null; Node<E> newNode = new Node<>(pred, e, null); // 如果pred 为null ,则表示 为第一个节点,否则为下一个节点 if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } //如果后置节点为空,则表示是在队尾增加的数据 if (succ == null) { //设置为尾节点 last = pred; } else { //否则为不是队尾,更新前置后置节点 pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; } /** * 取出 index 节点 * Returns the (non-null) Node at the specified element index. */ Node<E> node(int index) { // assert isElementIndex(index); // 如果index 小于 size/2,表示为前半部分,则从头部开始找 if (index < (size >> 1)) { // 把第一个节点的值赋给x Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { // 如果index 大与等于size/2,表示为后半部分,则从后面开始找 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } // 检测index位置是否合法 private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //索引是否处于在 0-size之间 private boolean isPositionIndex(int index) { return index >= 0 && index <= size; }
- 源码如下
4.3 添加方法
-
API
- boolean add(E e) 将指定的元素追加到此列表的末尾。
- void add(int index, E element) 在此列表中的指定位置插入指定的元素。
- boolean addAll(Collection<? extends E> c) 按照指定集合的迭代器返回的顺序将指定集合中的所有元素追加到此列表的末尾。
- boolean addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
- void addFirst(E e) 在该列表开头插入指定的元素。
- void addLast(E e) 将指定的元素追加到此列表的末尾。
-
1、add(E e) 源码跟 addLast(E e) 中调用的都是 linkLast(e); 都是将元素追加到此列表的末尾。
- 源码如下
public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { final Node<E> l = last; // 前置节点设置为l,值为e,后置节点为null final Node<E> newNode = new Node<>(l, e, null); last = newNode; //若l最后一个节点为空,则表明列表中还没有元素,first也应该指向newNode if (l == null) first = newNode; else //否则,前last的后继指向当前节点 l.next = newNode; size++; modCount++; }
-
2、addFirst(E e) 在该列表开头插入指定的元素
- 源码如下
将e元素链接成列表的第一个元素
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
// 前置节点设置为null,值为e,后置节点为f
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
//若f(第一个节点)为空,则表明列表中还没有元素,last 也应该指向newNode
if (f == null)
last = newNode;
else
否则,前first的前置节点指向当前节点
f.prev = newNode;
size++;
modCount++;
}
- 3、** addAll(Collection<? extends E> c) ** 源码跟 addAll(int index, Collection<? extends E> c) 中调用的都是 addAll(size, c); 向指定位置添加元素集合。前面分析构造方法已经提过,此处不做过多解释;
- 4、 add(int index,E e):在指定位置添加元素
- 源码分析
public void add(int index, E element) {
//校验 索引是否合法
checkPositionIndex(index);
if (index == size)
//如果index == size 表示为最后一个节点,执行连接最后一个节点方法
linkLast(element);
else
//否则连接前一个节点
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// pred 指向当前索引节点的前置节点,
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++;
}
4.4 删除方法
- API
- 1、 E remove() 检索并删除此列表的头(第一个元素)。
- 2、 E remove(int index) 删除该列表中指定位置的元素。
- 3、 boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。
- 4、 E removeFirst() 从此列表中删除并返回第一个元素。
- 5、 E removeLast() 从此列表中删除并返回最后一个元素。
- 源码分析
- 1、 E remove() 检索并删除此列表的头(第一个元素)
- 源码分析
public E remove() { return removeFirst(); } public E removeFirst() { //把成员变量第一个节点元素赋值给f final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } //断开连接 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; //把第一个节点元素值赋值给element final E element = f.item; //把第一个节点的后置节点赋值给next final Node<E> next = f.next; f.item = null; f.next = null; // help GC // 把第一个节点的引用指向第一个节点的后置节点,也就是第二个node节点 first = next; //如果下个节点为空了,则表示列表中无元素了,同时把last 也指也null if (next == null) last = null; else //把第二个节点的前置节点设置为null next.prev = null; size--; modCount++; return element; }
- 2、 E remove(int index) 删除该列表中指定位置的元素。
- 源码分析
public E remove(int index) { //校验元素索引是否合格 checkElementIndex(index); return unlink(node(index)); } private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private boolean isElementIndex(int index) { return index >= 0 && index < size; } // 取出 index 节点 Node<E> node(int index) { // 如果index 小于 size/2,表示为前半部分,则从头部开始找 if (index < (size >> 1)) { //右移一位就是除以2 // 把第一个节点的值赋给x Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { // 如果index 大与等于size/2,表示为后半部分,则从后面开始找 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } // aaa -> bbb -> ccc //解绑 bbb E unlink(Node<E> x) { // assert x != null; //把当前元素值 赋值给element final E element = x.item; //next的引用指向 当前节点的后置节点 final Node<E> next = x.next; //prev 的引用指向 当前节点的前置节点 final Node<E> prev = x.prev; //如果前置节点为null 则表示为第一个节点, if (prev == null) { first = next; } else { // aaa -> bbb -> ccc //bbb的前置(aaa )的后置节点指向bbb的后置节点(ccc),即跨过了bbb prev.next = next; // x.prev已舍弃,置空引用即可 x.prev = null; } // 后置为null,说明x为最后一个节点 if (next == null) { // last指向x的前置 last = prev; } else { // x的后置的前置指向x的前置,即略过了x next.prev = prev; // x.next已舍弃,置空引用即可 x.next = null; } // 引用置空 x.item = null; size--; modCount++; // 返回所删除的节点的元素值 return element; }
- 3、 boolean 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; }
- 4、 E removeFirst() 从此列表中删除并返回第一个元素。
- 源码分析
public E removeFirst() { //把成员变量第一个节点元素赋值给f final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } //断开连接 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; //把第一个节点元素值赋值给element final E element = f.item; //把第一个节点的后置节点赋值给next final Node<E> next = f.next; f.item = null; f.next = null; // help GC // 把第一个节点的引用指向第一个节点的后置节点,也就是第二个node节点 first = next; //如果下个节点为空了,则表示列表中无元素了,同时把last 也指也null if (next == null) last = null; else //把第二个节点的前置节点设置为null next.prev = null; size--; modCount++; return element; }
- 5、 E removeLast() 从此列表中删除并返回最后一个元素。
- 源码分析
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(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;
}
4.5 修改方法
- E set(int index, E element) 用指定的元素替换此列表中指定位置的元素。
- 源码分析
public E set(int index, E element) {
//校验
checkElementIndex(index);
//当前索引节点 并把把X引用指向 当前索引节点
Node<E> x = node(index); // 遍历查找索引节点方法
// 保留旧值,仅供返回使用
E oldVal = x.item;
// 新值替换索引位置的值
x.item = element;
return oldVal;
}
4.6 获取方法
- E get(int index) 返回此列表中指定位置的元素。
- 源码分析
public E get(int index) { checkElementIndex(index); //遍历方式获取到索引节点之后,直接获取索引节点item值返回; return node(index).item; }
4.7 转换方法
- toString() 返回此集合的字符串表示形式。跟ArrayList 一样,也是继承自AbstractCollection 的toString(),方法;
- 源码分析
public String toString() { Iterator<E> it = iterator(); if (! it.hasNext()) return "[]"; StringBuilder sb = new StringBuilder(); sb.append('['); for (;;) { //无限循环 E e = it.next(); sb.append(e == this ? "(this Collection)" : e); if (! it.hasNext()) return sb.append(']').toString(); sb.append(',').append(' '); } }
4.8 清空方法
- void clear() 从列表中删除所有元素。
- 源码分析
public void clear() { // Clearing all of the links between nodes is "unnecessary", but: // - helps a generational GC if the discarded nodes inhabit // more than one generation // - is sure to free memory even if there is a reachable Iterator for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; }