Java集合框架:LinkedList
简单介绍:
LinkedList 也是List接口的实现类。一样有序且允许重复,但是与ArrayList的结构不同。ArrayList是基于数组的结构。而ArrayList是基于双向链表的结构。链表的元素的内存空间可以不连续。在插入和删除的效率比较高,随机访问的速度比较慢。
时间复杂度:
add:O(1)
remove:O(1)
get:O(n)
set:O(n)
类名定义:
节点类
节点类为实际的存储元素的类,一共有3个变量,item数据域,next后一个节点,prev前一个节点.节点类的构造函数的参数顺序为:前一节点,元素,后一节点。
成员变量
LinkedList的成员变量就3个,size实际元素个数,first头节点,last尾部节点,都用transient关键字修饰,意味着在序列化时将不会被序列化
添加元素
void linkLast(E e) {
//获取last节点 final Node<E> l = last;
//new一个以last节点为前驱节点的元素 final Node<E> newNode = new Node<>(l, e, null);
//last指向新的尾部节点。 last = newNode;
//如果尾部节点为空,则将first节点指向新的节点 if (l == null) first = newNode;
//如果尾部元素不为空,则原来的last元素节点的next域为新的节点 else l.next = newNode;
//元素个数+1,结构修改次数+1 size++; modCount++; }
下面引用其他博客图示来简要介绍一下add的过程
首先new一个LinkedList实例对象的时候都是Null。list.add(5),先新建一个前驱节点为null,数据域为5,末尾节点为null的节点。last指向这个节点。由于原先的last为Null,所以first也会指向这个节点,即形成了第二行的图示。
list.add(6)。类似的步骤,先获取当前尾部节点(即5节点),新建一个prev域为5节点,数据域为6,next域为Null的节点,last指向这个节点。原先的last不为空,所以5节点next域指向6节点。形成一个双向的指针。
删除元素
public E remove() { return removeFirst(); }
public E removeFirst() { 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; 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; }
不带参数将直接removeFirst,如果没有元素,则抛出
NoSuchElementException异常。否则执行
unlinkFirst(Node<E> first).简要分析:定义element和next节点来存储first指向的元素的item和next。然后直接将first指向的节点的item和next域都置null。然后判断next是否为空,如果不为空,则first=next(first指向下一个节点)
为空的话,直接将prev域也置null就可以了。(源码都标注出了置null是为了方便GC。。哈哈,我一般写代码都直接指向下一个节点断开就好了。。要改。。)
unlinkLast也是类似,就不写了。
带下标的remove:
public E remove(int index) { checkElementIndex(index);//检查下标是否越界 return unlink(node(index)); }
node(index)根据下标寻找元素的方法
Node<E> node(int index) { // assert isElementIndex(index); //如果Index小于元素个数的一般,则从first开始,按next的方式往后遍历 if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { //如果index大于等于元素的一半,则从last开始,按prev的方式往前遍历。 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
断开该节点的unlink方法:(这方法其实是比较基本的双向链表remove的方法了。简单注释一下就好了。)
E unlink(Node<E> x) { // assert x != null; //定义3个常量存储要删除的节点的3个变量的值 final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; //如果prev域为空,意味着当前元素为first节点。直接first指向下一个节点就好。 否则prev域的节点的next域直接指向当前节点的下一节点。当前的节点的prev置null(有点绕,但是用过链表应该都能懂,就是改变指针指向的方法方法而已。) if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } //如果next域为null,意味着当前节点为last节点,直接last节点指向prev指向的节点就好。否则next节点的prev指向当前节点的prev指向的节点。当前next置null if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } //最后数据域置null,元素个数-1,结构改变次数+1 x.item = null; size--; modCount++; return element; }
remove(Object o)
修改元素
set方法一样是检查下标,通过node(index)方法遍历到要找的节点,然后替换。返回旧值
获取元素
get方法,检查下标,遍历下标找到节点。
其他
由于LinkedList方法也实现了Deque(双端队列),所以双端队列的方法,push,pop,peek等也可以实现。暂时不介绍了。数据结构写到队列的时候还会提一下。这些都是比较基本的数据结构的方法。
图片来源:https://www.cnblogs.com/leesf456/p/5308843.html