博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
LinkedList源码阅读分析
阅读量:6115 次
发布时间:2019-06-21

本文共 8706 字,大约阅读时间需要 29 分钟。

LinkedList的底层是一个双向链表结构,因此他具有链表的特性,插入删除快,查找慢。更有意思的是LinkedList可以认为是集链表与队列与一身的一个实现类。下面通过阅读源码来理解一下。

实际结构

双向链表

private static class Node
{ E item; Node
next; Node
prev; Node(Node
prev, E element, Node
next) { this.item = element; this.next = next; this.prev = prev; } }复制代码

LinkedList内部维护一个Node类,从代码上可知该类表示一个双向链表。

成员变量

transient int size = 0;transient Node
first;transient Node
last;复制代码

size 表示当前链表的大小, first表示头节点 last表示尾节点

构造函数

public LinkedList() {    }复制代码

默认无参构造函数,什么都不做。

public LinkedList(Collection
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
node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node
x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node
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
succ) { // assert succ != null; final Node
pred = succ.prev; final Node
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
x) { // assert x != null; final E element = x.item; final Node
next = x.next; final Node
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
x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node
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
x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node
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
x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node
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
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
f = first; final Node
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
l = last; final Node
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
f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); }复制代码
public E pop() {        return removeFirst();    }复制代码
public E pollFirst() {        final Node
f = first; return (f == null) ? null : unlinkFirst(f); }复制代码
public E poll() {        final Node
f = first; return (f == null) ? null : unlinkFirst(f); }复制代码

来看私有方法unlinkFirst

private E unlinkFirst(Node
f) { // assert f == first && f != null; final E element = f.item; final Node
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
l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); }复制代码
public E pollLast() {        final Node
l = last; return (l == null) ? null : unlinkLast(l); }复制代码

来看私有方法unlinkLast

private E unlinkLast(Node
l) { // assert l == last && l != null; final E element = l.item; final Node
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
f = first; return (f == null) ? null : f.item; }复制代码
public E element() {        return getFirst();    }复制代码
public E peekFirst() {        final Node
f = first; return (f == null) ? null : f.item; }复制代码
public E pollFirst() {        final Node
f = first; return (f == null) ? null : unlinkFirst(f); }复制代码

转载地址:http://aznka.baihongyu.com/

你可能感兴趣的文章
jqgrid滚动条宽度/列显示不全问题
查看>>
在mac OS10.10下安装 cocoapods遇到的一些问题
查看>>
angularjs表达式中的HTML内容,如何不转义,直接表现为html元素
查看>>
css技巧
查看>>
Tyvj 1728 普通平衡树
查看>>
[Usaco2015 dec]Max Flow
查看>>
javascript性能优化
查看>>
多路归并排序之败者树
查看>>
java连接MySql数据库
查看>>
转:Vue keep-alive实践总结
查看>>
android studio修改新项目package名称
查看>>
深入python的set和dict
查看>>
C++ 11 lambda
查看>>
Hadoop2.5.0 搭建实录
查看>>
实验吧 recursive write up
查看>>
High-speed Charting Control--MFC绘制图表(折线图、饼图、柱形图)控件
查看>>
go test命令參数问题
查看>>
linux 搜索文本
查看>>
超实用Mac软件分享(二)
查看>>
Android JSON数据解析
查看>>