前言

本篇主要记录Java集合类中LinkedList的用法、结构以及部分实现。

LinkedList简介

LinkedList是一个实现了List接口和Deque接口的双端链表。 它实现了的其他接口还有Cloneable, java.io.Serializable,另外他也继承了AbstractSequentialList抽象类。

LinkedList底层的链表结构使它支持高效的插入和删除操作,实现了Deque接口,使得LinkedList类也具有队列的特性;
LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:

List list=Collections.synchronizedList(new LinkedList(...));

内部结构分析

LinkedList的内部结构如下图所示:

Node就是链表中的节点。那么LinkedList类中的一个内部静态私有类Node就很好理解了:

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。这个类有三个属性,分别是前驱节点,本节点的值,后继结点。

LinkedList源码分析

构造方法

空构造方法:

public LinkedList() {}

用已有的集合创建链表的构造方法:

public LinkedList(Collection<? extends E> c) {this();addAll(c);
}

add方法

add(E e) 方法:将元素添加到链表尾部

public boolean add(E e) {linkLast(e);//这里就只调用了这一个方法return true;
}
/*** 链接。使得 e 作为最后一个元素。*/
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;elsel.next = newNode;//指向后继元素也就是指向下一个元素size++;modCount++;
}

add(int index,E e):在指定位置添加元素

public void add(int index, E element) {checkPositionIndex(index); //检查索引是否处于[0-size]之间if (index == size)//添加在链表尾部linkLast(element);else//添加在链表中间linkBefore(element, node(index));
}

linkBefore方法需要给定两个参数,一个插入节点的值,一个指定的node,所以我们又调用了Node(index)去找到index对应的node

addAll(Collection c ):将集合插入到链表尾部

public boolean addAll(Collection<? extends E> c) {return addAll(size, c);
}

addAll(int index, Collection c): 将集合从指定位置开始插入

public boolean addAll(int index, Collection<? extends E> c) {//1:检查index范围是否在size之内checkPositionIndex(index);//2:toArray()方法把集合的数据存到对象数组中Object[] a = c.toArray();int numNew = a.length;if (numNew == 0)return false;//3:得到插入位置的前驱节点和后继节点Node<E> pred, succ;//如果插入位置为尾部,前驱节点为last,后继节点为nullif (index == size) {succ = null;pred = last;}//否则,调用node()方法得到后继节点,再得到前驱节点else {succ = node(index);pred = succ.prev;}// 4:遍历数据将数据插入for (Object o : a) {@SuppressWarnings("unchecked") E e = (E) o;//创建新节点Node<E> newNode = new Node<>(pred, e, null);//如果插入位置在链表头部if (pred == null)first = newNode;elsepred.next = newNode;pred = newNode;}//如果插入位置在尾部,重置last节点if (succ == null) {last = pred;}//否则,将插入的链表与先前链表连接起来else {pred.next = succ;succ.prev = pred;}size += numNew;modCount++;return true;
}

上面可以看出addAll方法通常包括下面四个步骤:

  1. 检查index范围是否在size之内
  2. toArray()方法把集合的数据存到对象数组中
  3. 得到插入位置的前驱和后继节点
  4. 遍历数据,将数据插入到指定位置

addFirst(E e): 将元素添加到链表头部

 public void addFirst(E e) {linkFirst(e);}
private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点first = newNode;//如果链表为空,last节点也指向该节点if (f == null)last = newNode;//否则,将头节点的前驱指针指向新节点,也就是指向前一个元素elsef.prev = newNode;size++;modCount++;
}

addLast(E e): 将元素添加到链表尾部,与 add(E e) 方法一样

public void addLast(E e) {linkLast(e);
}

根据位置取数据的方法

get(int index): 根据指定索引返回数据

public E get(int index) {//检查index范围是否在size之内checkElementIndex(index);//调用Node(index)去找到index对应的node然后返回它的值return node(index).item;
}
Node<E> node(int index) {// 获取某位置的Node节点// 判断索引值是在链表前半部分还是后半部分,前半部分从头遍历// 后半部分从尾遍历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=0)数据方法:

public E getFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return f.item;
}
public E element() {return getFirst();
}
public E peek() {final Node<E> f = first;return (f == null) ? null : f.item;
}public E peekFirst() {final Node<E> f = first;return (f == null) ? null : f.item;
}

区别:
getFirst(),element(),peek(),peekFirst()
这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,其中getFirst()element() 方法将会在链表为空时,抛出异常。

element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException异常。

获取尾节点(index=-1)数据方法:

 public E getLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return l.item;}
public E peekLast() {final Node<E> l = last;return (l == null) ? null : l.item;
}

两者区别:
getLast() 方法在链表为空时,会抛出NoSuchElementException,而peekLast() 则不会,只是会返回 null

根据对象得到索引的方法

int indexOf(Object o): 从头遍历找

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;
}

int lastIndexOf(Object o): 从尾遍历找

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;
}

检查链表是否包含某对象的方法

contains(Object o): 检查对象o是否存在于链表中

 public boolean contains(Object o) {return indexOf(o) != -1;}

删除方法

remove() ,removeFirst(),pop(): 删除头节点

public E pop() {return removeFirst();
}
public E remove() {return removeFirst();
}
public E removeFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return unlinkFirst(f);
}

removeLast(),pollLast(): 删除尾节点

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);
}

区别: removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。

remove(Object o): 删除指定元素

public boolean remove(Object o) {//如果删除对象为nullif (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;
}

当删除指定对象时,只需调用remove(Object o)即可,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false。

unlink(Node<E> x)方法:

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;
}

remove(int index):删除指定位置的元素

public E remove(int index) {//检查index范围checkElementIndex(index);//将节点删除return unlink(node(index));
}

一些API

返回值 方法参数及作用描述
boolean add(E e) 将指定的元素追加到此列表的末尾。
void add(int index, E element) 在此列表中的指定位置插入指定的元素。
boolean addAll(Collection<? extends E> c) 按照指定集合的迭代器返回的顺序将指定集合中的所有元素追加到此列表的末尾。
boolean addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
void addFirst(E e) 在该列表开头插入指定的元素。
void addLast(E e) 将指定的元素追加到此列表的末尾。
void clear() 从列表中删除所有元素。
Object clone() 返回此 LinkedList的浅版本。
boolean contains(Object o) 如果此列表包含指定的元素,则返回 true
Iterator<E> descendingIterator() 以相反的顺序返回此deque中的元素的迭代器。
E element() 检索但不删除此列表的头(第一个元素)。
E get(int index) 返回此列表中指定位置的元素。
E getFirst() 返回此列表中的第一个元素。
E getLast() 返回此列表中的最后一个元素。
int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
ListIterator<E> listIterator(int index) 从列表中的指定位置开始,返回此列表中元素的列表迭代器(按适当的顺序)。
boolean offer(E e) 将指定的元素添加为此列表的尾部(最后一个元素)。
boolean offerFirst(E e) 在此列表的前面插入指定的元素。
boolean offerLast(E e) 在该列表的末尾插入指定的元素。
E peek() 检索但不删除此列表的头(第一个元素)。
E peekFirst() 检索但不删除此列表的第一个元素,如果此列表为空,则返回 null
E peekLast() 检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null
E poll() 检索并删除此列表的头(第一个元素)。
E pollFirst() 检索并删除此列表的第一个元素,如果此列表为空,则返回 null
E pollLast() 检索并删除此列表的最后一个元素,如果此列表为空,则返回 null
E pop() 从此列表表示的堆栈中弹出一个元素。
void push(E e) 将元素推送到由此列表表示的堆栈上。
E remove() 检索并删除此列表的头(第一个元素)。
E remove(int index) 删除该列表中指定位置的元素。
boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。
E removeFirst() 从此列表中删除并返回第一个元素。
boolean removeFirstOccurrence(Object o) 删除此列表中指定元素的第一个出现(从头到尾遍历列表时)。
E removeLast() 从此列表中删除并返回最后一个元素。
boolean removeLastOccurrence(Object o) 删除此列表中指定元素的最后一次出现(从头到尾遍历列表时)。
E set(int index, E element) 用指定的元素替换此列表中指定位置的元素。
int size() 返回此列表中的元素数。
Spliterator<E> spliterator() 在此列表中的元素上创建late-binding故障快速 Spliterator
Object[] toArray() 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
<T> T[] toArray(T[] a) 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。

最后

本文阐述了Java集合类LinkedList的内部结构以及部分源码实现,并总结了该集合的一些常用方法。

【Java】集合-LinkedList详解相关推荐

  1. Java 集合框架 详解

    一.Java 集合框架概述 集合框架是一个用来代表和操纵集合的统一架构(java集合框架位于java.util包中).所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.例如 Collec ...

  2. Java中LinkedList详解

    Java中LinkedList详解 LinkedList底层是双向链表 单向链表 双向链表 LinkedList新增的方法 主要增加了针对头结点与尾结点进行操作的方法, 即针对第一个元素和最后一个元素 ...

  3. Java集合框架详解

    一.集合框架图 简化图: 说明:对于以上的框架图有如下几点说明 1.所有集合类都位于java.util包下.Java的集合类主要由两个接口派生而出:Collection和Map,Collection和 ...

  4. Java集合核心详解【十分钟带你了解整个集合体系】

    前言: 集合是Java中非常重要的一章,学习难度也相对较大,不会很快就能掌握,这里我们先对集合框架有一个大概的了解,记住其中的基础知识,后面深入研究某一个集合时,才能更好的掌握. 文章目录 一.集合介 ...

  5. Java 集合框架详解

    1.集合框架的设计目标 该框架必须是高性能的.基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的. 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性. 对一个集合的扩展和适应必须 ...

  6. 一文带你了解-Java集合超详解(破天荒总结)

    集合的特点 集合的特点主要有如下两点: 对象封装数据,对象多了也需要存储.集合用于存储对象. 对象的个数确定可以使用数组,对象的个数不确定的可以用集合.因 为集合是可变长度的. 集合和数组的区别 数组 ...

  7. java集合详解_Map、Set、List及其子类和接口你都明白吗?看这篇Java集合超详解

    前言: 为什么出现集合类? 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式. 数组和集合类同是容器,有何不同? 长度上:数组 ...

  8. Java 集合体系详解——List体系有序集合

    引言 面向对象语言对事物的体现必然是以对象的形式,Java工程师为了方便多多个对象的操作,就对对象进行存储,集合就是存储对象的一种方式,他们的底层都是基于不同的数据结构.当然集合和数组一样都是容器,数 ...

  9. java集合(详解)

          文章目录     什么是集合? Collection的基本使用 接口实现类 Collections工具类 Map接口及其实现类 Map是不是集合? 什么是集合? 集合是指具有某种特定性质的 ...

最新文章

  1. 10个省时间的 PyCharm 技巧
  2. ustc小道消息20211227
  3. purge table table_name的一点测试!
  4. 总结:服务器硬件对性能的影响
  5. SAP S/4HANA生产订单创建之后,为什么会自动执行action
  6. python爬虫实现网页采集器
  7. Ucloud香港1h1g云服务器低至126元一年而且可开3年限时
  8. 你知道吗?还有比自回归方式更快更好的序列生成!
  9. 在函数‘_start’中:对‘main’未定义的引用
  10. 关于jvm的OutOfMemory:PermGen space异常的解决
  11. 解决卡米,安心卸载MIUI预装软件。
  12. JS核心之封装继承多态(一)
  13. RiceQuant开源框架RQAlpha阅读笔记(转)
  14. 华东师范大学 计算机 博士 毕业论文,【学位】华东师范大学博士、硕士学位论文基本格式要求...
  15. PMP工作绩效数据、信息和报告三者的区别
  16. python实训日志_最新Python实训周总结
  17. 解决hadoop:未找到命令;hadoop:未找到命令问题
  18. 直播系统解决方案:直播平台如何开发搭建
  19. 布局资本市场的合生创展集团,能靠“买买买”出圈吗?
  20. bilibili注册页面编码HTML码,哔哩哔哩bilibili新人邀请码在哪填写 B站怎么绑定输入邀请码方法...

热门文章

  1. 薪资提不上去是因为你不懂市场需求
  2. 实例分割总结 Instance Segmentation Summary(Center Mask、Mask-RCNN、PANNet、Deep Mask和Sharp Mask)
  3. 解神者神迹PHP奥义高阶,解神者新增神迹介绍 神迹效果调整介绍
  4. dota2 服务器尚未更新到最新版本,教程:DOTA2更新后仍提示版本过低怎么办?
  5. show-overflow-tooltip不生效
  6. 百度竞价排名服务问答
  7. Mask Rcnn代码与原理相结合解析
  8. 火山引擎打造企业级 ByteHouse ,打通 ClickHouse 落地企业的“最后一公里”
  9. APOpenSdk支付宝分享Cocoapods
  10. c语言编程IP地址转换,用C语言将二进制转换为IP地址