• 概述

a, 我们知道LinkedList它的数据存储方式是双向链表,基于链表存储的特性, LinkedList具有查询较慢(顺序访问)但增加/删除较快(虽然要遍历到指定位置, 但是相对于数组存储来说不需要移位)的特点

b, 双向链表的作用, 我们在查询的时候,可以从前往后遍历, 也可以从后往前遍历, 具体使用哪种方式取决于我们要查询的节点是靠后一点还是靠前一点

c, LinkedList内部每个节点用私有内部类Node表示, LinkedList通过first和last引用分别指向链表的第一个和最后一个元素, 当链表为空时, first和last都为null值

d, Node里面有三个元素: item当前节点的值。 next 指向当前节点的后一个节点。prev 指向当前节点的前一个节点

e, LinkedList不存在容量不足的情况。 因为是链表,动态获取内存, 不需要我们去扩容

f, LinkedList可以存储null值

g, LinkedList因为实现了Queue接口, 所以还能当队列使用

  • LinkedList的继承关系

LinkedList 继承自 AbstractSequentialList,同时又实现了 List 和 Deque 接口

java集合类之LinkedList_链表

 

从实现上,AbstractSequentialList 提供了一套基于顺序访问的接口。所以LinkedList作为其子类, 也是基于顺序访问。

另外,LinkedList还实现了Deque(double ended queue),Deque又继承自Queue接口。这样LinkedList就具备了队列的功能

  • LinkedList的常用方法

先看构造方法, LinkedList提供了两种构造方法, 无参构造方法会创建一个空的list 

public LinkedList(Collection<? extends E> c) 返回一个包含了指定集合的list, 注意这里参数不为空, 否则会抛出空指针异常

java集合类之LinkedList_链表_02

 

 LinkedList在查询first和last节点时并不需要遍历, 可以一次定位到对应的节点,然后取出节点的值(item)

java集合类之LinkedList_构造方法_03

 

 同理, 删除first或last节点时也不需要遍历

 java集合类之LinkedList_数组_04

 

 我们看到这里调用了unlinkFirst和unLinkLast方法, 让我们看看unlink方法做了什么

java集合类之LinkedList_时间复杂度_05

 

 这里的节点是non-null的, 因为removeFirst()/removeLast()方法里已经做了判空操作

 

向list的首部或尾部添加元素

java集合类之LinkedList_时间复杂度_06

 

 java集合类之LinkedList_构造方法_07

 

上面描述的都是对首节点/尾节点的操作, 并不需要遍历, 因此性能上与arrayList并无区别

接下来看看需要遍历的情况 

向指定位置添加元素

java集合类之LinkedList_时间复杂度_08

 

 我们看到这里首先做了index合法性检查,必须>=0且<=size, 否则会抛出数组越界异常

java集合类之LinkedList_链表_09

 

 linkBefore方法也比较简单, 看注释, insert element before non-null node

java集合类之LinkedList_构造方法_10

 

 那么它是怎么定位到这个non-null  node的呢?

java集合类之LinkedList_时间复杂度_11

 

 注意这里, 为了提高查询效率, 会先判断index在list中的位置,决定遍历的顺序

当然即便如此, LinkedList的查询效率还是不如Arraylist, 因为ArrayList基于数组存储,不需要遍历

理解了这个方法之后, 其他方法都比较好理解了, 不再赘述

 

接下来我们重点看看LinkedList的遍历 

通常来说,我们遍历一个list有两种方式:

1)普通for循环(下标递增或递减,达到size/0时停止遍历)

public static void main(String[] args)
{
    List<Integer> linkedList = 
            new LinkedList<Integer>();
    
    for (int i = 0; i < 100; i++)
        linkedList.add(i);
    for (int i = 0; i < 100; i++)
        System.out.println(linkedList.get(i));
}
这种方法也可以称为get方法, 因为最终调用了list.get(i)来获取元素
该方法用于ArrayList没有问题, 因为ArrayList基于数组存储, get(i)方法可以直接获得对应的元素, 几乎等同于ArrayList的Iterator, 我们看下源码

java集合类之LinkedList_数组_12

 

return (E) elementData[lastRet = i];  和get(i)方法几乎一样 

java集合类之LinkedList_迭代器_13

 

但是对于LinkedList, 我们在上文中看到get(i)方法需要遍历整个list,这在数据量大的时候将是一个非常耗时的操作

 2)使用迭代器

因此对于LinkedList, 我们强烈建议使用迭代器(for each的实现几乎等价于迭代器)

java的collections继承自Iterable接口, 因此所有的集合类都可以使用迭代器进行遍

    for(Iterator iter = list.iterator(); iter.hasNext();)  

    iter.next();  

写法非常简便, 而且效率远远高于get方式, 为什么呢?我们看下LinkedList源码

java集合类之LinkedList_构造方法_14

 

 从上面代码中可以看出LinkedList迭代器的next函数只是通过next指针快速得到下一个元素并返回。而get方法会从头遍历直到index下标,查找一个元素时间复杂度为哦O(n),遍历的时间复杂度就达到了O(n2)。
所以对于LinkedList的遍历推荐使用foreach(迭代器),避免使用get方式遍历。

 

几种其他的遍历方式

如果我们遍历完之后就不再使用这个list了, 我们还可以使用removeFirst()或removeLast()方法,效率更高

try {  

 while(list.removeFirst() != null) ;  

catch (NoSuchElementException e) {  

}  

因为LinkedList查询first/last节点时不需要遍历