LinkedList


LinkedList 底层采用双向链表数据结构存储元素,由于链表的内存地址非连续,所以它不具备随机访问的特点,但由于它利用指针连接各个元素,所以插入、删除元素只需要操作指针,不需要移动元素,故具有增删快、查询慢的特点。 它也是一个非线程安全的集合。 LinkedList适用于先进先出和先入后出的场景在队列源码中被频繁使用。
在这里插入图片描述


整体架构

LinkedList 底层数据结构是一个双向链表,整体结构如下图所示:
在这里插入图片描述
关于上面的双向链表需要注意的是:

1:当链表中没有数据时,first 和 last 是同一个节点,前后指向都是 null。

2:因为是个双向链表,只要机器内存足够强大,是没有大小限制的。

链表中的元素叫做 Node,进入源码看下 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;
  }
}

LinkedList的主要操作

 //初始化方法1
HashSet<Object> hashSet = new HashSet<>();
hashSet.add(111);
hashSet.add(222);
LinkedList<Object> linkedList1 = new LinkedList<>(hashSet);

//初始化方法2
LinkedList<Object> linkedList = new LinkedList<>();
boolean test = linkedList.add("test");

//单向迭代器
Iterator<Object> iterator = linkedList.iterator();
while (iterator.hasNext()) {
	iterator.remove();
    Object next = iterator.next();
    System.out.println(next);
}

//双向迭代器
ListIterator<Object> previousIterator = linkedList.listIterator(0);
while (previousIterator.hasPrevious()) {
    Object previous = previousIterator.previous();
    System.out.println(previous);
}

Object o2 = linkedList.get(0);

boolean empty = linkedList.isEmpty();
Object o1 = linkedList.removeFirst();
Object o = linkedList.remove(0);
int num = linkedList.size();
System.out.println(num);

新增节点

追加节点时,可以选择追加到链表头部,还是追加到链表尾部,add 方法默认是从尾部开始追加addFirst 方法是从头部开始追加,我们分别来看下两种不同的追加方式:

add方法:

 /**
  * Links e as last element.
  */
 void linkLast(E e) {
     final Node<E> l = last;// 将尾节点暂存在节点I中
     final Node<E> newNode = new Node<>(l, e, null);// 通过有参构造函数初始化新增节点
     last = newNode;// 将新增节点变成尾节点
     if (l == null)
         first = newNode;//如果链表为空,头尾节点都是同一个节点,均为新建的节点
     else
         l.next = newNode;// 如果不为空,将前尾节点的next节点指向新增节点
     size++;// 链表大小加一
     modCount++;// 版本加一
 }

使用动态图清晰的展示一下新增节点的操作:
在这里插入图片描述
addFirst方法:

/**
  * Links e as first element.
  */
 private void linkFirst(E e) {
     final Node<E> f = first;// 将头节点暂存在f中
     final Node<E> newNode = new Node<>(null, e, f);//通过有参构造初始化新增到链头部的节点
     first = newNode;// 将新增节点赋值给first节点
     if (f == null)
         last = newNode;// 如果链为空,头尾节点都是同一个节点,均为新建的节点
     else
         f.prev = newNode;// 不为空的话,将前头结点的前置节点指向新增节点(新的头节点)
     size++;
     modCount++;
 }

头部追加节点和尾部追加节点非常类似,只是前者是移动头节点的 prev 指向,后者是移动尾节点的 next 指向。


删除节点

节点删除的方式和追加类似,可以选择从头部删除,也可以选择从尾部删除,删除操作会把节点的值,前后指向节点都置为 null,帮助 GC 进行回收。

/**
  * Unlinks non-null first node f. 从头删除节点,f是头结点
  */
 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;// 帮助GC回收垃圾
     f.next = null; // help GC
     first = next;// 头结点的下一个结点成为头结点
     if (next == null)
         last = null;// 如果链表在删除头结点之前只有一个节点,那么删除后链表为空
     else
         next.prev = null;// 如果不为空,新的头节点的前置节点指向空
     size--;
     modCount++;
     return element;//返回删除节点的值
 }

从尾节点删除节点也是一样的步骤,从源码中发现,链表结构的节点删除、新增代码都非常简单,因此LinkedList新增和删除速度很快。


查询节点

链表查询某一个节点是比较慢的,需要挨个循环查找才行,查询方法代码如下:

/**
  * Returns the (non-null) Node at the specified element index.(根据链表索引位置查询节点)
  */
 Node<E> node(int index) {
     // assert isElementIndex(index);

     if (index < (size >> 1)) {// 如果index处于队列的前半部分,从头结点开始遍历(size>>1是除以二)
         Node<E> x = first;
         for (int i = 0; i < index; i++)// 循环到index的前一个节点
             x = x.next;
         return x;
     } else {// 如果index处于队列的后半部分,从尾节点开始遍历
         Node<E> x = last;
         for (int i = size - 1; i > index; i--)// 遍历到index后一个节点
             x = x.prev;
         return x;
     }
    }

LinkedList 并没有采用从头循环到尾的做法,而是采取了简单二分法,首先看看 index 是在链表的前半部分,还是后半部分。如果是前半部分,就从头开始寻找,反之亦然。通过这种方式,使循环的次数至少降低了一半,提高了查找的性能,这种思想值得借鉴。


方法对比

LinkedList 实现了 Queue 接口,使得 LinkedList 可以用作双端队列。在新增、删除、查询等方面增加了很多新的方法,这些方法在平时特别容易混淆,在链表为空的情况下,返回值也不太一样,列一个表格,方便记录:

方法含义返回异常返回特殊值底层实现
新增add(e)offer(e)底层实现相同
删除remove()poll(e)链表为空时,remove 会抛出异常,poll 返回 null。
查找element()peek()链表为空时,element 会抛出异常,peek 返回 null。

迭代器

因为 LinkedList 要实现双向的迭代访问,所以使用 Iterator 接口肯定不行了,因为 Iterator 只支持从头到尾的访问。Java 设计者新增了一个迭代接口,叫做:ListIterator,这个接口提供了向前和向后的迭代方法,如下所示:

迭代顺序方法
从尾到头迭代hasPrevious、previous、previousIndex
从头到尾迭代hasNext、next、nextIndex

在这里插入图片描述
上图中LinkedList调用的是ListIterator,源码如下所示:

public ListIterator<E> listIterator(int index) {// 从下标index处开始迭代输出
   checkPositionIndex(index);// 判断index索引是否存在
   return new ListItr(index);// 返回迭代器实体
}

上述源码中,return new ListItr(index); 返回双向迭代器操作实体,部分代码如下:

private class ListItr implements ListIterator<E> {// 双向迭代器
	private Node<E> lastReturned;// 上一次执行next()或previous()时节点位置
	private Node<E> next;// 下一个节点
	private int nextIndex;// 下一个节点的位置
	private int expectedModCount = modCount;// 期望版本号=目前最新版本号
	
	ListItr(int index) {// 有参构造函数
	    // assert isPositionIndex(index);
	    next = (index == size) ? null : node(index);// 获取下一个节点
	    nextIndex = index;//获取下一个要迭代输出节点的索引
	}
	......

从头到尾方向的迭代:

public E next() {//迭代输出操作函数
	checkForComodification();// 检查期望版本号是否有变化
	if (!hasNext())// 再次检查是否有下一个结点
	    throw new NoSuchElementException();
	
	lastReturned = next;// 将next节点(初始化迭代器时index指向node)赋值给lastReturned(存储需要迭代输出的节点)
	next = next.next;// 将要输出的当前节点的next节点赋值给next本身
	nextIndex++;// 节点下标加一
	return lastReturned.item;// 返回存储在lastReturned中的迭代输出节点值
	}

从尾到头方向的迭代:

public E previous() {
	checkForComodification();
	if (!hasPrevious())
	    throw new NoSuchElementException();
	//next为空场景:1.第一次迭代到了尾节点 2.上一次操作把尾节点删除了
	lastReturned = next = (next == null) ? last : next.prev;// next不为空直接取前置节点
	nextIndex--;// 节点索引减一
	return lastReturned.item;
	}

迭代器删除元素

public void remove() {// 迭代删除方法
	checkForComodification();
	if (lastReturned == null)//如果lastReturned为空说明迭代器没有初始化,迭代器中没有值,或者lastReturned时尾节点的next
	    throw new IllegalStateException();
	
	Node<E> lastNext = lastReturned.next;//将要删除节点的next节点存储在lastNext中
	unlink(lastReturned);// 删除当前节点
	if (next == lastReturned)// 当从尾到头迭代的时候,第一次迭代要删除最后一个节点
	    next = lastNext;// lastReturned是尾节点,lastNext也为空,所以next也是空
	else
	    nextIndex--;
	lastReturned = null;
	expectedModCount++;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lw中

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值