zoukankan      html  css  js  c++  java
  • Java集合总结(一):列表和队列

    java中的具体容器类都不是从头构建的,他们都继承了一些抽象容器类。这些抽象容器类,提供了容器接口的部分实现,方便具体容器类在抽象类的基础上做具体实现。容器类和接口的关系架构图如下:

    虚线框表示接口,有Collection, List, Set, Queue, Deque和Map。

    有六个抽象容器类:

    • AbstractCollection: 实现了Collection接口,被抽象类AbstractList, AbstractSet, AbstractQueue继承,ArrayDeque也继承自AbstractCollection (图中未画出)。
    • AbstractList:父类是AbstractCollection,实现了List接口,被ArrayList, AbstractSequentialList继承。
    • AbstractSequentialList:父类是AbstractList,被LinkedList继承。
    • AbstractMap:实现了Map接口,被TreeMap, HashMap, EnumMap继承。
    • AbstractSet:父类是AbstractCollection,实现了Set接口,被HashSet, TreeSet和EnumSet继承。
    • AbstractQueue:父类是AbstractCollection,实现了Queue接口,被PriorityQueue继承。

    这些抽象类都提供了相应接口的基础实现,同时定义了一些子类必须重写的抽象方法 ,相当于定义了一个模板,这有点类似于一个设计模式——模板方法模式

    下面主要总结一下上图中具体实现类的一些细节和特性:

    ArrayList

    内部使用Object数组实现、默认初始大小是10,可手动指定,扩容策略是 newCapacity = oldCapacity + (oldCapacity >> 1),
    即每次增大到原来的1.5倍,但不会小于最小值,最大容量是Integer.MAX_VALUE,即0x7fffffff。扩容时将一个新建一个数组将原来数组的内容复制过去。

    没用泛型数组E[]原因个人猜测是因为java中的泛型是在jdk1.5才开始支持的,在这之前都用的Object,为了保持向前的兼容性不得不这么做,jdk1.6中新增的ArrayDeque内部就是泛型数组E[]。

    LinkedList
    内部使用链表实现,可以用作队列(Queue),队列两端都可以操作,尾部添加,头部查看和删除。也可以用作双端队列(Deque)(双向链表)(双向遍历、可以实现栈的功能)。

    内部只维护头和尾结点,构造方法不能指定大小,按需分配空间。

    性能

    • 不可以随机访问,必须从头或尾开始查找,效率为O(N/2)(用了二分法)
    • 按照内容查找效率为O(N/2)
    • 两端添加删除效率为O(1)
    • 中间插入删除先定位 ,效率为O(n),但插入或者删除本身效率很高,为O(1)

    ArrayDeque
    这是jdk1.6新增的一个类。内部维护一个泛型数组E[],使用head和tail两个int索引表示链表头和尾(其中tail指向下一个空位),使得物理上简单的从头到尾的数组变成了逻辑上循环的数组,可以作为链表使用。默认初始数组大小是16,扩容策略是乘以2

    • 如果head和tail相同,则数组为空,长度为0。
    • 如果tail大于head,则第一个元素为elements[head],最后一个为elements[tail-1],长度为tail-head,元素索引从head到tail-1。
    • 如果tail小于head,且为0,则第一个元素为elements[head],最后一个为elements[elements.length-1],元素索引从head到elements.length-1。
    • 如果tail小于head,且大于0,则会形成循环,第一个元素为elements[head],最后一个是elements[tail-1],元素索引从head到elements.length-1,然后再从0到tail-1。

    可以通过构造方法指定初始数组大小numElements,但是实际的大小是:

    • 如果numElements小于MIN_INITIAL_CAPACITY,则分配的数组长度就是MIN_INITIAL_CAPACITY,它是一个静态常量,值为8。
    • 在numElements大于等于8的情况下,分配的实际长度是严格大于numElements并且为2的整数次幂的最小数。比如,如果numElements为10,则实际分配16,如果numElements为32,则为64。

    使用2的幂次方是为了保证

    public void addLast(E e) {
            if (e == null)
                throw new NullPointerException();
            elements[tail] = e;
            if ( (tail = (tail + 1) & (elements.length - 1)) == head)
                doubleCapacity();
        }

    (tail + 1) & (elements.length - 1)能定位到正确下一个位置tail,比如说,如果elements.length为8,则(elements.length - 1)为7,二进制为0111,对于负数-1,与7相与,结果为7,对于正数8,与7相与,结果为0,都能达到循环数组中找下一个正确位置的目的。

    扩容之后会重新排列数组,head是0,tail是当前元素数。

    性能

    • 在两端添加、删除元素的效率很高,动态扩展需要的内存分配以及数组拷贝开销可以被平摊,具体来说,添加N个元素的效率为O(N)。
    • 根据元素内容查找和删除的效率比较低,为O(N)。

    简单总结:

    与ArrayList和LinkedList不同,ArrayDeque没有索引位置的概念,不能根据索引位置进行操作。
    ArrayDeque和LinkedList都实现了Deque接口,应该用哪一个呢?如果只需要Deque接口,从两端进行操作,一般而言,ArrayDeque效率更高一些,应该被优先使用,不过,如果同时需要根据索引位置进行操作,或者经常需要在中间进行插入和删除,则应该选LinkedList。

  • 相关阅读:
    CSS之旅——第二站 如何更深入的理解各种选择器
    CSS之旅——第一站 为什么要用CSS
    记录一些在用wcf的过程中走过的泥巴路 【第一篇】
    asp.net mvc 之旅—— 第二站 窥探Controller下的各种Result
    asp.net mvc 之旅—— 第一站 从简单的razor入手
    Sql Server之旅——终点站 nolock引发的三级事件的一些思考
    Sql Server之旅——第十四站 深入的探讨锁机制
    Sql Server之旅——第十三站 对锁的初步认识
    Sql Server之旅——第十二站 sqltext的参数化处理
    Sql Server之旅——第十一站 简单说说sqlserver的执行计划
  • 原文地址:https://www.cnblogs.com/JackPn/p/9419349.html
Copyright © 2011-2022 走看看