上一篇讲了ArrayList,它有一个"孪生兄弟"--LinkedList,这两个集合类总是经常会被拿来比较,今天就分析一下LinkedList,然后总结一下这俩集合类的不同

首先看看其成员变量   

    transient int size = 0;//所含元素数量

    transient Node<E> first;//链表的首项

    transient Node<E> last;//链表的尾项

ArrayList是基于数组的,而LinkedList是用链表来实现的,链表不需要考虑容量问题,所以LinkedList的没有容量相关的成员变量,链表图例:

LinkedList中的节点是以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;
}
}

构造函数,很简单 

    /**
* 空集合
*/
public LinkedList() {
} /**
* 传入一个集合对象
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}

接下来看看具体对集合的操作方法,先看看add(E e)

public boolean add(E e) {
linkLast(e);
return true;
}   /**
* 新添加的元素直接链接到链表中的最后一个
*/
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++; }

非常简单,没有什么复杂的操作,就是涉及到几个引用的重新指向而已,常数时间复杂度(与具体的集合规模大小无关);这里想想上一篇写的ArrayList的add方法,需要考虑扩容问题,各种判断,然后在需要扩容的情况下,进行数组拷贝,线性时间复杂度(与集合所含元素成线性关系)。

再看看add(int index, E element)指定一个位置插入元素:

 public void add(int index, E element) {
checkPositionIndex(index);//index是否在合理范围内 if (index == size)//如果插入位置刚好在链表的尾部
linkLast(element);//直接链接到尾节点
else
linkBefore(element, node(index));//插入到指定位置上
}
//获取到指定位置的节点对象
Node<E> node(int index) {
//这里有个小技巧,如果位置在前半段,那就从首节点开始遍历,否则从尾节点开始倒叙遍历
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;
}
} //插入到某个节点之前,主要还是引用的重新指向操作
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;//新节点作为旧节点的后继的前驱
size++;
modCount++;
}

用图来表示这个具体的插入过程:

  

这时候想想ArraryList和LinkedList的插入方法,到底谁高呢?我们之前都知道LinkedList的动态操作效率应该是比ArraryList的要高的,这里我写了个测试方法:  

List<String> arrayList = new ArrayList<>();
List<String> linkedList = new LinkedList<>();
for (int i = 0; i < 50000; i++) {
arrayList.add(i+"");
linkedList.add(i+"");
}
long millis01 = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
arrayList.add(i,"test");
}
long millis02 = System.currentTimeMillis();
System.out.println("ArrayList用时:"+(millis02-millis01)+"ms");
for (int i = 0; i < 50000; i++) {
linkedList.add(i,"test");
}
long millis03 = System.currentTimeMillis();
System.out.println("linkedList用时:"+(millis03-millis02)+"ms");     结果

       ArrayList用时:540ms
       linkedList用时:2161ms

结论:ArrayList效率高很多,主要是因为linkedList的add(int index, E element)在执行插入操作之前需要遍历查找对应位置的节点,当然具体的插入操作是基本不影响效率的。

linkedList的移除操作的主要核心方法E unlink(Node<E> x): 

E unlink(Node<E> x) {
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;//设为null,等待gc
} if (next == null) {
last = prev;
} else {
next.prev = prev;//前驱作为后继的前驱
x.next = null;//设为null 等待gc
} x.item = null;//设为null,等待gc
size--;
modCount++;
return element;
}

还是那句话,链表的具体动态操作只是涉及到常数操作的引用重新指向,但是如果在具体动态操作之前有需要进行遍历查找的话,就很影响效率了。

接下来看看linkedList的查找操作:

 public E get(int index) {
checkElementIndex(index);
return node(index).item;
}

  就是调用node(index),我们上面分析过,就是遍历,效率自然是比较低的,没什么好说的。

最后对比一下LinkedList和ArrayList遍历的效率:

  

 List<String> linkedList = new LinkedList<>();
List<String> arrayList= new ArrayList<>(1000000);
for (int i = 0; i < 1000000; i++) {
linkedList.add(i+"");
arrayList.add(i+"");
}
long millis1 = System.currentTimeMillis();
for (String s : linkedList) {
System.out.print(s);
}
System.out.println();
long millis2 = System.currentTimeMillis();
System.out.println("linkedList耗时-----"+(millis1-millis2));
for (String s : arrayList) {
System.out.print(s);
}
System.out.println();
long millis3 = System.currentTimeMillis();
System.out.println("arrayList耗时-----"+(millis3-millis2));   结果:linkedList耗时------1702
     arrayList耗时-----1664

  根据多次运行结果上来说,两者的效率相当,没有明显的差别,首先我们知道增强的for循环是java中的语法糖,本质上是使用的是迭代器遍历,首先可以知道ArrayList使用迭代器和对数组时候用普遍的for本质上是没有差别,都是通过索引值递增实现;我们主要看一下LinkedList中迭代器的实现:

private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;//最后一次读取的节点
private Node<E> next;//下一个读取的节点
private int nextIndex;//序号,记录读到哪个位置
private int expectedModCount = modCount; ListItr(int index) {
next = (index == size) ? null : node(index);
nextIndex = index;
}
//是否还有下一个元素
public boolean hasNext() {
return nextIndex < size;
}
//获取下一个元素
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException(); lastReturned = next;//记录本次使用的节点
next = next.next;//记录下次遍历的节点
nextIndex++;
return lastReturned.item;
}
}

可以看到,链表的遍历主要是通过前一个元素保存的下一个对象的引用来实现的,时间复杂度都线性的,所以在使用迭代器遍历上,LinkedList和ArrayList没有明显的差别;

这里提一下ListIterator和Iterator,ListIterator是List类型的集合特有的,因为List是有序的,我们可以通过索引指定迭代器遍历集合的区间,所以可以实现ListIterator接口。

总结:1.对于随机(根据索引)访问,ArrayList绝对地优于LinkedList,因为LinkedList要进行遍历查找

   2.对于新增和删除操作,LinkedList不一定会效率比ArrayList高,因为在根据索引值进行新增和删除的方法中,需要首先通过索引值进行查找然后在进行具体的动态操作,LinkedList的效率实际上也是不如ArrayList的,这一点网上很多说法都不是很准确

  

  

  

JDK集合框架--LinkedList的更多相关文章

  1. Java自学-集合框架 LinkedList

    Java集合框架 LinkedList 序列分先进先出FIFO,先进后出FILO FIFO在Java中又叫Queue 队列 FILO在Java中又叫Stack 栈 示例 1 : LinkedList ...

  2. JDK(一)JDK集合框架

    JDK中的集合框架分为两大类:Collection和Map.Collection以一组Object的形式保存元素,Map以Key-Value对的形式保存元素. 上图列出的类并不完整,只列举了平时比较常 ...

  3. JDK集合框架源码分析 - 简单概要

    1.类继承体系 在集合框架的类继承体系中,最顶层有两个接口Collection.Map: Collection 表示一组纯数据 Map 表示一组key-value对 Collection的类继承体系: ...

  4. Java8集合框架——LinkedList源码分析

    java.util.LinkedList 本文的主要目录结构: 一.LinkedList的特点及与ArrayList的比较 二.LinkedList的内部实现 三.LinkedList添加元素 四.L ...

  5. 集合框架——LinkedList集合源码分析

    目录 示例代码 底层代码 第1步(初始化集合) 第2步(往集合中添加一个元素) 第3步(往集合中添加第二个元素) 第4步(往集合中添加第三个元素) LinkedList添加元素流程示意图 第5步(删除 ...

  6. JDK集合框架--综述

       接下来的几篇博客总结一下对jdk中常用集合类知识,本篇博客先整体性地介绍一下集合及其主要的api: 从整体上来说,集合分两大类collection和map: 首先来看看Collection: c ...

  7. JDK集合框架--ArrayList

    ArrayList,从类名就可以看出来,这是由数组实现的List,即内部是用数组保存元素的有序集合.先看看主要的成员变量,比较简单: public class ArrayList<E> e ...

  8. 集合框架-LinkedList集合(有序不唯一)

    1 package cn.itcast.p2.linkedlist.demo; 2 3 import java.util.Iterator; 4 import java.util.LinkedList ...

  9. 集合框架-LinkedList集合练习(堆栈和队列)

    1 package cn.itcast.p2.linkedlist.test; 2 3 import java.util.LinkedList; 4 5 /* 6 * 请使用LinkedList来模拟 ...

随机推荐

  1. Python的字符串和列表和字典的方法/函数

    字符串 S.find()#可指定范围查找字串,返回索引值,否则返回-1 S.index()#同find,只是找不到的之后返回异常 S.count()#返回找到字串的个数 S.lower()#转小写 S ...

  2. 三. 200多万元得到的创业教训--创业并不须要app

    摘要:有个点子,研发app或站点,推广,不断改进,探索盈利模式.这个通用的移动互联网创业流程.但我觉得.在某些特定的商业模式下,"研发app或站点"这步能够砍掉或推迟. 健生干货分 ...

  3. Android进阶图片处理之三级缓存方案

    图片的三级缓存 一.概述 一開始在学习Android的时候.处理图片的时候,每次获取图片都是直接从网络上面载入图片. 可是在开发项目的过程中,每次点击进入app里面,图片都要慢慢的再一次从网络上面载入 ...

  4. Xcode6 设置LaunchImage图标

    最近设置LaunchImage图标时发现怎么都没有效果,后来发现是Xcode6中新建项目的时候会默认添加一个LaunchScreen.xib的文件,我们启动程序的时候也会发现,加载的时LaunchSc ...

  5. [IT练习册]Python练习项目 思路

    1.爬虫:爬取如下网站一年的内容. http://www.calvarymoravian.org/dailytext 2.蛇形棋: 开发一个类似蛇形棋的游戏.最好基于Web. 3.爬虫+通讯录: 从公 ...

  6. perl BEGIN block and END block

    1 本质上就是一段代码 BEGIN在程序运行前执行,END在程序运行之后执行. 2 BEGIN END的行为和所在的位置无关 也就是说,无论BEGIN和END block位于代码的哪里,最先执行的是B ...

  7. 显示 目录 大小 du

    [root@hadoop1 /]# du -BG  -d 11G ./boot0G ./dev63G ./homedu: 无法访问"./proc/6689/task/6689/fd/4&qu ...

  8. linux命令alias永久配置

    需求:清屏的指令是:clear,感觉用着不爽,写这个命令太慢,想自定义命令:cls 解决:命令别名指令:alias 1:查看当前系统中有哪些别名:命令行输入:alias 2:添加我们自己的别名:ali ...

  9. eclipse svn修改用户名密码

    1,svn客户端删除 2,eclipse 补充,上图为网友资料,但实际操作过程中,发现eclipse中svn为svnkit,但相应目录下无.keyring文件,最后按步骤2删除auth下所有文件,再次 ...

  10. codeforces 688C C. NP-Hard Problem(bfs判断奇数长度环)

    题目链接: C. NP-Hard Problem time limit per test 2 seconds memory limit per test 256 megabytes input sta ...