文档章节

LinkdedList

大白来袭
 大白来袭
发布于 2017/06/28 08:37
字数 2298
阅读 17
收藏 0

LinkdedList特点

LinkedList是基于链表实现的,所以先讲解一下什么是链表。链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储有其下一个存储单元的地址(下一个存储单元的地址是必要的,有些存储结构还存放有其前一 个存储单元的地址),每次查找数据的时候,通过某个存储单元中的下一个存储单元的地址寻找其后面的那个存储单元。

LinkedList是一种双向链表,看一下LinkedList的基本存储单元,它是LinkedList中的一个内部类:

    private static class Entry<E> {
        E element;
        Entry<E> next;
        Entry<E> previous;
        ...
    }
关注点 结论
是否允许为空 允许
是否允许重复 允许
是否有序 有序
是否线程安全 非线程安全

LinkdedList底层

添加元素

    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    {
        private transient Entry<E> header = new Entry<E>(null, null, null);
        private transient int size = 0;
        /**
         * Constructs an empty list.
         */
        public LinkedList() {
            header.next = header.previous = header;
        }
        ...
    }

new了一个Entry出来名为header,Entry里面的previous、element、next都为null,执行构造函数的时候,将 previous和next的值都设置为header的引用地址,还是用画图的方式表示。32位JDK的字长为4个字节,而目前64位的JDK一般采用的 也是4字长,所以就以4个字长为单位。header引用地址的字长就是4个字节,假设是0×00000000,那么执行 完”List<String> list = new LinkedList<String>()”之后可以这么表示:

    public boolean add(E e) {
         addBefore(e, header);
         return true;
    }


    private Entry<E> addBefore(E e, Entry<E> entry) {
        //实例化Entry,值为e,next为entry,previous为entry.previous
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
        //新实例化前一个元素的next指向新实例自己
        newEntry.previous.next = newEntry;
        //新实例化后一个元素的previous指向新实例自己
        newEntry.next.previous = newEntry;
        //链表长度加1
        size++;
        //采用fast-fail机器
        modCount++;
        return newEntry;
    }

①②③为实例化操作,④⑤为给新实例化前后元素的next/previous指向新实例自己

查看元素

    public E get(int index) {
        return entry(index).element;
    }

    private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
           throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        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;
        }
        return e;
    }

这段代码就体现出了双向链表的好处了。双向链表增加了一点点的空间消耗(每个Entry里面还要维护它的前置Entry的引用),同时也增加了一定的编程复杂度,却大大提升了效率。

由于LinkedList是双向链表,所以LinkedList既可以向前查找,也可以向后查找,第6行~第12行的作用就是:当index小于数组大小 的一半的时候(size >> 1表示size / 2,使用移位运算提升代码运行效率),向后查找;否则,向前查找。

这样,现在数据结构里面有10000个元素,刚巧查找的又是第10000个元素的时候,就不需要从头遍历10000次了,向后遍历即可,一次就能找到我要的元素。

删除元素

和ArrayList一样,LinkedList支持按元素删除和按下标删除,前者会删除从头开始匹配的第一个元素。

    public E remove(int index) {
         return remove(entry(index));
    }

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

第3步、第4步、第5步将待删除的Entry的previous、element、next都设置为了null,这三步的作用是让虚拟机可以回收这个Entry。

但是,这个问题我稍微扩展深入一点:按照Java虚拟机HotSpot采用的垃圾回收检测算法—-根节点搜索算法来说,即使previous、 element、next不设置为null也是可以回收这个Entry的,因为此时这个Entry已经没有任何地方会指向它了,(tail表示尾部最后一个元素)tail的 previous与header的next都已经变掉了,所以这块Entry会被当做”垃圾”对待。之所以还要将previous、element、 next设置为null,可能是为了兼容另外一种垃圾回收检测算法—-引用计数法,这种垃圾回收检测算法,只要对象之间存在相互引用,那么这块内存 就不会被当作”垃圾”对待。

插入元素

    public void add(int index, E element) {
        addBefore(element, (index==size ? header : entry(index)));
    }


    private Entry<E> addBefore(E e, Entry<E> entry) {
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
        newEntry.previous.next = newEntry;
        newEntry.next.previous = newEntry;
        size++;
        modCount++;
       return newEntry;
    }

LinkedList和ArrayList的对比

1、顺序插入速度ArrayList会比较快。因为ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置塞一个数据就好 了;LinkedList则不同,每次顺序插入的时候LinkedList将new一个对象出来,如果对象比较大,那么new的时间势必会长一点,再加上 一些引用赋值的操作,所以顺序插入LinkedList必然慢于ArrayList

2、 LinkedList更适用于存储较少元素。基于上一点,因为LinkedList里面不仅维护了待插入的元素,还维护了Entry的前置Entry和后继Entry,如果一个LinkedList中的Entry非常多,那么LinkedList将比ArrayList更耗费一些内存

3、使用各自遍历效率最高的方式,ArrayList的遍历效率会比LinkedList的遍历效率高一些

4、 有些说法认为LinkedList做插入和删除更快,这种说法其实是不准确的:

(1)LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Entry的引用地址

(2)ArrayList做插入、删除的时候,慢在数组元素的批量copy,快在寻址

所以,如果待插入、删除的元素是在数据结构的前半段尤其是非常靠前的位置的时候,LinkedList的效率将大大快过ArrayList,因为 ArrayList将批量copy大量的元素;越往后,对于LinkedList来说,因为它是双向链表,所以在第2个元素后面插入一个数据和在倒数第2 个元素后面插入一个元素在效率上基本没有差别,但是ArrayList由于要批量copy的元素越来越少,操作速度必然追上乃至超过 LinkedList。

从这个分析看出,如果你十分确定你插入、删除的元素是在前半段,那么就使用LinkedList;如果你十分确定你删除、删除的元素在比较靠后的位置,那 么可以考虑使用ArrayList。如果你不能确定你要做的插入、删除是在哪儿呢?那还是建议你使用LinkedList吧,因为一来 LinkedList整体插入、删除的执行效率比较稳定,没有ArrayList这种越往后越快的情况;二来插入元素的时候,弄得不好ArrayList 就要进行一次扩容,扩容消耗时间又消耗空间。

对LinkedList以及ArrayList的迭代

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable

注意到ArrayList是实现了RandomAccess接口而LinkedList则没有实现这个接口,关于RandomAccess这个接口的作用,看一下JDK API上的说法:

public interface RamdomAcess

为此,我写一段代码证明一下这一点,注意,虽然上面的例子用的Iterator,但是做foreach循环的时候,编译器默认会使用这个集合的Iterator,具体可参见foreach循环原理。测试代码如下:

    public class TestMain
    {
        private static int SIZE = 111111;
        private static void loopList(List<Integer> list)
        {
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < list.size(); i++)
            {
                list.get(i);
            }
            System.out.println(list.getClass().getSimpleName() + "使用普通for循环遍历时间为" + (System.currentTimeMillis() - startTime) + "ms");
            startTime = System.currentTimeMillis();
            for (Integer i : list)
            {
              ...
            }
            System.out.println(list.getClass().getSimpleName() + "使用foreach循环遍历时间为" + (System.currentTimeMillis() - startTime) + "ms");
        }

        public static void main(String[] args)
        {
            List<Integer> arrayList = new ArrayList<Integer>(SIZE);
            List<Integer> linkedList = new LinkedList<Integer>();
            for (int i = 0; i < SIZE; i++)
            {
                arrayList.add(i);
                linkedList.add(i);
            }
            loopList(arrayList);
            loopList(linkedList);
            System.out.println();
        }
    }
    //截取三次运行结果

    ArrayList使用普通for循环遍历时间为6ms
    ArrayList使用foreach循环遍历时间为12ms
    LinkedList使用普通for循环遍历时间为38482ms
    LinkedList使用foreach循环遍历时间为11ms


    ArrayList使用普通for循环遍历时间为5ms
    ArrayList使用foreach循环遍历时间为12ms
    LinkedList使用普通for循环遍历时间为43287ms
    LinkedList使用foreach循环遍历时间为9ms


    ArrayList使用普通for循环遍历时间为4ms
    ArrayList使用foreach循环遍历时间为12ms
    LinkedList使用普通for循环遍历时间为22370ms
    LinkedList使用foreach循环遍历时间为5ms

ArrayList优先考虑for循环,LinkedList优先要用foreach

本文转载自:https://www.cnblogs.com/xrq730/p/5005347.html

大白来袭
粉丝 4
博文 41
码字总数 13667
作品 0
海淀
程序员
私信 提问

暂无文章

64.监控平台介绍 安装zabbix 忘记admin密码

19.1 Linux监控平台介绍 19.2 zabbix监控介绍 19.3/19.4/19.6 安装zabbix 19.5 忘记Admin密码如何做 19.1 Linux监控平台介绍: 常见开源监控软件 ~1.cacti、nagios、zabbix、smokeping、ope...

oschina130111
今天
13
0
当餐饮遇上大数据,嗯真香!

之前去开了一场会,主题是「餐饮领袖新零售峰会」。认真听完了餐饮前辈和新秀们的分享,觉得获益匪浅,把脑子里的核心纪要整理了一下,今天和大家做一个简单的分享,欢迎感兴趣的小伙伴一起交...

数澜科技
今天
7
0
DNS-over-HTTPS 的下一代是 DNS ON BLOCKCHAIN

本文作者:PETER LAI ,是 Diode 的区块链工程师。在进入软件开发领域之前,他主要是在做工商管理相关工作。Peter Lai 也是一位活跃的开源贡献者。目前,他正在与 Diode 团队一起开发基于区块...

红薯
今天
10
0
CC攻击带来的危害我们该如何防御?

随着网络的发展带给我们很多的便利,但是同时也带给我们一些网站安全问题,网络攻击就是常见的网站安全问题。其中作为站长最常见的就是CC攻击,CC攻击是网络攻击方式的一种,是一种比较常见的...

云漫网络Ruan
今天
12
0
实验分析性专业硕士提纲撰写要点

为什么您需要研究论文的提纲? 首先当您进行研究时,您需要聚集许多信息和想法,研究论文提纲可以较好地组织你的想法, 了解您研究资料的流畅度和程度。确保你写作时不会错过任何重要资料以此...

论文辅导员
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部