0%
这是一片思考的空间 -- arthinking
Spring 重构&代码整洁之道 软件设计 JVM 并发编程 数据结构与算法 分布式 存储 网络 微服务 设计模式
Java技术栈 - 涉及Java技术体系

Java源码分析 - LinkedList双向链表源码分析

LinkedList就传说中的双向链表了。是List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

查看LinkedList的构造函数:

/**
* Constructs an empty list.
*/
public LinkedList() {
header.next = header.previous = header;
}

这里的header变量的定义如下:

private transient Entry header = new Entry(null, null, null);

表示双向循环链表的头结点。其中的Entry定义了双向链表的结构:

private static class Entry {
E element;
Entry next;
Entry previous;

Entry(E element, Entry<E> next, Entry<E> previous) {
    this.element = element;
    this.next = next;
    this.previous = previous;
}

}

Entry中包括数据元素、前续结点和后续结点。

构造函数即是初始化一个只包含header头结点一个元素的双向链表。

可以得出:LinkedList实质上就是一个双向链表,并把header作为头结点。

查看LinkedList的add(E)方法:

public boolean add(E e) {
addBefore(e, header);
return true;
}

add(E)方法主要指向了addBefore(E, Entry)函数:

private Entry addBefore(E e, Entry entry) {
Entry newEntry = new Entry(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}

这个方法中传入的第二个参数entry为header头结点,表示把元素插入到header头结点之前。然后后两句分别表示设置前续结点的后续结点和后续结点的前续结点。以便连接成双向链表。

可以得出:LinkedList的add(E)方法把新结点插入到header头结点之前,即列表的结尾。

查看LinkedList的set(int, E)方法:

/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
Entry e = entry(index);
E oldVal = e.element;
e.element = element;
return oldVal;
}

这个函数主要是设置某个索引位置的结点。首先调用了entry(int)方法:

/**
* Returns the indexed entry.
*/
private Entry entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Entry e = header;
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}

这里首先判断需要设置的位置是否越界,如果越界则抛出异常。然后通过循环找到需要替换的结点。

接下来回到set(int ,E)函数,把旧的结点替换成新的结点,并返回旧的结点。

可以得出:set(int ,E)方法需要循环遍历链表,时间开销比较大。

查看LinkedList的push(E)方法:

/**
* Pushes an element onto the stack represented by this list. In other
* words, inserts the element at the front of this list.
*
*

This method is equivalent to {@link #addFirst}.
*
* @param e the element to push
* @since 1.6
*/
public void push(E e) {
addFirst(e);
}

push(E)方法主要调用了addFirst(E)方法:

/**
* Inserts the specified element at the beginning of this list.
*
* @param e the element to add
*/
public void addFirst(E e) {
addBefore(e, header.next);
}

这里继续调用addBefore(E, Entity)表示把新节点插入到header头结点之后。

可以得出:push(E)方法主要是把新节点插入到header头结点之后。

查看LinkedList的pop()方法:

/**
* Pops an element from the stack represented by this list. In other
* words, removes and returns the first element of this list.
*
*

This method is equivalent to {@link #removeFirst()}.
*
* @return the element at the front of this list (which is the top
* of the stack represented by this list)
* @throws NoSuchElementException if this list is empty
* @since 1.6
*/
public E pop() {
return removeFirst();
}

这里继续调用了removeFirst()方法:

/**
* Removes and returns the first element from this list.
*
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
return remove(header.next);
}

这里继续调用remove(Entry)方法:

/**
* Retrieves and removes the head (first element) of this list.
*
* @return the head of this list
* @throws NoSuchElementException if this list is empty
* @since 1.5
*/
private E remove(Entry e) {
if (e == header)
throw new NoSuchElementException();

    E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
    e.next = e.previous = null;
    e.element = null;
size--;
modCount++;
    return result;

}

在调用remove(Entry)方法时,传入了header的下一个结点。如果传入的下一个结点是header的话,表示此事链表中只有一个结点header,此时抛出异常。如果找到则删除该节点(注意这里双向链表删除结点时的步骤),并返回被删除的结点。

可以得出:pop()方法删除的是header头结点之后的结点,并返回被删除的结点。

★ LinkedList实质上就是一个双向链表,并把header作为头结点。 ★ LinkedList的add(E)方法把新结点插入到header头结点之前。 ★ set(int ,E)方法需要循环遍历链表,时间开销比较大。 ★ push(E)方法主要是把新节点插入到header结点之前 ★ pop()方法删除的是header头结点之后的结点,并返回被删除的结点。

欢迎关注我的其它发布渠道

订阅IT宅
内功修炼
Java技术栈
Java架构杂谈是IT宅精品文章公众号,欢迎订阅:
📄 网络基础知识:两万字长文50+张趣图带你领悟网络编程的内功心法 📄 HTTP发展史:三万长文50+趣图带你领悟web编程的内功心法 📄 HTTP/1.1:可扩展,可靠性,请求应答,无状态,明文传输 📄 HTTP/1.1报文详解:Method,URI,URL,消息头,消息体,状态行 📄 HTTP常用请求头大揭秘 📄 HTTPS:网络安全攻坚战 📄 HTTP/2:网络安全传输的快车道 📄 HTTP/3:让传输效率再一次起飞 📄 高性能网络编程:图解Socket核心内幕以及五大IO模型 📄 高性能网络编程:三分钟短文快速了解信号驱动式IO 📄 高性能网络编程:彻底弄懂IO复用 - IO处理杀手锏,带您深入了解select,poll,epoll 📄 高性能网络编程:异步IO:新时代的IO处理利器 📄 高性能网络编程:网络编程范式 - 高性能服务器就这么回事 📄 高性能网络编程:性能追击 - 万字长文30+图揭秘8大主流服务器程序线程模型
📄 Java内存模型:如果有人给你撕逼Java内存模型,就把这些问题甩给他 📄 一文带你彻底理解同步和锁的本质(干货) 📄 AQS与并发包中锁的通用实现 📄 ReentrantLock介绍与使用 📄 ReentrantReadWriteLock介绍与使用 📄 ReentrantLock的Condition原理解析 📄 如何优雅的中断线程 📄 如何优雅的挂起线程 📄 图解几个好玩的并发辅助工具类 📄 图解BlockingQueue阻塞队列
📄 消息队列那么多,为什么建议深入了解下RabbitMQ? 📄 高并发异步解耦利器:RocketMQ究竟强在哪里? 📄 Kafka必知必会18问:30+图带您看透Kafka
📄 洞悉MySQL底层架构:游走在缓冲与磁盘之间 📄 SQL运行内幕:从执行原理看调优的本质 📄 洞悉Redis技术内幕:缓存,数据结构,并发,集群与算法