Java之LinkedList源码解析
下文笔者讲述Linkedlist源码解析,如下所示
LinkedList双向链表--记录first属性和last属性
JDK8中,开始对外提供三个添加节点的方法
linkLast(E)方法的功能:
LinkedList类图

从LinkedList的类图中 我们可以看出LinkedList实现Iterable、Queue 、Collection、List接口 LinkedList自身还有一大特点: 就是底层数据采用链接进行存储,而且是一个双向列表
Node类
private static class Node<E> { //主要用于在当前Node节点上的存储的数据对象 E item; //类型也是Node,表示当前节点指向的下一个节点 Node<E> next; //类型也是Node,表示当前节点指向的上一个节点 Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
LinkedList双向链表--记录first属性和last属性
size属性用于记录双向链表的当前长度
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; /** * Constructs an empty list. */ public LinkedList() { } first == null && last == null: 当双向链表没有任何数据对象的时候 first属性和last属性都为null first == last: 当双向链表只有一个对象的时候 first属性和last属性一定指向同一个节点 first != null && last != null: 当双向链表至少有一个数据对象的时候 first属性和last属性都不可能为null LinkedList集合内部场景是不可能出现 (first != null && last == null)或 (first == null && last != null) 因为first属性和last属性要么都为null,要么都不为null
JDK8中,开始对外提供三个添加节点的方法
linkFirst(E)方法,linkLast(E)方法和linkBefore(E,Node)
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++; } 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++; } //但是对外只提供 //add(E)、addLast(E)、addFirst(E)方法例:对外提供三个方法的源码
public boolean add(E e) { linkLast(e); return true; } public void addLast(E e) { linkLast(e); } public void addFirst(E e) { linkFirst(e); }
linkFirst源码
/** * Links e as first element. */ private void linkFirst(E e) { //使用一个临时的变量记录操作前first属性的信息 final Node<E> f = first; //创建一个数据信息为e的新节点,该节点前置节点引用为null,后置节点引用指向原先的头节点 final Node<E> newNode = new Node<>(null, e, f); //因为要在双向链表头部添加新的节点,将first属性中的信息重新设置 first = newNode; //条件成立,说明双向链表没有任何节点 if (f == null) //将last节点也指向新的节点,这样first和last节点属性同时指向同一个节点 last = newNode; else //不成立,说明双向链表至少有一个节点,只需要把原来的头节点的前置节点引用指向新的头节点 f.prev = newNode; //双向链表长度 + 1 size++; //linkedList集合的操作次数 + 1 modCount++; }
linkLast(E)方法的功能:
可以在当前双向链表的尾节点之后添加一个新的节点,并且调整last属性的指向位置
void linkLast(E e) { //使用一个临时变量来记录操作前的last属性信息 final Node<E> l = last; //创建一个新的节点,item属性值为e,新节点的前置对象指向原来的尾节点,后置节点为null final Node<E> newNode = new Node<>(l, e, null); //因为要在双向链表的尾节点添加新的节点,将last属性中的信息重新设置 last = newNode; //条件成立,说明双向链表没有任何节点 if (l == null) //将first节点指向新的节点,first和last都同时指向同一个节点 first = newNode; else //不成立,双向链表至少有一个节点,将原来的尾节点的后置节点指向新的尾节点 l.next = newNode; //双向链表长度 + 1 size++; //linkedList集合的操作次数 + 1 modCount++; }
linkBefore(E,Node)方法的功能:
在指定的节点前的索引位置上插入一个新的节点 注意事项: LinkedList集合的操作逻辑可以保证这里的succ入参一定不为null 并且一定已经存储于当前LinkedList集合中的某个位置
linkBefore源码
void linkBefore(E e, Node<E> succ) { // assert succ != null; //创建一个变量,记录当前succ的前置节点引用(可能为null) final Node<E> pred = succ.prev; //创建一个新的节点,该节点的前置节点引用指向succ节点的前置节点,该节点的后置节点引用指向succ节点 final Node<E> newNode = new Node<>(pred, e, succ); //将succ节点的前置节点重新设置为刚刚创建的新的节点 succ.prev = newNode; //条件成立,说明当前succ节点原本就是双向链表的头节点,可以看作当前的操作其实就是在链表的头部添加一个新的节点 if (pred == null) //这个时候将first属性的指向新创建的节点 first = newNode; else //不成立,将succ的前置节点的后置节点设置为当前新创建的节点 pred.next = newNode; //双向链表长度 + 1 size++; //linkedList集合的操作次数 + 1 modCount++; }
LinkedList集合中元素移除方法
unlinkFirst(Node)方法 unlinkLast(Node)方法 unlink(Node)方法 ==================================================== LinkedList集合对外提供 removeFirst()、removeLast()、remove(Object)
remove方法源码
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; }
removeFirst源码
public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); }
removeLast源码
public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); }
unlinkFirst(Node)方法功能
unlinkFirst(Node)方法的功能: 用于移除LinkedList集合中双向链表的头节点 并重新设置它的后置节点为新的头节点 该方法入参f就是当前双向链表的头节点 该参数一定不为null
unlinkFirst源码
private E unlinkFirst(Node<E> f) { // assert f == first && f != null; //定义一个element变量,记录当前双向链表头节点的数据对象,以便方法最后将其返回 final E element = f.item; //创建一个next变量,记录当前双向链表头节点的后置节点引用,可能为null final Node<E> next = f.next; //设置当前双向链表头节点的数据对象为null,后置节点引用设置为null f.item = null; f.next = null; // help GC (帮助进行GC(java的垃圾回收机制)) //设置双向链表新的头节点为当前头节点的后置节点 first = next; //条件处理,说明完成头节点的移除操作,当前双向链表已经没有任何节点 if (next == null) //将last属性设置为null last = null; else //不成立,设置新的头节点前置节点设置为null,因为新的头节点的前置节点指向是原先的头节点 next.prev = null; //双向链表长度 - 1 size--; //LinkedList集合操作次数 + 1 modCount++; return element; }
unlinkLast(Node)方法的功能
unlinkLast(Node)方法的功能: 可移除LinkedList集合中双向链表的尾节点 且重新设置它的前置节点为新的尾节点 该方法入参l就是当前双向链表的尾节点 该参数不能为null
unlinkLast源码
private E unlinkLast(Node<E> l) { // assert l == last && l != null; //定义一个element变量记录当前双向链表尾节点中的数据对象,以便在最后将其返回 final E element = l.item; //创建一个prev变量,记录当前双向链表尾节点中的前置节点,该变量可能为null final Node<E> prev = l.prev; //设置当前双向链表尾节点中的数据为null,前置对象引用为null l.item = null; l.prev = null; // help GC 帮助进行GC(垃圾回收) //设置双向链表新的尾节点为当前尾节点的前置节点 last = prev; //条件处理,说明移除完尾节点之后双向链表已经没有任何节点了 if (prev == null) //设置头节点为null first = null; else //不成立,设置新的尾节点后置节点设置为null,因为新的尾节点的后置节点指向是原先的尾节点 prev.next = null; //双向链表长度 - 1 size--; //LinkedList集合操作次数 + 1 modCount++; return element; }
unlink(Node)方法功能
unlink(Node)方法的功能: 从双向链表中移除指定的节点 其入参x所指向的节点一定位于双向链表中
unlink(Node)方法源码
E unlink(Node<E> x) { // assert x != null; //定义一个element变量,记录当前节点中的数据对象,以便方法最后返回 final E element = x.item; //创建一个next节点,记录当前节点中的后置节点引用,可能为null final Node<E> next = x.next; //创建一个prev节点,记录当前节点中的前置节点引用,可能为null final Node<E> prev = x.prev; //如果条件成立,说明被移除的x节点是双向链表的头节点 if (prev == null) { //将x的后置节点设置为新的头节点 first = next; } else { //将x的前置节点中的后置节点设置为移除的x节点的后置节点 prev.next = next; //将移除的x节点的前置节点设置为null x.prev = null; } //如果条件成立,说明被移除的x节点是双向链表的尾节点 if (next == null) { //将移除的x的节点的前置节点设置为新的尾节点 last = prev; } else { //将x的后置节点中的前置节点设置为移除x节点的前置节点 next.prev = prev; //将移除的x节点的后置节点设置为null x.next = null; } //将移除的x节点中的数据对象设置为null x.item = null; //双向链表长度 - 1 size--; //LinkedList集合操作次数 + 1 modCount++; return element; }
LinkedList获取数据的方法
由于LinkedList中采用链表的结构存储数据 所以我们获取数据时,不能使用索引的方式获取数据 但是LinkedList对外提供一个node(int)方法 但是此方法的底层逻辑: 是采用遍历LinkedList检索数据,所以其查询效率相对于ArrayList低效
node源码
Node<E> node(int index) { // assert isElementIndex(index); //如果条件成立,说明当前指定的index号索引位在当前双向链表的前半段 if (index < (size >> 1)) { //从当前双向链表的头节点开始向后依次查询 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { //不成立,说明当前指定的index号索引位在双向链表的后半段,从当前双向链表的尾节点开始向后依次查询 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
indexOf(object)
此方法的核心原理: 也是采用遍历的方法依次查找LinkedList中的元素 当匹配到符合条件的元素时,则返回index位置 当匹配失败时,则返回-1
indexOf源码
public int indexOf(Object o) { int index = 0; //如果入参o为null if (o == null) { //从头节点开始向后遍历,直到找到某个item属性值为null的节点 for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { //不为null,从头节点开始向后遍历,直到找到某个item属性值向的内存地址和传入参数o指向的内存地址相同的节点 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。