Java基础 集合框架中最灵活的数据结构之一LinkedList

发布于:2025-07-01 ⋅ 阅读:(26) ⋅ 点赞:(0)


LinkedList 是 Java 集合框架中最灵活的数据结构之一,基于的 双向链表实现,它同时实现了 ListDequeQueue 接口,因此既可以作为List列表使用,也可以作为 队列(Queue)双端队列(Deque)使用。

核心特性

双向链表结构(双端队列)

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

如果单从数据结构看是属于双向链表(prev和next),对于队首和队尾的操作但是O(1),但是LinkedList实现了三个接口:列表 基本队列 双端队列,所以LinkedList三种都可以使用

多接口实现

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}

实现的接口列表:

  • List:有序集合,支持索引访问
  • Deque:双端队列操作
  • Queue:队列操作(FIFO)

继承的抽象类AbstractSequentialList
1.继承AbstractList类,AbstractSequentialList为顺序访问的List结构提供了抽象父类,其子类为LinkedList

public abstract class AbstractSequentialList<E> extends AbstractList<E> {}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> 

2.AbstractSequentialList要求子类必实现size()、listIterator(int index)方法

	//来源从AbstractCollection
	public abstract int size();
	//来源自AbstractList
    public abstract ListIterator<E> listIterator(int index);

3.AbstractSequentialList中有个抽象方法listIterator

 public abstract ListIterator listIterator(int index)

4.AbstractSequentialList还提供了get(int index)、set(int index, E element)、add(int index, E element)、remove(int index)等方法的默认通用实现

	// 示例其中一个get方法实现
    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }
    // ...................

动态扩容或收缩

扩容机制(添加元素)

1.无需预先指定容量,初始队列空,大小从开始
2.调整相邻节点的引用关系
3.更新头/尾节点引用(如果需要)
4.增加 size 计数

搞一个printListState 实时打印链表内部结构

    private static void printListState(LinkedList<String> list, String operation) {
        System.out.println("\n===== 操作: " + operation + " =====");
        System.out.println(operation+"后大小: " + list.size());

        if (list.isEmpty()) {
            System.out.println("头节点 -> NULL");
            System.out.println("尾节点 -> NULL");
            System.out.println("链表结构: [空]");
            return;
        }

        // 打印头尾指针
        System.out.println("头节点 -> " + list.getFirst());
        System.out.println("尾节点 -> " + list.getLast());

        // 构建链表结构可视化
        StringBuilder structure = new StringBuilder();

        // 添加头部标记
        structure.append("Head -> ");

        // 遍历所有节点
        for (int i = 0; i < list.size(); i++) {
            structure.append("[").append(list.get(i)).append("]");

            // 添加节点连接 展示结构
            if (i < list.size() - 1) {
                structure.append(" ⇄ ");
            }
        }

        // 添加尾部标记
        structure.append(" <- Tail");

        System.out.println("链表结构: " + structure);
        System.out.println("节点详情:");

        // 打印每个节点的前后关系
        for (int i = 0; i < list.size(); i++) {
            String prev = (i == 0) ? "NULL" : "[" + list.get(i - 1) + "]";
            String next = (i == list.size() - 1) ? "NULL" : "[" + list.get(i + 1) + "]";

            System.out.printf("节点 [%s]: prev=%s, next=%s%n",
                    list.get(i), prev, next);
        }

    }
        LinkedList<String> list = new LinkedList<>();
        System.out.println(list.size());
        printListState(list, "初始空链表大小");

		 // 添加元素 (扩容)
        list.add("A");
        printListState(list, "添加 A");
        list.add("B");
        printListState(list, "添加 B");

        list.addFirst("X");
        printListState(list, "头部添加 X");

        list.add(2, "Y");
        printListState(list, "索引 2 处添加 Y");

打印

===== 操作: 初始空链表大小:0 =====
初始空链表大小:0后大小: 0
头节点 -> NULL
尾节点 -> NULL
链表结构: [空]

===== 操作: 添加 A =====
添加 A后大小: 1
头节点 -> A
尾节点 -> A
链表结构: Head -> [A] <- Tail
节点详情:
节点 [A]: prev=NULL, next=NULL

===== 操作: 添加 B =====
添加 B后大小: 2
头节点 -> A
尾节点 -> B
链表结构: Head -> [A] ⇄ [B] <- Tail
节点详情:
节点 [A]: prev=NULL, next=[B]
节点 [B]: prev=[A], next=NULL

===== 操作: 头部添加 X =====
头部添加 X后大小: 3
头节点 -> X
尾节点 -> B
链表结构: Head -> [X] ⇄ [A] ⇄ [B] <- Tail
节点详情:
节点 [X]: prev=NULL, next=[A]
节点 [A]: prev=[X], next=[B]
节点 [B]: prev=[A], next=NULL

===== 操作: 索引 2 处添加 Y =====
索引 2 处添加 Y后大小: 4
头节点 -> X
尾节点 -> B
链表结构: Head -> [X] ⇄ [A] ⇄ [Y] ⇄ [B] <- Tail
节点详情:
节点 [X]: prev=NULL, next=[A]
节点 [A]: prev=[X], next=[Y]
节点 [Y]: prev=[A], next=[B]
节点 [B]: prev=[Y], next=NULL

通过节点Node添加链表关系进行扩容,初始队列为空

收缩机制(删除元素)

1.断开目标节点的前后连接
2.将目标节点的前后引用设为 null(帮助 GC)
3.更新头/尾节点引用(如果需要)
4.减少 size 计数

        // 删除元素 (收缩)
        list.remove("B");
        printListState(list, "删除 B");

        list.removeFirst();
        printListState(list, "删除头部");

        list.removeLast();
        printListState(list, "删除尾部");

        // 清空链表
        list.clear();
        printListState(list, "清空链表");

打印输出

===== 操作: 删除 B =====
删除 B后大小: 3
头节点 -> X
尾节点 -> Y
链表结构: Head -> [X] ⇄ [A] ⇄ [Y] <- Tail
节点详情:
节点 [X]: prev=NULL, next=[A]
节点 [A]: prev=[X], next=[Y]
节点 [Y]: prev=[A], next=NULL

===== 操作: 删除头部 =====
删除头部后大小: 2
头节点 -> A
尾节点 -> Y
链表结构: Head -> [A] ⇄ [Y] <- Tail
节点详情:
节点 [A]: prev=NULL, next=[Y]
节点 [Y]: prev=[A], next=NULL

===== 操作: 删除尾部 =====
删除尾部后大小: 1
头节点 -> A
尾节点 -> A
链表结构: Head -> [A] <- Tail
节点详情:
节点 [A]: prev=NULL, next=NULL

===== 操作: 清空链表 =====
清空链表后大小: 0
头节点 -> NULL
尾节点 -> NULL
链表结构: [空]

  1. 多接口实现

  2. 非线程安全

    • 多线程环境下需要外部同步

性能特点

操作 时间复杂度 说明
头部插入/删除 O(1) 直接操作头节点
尾部插入/删除 O(1) 直接操作尾节点
指定位置插入/删除 O(n) 需要遍历到目标位置
随机访问 (get/set) O(n) 需要从头/尾遍历
搜索元素 (indexOf) O(n) 必须遍历整个列表

链表节点核心处理逻辑

新增/插入节点核心逻辑:

linkFirst(E e):链表首 新增节点

private void linkFirst(E e) {
    // 1. 保存当前头节点
    final Node<E> f = first;
    // 2. 创建新节点(前驱为null,后继为当前头节点)
    final Node<E> newNode = new Node<>(null, e, f);
    // 3. 更新头指针
    first = newNode; // 新节点成为头节点
    // 4. 处理链表为空的情况
    if (f == null) {
        last = newNode; // 新节点也是尾节点
    } else {
        f.prev = newNode; // 原头节点的prev指向新节点
    }
    // 5. 更新链表状态
    size++;
    modCount++;
}

linkLast(E 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; // 如果链表为空,新节点也是头节点
    } else {
        l.next = newNode; // 否则将原尾节点的next指向新节点
    }
    
    size++; // 更新链表大小
    modCount++; // 修改计数器
}

linkBefore(E e, Node succ): 指定节点之前插入新元素
指定节点succ之前插入新元素e

void linkBefore(E e, Node<E> succ) {
    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; // 否则前驱节点的next指向新节点
    }
    
    size++; // 更新链表大小
    modCount++; // 修改计数器
}

删除/移除节点核心逻辑:

unlink(Node x): 移除指定节点

E unlink(Node<E> x) {
    // 1. 保存被移除节点的值
    final E element = x.item;
    // 2. 获取相邻节点
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    // 3. 处理前驱节点链接
    if (prev == null) {
        first = next; // x 是头节点
    } else {
        prev.next = next; // 前驱节点指向后继节点
        x.prev = null; // 断开与前驱节点的链接
    }
    // 4. 处理后继节点链接
    if (next == null) {
        last = prev; // x 是尾节点
    } else {
        next.prev = prev; // 后继节点指向前驱节点
        x.next = null; // 断开与后继节点的链接
    }
    // 5. 清理节点数据
    x.item = null; // 帮助垃圾回收
    // 6. 更新链表状态
    size--;
    modCount++;
    return element;
}

unlinkFirst(Node f): 移除 链表头节点

private E unlinkFirst(Node<E> f) {
    // 1. 保存头节点的值
    final E element = f.item;
    // 2. 获取头节点的下一个节点
    final Node<E> next = f.next;
    // 3. 清理当前头节点(帮助GC)
    f.item = null;
    f.next = null; // 断开与链表的连接
    // 4. 更新头指针
    first = next;
    // 5. 处理链表只有一个元素的情况
    if (next == null) {
        last = null; // 链表变为空
    } else {
        next.prev = null; // 新头节点的prev置空
    }
    // 6. 更新链表大小和修改计数
    size--;
    modCount++;
    // 7. 返回被移除的元素值
    return element;
}

unlinkLast(Node l): 移除 链表尾节点
unlinkLast()LinkedList 内部用于移除尾节点

private E unlinkLast(Node<E> l) {
    // 1. 保存尾节点的值
    final E element = l.item;
    // 2. 获取尾节点的前驱节点
    final Node<E> prev = l.prev;
    // 3. 清理当前尾节点
    l.item = null;    // 释放数据引用
    l.prev = null;    // 断开前向链接
    // 4. 更新尾指针
    last = prev;      // 新的尾节点
    // 5. 处理链表只有一个元素的情况
    if (prev == null) {
        first = null; // 链表变为空
    } else {
        prev.next = null; // 新尾节点的next置空
    }
    // 6. 更新链表状态
    size--;
    modCount++;
    // 7. 返回被移除的元素值
    return element;
}

LinkedList内部操作方法

新增/插入节点方法:

offer(E e) :添加元素系列方法
本质是调LinkedList.add方法,因LinkedList不限制容量大小,LinkedList.offer方法和add方法无差别

	// 默认从队尾巴添加元素
    public boolean offer(E e) {
        return add(e);
    }
    //添加元素成新的队首
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }
	// 添加元素成新的队尾
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

add(E e):添加元素系列方法
在尾部添加元素,底层逻辑调用linkLast

	// 默认从队尾添加元素
	public boolean add(E e) {
    	linkLast(e); // 核心逻辑在 linkLast 方法中
    return true;}
    //指定位置添加元素
    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);
    }
    //添加元素尾部插入
    public void addLast(E e) {
        linkLast(e);
    }
    //添加一个已存在集合的全部元素 从队列尾开始插入
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    //添加一个已存在集合的全部元素 从队列指定位置index开始插入
    public boolean addAll(int index, Collection<? extends E> c) {}

push(E e):(压栈)本质调用addFirst(e)

    public void push(E e) {addFirst(e);}

删除/移除 并返回删除节点方法:

remove():移除并返回头元素方法 系列

	// 移除队首节点并返回,移除队首节点操作底层是调用unlinkFirst
	// 如果队列为空 返回NoSuchElementException异常
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
	// 移除队尾节点并返回,移除队尾节点操作底层是调用unlinkLast
	// 如果队列为空 返回NoSuchElementException异常
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

	//移除元素 参数为空 默认执行removeFirst方法
    public E remove() {
        return removeFirst();
    }

    //1.移除指定位置的元素,检查索引是否合法(在0到size-1范围内),有问题则抛出IndexOutOfBoundsException
    //2.通过node(index)方法获取指定索引处的节点
    //3.调用unlink(Node<E> x)方法移除该节点
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
	
	//调用remove(Object o)方法,所以和remove(Object o)作用一样
	public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

	//移除链表中第一次出现的指定元素(如果存在)
    public boolean remove(Object o) {
        if (o == null) {// 处理 null 值的移除
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {// 处理非 null 值的移除
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

	移除链表中最后出现的指定元素(如果存在) (从队尾开始遍历)
    public boolean removeLastOccurrence(Object o) {
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

poll()移除并返回头元素方法 系列

	// 移除队首节点并返回,移除队首节点操作底层是调用unlinkFirst
	public E poll() {
   	 final Node<E> f = first;
    	return (f == null) ? null : unlinkFirst(f);
	}
	// 移除队首节点并返回,移除队首节点操作底层是调用unlinkFirst
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
     移除队尾节点并返回,移除队尾节点操作底层是调用unlinkLast
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

peek():返回元素节点系列方法 队列为空返回null

	//获取队首节点 队列为空返回null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    //获取队首节点 队列为空返回null
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
     //获取队尾节点 队列为空返回null
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

pop():(弹栈)直接调用removeFirst方法 作用一样

    public E pop() {
        return removeFirst();
    }

查找返回节点(不删除)方法:

getFirst():返回队首节点,队列为空 抛出 NoSuchElementException

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

getLast():返回队尾节点,队列为空 抛出 NoSuchElementException

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

element():直接调用getFirst方法 作用一样

    public E element() {return getFirst();}

定位节点索引位置:

indexOf():查找元素第一次出现位置 (从头为0开始)

public int indexOf(Object o) {
    int index = 0;  // 1. 初始化索引为0
    if (o == null) {
        // 2. 处理 null 值的查找
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;  // 找到匹配项
            index++;
        }
    } else {
        // 3. 处理非 null 值的查找
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;  // 找到匹配项
            index++;
        }
    }
    return -1;  // 4. 未找到返回-1
}

lastIndexOf(Object o):查找元素最后一次出现位置(从尾开始)

public int lastIndexOf(Object o) {
    int index = size; // 1. 初始化索引为链表大小
    if (o == null) {
        // 2. 处理 null 值的查找
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        // 3. 处理非 null 值的查找
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1; // 4. 未找到返回 -1
}

应用场景

LinkedList实现的接口有三个,所以应用的场景也有三个

作为 List 使用(列表)

// 创建 LinkedList
List<String> list = new LinkedList<>();

// 添加元素
list.add("A");          // 尾部添加
list.add(1, "B");       // 指定位置插入

// 访问元素
String first = list.get(0);  // 获取第一个元素
String last = list.get(list.size() - 1);  // 获取最后一个元素

// 遍历元素
for (String item : list) {
    System.out.println(item);
}

// 删除元素
list.remove(0);         // 删除第一个元素
list.remove("B");       // 删除指定元素

作为 Queue 使用 (基本队列FIFO)

    public static void main(String[] args) {
        //首先对于Queue而言,操作结果有两种类型的返回结果
        // 1.报错抛出异常:add remove element 等方法
        // 2.返回特殊值:null 或者 false(推荐使用):offer poll peek等方法
        
        //Queue中的阻塞队列ArrayBlockingQueue 可以设置队列容量,超过值添加会报错
        Queue<String> blockQueue = new ArrayBlockingQueue<>(2);
        // 对于有容量限制的队列添加元素到边界值 会返回.返回特殊值
        System.out.println(blockQueue.offer("A")); //添加成功 返回true
        System.out.println(blockQueue.offer("A")); //添加成功 返回true
        System.out.println(blockQueue.offer("A")); //添加失败 返回false

        System.out.println(blockQueue.add("A")); //添加失败 抛出已满的异常IllegalStateException: Queue full

        //如果用LinkedList作为队列的子类,是没有设置容量大小 默认就是可无限大小
        Queue<String> queue = new LinkedList<>();
        System.out.println(queue.poll()); //推荐(返回null)
        System.out.println(queue.remove("A")); //false
//        System.out.println(queue.remove()); //抛出没有元素异常NoSuchElementException

        //因为LinkedList没有容量限制 所以添加多少元素都可以
        System.out.println(queue.offer("A")); //true
        System.out.println(queue.offer("A")); //true
        // 其次验证了LinkedList链表是不关心节点元素是什么 可重复元素节点
        System.out.println(queue.add("B")); //true
        System.out.println(queue.add("B")); //true
    }

作为 Deque 使用 (双端队列)

单从数据节点结构看LinkedList,有Node prev 和next属于双向链表的结构,可以同时定位队列头和尾巴,对于队头和队尾的移除添加操作都是O(1)
对于基本队列(单向链表) 想要操作队尾就需要从队首一路遍历到队尾,所以两种的应用上
1.如果需要频繁执行队首和队尾的场景 选用双端队列
2.如果只需要关注队首的场景,使用基本队列,比较维护prev指针和tail节点需要额外开销

Deque<String> deque = new LinkedList<>();

// 从队列前端操作:添加节点成为新的队首
deque.addFirst("Front");
deque.offerFirst("NewFront");
String front = deque.removeFirst();

// 从队列后端操作节点:队尾添加节点
deque.addLast("End");
deque.offerLast("NewEnd");
String end = deque.removeLast();

// 栈操作 (LIFO)
deque.push("StackTop");  // 压栈方法:push 底层调用addFirst()
String top = deque.pop(); // 弹栈方法:pop 底层调用removeFirst()

使用场景推荐

  1. 频繁在头尾操作数据

    • 实现队列/双端队列
    • 浏览器历史记录管理
    • 撤销操作栈(作为栈使用)
  2. 不需要频繁随机访问

    • 按顺序处理数据流
    • 实现消息队列系统
  3. 动态数据集合

    • 元素数量变化频繁
    • 无法预估最大容量

与 ArrayList 对比

特性 LinkedList ArrayList
底层结构 双向链表 动态数组
随机访问 慢 (O(n)) 快 (O(1))
头尾插入/删除 快 (O(1)) 头慢 (O(n)) 尾快 (O(1))
中间插入/删除 慢 (O(n)) 慢 (O(n))
内存占用 较高 (每个节点额外存储两个指针) 较低
内存连续性 非连续 连续

LinkedList 线程不安全

LinkedList 线程不安全体现

情况1:多个线程同时修改链表结构被破坏
当多个线程同时修改链表结构时,会导致链表指针混乱:

LinkedList<Integer> list = new LinkedList<>();
ExecutorService executor = Executors.newFixedThreadPool(2);

// 线程1:添加元素
executor.submit(() -> {
    for (int i = 0; i < 1000; i++) {
        list.add(i);
    }
});

// 线程2:移除元素
executor.submit(() -> {
    for (int i = 0; i < 1000; i++) {
        if (!list.isEmpty()) {
            list.removeFirst();
        }
    }
});

executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);

System.out.println("最终大小: " + list.size());
// 可能输出:最终大小: 325 (预期为0)

情况2:丢失更新(Add 操作冲突)
当多个线程同时添加元素时:

// 两个线程同时添加元素
executor.submit(() -> {
    for (int i = 0; i < 1000; i++) {
        list.add(i);
    }
});

executor.submit(() -> {
    for (int i = 0; i < 1000; i++) {
        list.add(i * 100);
    }
});

// 预期大小: 2000
// 实际大小: 可能小于2000(如1892)

情况3:快速失败机制ConcurrentModificationException
当遍历链表时另一个线程修改结构:

executor.submit(() -> {
    try {
        for (Integer num : list) { // 触发迭代器
            Thread.sleep(10); // 模拟处理
        }
    } catch (ConcurrentModificationException e) {
        System.out.println("并发修改异常!");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

executor.submit(() -> {
    for (int i = 0; i < 100; i++) {
        list.add(i); // 修改结构
    }
});

情况4:死循环和内存泄漏
遍历时进入死循环,节点无法被GC回收最终JVM内存溢出报错

情况5:脏读
一个线程修改时,另一个线程读取到中间状态:

// 线程1添加元素
list.add("A");
list.add("B");

// 线程2在添加过程中读取
// 可能看到: [A] 而不是 [A, B]

解决方案

方案一:使用同步包装器(最简方案)

List<String> syncList = Collections.synchronizedList(new LinkedList<>());
// 写入操作自动同步
syncList.add("item");
// 遍历时需要手动同步
synchronized (syncList) {
    for (String item : syncList) {
        // 处理元素
    }
}

方案二:使用并发集合(最佳方案)
ConcurrentLinkedDeque (非阻塞)
1.无锁算法(CAS实现)2.高并发性能 3.弱一致性迭代器 4.无大小限制

// 不设置队列容量大小,无限制
Deque<String> concurrentDeque = new ConcurrentLinkedDeque<>();
// 并发安全操作
concurrentDeque.addFirst("head");
concurrentDeque.addLast("tail");
String item = concurrentDeque.pollFirst();
// 弱一致性迭代器 操作方法一旦执行必定执行,但结果可能返回也可能不返回

LinkedBlockingDeque (阻塞)
1.有界/无界可选 2.阻塞操作 3.锁分离(put锁和take锁)4.支持超时操作

// 界限可以选择队列给定初始容量大小
BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>(100);
// 生产者
new Thread(() -> {
    while (true) {
        blockingDeque.put("data"); // 阻塞如果队列满
    }
}).start();

// 消费者
new Thread(() -> {
    while (true) {
        String data = blockingDeque.take(); // 阻塞如果队列空
        // 处理数据
    }
}).start();
//生产消费模式互为条件
//阻塞时候 等待合适执行时机再执行操作

方案三: 使用显式锁(更细粒度控制)
优点:灵活控制锁范围 缺点:需手动管理锁

LinkedList<String> list = new LinkedList<>();
ReentrantLock lock = new ReentrantLock();

// 添加元素
lock.lock();
try {
    list.add("data");
} finally {
    lock.unlock();
}

// 批量操作
lock.lock();
try {
    for (int i = 0; i < 100; i++) {
        list.add("item-" + i);
    }
} finally {
    lock.unlock();
}

方案四:使用CopyOnWriteArrayList(特定场景)

List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
// 安全遍历(迭代器使用快照)
for (String item : copyOnWriteList) {
    // 即使有修改也不会抛异常
}
// 添加元素(复制整个数组)
copyOnWriteList.add("new item");

适用场景:
1.读多写少(读操作比写操作多100倍以上)
2.遍历操作频繁
3.数据量不大

不适用场景:
1.频繁修改
2.大数据量
3.需要双端队列功能

注意事项

1.避免使用索引遍历

   // 错误方式 - O(n²) 时间复杂度
   for (int i = 0; i < list.size(); i++) {
       System.out.println(list.get(i));
   }
   
   // 正确方式 - 使用迭代器 (O(n))
   for (Iterator<String> it = list.iterator(); it.hasNext();) {
       System.out.println(it.next());
   }

2.线程安全方案

   List<String> syncList = Collections.synchronizedList(new LinkedList<>());

3.空值处理

  • LinkedList 允许存储 null 元素
  • 但队列操作时需注意 poll() 可能返回 null

总结

LinkedList 是 Java 集合框架中最灵活的数据结构之一,特别适合需要频繁在集合两端进行操作的场景。它的双向链表结构提供了高效的头部/尾部操作,但随机访问性能较差。根据实际需求选择合适的接口(List/Queue/Deque)进行操作,可以充分发挥其优势。在需要快速随机访问的场景下,应优先考虑 ArrayList


网站公告

今日签到

点亮在社区的每一天
去签到