内容简介:LinkedList的底层是一个双向链表结构,因此他具有链表的特性,插入删除快,查找慢。更有意思的是LinkedList可以认为是集链表与队列与一身的一个实现类。下面通过阅读源码来理解一下。LinkedList内部维护一个Node类,从代码上可知该类表示一个双向链表。size 表示当前链表的大小, first表示头节点 last表示尾节点
LinkedList的底层是一个双向链表结构,因此他具有链表的特性,插入删除快,查找慢。更有意思的是LinkedList可以认为是集链表与队列与一身的一个实现类。下面通过阅读源码来理解一下。
实际结构
双向链表
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内部维护一个Node类,从代码上可知该类表示一个双向链表。
成员变量
transient int size = 0; transient Node<E> first; transient Node<E> last; 复制代码
size 表示当前链表的大小, first表示头节点 last表示尾节点
构造函数
public LinkedList() { } 复制代码
默认无参构造函数,什么都不做。
public LinkedList(Collection<? extends E> c) { this(); addAll(c); } 复制代码
通过一个集合来构造,首先创建一个空的自己,然后将集合c追加到自己的末尾。
添加元素
添加
public boolean add(E e) { linkLast(e); return true; } 复制代码
追加到链表末尾。
指定位置添加
public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } 复制代码
逻辑:校验index是否合法,否则抛出越界异常。如果index是最后一个位置,那么调用linkLast方法追加到原链表最后,否则插在指定位置。 这里有两个私有方法需要看一下
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; } } 复制代码
根据索引index,获取指定位置的节点。这里做了一个优化,如果index在前半部分,那么就从first开始往下找,如果index在后半部分,那就从last往前开始找。
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++; } 复制代码
在succ节点之前插入一个节点,该节点元素值为e。 逻辑:先找到原succ节点的前一个节点,缓存下来,记为pred。新建一个节点,该 节点的元素值为e,prev指针指向pred节点,next指针指向succ节点。succ节点的prev指针指向新节点. 此时需要判断,如果原来succ节点是头结点,那么新添加的节点为新的first头结点。否则的话, 原来的pred节点的prev指针指向我这个新节点,那么我就插入到了pred节点和succ节点的中间。
移除元素
指定位置移除
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } 复制代码
来看私有方法 unlink
E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; } 复制代码
逻辑:先缓存下我这个节点的prev和next节点. 如果原来我是头节点first,那么只需把first节点指向我的next,我就从原始链表中脱离了。否则的话我的prev节点的next指针指向我的next节点。
如果原来我是尾节点last,那么只需把last节点指向我的prev,我就从原始链表中脱离了。否则的话,我的next节点的prev指针指向我的prev节点.
脱离链表后,需要把我的元素置为null,否则会造成内存泄露。
指定元素移除
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; } 复制代码
逻辑相似,通过从头节点遍历的方式查找到要删除的第一个节点,然后调用unlink方法进行移除.
查找
public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; } 复制代码
顺序查找,找到第一个就返回该值索引,如果没找到就返回-1.
public int lastIndexOf(Object o) { int index = size; if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; } 复制代码
倒序查找,找到第一个就返回该值索引,如果没找到就返回-1.
获取
public E get(int index) { checkElementIndex(index); return node(index).item; } 复制代码
获取指定位置的元素值。
修改
public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; } 复制代码
修改指定位置的元素值,返回旧值。
以上的增加、删除、查找接口都是实现List接口,而LinkedList不仅实现了List接口,还实现的Deque接口,双向队列。因此它作为一个双向队列,还提供了相应的增删查改接口。
队列的特点是先入先出。所以不会提供指定位置的增删查改功能,只能读写头尾节点。
添加
添加到开头
public void addFirst(E e) { linkFirst(e); } 复制代码
public boolean offer(E e) { return add(e); } 复制代码
public boolean offerFirst(E e) { addFirst(e); return true; } 复制代码
public void push(E e) { addFirst(e); } 复制代码
私有方法 linkFirst
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++; } 复制代码
逻辑:新建一个节点,该节点的元素值为e,prev指针为null,next指针为原来头结点。然后再把first头结点指针指向新节点。此处需要注意的是如果原来没有节点,当前新增节点为第一个节点,那么last指针也指向该节点。
添加到末尾
public void addLast(E e) { linkLast(e); } 复制代码
私有方法 linkLast
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++; } 复制代码
逻辑:新建一个节点,该节点的元素值为e,prev指针为原来的尾节点,next指针为null,然后再把last指针指向该节点。此处需要注意的是,如果原来没有节点,当前新增节点为第一个节点,那么first指针也指向该节点。
移除元素
移除头节点
public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } 复制代码
public E pop() { return removeFirst(); } 复制代码
public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } 复制代码
public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } 复制代码
来看私有方法unlinkFirst
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; } 复制代码
逻辑:要移除一个节点,则将该节点的prev指针、next指针都置为null,那么当前节点就从原链表中脱离了。同时要将这个节点的元素置为null,否则会造成内存泄露. 再将first节点置为原节点的next。此时需要注意如果原来只有一个节点,移除之后last节点变成了null.
移除尾节点
public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); } 复制代码
public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); } 复制代码
来看私有方法unlinkLast
private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; } 复制代码
相同的逻辑,该节点的prev指针、next指针均置为null,则该节点从原链表中脱离出来,此时需要将该节点的元素置为null,避免内存泄露。 同时需要注意如果原链表只有这一个节点,那么移除该节点后,first节点指向null。
读取数据
public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } 复制代码
public E element() { return getFirst(); } 复制代码
public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; } 复制代码
public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
游戏运营:高手进阶之路
饭大官人 / 电子工业出版社 / 2018-1-1 / 79.00元
《游戏运营:高手进阶之路》是一本系统的、成体系的、注重运营效能、强化系统思维、提升专业认知的书籍。《游戏运营:高手进阶之路》几乎完整覆盖了一个游戏运营人员日常工作中的方方面面,并从工作中具体的业务场景出发,归纳整理出各种解决问题的方法论。《游戏运营:高手进阶之路》为广大游戏从业者建立了完整的知识技能成长体系,包含两大岗位基本功—内容输出和协作推进,四大职业技能—活动策划、版本管理、用户运营、数据分......一起来看看 《游戏运营:高手进阶之路》 这本书的介绍吧!