LinkedList是一个双向链表,它主要有两个表示头尾节点的成员变量first 、last,因为有头尾两个节点,使其很方便分别从头尾操作数据。LinkedList通过内部类Node来保存元素 ,一个Node对象表示一个链表的节点,有多少个元素就需要同样个Node节点。如果要添加元素,则新建一个Node节点,保存这个元素,同时指定其前驱节点和后继节点的引用。若要删除一个元素,则将取消此元素对应的Node节点在链表中的前驱后继关系。
Node类有3个成员变量:
1)表示 当前节点保存的元素item ,
2)表示后继节点的引用next
3)表示前驱节点的引用 prev
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类有这3个成员能够很清楚的表示自己的逻辑结构。首先能确定自己的位置,前驱后prev/继节next点的引用指明了当前节点在链表结构中的位置;另外成员变量item确定了当前节点要保存的元素。
其成员变量主要是表示链表头 尾节点的两个成员变量:头节点first 尾节点last
因为有头尾两个节点,可以很方便地从头部或从尾部这两个方向进行逻辑操作。
只用一个头节点也可实现单向链表,它的效率不高,因为只能从单向从头到尾遍历,不能根据实际情况选择更优的遍历方向。
// 链表的所含元素个数 transient int size = 0; // 链表的头节点 transient Node<E> first; // 链表的尾节点 transient Node<E> last; /* * 此属性在本类中没有,此属性是从父类AbstractSequentialList中继承而来 * 链表结构被修改的次数,防止在迭代遍历过程中,修改链表结构 */ protected int modCount = 0;
构造方法有两个,一个默认构造方法,将初始化一个空链表;另一个带单参数的构造方法,将初始化后,此链表将包含参数c的所有元素
public LinkedList() { } //头尾节点都是null的空链表 //初始化后,包含集合c所有元素的链表 public LinkedList(Collection<? extends E> c) { this(); addAll(c);//添加集合c所有元素 }
1). public boolean add(E e) 在尾部添加一个元素
此方法去调用了一个对链表尾部连接新元素的方法linkLast(E)
public boolean add(E e) { linkLast(e);//在尾部链接一个新元素 return true; }
此时去看看linkLast(E)的实现细节:
在添加新元素时要注意特殊情况,即要考虑当前的链表为空、不含任何元素的可能,此时需要将当前新添加的节点设为头节点
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++;//添加一个元素,链表结构被修改一次,modCount自加一 }
2)public void add(int index, E element) 在指定下标处添加元素
此方法有两个分支,若插入的下标位置等于链表元素个数,则可直接在链表尾部添加要插入的元素;而其它一般情况则调用linkBefore(E,Node)方法,指定的节点之前插入元素。
而linkBefore(E,Node)方法又调用了Node<E> node(int)方法。
public void add(int index, E element) { checkPositionIndex(index); //检查下标是否合法 if (index == size) //插入的位置index=size即可直接在链表尾部添加元素 linkLast(element); else //在指定的节点之前插入元素 linkBefore(element, node(index)); }
先来看看node(int)方法是如何根据下标查找到下标对应的Node节点
node(int)方法中有两个分支,根据index是否小于size的一半作为进入不同分支的条件。此处分支条件是用来确定待查找的节点 与头/尾节点的距离,若待查找的节点和头节点较近,就从头节点开始查找;若待查找的节点与尾节点较近,则从尾节点开始遍历查找。这主要是从遍历的效率角度考虑的。
Node<E> node(int index) { //index作为循环遍历停止的条件 if (index < (size >> 1)) { // 如果index小于size的一半,则从头部开始向后查找 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; //将当前节点的后继节点赋值给当前节点 return x; } else { // 如果index大于size的一半,则从尾部开始向前查找 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev;//将当前节点的前驱节点赋值给当前节点 return x; } }
最后来看linkBefore(E,Node)如何在指定的节点前添加指定的元素
其中也对插入位置的进行了判断,如果是在头部插入节点,则需要将头节点的引用更新为待插入节点。
void linkBefore(E e, Node<E> succ) { //插入点节点succ的前驱节点pred final Node<E> pred = succ.prev; /* * 待插入的节点, * 此节点将取代插入点节点succ,插入点succ将后移一位 * 此节点的前驱是插入点节点的前驱节点pred,其后继节点就是插入点节点succ */ final Node<E> newNode = new Node<>(pred, e, succ); //插入点节点的前驱是待插入节点 succ.prev = newNode; if (pred == null) /* * 插入点节点的前驱节点pred为为空,表明此时在头部直接插入节点 * 那么链表更新后,当前的待插入节点就是头节点 */ first = newNode; else /* * 插入点节点的前驱节点pred的后继节点是待插入节点 */ pred.next = newNode; size++; modCount++; }
3) public E remove(int index)
public E remove(Object o)
5)public E set(int index, E element)
6)public E get(int index)
跟踪LinkedList源码,通过分析双向链表实现原理,自定义一个双向链表
原文:https://www.cnblogs.com/gocode/p/11519541.html