LinkedList的局限

  java.util.LinkedList是双向链表,这个大家都知道,比如Java的基础面试题喜欢问ArrayList和LinkedList的区别,在什么场景下用。大家都会说LinkedList随机增删多的场景比较合适,而ArrayList的随机访问多的场景比较合适。更进一步,我有时候会问,LinkedList.remove(Object)方法的时间复杂度是什么?有的人回答对了,有的人回答错了。回答错的应该是没有读过源码。

    理论上说,双向链表的删除的时间复杂度是O(1),你只需要将要删除的节点的前节点和后节点相连,然后将要删除的节点的前节点和后节点置为null即可,

//伪代码
node.prev.next=node.next;
node.next.prev=node.prev;
node.prev=node.next=null;

这个操作的时间复杂度可以认为是O(1)级别的。但是LinkedList的实现是一个通用的数据结构,因此没有暴露内部的节点Entry对象,remove(Object)传入的Object其实是节点存储的value,这里还需要一个查找过程:

  public boolean remove(Object o) {
        if (o==null) {
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            //查找节点Entry
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (o.equals(e.element)) {
                    //删除节点
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }

    删除节点的操作就是刚才伪代码描述的:

   private E remove(Entry<E> e) {
        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;
    }

    因此,显然,LinkedList.remove(Object)方法的时间复杂度是O(n)+O(1),结果仍然是O(n)的时间复杂度,而非推测的O(1)复杂度。最坏情况下要删除的元素是最后一个,你都要比较N-1次才能找到要删除的元素。

    既然如此,说LinkedList适合随机删减有个前提,链表的大小不能太大,如果链表元素非常多,调用remove(Object)去删除一个元素的效率肯定有影响,一个简单测试,插入100万数据,随机删除1000个元素:

        final List<Integer> list = new LinkedList<Integer>();
        final int count = 1000000;
        for (int i = 0; i < count; i++) {
            list.add(i);
        }
        final Random rand=new Random();
        long start=System.nanoTime();
        for(int i=0;i<1000;i++){
            //这里要强制转型为Integer,否则调用的是remove(int)
            list.remove((Integer)rand.nextInt(count));
        }
        System.out.println((System.nanoTime()-start)/Math.pow(10, 9));

   
    在我的机器上耗时近9.5秒,删除1000个元素耗时9.5秒,是不是很恐怖?注意到上面的注释,产生的随机数强制转为Integer对象,否则调用的是remove(int)方法,而非remove(Object)。如果我们调用remove(int)根据索引来删除:

        for(int i=0;i<1000;i++){
            list.remove(rand.nextInt(list.size()-1));
        }

    随机数范围要递减,防止数组越界,换成remove(int)效率提高不少,但是仍然需要2.2秒左右(包括了随机数产生开销)。这是因为remove(int)的实现很有技巧,它首先判断索引位置在链表的前半部分还是后半部分,如果是前半部分则从head往前查找,如果在后半部分,则从head往后查找(LinkedList的实现是一个环):

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

     最坏情况下要删除的节点在中点左右,查找的次数仍然达到n/2次,但是注意到这里没有比较的开销,并且比remove(Object)最坏情况下n次查找还是好很多。

    总结下,LinkedList的两个remove方法,remove(Object)和remove(int)的时间复杂度都是O(n),在链表元素很多并且没有索引可用的情况下,LinkedList也并不适合做随机增删元素。在对性能特别敏感的场景下,还是需要自己实现专用的双向链表结构,真正实现O(1)级别的随机增删。更进一步,jdk5引入的ConcurrentLinkedQueue是一个非阻塞的线程安全的双向队列实现,同样有本文提到的问题,有兴趣可以测试一下在大量元素情况下的并发随机增删,效率跟自己实现的特定类型的线程安全的链表差距是惊人的。

    题外,ArrayList比LinkedList更不适合随机增删的原因是多了一个数组移动的动作,假设你删除的元素在m,那么除了要查找m次之外,还需要往前移动n-m-1个元素。

文章转自庄周梦蝶  ,原文发布时间 2010-09-16

时间: 2024-09-17 04:27:42

LinkedList的局限的相关文章

.NET事件监听机制的局限与扩展分析_实用技巧

本文实例分析了.NET事件监听机制的局限与扩展.分享给大家供大家参考.具体分析如下: .NET中把"事件"看作一个基本的编程概念,并提供了非常优美的语法支持,对比如下C#和Java代码可以看出两种语言设计思想之间的差异. 复制代码 代码如下: // C# someButton.Click += OnSomeButtonClick; 复制代码 代码如下: // Java someButton.addActionListener(     new ActionListener(){    

SEO早已不再局限于搜索引擎了

一谈到SEO优化,想必大家最熟悉的还是百度百科中最正统的解释,即翻译成中文就是搜索引擎优化.而这里所指的搜索引擎在当时的理解基本上指的主要就是类似于谷歌.百度.雅虎(还有很多,就不一一列举出来了)等等这一类的大型搜索引擎.只是发展到如今,搜索引擎所覆盖的面越来越广阔了,小到一个普通的DZ论坛,大到诸如淘宝等等的网站,都有自己的搜索,这也使得SEO早已不再局限于搜索引擎了. 下面我就列举一些用途较为广泛的SEO方向给大家参考: 一.微博 微博的随意性在刚刚上市的时候吸引了无数人疯拥而至,只是时至今

SEO不要只局限于网站 应上升到搜索营销层面

有搜索的地方就有SEO,比如淘宝有搜索,阿里巴巴也有搜索,连微博都有搜索.其它的搜索都比较简单,规则最多最麻烦的是综合搜索引擎.如果一般的是做网站SEO的,去做阿里旺铺 的SEO就很简单,因为我之前有近四年的SEO经验.来到现在的这家公司需要做阿里巴巴,自然这个重任就落在了我的身上.研究阿里巴巴的搜索规则很简单,因为之前有全网搜索的经验,毕 竟阿里巴巴只是一个站内搜索,连收录问题都不用解决,比做百度容易很多,很容易就获取到了流量,不像做网站SEO. 做网站SEO解决收录问题都不容易,搞定排名没两

用户体验设计:在局限条件下的设计

马歇•布劳耶(Marcel Breuer,1902–1981),1925年设计了世界上第一把钢管椅子.上图是他设计的另外一个产品,一张桌子,一张由一根钢管弯曲而成的桌子,并不是将钢管切断再焊接,这样设计,除了看上去有意思,还有一个很重要的原因:成本低.当然,以现在的工业水平来看,是否焊接对成本的影响已经没那么大了.然而在当时,工艺上的区别却是一个需要考虑的因素,因为这样的局限,促成了上面这个产品.类似的技术局限其实始终都存在.社会生产从来就是在技术的局限下发展的,或者说,社会发展水平是技术的发展

解读现今国内返利网发展存在的三大局限

  在国内电商的浪潮下,返利网可以说也发展得风风火火的.虽然返利网没有电商巨头,天猫.京东.苏美等大手笔的营销模式,也没有孪生兄弟团购有诱人的价格,但从06年第一批返利网在国内兴起的时候,返利网一步步的实现盈利与稳步发展.但是有发展就必然存在着一定的局限,现今返利网模式亦然面临着很多自身的问题.这些问题不及时解决好,势必会对返利网的发展造成影响,下面笔者就庖丁解牛分析其中的三大局限. 返利网过度依赖于其他电商平台 众所周知,返利网的运营模式是借助其他的电商平台,所扮演的角色类似于中介,将其目标用

今日无米:突破局限单页站也能引爆流量

  不要局限与搞指数的关键字来做,小指数的关键字同样能做到流量大几千. 下面来分析下我发现的这个网站,这个网站爱站显示百度收录六页,实际只有三页,但是每天从百度来的流量通过爱站分析,流量在四千到五千左右,而且还有其他搜索引擎,及其其他网站流量来源.   这种流量很最准ip/PV的比率很高更具,网站制作也非常简单利用,GOOGLE自定义搜索.下面是我仿制的这两天的数据,怎么样大家可以自己看下.   域名刚刚注册几天,SEO的表现还不是很好,因为没有去做推广没外链.我为什么会选择wangpansou

Java集合源码剖析:LinkedList源码剖析

LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全的,只在单线程下适合使用. LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆. LinkedList源码剖析 LinkedList的源码如下(加入了比较详细的注释): package java.util; publi

Java集合学习(五) LinkedList详细介绍(源码解析)和使用示例

前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类--LinkedList. 和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList. 第1部分 LinkedList介绍 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedLis

.NET Framework源码研究系列之---ArrayList与LinkedList

在上一篇<.NET Framework源码研究系列之---马甲List>中我们一起研究了.NET中 List的源代码,也得到一些兄弟的热心反馈.其中一位仁兄说希望看到ArrayList与LinkedList源 代码,所以今天就以此为话题,大家一起看一下.NET中是如何实现ArrayList和LinkedList 的. 我们先看ArrayList和LinkedList在.NET中的位置,ArrayList的命名空间是 System.Collections,LinkedList的命名空间是Syst