zoukankan      html  css  js  c++  java
  • Java8源码分析-ArrayList、LinkedList、Vector

    java集合框架体系图
    ArrayList
    LinkedList
    Vector
    集合涉及的类
    Iterator
    Array

    集合框架体系

    集合框架体系图

    List接口:
     1.有序的
     2.允许多个null元素
     3.具体的实现类常用的:ArrayList、Vector、LinkedList
    在实际开发中,我们如何选择list的具体实现类:
     1.安全性问题(多线程)
     2.是否频繁插入,删除操作
     3.是否是存储后遍历

    下面就来介绍了三种常用的集合类

    1.ArrayList

    1.1.内部为数组,初始化长度为10

        private static final int DEFAULT_CAPACITY = 10;   
        transient Object[] elementData;
    

    1.2.增加元素与扩容

    1.增加元素:在不扩容的情况下,会把元素插入到尾部。在扩容的情况下,把新元素添加到扩容以后的数组中

    2.扩容:把原来的数组复制到另一个内存空间更大的数组中。增加0.5倍,最大容量为Integer.MAX_VALUE-8

    在ensureCapacityInternal()方法中调用了grow()获得新数组的容量

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!! 确保容量
            elementData[size++] = e; //将新元素加到数组后面
            return true;
        }
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//源码注释为一些虚拟机在数组中保留一些标题字,所以要减少1个为
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1); //如果超出了数组大小,正常情况下,1.5倍
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)//极端情况,快满了,限制大小
                newCapacity = hugeCapacity(minCapacity);//扩容上限为0x7fffffff-8 即int大小-8
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    1.3.详解 int hugeCapacity(int minCapacity) 方法

    在上面的grow(int minCapacity)中调用了此方法来获得扩容的大小

        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    
    

    内存图
    解析:当minCapacity到临界值时,即当minCapacity*1.5 >MAX_ARRAY_SIZE 就会调用hugeCapacity(minCapacity)
    ,minCapacity值的临界值范围可能是MAX_ARRAY_SIZE/1.5 ~ MAX_ARRAY_SIZE-1
    这个时候 newCapacity 取得是 MAX_ARRAY_SIZE或者Integer.MAX_VALUE
    溢出情况:当minCapacity加到了Integer.MAX_VALUE 的时候,再加1,就变成负数了,所以这里就判断为负数的时候,抛出溢出异常

    1.4.删除 remove(int index)

    remove的时候,1.定位到位置、2.然后将后面的元素往前挪一位、3.数组长度--size(减一),最后一个元素置空
    (数组和链表,效率比较:删除一个元素的时候,数组和链表复杂度都是o(n),但是链表只要定位到了,那删除就是o(1),也就是链表效率高)

        public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    

    1.5.clear()的时候,不仅仅设置size=0就行了,要遍历设置为null

    因为要释放内存,将引用删掉,垃圾管理器就会去清理堆内存里面的对象数据

       public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }
    
    

    1.6 ArrayList与Vector的区别

    ArrayList线程不安全,适合在单线程中使用,效率较高
    Vector线程安全,适合在多线程中使用,但效率较低

    1.7.Iterator in java.util.ArrayList

    1.7.1.next()的实现

            public E next() {
                checkForComodification();
                int i = cursor;//cursor 默认值为0
                if (i >= size)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1; //每次完了只会cursor都会增加1
                return (E) elementData[lastRet = i];
            }
    
    

    1.7.2.hasNext()的实现

    当前位置不是最大值的时候,返回true

    public boolean hasNext() {
                return cursor != size;
            }
    

    1.7.3.remove()的实现

    删除当前位置

            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    

    2.LinkedList

    2.1.内部为双链表结构,静态内部类Node作为元素结构

    类的成员变量有:

    transient int size = 0;//链表大小
    transient Node<E> first;//头部节点
    transient Node<E> last;//尾部节点
    

    2.2.静态内部类作为元素结构

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

    2.3.增加 linkFirst(E e),连接到第一个节点,即头部

    addFirst(E e)调用的就是此方法
    示意图

        /**
         * Links e as first element.
         */
        private void linkFirst(E e) {
            final Node<E> f = first;//1.临时设置f为头部
            final Node<E> newNode = new Node<>(null, e, f);//2.创建一个元素
            first = newNode;//3.将头部指向新节点
            if (f == null)
                last = newNode; //如果头部是null,说明是第一个节点
            else
                f.prev = newNode; //4.然后将旧的头结点上一个指向新节点的尾部
            size++;
            modCount++;
        }
    

    2.4.增加 linkLast(E e)

    链接到最后一个节点,即尾部,比较简单,两个步骤。减少了arraylist扩容的步骤,效率大大提高,很舒服

    addLast(E e) 、add(E e)调用的就是此方法

        /**
         * Links e as last element.
         */
        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++;
        }
    
    

    2.5.遍历 根据索引查询元素

    根据索引来查询该位置的元素就体现了链表的弊端,复杂度o(n),而数组是o(1)
    get(int index) 用的就是它
    这里用了一个技巧, 通过index判断在前半段还是后半段。如果是前半段就从头部开始查询,否则从后半段查询,这样大概可以减少一半的时间

        Node<E> node(int index) {
            // assert isElementIndex(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;
            }
        }
    

    2.6.删除 remove(Object o)

    删除,首先要遍历查询到位置,遍历的阶段比数组稍慢。
    但是查询到位置之后,链表删除效率就更高,o(1),数组是o(n)

        public boolean remove(Object o) {
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null) {
                        unlink(x);//查询到node之后,元素指针移动的删除操作 o(1) 区别于数组
                        return true;
                    }
                }
            } else {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item)) {
                        unlink(x);
                        return true;
                    }
                }
            }
            return false;
        }
    
    

    2.7.定位 indexOf(Object o) lastIndexOf(Object o)

    找第一个,从头遍历。找最后一个,从尾遍历。效率和数组基本一样,都是遍历

        public int indexOf(Object o) {
            int index = 0;
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null)
                        return index;
                    index++;
                }
            } else {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item))
                        return index;
                    index++;
                }
            }
            return -1;
        }
    

    3.Vector

    和ArrayList一样,都是由数组实现的。
    不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,在public方法加入了synchronized修饰符,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。

    3.1.扩容

        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
    

    如果capacityIncrement(增加容量)传来的<=0,则oldCapacity+oldCapacity 即扩容两倍,可能就是因为要能同步,从而此集合可能会频繁插入数据,所以干脆就每次增加两倍。当数据量比较大的时候,Vector比较有优势

    集合涉及的类

    Iterator

    Iterator是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口如下:
     forEachRemaining(Consumer<? super E> action):为每个剩余元素执行给定的操作,直到所有的元素都已经被处理或行动将抛出一个异常
     hasNext():如果迭代器中还有元素,则返回true。
     next():返回迭代器中的下一个元素
     remove():删除迭代器新返回的元素。

    Array

    思考

    1.像这种方法为什么不这是public 而是private?即为什么不想被外部使用?

    //判断index是否在有效范围内
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
    

    2.LinkedList为什么要实现Deque?

    https://segmentfault.com/q/1010000000591418

  • 相关阅读:
    SimpleDateFormat解析的时区问题
    linux之cp/scp命令+scp命令详解
    java.net.SocketException: java.security.NoSuchAlgorithmException
    Gradle使用手册(一):为什么要用Gradle?
    js_实用
    exp.validate.js
    MySQL实用技巧
    MongoDB 用户配置
    js 图片处理 Jcrop.js API
    MySQL连接池
  • 原文地址:https://www.cnblogs.com/jkwll/p/12942873.html
Copyright © 2011-2022 走看看