Java集合框架源码分析之LinkedList

本文详细解读了Java LinkedList的实现机制,包括类声明、构造器、节点类型、添加与删除元素的方法,以及时间复杂度分析。通过内部类的使用,展示了如何在集合框架中封装底层数据结构与迭代器,同时强调了双向链表的优势与空间换时间的设计策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开篇寄语:与其期盼未来,不如专注当前。


总述: LinkedList是一种底层采用链表数据结构实现的List,它自然也具备List的特点,允许重复元素。


1. 类声明:


    public class LinkedList
  
    extends AbstractSequentialList
   
     implements List
    
     , Deque
     
      , Cloneable, java.io.Serializable

     
    
   
  
可以看出,LinkedList实现了一个新的接口Deque。Deque表示双端队列,那么,什么是双端队列呢?如果一个队列既可以在队头插入(删除)元素,又能在队尾插入(删除)元素,那么我称之为双端队列。其他三个接口在博文Java集合框架源码分析之ArrayList中已经描述,在此不再赘述。


2. 构造器

    private transient Entry
  
    header = new Entry
   
    (null, null, null);
    private transient int size = 0;
    
    public LinkedList() {
        header.next = header.previous = header;
    }

    public LinkedList(Collection
     c) {
	this();
	addAll(c);
    }

   
  
header属性是链表的头节点,Entry是链表节点的类型,它以LinkedList私有静态内部类的方式存在,其是 理解 LinkedList各项功能的核心。后续你会发现,Java集合框架大量的使用了这种内部类的方式来封装底层基础数据结构以及迭代器。这样做的好处是封装内部表示,防止外部程序继承修改。


3. 节点类型


    private static class Entry
   
     {
    	E element;
    	Entry
    
      next;
    	Entry
     
       previous;
    
    	Entry(E element, Entry
      
        next, Entry
       
         previous) {
    	    this.element = element;
    	    this.next = next;
    	    this.previous = previous;
    	}
    }

       
      
     
    
   
可以发现,这种节点实现是一个双向链表,就是说一个链表节点既保存了前一个节点的引用,又保存了后一个节点的引用。相比于单向链表,使得遍历操作更加灵活,并且也使得实现丰富的插入、删除、查询操作语意更加简单。为什么这么说呢?比如,要插入A元素到链表中指定位置,那么可以拿这个位置同链表的Size作比较,如果在链表的前半段,就从首节点开始遍历;如果在链表的后半段,则从尾节点开始遍历。这个世界是平衡的,带来了便利性和节省操作时间的同时,占用了额外的空间。这其实就是一种用空间换取时间的策略,也是java.lang.ThreadLocal类(一种并发编程手段,线程级上下文的常用实现)的设计思想。

4. 添加元素


    /**
     * 在该List的首节点处,插入指定元素
     */
    public void addFirst(E e) {
	    addBefore(e, header.next);
    }
    
    /**
     * 在该List的尾节点处,插入指定元素
     */
    public void addLast(E e) {
	    addBefore(e, header);
    }

    /**
     * 默认增加到List的尾节点
     */
    public boolean add(E e) {
    	addBefore(e, header);
            return true;
    }
    
    /**
     * 在该List的指定索引处,插入指定元素
     */
    public void add(int index, E element) {
        addBefore(element, (index==size ? header : entry(index)));
    }
    
    private Entry
  
    addBefore(E e, Entry
   
     entry) {
    	Entry
    
      newEntry = new Entry
     
      (e, entry, entry.previous);
    	newEntry.previous.next = newEntry;
    	newEntry.next.previous = newEntry;
    	size++;
    	modCount++;
    	return newEntry;
    }

     
    
   
  

可以看出这三个方法封装的内聚性很好,符合单一职责原则,因此复用性很好。我们在平常的编码中,完全可以借鉴这种技巧。这样的代码,看起来就很漂亮。除了这三个方法,还有一些队列的常用操作实现,如:offer、push等等,会在后续队列的源码分析博文中,再详细介绍。


5. 删除元素


    /**
     * 删除List的首节点
     */
    public E removeFirst() {
	    return remove(header.next);
    }

    /**
     * 删除List的尾节点
     */
    public E removeLast() {
	    return remove(header.previous);
    }
    
    /**
     * 删除List指定索引处的元素
     */    
    public E remove(int index) {
        return remove(entry(index));
    }
    
    /**
     * 删除List的指定元素
     */    
    public boolean remove(Object o) {
        if (o==null) {
            for (Entry
   
     e = header.next; e != header; e = e.next) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            for (Entry
    
      e = header.next; e != header; e = e.next) {
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }
    
    private E remove(Entry
     
       e) {
    	if (e == header)
    	    throw new NoSuchElementException();
    
            E result = e.element;
    	e.previous.next = e.next;
    	e.next.previous = e.previous;
            e.next = e.previous = null;
            e.element = null;
    	size--;
    	modCount++;
            return result;
    }    

    /**
     * 返回指定索引处的元素
     */    
    private Entry
      
        entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry
       
         e = header;
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }

       
      
     
    
   
modCount的作用描述请参见博文 Java集合框架源码分析之ArrayList中的描述。

7. 时间复杂度

| 查询指定索引元素的时间复杂度为O(N),由于采用双向链表实现,因此在实际应用中,还是要比单向链表实现节省一半时间。
| 插入元素的时间复杂度为O(1);插入元素到指定索引处的时间复杂度为O(N),其实主要是查询索引的时间耗费,实际插入操作的时间复杂度为O(1),因为链表的结构决定了其     插入操作无需移动元素。
| 删除元素和插入一样。

8. 其他

迭代器、克隆实现、序列化等实现细节就不赘述了,基本思想已经在 博文 Java集合框架源码分析之ArrayList中描述过了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值