Java常用集合之LinkedList(深入源码)

目录

(1)LinkedList概述

(2)LinkedList特点

(3)LinkedList特有方法

(4)ArrayList与LinkedList对比

(5)LinkedList源码分析

(1)成员变量

(2)构造函数

(3)节点类

(4)add()

(5)remove()

(6)remove(Object o)

 (7)contains(Object o)

(8)get(int index)


(1)LinkedList概述

LinkedList底层是通过双向链表实现的。除了可以作为链表,还可以当做栈、普通队列和双端队列来使用。

在早期,LinkedList底层是双向循环链表。但在java7、8时,改成了双向链表。即头结点的prev和尾结点的next都为null

不支持随机访问,支持克隆、序列化。(可以通过遍历来实现顺序访问,效率比较低)

线程不安全。

为什么不循环了

JDK 1.7的实现思想是,设置两个哨兵节点,分别指向头部和尾部,所以不用循环也能快速找到头尾节点,进行正向遍历或反向遍历。

(2)LinkedList特点

LinkedList底层数据结构是双向链表,查询慢,增删快

链式存储底层是用一个个节点(Node)链接而成的,每个节点都存储着一个对象值和下一个节点的位置(或上一个节点的位置)

(3)LinkedList特有方法

方法名说明
public void addFirst(E e)在该列表开头插入指定的元素
public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素

(4)ArrayList与LinkedList对比

Arraylist:

  • 优点:ArrayList是实现了基于数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
  • 缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。

LinkedList:

  • 优点:LinkedList基于链表结构,地址是任意的,所以在开辟内存空间的时候不需要一个连续的地址,而且增删效率高,只需要移动指针

  • 缺点:因为LinkedList要移动指针来挨个遍历元素,所以查询操作性能比较低。

    ​ 虽然LinkedList作为链表,增删效率很高,但是需要遍历才能查找到要操作的元素,遍历的效率很低。

适用场景分析:

  • 当需要对数据进行多次随机访问的情况下选用ArrayList
  • 当需要对数据进行多次增加删除修改时采用LinkedList。

(5)LinkedList源码分析

LinkedList的亮点:

  • 由于它允许存null值,也允许按值查找,所以查找时做了特殊判断,如果为null,使用==查找。如果不为null,使用equals查找

  • 它提供了一个根据下标查找元素的方法,其实就是遍历链表时维护了一个计数器。

    它做了优化:如果要查找的下标大于链表长度的一半,即更靠近尾部,就从末尾向前查找。如果更靠近头部,就从头部向后查找

  • 其他的就是普通的链表实现

(1)成员变量

size:当前集合中存放元素的个数。链表不存在容量上限

first:头结点

last:尾结点

(2)构造函数

无参构造:

什么都没有做,平平无奇

有参构造:

指定一个集合来实例化LinkedList。调用了无参构造完成初始化,然后调用了addAll()方法,将原先集合的元素填充到新的LinkedList中。

(3)节点类

 

非常简洁。

item:元素值

next:后继节点

prev:前驱节点

有参构造:前驱结点、节点值、后继节点

(4)add()

在链表末尾添加元素

调用了linkLast()方法:

将指定元素实例化成一个新节点,添加在链表的末尾。这里也存在modCount,所以LinkedList也有迭代时的快速失败机制。

(5)remove()

删除链表的第一个元素

 

调用了removeFirst()方法:

如果链表为空,抛出异常:没有这个参数。否则调用unlinkFirst()方法:

先保留头结点的元素值和后继引用,然后将它们置null。

然后让第二个节点成为头结点。尾结点的后继节点是指向first的,所以不用发生变化。

如果此时头结点为空,说明链表为空,就将尾结点置null;否则将新的头结点的prev置null(原先指向原来的first头结点)。

此时原先的头结点三个属性全部为null,可以被GC回收。

最后返回被删除节点的元素值。

(6)remove(Object o)

按照元素值删除节点

和ArrayList的处理方式类似,先判断元素是否为null,如果为null,就用“==”匹配;如果不为null,就用“equals()”匹配

按顺序遍历节点,如果找到了匹配的元素,就调用unLink()方法删除它:

首先存储要删除节点的全部属性;

判断前驱结点是否为null。如果为null,说明要删除的是头结点;

判断后继节点是否为null。如果为null,说明要删除的是尾节点;

操作完成后,将节点的所有属性置为null,方便GC回收。

 (7)contains(Object o)

 

调用indexOf(Object o)进行查找。它返回的是该元素的索引,如果没找到就返回-1。而contains方法做了加工,返回boolean值。

indexOf(Object o)方法,还是先判断元素是否为null,然后维护一个索引,遍历节点,找到元素就返回当时的索引,没找到就返回-1

这种随机查找也没有办法做优化,所以最坏情况会完整遍历一次整个链表,效率很低。

(8)get(int index)

根据“索引”查找元素。链表中不存在索引,所以需要在遍历的过程中手动维护一个索引。

 

先调用checkElementIndex()方法,判断索引是否合法:

如果合法,调用node(int index)方法,按照索引返回节点:

这里做了优化,使用二分查找法提高索引查找的效率

也很简单,判断索引的值和size的大小关系。如果索引小于size的一半,就从头节点开始遍历;如果索引大于size的一半,就从尾节点开始反向遍历。

这样,只需要遍历一半的链表,提升了一些查找速度。这也得益于双向链表的设计。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值