LinkedList

LinkedList (可参考)

概括:
  1. LinkedList是一个实现了List接口和Deque接口的双端链表。与ArrayList相比,ArrayList直接继承自AbstractList,而LinkedList继承自AbstractSequentialList,然后再继承自AbstractList。另外LinkedList没有实现RandomAccess接口,说明它不具备随机快速访问任一元素的能力,它访问元素时需要通过遍历节点来获取,至于从链头还是链尾取决于所要访问的元素的位置
  2. LinkedList实现了Deque接口(即双端队列)。双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出。
  3. LinkedList允许元素为null。
  4. LinkedList与索引相关的操作可能从链表头开始遍历到链表尾部,也可能从尾部遍历到链表头部,这取决于看索引更靠近哪一端,所以它改查效率低,增删效率高,这点和ArrayList是相反的。
  5. 它是线程不安全的,要想实现LinkedList为线程安全的,可通过List<String> list = Collections.synchronizedList(new LinkedList<>());实现

在这里插入图片描述
在这里插入图片描述

全局说明:第 index 个节点指的是下标为 index-1 处的节点
注意点:
  • (1)LinkedList中的每个元素存储在Node节点中,Node是LinkedList类的内部类,LinkedList类中只有三个属性,分别是size(node节点数)、first(指向首个节点)、last(指向最后一个节点)。只要记录着首个节点和最后一个节点,便可以根据其找到其他中间节点

在这里插入图片描述
在这里插入图片描述

  • (2)LinkedList的构造方法有两个

在这里插入图片描述
其中LinkedList(Collection<? extends E> c)构造方法内调用的是addAll方法,整个方法其实就是如何操作链表的具体实现。该方法的详细解读如下

/**
    * 往下标为index处插入多个新节点,比如index=1时,说明是往第一个节点后面插入新节点
    * @throws IndexOutOfBoundsException {@inheritDoc}
    * @throws NullPointerException if the specified collection is null
    */
   public boolean addAll(int index, Collection<? extends E> c) {
       // 校验index是否大于当前链表的总节点数,会则抛出IndexOutOfBoundsException异常
       checkPositionIndex(index);

       // 将集合转换为数组,若集合的大小为0,则返回false
       Object[] a = c.toArray();
       int numNew = a.length;
       if (numNew == 0)
           return false;

       // succ代表第index个节点的下一节点,pred代表第index个节点
       Node<E> pred, succ;
       // 代表往链尾插入多个新节点,succ为null,pred为链尾
       if (index == size) {
           succ = null;
           pred = last;
       } else {
           /**
            * 代表往链表中间插入新节点,所以succ代表第index个节点的下一节点,pred代表第index个节点
           */
           succ = node(index);
           pred = succ.prev;
       }

       // 遍历即将插入的新节点数组
       for (Object o : a) {
           @SuppressWarnings("unchecked") E e = (E) o;
           // 将当前遍历的节点的prev属性指向前节点,若当前遍历的节点插入的位置为链头,则pred为null
           Node<E> newNode = new Node<>(pred, e, null);
           // 第一次遍历时,pred可能为null,pred为null表示当前遍历的节点是链头
           if (pred == null)
               first = newNode;
           else
               // 将第index个节点的next属性指定指向当前遍历的节点
               pred.next = newNode;
           // 将pred置为当前遍历的节点
           pred = newNode;
       }

       // pred代表遍历到的最后一个节点
       // 只有当往链尾插入新节点时,succ才会为null,则pred为链尾,将last指向该节点
       if (succ == null) {
           last = pred;
       } else {
           // 否则说明是在链中间插入新节点,则件将 pred 的 next属性指向 起初第index个节点的下一个节点
           // 起初第index个节点的下一个节点的prev属性指向 pred
           pred.next = succ;
           succ.prev = pred;
       }

       size += numNew;
       modCount++;
       return true;
   }
  • (3)linkedlist的添加操作实现较为简单,其中 add 和 addLast 方法的实现是一样的
   // 添加新节点,默认是往链尾添加
   public boolean add(E e) {
       linkLast(e);
       return true;
   }
   
   // 往第 index 个节点后添加新节点
   public void add(int index, E element) {
       checkPositionIndex(index);

       if (index == size)
           linkLast(element);
       else
           linkBefore(element, node(index));
   }
	
   // 往链头添加新节点
   public void addFirst(E e) {
       linkFirst(e);
   }

   // 往链尾添加新节点,和 add 方法一样
   public void addLast(E e) {
       linkLast(e);
   }
	
	
   // 将新节点绑定到链头
   private void linkFirst(E e) {
       final Node<E> f = first;
       final Node<E> newNode = new Node<>(null, e, f);
       first = newNode;
       if (f == null)
           last = newNode;
       else
           f.prev = newNode;
       size++;
       modCount++;
   }

   // 将新节点绑定到链尾
   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;
       else
           l.next = newNode;
       size++;
       modCount++;
   }
	

   // 将新节点绑定到succ节点前
   void linkBefore(E e, Node<E> succ) {
       // assert succ != null;
       final Node<E> pred = succ.prev;
       final Node<E> newNode = new Node<>(pred, e, succ);
       succ.prev = newNode;
       if (pred == null)
           first = newNode;
       else
           pred.next = newNode;
       size++;
       modCount++;
   }
  • (4)linkedlist删除节点
    linkedlist删除节点底层调用到的方法是 unlinkFirstunlinkLastunlink**。源码如下**
   // 移除第一个节点
   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;
       f.next = null; // help GC
       first = next;
       if (next == null)
           last = null;
       else
           next.prev = null;
       size--;
       modCount++;
       return element;
   }

   // 移除最后一个节点
   private E unlinkLast(Node<E> l) {
       // assert l == last && l != null;
       final E element = l.item;
       final Node<E> 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;
   }

   // 移除某个节点
   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;
   }
  • (5)linkedlist是通过逐个遍历的方式获取元素的,不过LinkedList在遍历开始前会根据所要遍历的元素的位置来判断从链头还是链尾开始遍历,源码如下
public E get(int index) {
       // 校验index是否大于链长
       checkElementIndex(index);
       // 获取第 index+1 个节点
       return node(index).item;
   }


   /**
    * 根据index返回节点,index为0返回链头节点,index为1返回第二个节点
    */
   Node<E> node(int index) {
       // 判断index是否小于size的一半,是则从链头遍历
       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;
       }
   }
  • (6)linkedlist的clear方法是将节点中的属性置为null,方便gc回收,同时将LinkedList中的first和last节点置为null
public void clear() {  
	for (Node<E> x = first; x != null; ) {
		Node<E> next = x.next;
		x.item = null;
		x.next = null;
		x.prev = null;
		x = next;
	}
	first = last = null;
	size = 0;
	modCount++;
}
  • (7)linkedlist实现了Deque接口,使其具备了双端队列的属性。双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出。
    在这里插入图片描述

  • (8)linkedlist的first节点和last节点为什么都加上了transient关键字,这样在对LinkedList进行序列化和反序列化时不就无法将元素信息保存起来吗?
    在这里插入图片描述

答:其实LinkedList中重写了writeObject和readObject的方法,当对LinkedList进行序列化和反序列化时将分别调用重写了的writeObject和readObject方法
在这里插入图片描述

### 什么是LinkedList数据结构 LinkedList 是一种基于链表的数据结构,它在 Java 中通过 `java.util.LinkedList` 类实现。它既可以作为列表使用,也可以作为队列或双端队列使用[^1]。LinkedList 内部通过双向链表实现,这意味着每个节点包含前驱和后继指针,从而允许高效的插入和删除操作。 --- ### LinkedList 的构造方法 以下是 `LinkedList` 提供的两种主要构造方法: 1. **无参构造方法**: ```java public LinkedList() ``` 创建一个空的 `LinkedList` 实例[^1]。 2. **基于集合的构造方法**: ```java public LinkedList(Collection<? extends E> c) ``` 使用指定集合中的元素创建一个新的 `LinkedList` 实例,元素顺序与集合的迭代器返回顺序一致。 --- ### LinkedList 的常用方法 以下是一些常用的 `LinkedList` 方法及其功能: #### 基本操作 - **add(E e)**:将指定元素追加到列表末。 - **add(int index, E element)**:在指定位置插入元素。 - **remove(Object o)**:移除首次出现的指定元素(如果存在)。 - **remove(int index)**:移除指定位置的元素并返回该元素。 #### 访问元素 - **get(int index)**:返回指定位置的元素。 - **indexOf(Object o)**:返回首次出现的指定元素的索引,如果不存在则返回 `-1`。 #### 队列操作 - **offer(E e)**:将元素添加到列表末(等同于 `add`)。 - **poll()**:获取并移除列表头部的元素。 - **peek()**:获取但不移除列表头部的元素。 #### 栈操作 - **push(E e)**:将元素压入栈顶(等同于在列表头部插入元素)。 - **pop()**:从栈顶弹出元素(等同于移除列表头部的元素)。 --- ### 示例代码 以下是一个简单的 `LinkedList` 使用示例: ```java import java.util.LinkedList; public class LinkedListExample { public static void main(String[] args) { // 创建一个空的 LinkedList LinkedList<String> list = new LinkedList<>(); // 添加元素 list.add("Apple"); list.add("Banana"); list.add("Cherry"); // 插入元素到指定位置 list.add(1, "Date"); // 打印列表 System.out.println("List: " + list); // 移除元素 String removedElement = list.remove(2); System.out.println("Removed Element: " + removedElement); // 获取元素 String firstElement = list.get(0); System.out.println("First Element: " + firstElement); // 遍历列表 for (String fruit : list) { System.out.println(fruit); } } } ``` --- ### LinkedList 的优缺点 #### 优点 - **高效插入和删除**:由于内部是双向链表结构,插入和删除操作的时间复杂度为 O(1)[^3]。 - **支持多种操作**:可以作为列表、队列或栈使用[^4]。 #### 缺点 - **随机访问慢**:通过索引访问元素时,需要从头或遍历链表,时间复杂度为 O(n)[^3]。 --- ### 自定义实现 LinkedList 以下是一个简单的自定义 `LinkedList` 实现示例: ```java class Node<T> { T data; Node<T> next; Node(T data) { this.data = data; this.next = null; } } class MyLinkedList<T> { private Node<T> head; public MyLinkedList() { head = null; } public void add(T data) { if (head == null) { head = new Node<>(data); } else { Node<T> current = head; while (current.next != null) { current = current.next; } current.next = new Node<>(data); } } public void printList() { Node<T> current = head; while (current != null) { System.out.print(current.data + " -> "); current = current.next; } System.out.println("null"); } } public class CustomLinkedListExample { public static void main(String[] args) { MyLinkedList<Integer> list = new MyLinkedList<>(); list.add(1); list.add(2); list.add(3); list.printList(); // 输出: 1 -> 2 -> 3 -> null } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值