Java容器源码(四)——LinkedList源码分析(基于JDK8)
2019-08-24 21:42
429 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41799019/article/details/100055852
更多Java容器源码分析可以参考:Java容器源码分析系列(持续更新中!)
文章目录
更多Java容器源码分析可以参考:Java容器源码分析系列(持续更新中!)
(一)、概述
-
LinkedList是实现了List接口和Deque接口的双端链表。
-
Linked底层的数据结构是链表,不支持随机读取,但是在插入和删除方面就会显得很高效。
-
同时LinkedList它也实现了Deque接口,这使他也具有了队列的特性。
-
LinkedList是线程不安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchroizedList方法:
List list = Collections.synchronizedList(new 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; } }
- 因为LinkedList底层是由双向链表实现的,所以需要创建一个节点类进行存储
- 节点类中包含三个属性,分别用来存放节点中的元素,前驱节点、后继节点
(三)、类名
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- Linked继承自AbstractSequentialList、实现了List接口、Deque接口、Cloneable接口、Serializable接口。
- AbstractSequentialList:继承自AbstractList,而AbstractList又实现了List接口
- List接口:实际上这里只是表示符作用,因为AbstractList已经实现了List接口
- Deque接口:这个接口使得LinkedList具备一些队列的特性
- Cloneable接口:实现了浅克隆功能
- Serializabe接口:实现了序列化和反序列化的功能。
(四)、属性
/** * 标识链表的实际长度 */ transient int size = 0; /** * 用来表示链表中的头节点 */ transient Node<E> first; /** * 用来表示链表中的尾节点 */ transient Node<E> last;
- LinkedList拥有的三个属性都比较简单,分别是长度,头节点以及尾节点。这里就不需要做过多的解释。
(五)、构造方法
/** * 空的构造方法 */ public LinkedList() { } /** * 有参构造方法,参数为一个集合c */ public LinkedList(Collection<? extends E> c) { //调用无参的构造方法 this(); //调用addAll(c)方法 addAll(c); } public boolean addAll(Collection<? extends E> c) { //调用addAll(size,c)方法 return addAll(size, c); } /** * 检查插入的位置是否合法 */ private void checkPositionIndex(int index) { //调用方法 if (!isPositionIndex(index)) //如果位置符合法,就抛出异常 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 检查位置是否符合要求 */ private boolean isPositionIndex(int index) { //要求index必须>=0或者<=size return index >= 0 && index <= size; } /** * 获取index位置上的元素 */ Node<E> node(int index) { //通过移位操作,判断index位置离左端点近,还是右端点近 if (index < (size >> 1)) { //如果是离左端点近,就从左向右遍历,首先获取头节点 Node<E> x = first; //通过for循环遍历 for (int i = 0; i < index; i++) x = x.next; //返回index位置上的值 return x; } else { //如果是离右端点近,就从右向左遍历,首先获取尾节点的值 Node<E> x = last; //铜鼓for循环循环遍历 for (int i = size - 1; i > index; i--) x = x.prev; return x; } } /** * 插入集合中的所有元素方法 */ public boolean addAll(int index, Collection<? extends E> c) { //检查位置是否合法 checkPositionIndex(index); //将集合c转换为数组 Object[] a = c.toArray(); //计算元素的个数 int numNew = a.length; //如果集合为空,就直接不进行插入 if (numNew == 0) return false; //创建前驱节点pred和当前节点succ Node<E> pred, succ; //如果要插入的位置是最末尾 if (index == size) { //则当前节点为空 succ = null; //前驱节点就是最末尾的元素 pred = last; } else { //否则,获取index上的元素 succ = node(index); //根据succ,获得index-1上的节点 pred = succ.prev; } //遍历数组中的元素 for (Object o : a) { //进行强制类型转化 @SuppressWarnings("unchecked") E e = (E) o; //创建一个节点 Node<E> newNode = new Node<>(pred, e, null); //如果没有前驱节点,表明是一个空链表,就设置为头节点 if (pred == null) first = newNode; else //否则设置pred的后继节点为newNode pred.next = newNode; pred = newNode; } //如果当前节点为null,说明是要在末尾插值 if (succ == null) { //设置为尾节点 last = pred; } else { //反之,将新加上的链表连上后面的链表,形成双向链表 pred.next = succ; succ.prev = pred; } //链表的长度需要修改 size += numNew; //修改次数+1 modCount++; return true; }
- LinkedList主要有两个构造方法:一个是空的构造方法LinkedList(),另一个是参数为集合的有参构造方法LinkedList(Collection<? extends E> c)。
- 有参构造方法LinkedList(Collection<? extends E> c)中涉及到调用很多方法,下面我来一一解释其中的过程,所有方法的详细代码也都在上面的代码块中:
-
首先是调用了addAll (c )方法,将集合c作为参数进行传递
- 然后又调用addAll(size, c)方法,将链表的长度和集合c作为参数进行传递
- 进入到addAll()方法中,又马上调用了checkPositionIndex(index),这个方法主要是用来检查要插入的位置index是否合法。
- 紧接着,先将集合c转换为数组a。然后判断index位置的位置,获得当前节点succ以及前驱节点pred
- 然后使用增强for循环从数组中取出元素,逐一创建一个新节点,然后将节点插入到链表中。
- for循环完了之后,判断pred节点是否已经是尾节点,如果是,就设置为尾节点。否则,将后续的链表连上。
- 最后,要记得把链表的长度增加,并且修改次数+1
(六)、add()方法
/** * 在末尾添加单个元素的add方法 */ public boolean add(E e) { //调用linkLast在末尾添加上元素 linkLast(e); return true; } /** * 在指定位置添加上元素 */ public void add(int index, E element) { //查看index位置是否合法 checkPositionIndex(index); //查看想插入的位置是否为最末尾 if (index == size) //调用linkLast在最末尾插入元素 linkLast(element); else //调用linlBefore在index位置上插入元素 linkBefore(element, node(index)); } /** * 插入元素成为头节点 */ public void addFirst(E e) { //在第一个位置插入元素 linkFirst(e); } /** * 插入元素成为尾节点 */ public void addLast(E e) { //在最后一个位置插入元素 linkLast(e); } /** * 在第一个位置插入元素 */ private void linkFirst(E e) { //获取头节点的元素 final Node<E> f = first; //根据元素e创建一个新的节点 final Node<E> newNode = new Node<>(null, e, f); //设置头节点为newNode first = newNode; //如果f==null,代表原本是一个空链表 if (f == null) //同样设置尾节点为newNode last = newNode; else //否则,设置f的前驱节点为newNode f.prev = newNode; //数目+1 size++; //修改次数+1 modCount++; } /** * 在最后一个位置插入元素 */ void linkLast(E e) { //获取尾节点 final Node<E> l = last; //根据元素e创建一个新的节点newNode final Node<E> newNode = new Node<>(l, e, null); //设置尾节点为newNode last = newNode; //如果l==null,说明链表本来是空链表 if (l == null) //设置头节点为newNode first = newNode; else //否则设置尾节点的后继为newNode l.next = newNode; //数目+1 size++; //修改次数+1 modCount++; } /** * 在index位置上插入元素 */ void linkBefore(E e, Node<E> succ) { //通过当前节点succ蝴蝶前驱节点pred final Node<E> pred = succ.prev; //根据元素e创建一个新的节点newNode final Node<E> newNode = new Node<>(pred, e, succ); //设置newNode为succ的前驱节点 succ.prev = newNode; //如果前驱节点为null,那么newNode就作为头节点 if (pred == null) first = newNode; else //否则设置pred的后继节点为newNode pred.next = newNode; //数目加1 size++; //修改次数+1 modCount++; }
- 大家通过上面的源码可以发现,前面四个的add方法实现都是较为简单的,分别调用linkFirst()方法以及linkLast()方法还有LinkBefore()方法 ,所以这里也选择这两个方法来进行讲解。
- linkFirst()方法:
-
首先,获取头节点的元素,并且根据传进来的元素创建一个新的节点,并赋值为头节点
- 然后,查看之前的链表是否为空链表,如果是空链表,就设置newNode也为尾节点,否则设置f的前驱节点为newNode
- 最后长度+1,修改次数+1
- linkLast()方法:
-
首先,获取尾节点的元素,并且根据传进来的元素创建一个新的节点,并赋值为尾节点
- 然后,查看之前的链表是否为空链表,如果是空链表,就设置newNode也为头节点,否则设置f的后继节点为newNode
- 最后长度+1,修改次数+1
- linkBefore()方法:
-
首先,根据参数中传入进来的当前节点succ获取它的前驱节点,并给元素e创建一个新的节点
- 将succ的前驱节点赋值为newNode
- 根据判断pred是否为空,可以确定succ是否为头节点,如果不是就pred的后继节点设置为newNode
- 最后长度+1,修改次数+1
(七)、remove()方法
/** * 删除的方法 */ public E remove() { //调用删除头节点的方法 return removeFirst(); } /** * 删除指定的index位置的方法 */ public E remove(int index) { //查看index位置是否合法 checkElementIndex(index); //删除掉指定位置 return unlink(node(index)); } /** * 删除指定元素的方法 */ public boolean remove(Object o) { //查看对象是否为null if (o == null) { //for循环遍历每一个节点 for (Node<E> x = first; x != null; x = x.next) { //如果为null if (x.item == null) { //删除该节点 unlink(x); return true; } } } else { //遍历每一个节点 for (Node<E> x = first; x != null; x = x.next) { //如果节点的元素和o相等 if (o.equals(x.item)) { //删除该节点 unlink(x); return true; } } } //删除失败,返回false return false; } /** * 删除头节点元素 */ 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; //如果尾节点的元素为null,抛出异常 if (l == null) throw new NoSuchElementException(); //调用方法删除尾节点 return unlinkLast(l); } /** * 删除指定节点 */ E unlink(Node<E> x) { //获取x节点中的元素 final E element = x.item; //获取x节点的后继节点 final Node<E> next = x.next; //获取x节点的前驱节点 final Node<E> prev = x.prev; //如果前驱节点为null if (prev == null) { //那就将后继节点next设置为头节点 first = next; } else { //否则,让前驱节点的后继节点指向next prev.next = next; //设置x的节点为null,方便gc x.prev = null; } //如果后继节点为null if (next == null) { //设置前驱节点prev为尾节点 last = prev; } else { //否则设置next的前驱节点为prev next.prev = prev; //设置x的后继节点为null,方便gc x.next = null; } //设置x的元素为null,方便gc x.item = null; //数目减1 size--; //修改次数+1 modCount++; return element; } /** * 删除链表的首节点 */ private E unlinkFirst(Node<E> f) { // 获得首结点中的元素 final E element = f.item; //获取第二个位置的元素 final Node<E> next = f.next; //置为null,方便垃圾收集 f.item = null; f.next = null; // help GC //设置next为头节点 first = next; //如果next==null,说明成为空链表 if (next == null) //将last也置为null last = null; else //否则设置next的前驱节点为null next.prev = null; //数目-1 size--; //修改次数+1 modCount++; return element; } /** * 删除链表的最后一个节点 */ private E unlinkLast(Node<E> l) { // 获得链表的尾节点中的元素 final E element = l.item; //获得倒数第二个元素 final Node<E> prev = l.prev; //置为null,方便垃圾收集 l.item = null; l.prev = null; // help GC //将倒数第二个元素设置为尾节点 last = prev; //如果prev==null,说明成为了空链表 if (prev == null) //设置first==null first = null; else //否则设置prev的后置节点为null prev.next = null; //数目减1 size--; //修改次数+1 modCount++; return element; }
- 大家通过上面的源码可以发现,前面的几个remove()方法也是比较简单实现,分别调用unlink()方法以及unlinkLast()方法还有unlinkFirst()方法 ,所以这里也选择这三个方法来进行讲解。
- unlink()方法:
-
首先,获取x节点中的元素,并且获取x节点的后继节点next以及x节点的前驱节点prev
- 然后,如果前驱节点为null,那就将后继节点next设置为头节点。否则,让前驱节点的后继节点指向next
- 如果后继节点为null,设置前驱节点prev为尾节点。否则设置next的前驱节点为prev
- 最后长度-1,修改次数+1
- unlinkFirst()方法:
-
因为这个节点是private修饰的,所以我们外部无法直接调用。它是有限制传入的节点必须为头节点的。
- 首先获得头节点中的元素,然后获得第二个位置上的元素next,设置next节点为头节点
- 然后判断是否删除了原来节点之后成为了空链表,如果是,将last也置为null。否则设置next的前驱节点为null
- 最后长度-1,修改次数+1
- unlinkLast()方法:
-
因为这个节点是private修饰的,所以我们外部无法直接调用。它是有限制传入的节点必须为尾节点的。
- 首先获得尾节点中的元素,然后获得倒数第二个位置上的元素pred,设置pred节点为尾节点
- 然后判断是否删除了原来节点之后成为了空链表,如果是,将first也置为null。否则设置pred的后继节点为null
- 最后长度-1,修改次数+1
(八)、get()方法和peek()方法
/** * 获取指定位置上的值 */ public E get(int index) { //查看位置是否合法 checkElementIndex(index); //返回该位置上的值 return node(index).item; } /** * 获取头节点的元素 */ public E getFirst() { //获得头节点 final Node<E> f = first; //如果头节点为null,抛出异常 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 peek() { //获取首节点 final Node<E> f = first; //返回首节点元素 return (f == null) ? null : f.item; } /** * 获取首节点的元素 */ public E peekFirst() { //获得首节点 final Node<E> f = first; //弹出首节点的元素 return (f == null) ? null : f.item; } /** * 弹出尾节点的元素 */ public E peekLast() { //获得尾节点 final Node<E> l = last; //返回尾节点的元素 return (l == null) ? null : l.item; }
- get()方法和peek()方法都实现比较简单,上面注释详细,应该比较容易理解
- 这里主要对比区别。get方法返回首节点和尾节点的时候,如果为null,则会抛出异常。而
peek方法返回首节点和尾节点的时候不会抛出异常。
(九)、其他方法
/** * 查看index是否是在合法的范围中 */ private boolean isElementIndex(int index) { return index >= 0 && index < size; } /** * 查看index是否是合法的插入位置 */ private boolean isPositionIndex(int index) { return index >= 0 && index <= size; }
相关文章推荐
- LinkedList源码分析(基于JDK8)
- Java -- 基于JDK1.8的LinkedList源码分析
- LinkedList源码分析(基于JDK1.6)
- Java集合框架成员之LinkedList类的源码分析(基于JDK1.8版本)
- Java Collections Framework之LinkedList源码分析(基于JDK1.6)
- Java容器(三):LinkedList源码分析
- Java Collections Framework之Deque(LinkedList实现)源码分析(基于JDK1.6)(已补充)
- Java Collections Framework之Queue(LinkedList实现)源码分析(基于JDK1.6)
- Java容器源码(五)——HashMap源码分析(基于JDK8)
- LinkedList源码分析(基于jdk1.8)
- Android版数据结构与算法(三):基于链表的实现LinkedList源码彻底分析
- 一个学生对Linkedlist源码分析注释
- java源码分析之集合框架 ArrayList和LinkedList的区别05
- LinkedList源码分析
- java8源码分析LinkedList
- java集合框架04——LinkedList和源码分析
- Java集合之LinkedList源码分析
- 基于JDK1.8的LinkedList源码学习笔记
- Java集合源码分析(三)LinkedList
- LinkedList源码分析