LinkedList源码解析
来源:互联网 发布:达内c语言视频 编辑:程序博客网 时间:2024/06/06 07:14
Java版本
Java8
特点
- 同ArrayList一样,实现了List接口;
- 通过双向链表实现,在List中插入和删除更快;
- LinkedList可被当作队列,栈,双端队列操作;
- 顺序访问高效,随机访问效率较低;
- 同ArrayList,非线程安全
基本概念
- 链表
一种线性表,不按线性的顺序存储数据,在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,在插入的时候可以达到O(1)的复杂度,但是查找一个节点或者访问特定编号的节点(好像是用的是折半查找)则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。 - 单向链表
每个节点只包含后续节点的引用 - 双向链表
一个节点包含两个引用,上一个节点,下一个节点
实现原理
LinkedList的实现方式是这样的:
- LinkedList他在内存里面是离散的,不是连续。
- LinkedList里面的每一个元素都有下一个元素的引用。
- 当你想插入某个元素的时候,比如:已有一个LinkedList里面是 A B C三个元素(也就是,A的引用里面放着B,B的引用里面放着C,C的引用里面是null)现在你要在A和B之间插入D,他的做法是这样的将A的引用指向D,D引用指向B,B的引用依然指向C,C的引用还是null。 所以他插入的是非常的快,就是改一下引用。
- 当你遍历的时候他会这样做,先将A遍历出来然后找A的引用就是B,再将B遍历出来再找B的引用,将C遍历出来,所以遍历也很快。
LinkedList类定义
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- LinkedList继承AbstractSequentialList,其为List的简化实现,里面实现了使用iterator实现的方法;而ArrayList直接继承AbstractList;
- 实现了List接口
- 实现Deque接口,Deque一个线性collection,支持在两端插入和移除元素,定义了双端队列的操作;
- 实现Cloneable接口,能clone;
- 实现了Serializable,支持序列化;
- 相比于ArrayList,这里没有RandomAccess,ArrayList支持随机访问,且效率更高,遍历时使用get比迭代器更快;因此,对于遍历LinkedList使用iterator方法,应该要比for循环get()的方法快;
LinkedList类属性
transient int size = 0; // 存储的节点个数transient Node<E> first; // 第一个节点transient Node<E> last; // 最后一个节点//内部类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<E> first;
记录该链表存储中的第一个节点Node<E> last;
记录该链表存储中的最后一个节点。 这两个属性比较重要,当添加或者移除节点时,都需要重新判断判断第一个节点与最后一个节点的值。transient修饰变量,序列化时不输出。
private static class Node<E> {}
使用static修饰的静态内部类,使用Node(E)来表示链表中节点,item表示当前节点存储元素值,next表示下一个节点,prev表示前一个节点。
LinkedList构造方法
public LinkedList() {}public LinkedList(Collection<? extends E> c) { this(); addAll(c);}
- LinkedList()
构建一个空链表; - LinkedList(Collection<? extends E> c)
构建一个包含Collection元素的列表。其实是通过两步实现,先调用LinkedList()构造方法,再调用addAll()方法,将Collection中元素添加进来。
LinkedList核心方法
添加元素
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++;} public void addFirst(E e) { linkFirst(e);}public void addLast(E e) { linkLast(e);} public boolean add(E e) { linkLast(e); return true;} public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index); Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; Node<E> pred, succ; if (index == size) { succ = null; pred = last; } else { succ = node(index); 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.next = newNode; pred = newNode; } if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true;}
public void addFirst(E e);
头部添加元素,调用linkFirst()方法,新建一个node,将该node的next指向原来的first节点,同时将该节点赋值给first节点。public void addLast(E e);
向链表尾部添加节点,调用linkLast()方法。public boolean addAll(int index, Collection<? extends E> c);
向链表中index位置添加集合元素。在该方法中,创建两个节点,pred与succ来存储临时节点变量。pred用来存储插入位置的前节点,succ用来存储,插入前原先位置的节点。然后使用for循环,向链接中添加节点,每次循环,再创建一个新节点,该节点的prev属性指向pred,如果让pred不为空,则表示当前插入位置不链表头部,则将该pred的next属性指向需要插入的节点;若pred为空,则该节点就直接为该链表的头节点。然后再更新pred的值为当前插入的节点。再来处理,原先插入位置的节点succ,对于ArrayList,此时的操作时调用System.arraycope()方法,将所有后续节点往后移动一位,导致ArrayList的插入节点效率低。对于LinkedList,若succ不为空,只需将succ的prev属性,指向pred,同时将pred的next属性指向succ即可。同时统计链表个数的size自加。该过程就类似于,接自行车链条,从中间断开后,将新的放进去,然后再将前后的扣给扣上。在这里存在一个问题,该方法是在指定index位置上插入集合,那么如何寻找到指定位置的节点,这里调用的是node(index)方法,代码如下。该方法返回index位置的节点。这里不是一上来就从头开始遍历,先判断index,如果小于链表总长度的一半,就从头开始遍历,否则从尾部开始遍历,时间复杂度降为O(n/2)
Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
移除方法
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; }
public boolean remove(Object o)
移除链表中首次出现指定的元素。主要是调用unlink(Node<E> x)方法。E unlink(Node<E> x)
类似于更换自行车链条,通过删除节点,获取到前一个节点prev,下一个节点next,将prev.next指向next,将next的prev指向prev。
其它移除方法:
- remove():
获取并移除此列表的头(第一个元素)。 - remove(int index):
移除此列表中指定位置处的元素。 - removeFirst():
移除并返回此列表的第一个元素。 - removeLast():
移除并返回此列表的最后一个元素。 - removeFirstOccurrence(Object o):
从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。 - removeLastOccurrence(Object o):
从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。
查找方法
- get(int index):
返回此列表中指定位置处的元素。 - getFirst():返回此列表的第一个元素。
- getLast():
返回此列表的最后一个元素。 - indexOf(Object o):
返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。 - lastIndexOf(Object o):
返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
其它方法
pop(), push(), offer(), peek()等都类似于get(),remove()方法。
总结
- LindedList双向链表实现。
- 批量插入快,只用找到指定位置,再改变前后索引即可。不同于ArrayList需要移动元素。
- 随机访问慢,需要通过头或者尾节点,一个个遍历下去。
- 若要进行大量的随机访问,使用ArrayList;如果要经常从表中插入或删除元素,应使用LinkedList。
对于ArrayList可以参考博客,ArrayList源码分析
说明
个人对LinkedList一点总结,存在错误地方,欢迎指正。 整理过程中,参考了多人博客,但链接已经找不到,在此没有列出。若有不当之处,联系后将会进行更改。
- linkedList 源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- ArrayList LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList集合源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- LinkedList源码解析
- 从源码解析LinkedList
- LinkedList源码解析
- 2017 Multi-University Training Contest
- 【Portfolio】IC、IR 和 BR 详解
- FIDO U2F NFC协议
- 记录spring controller从页面接收参数的几种方法
- jquery 的 map类型操作
- LinkedList源码解析
- Java
- 不正确退出vim
- Struts2:通过action标签向页面传值
- 算法-重建二叉树
- Hadhoop与HBase服务器启动与停止相关操作
- HDU 1114 Piggy-Bank
- 一些常用git基本命令
- 遇到问题-----web前端----select默认选中无效