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

posted @ 2020-06-08 17:43  wullll  阅读(225)  评论(0编辑  收藏  举报