美文网首页
Java面试3——Java8List源码解析

Java面试3——Java8List源码解析

作者: 星辰学院 | 来源:发表于2020-04-11 17:47 被阅读0次

    关注【星辰学院】 http://xingchenxueyuan.com 更多知识和内容,一起打怪升级!

    ArrayList

    概览

    • ArrayList 是基于数组实现的,支持快速随机访问。

    • 数组的默认大小为 10。

      存储结构如图:

      Java面试3——ArrayList存储结构.png

    扩容

    • 添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5 倍。

      扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

    删除元素

    • 需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。
    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;
    }
    
    • System.arraycopy
      • 这是native方法,拷贝数组效率非常高
    * @param      src      the source array. 源数组
    * @param      srcPos   starting position in the source array. 源数组的起始位置
    * @param      dest     the destination array. 目标数组
    * @param      destPos  starting position in the destination data. 目标数组的起始位置
    * @param      length   the number of array elements to be copied. 复制的长度
    public static native void arraycopy(Object src,int srcPos,Object dest, int destPos,int length)
    

    Vector

    • 它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    
    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
    
        return elementData(index);
    }
    
    • 与 ArrayList 的比较
      • Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
      • Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。

    CopyOnWirteArrayList

    读写分离

    写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。

    写操作需要加锁,防止并发写入时导致写入数据丢失。

    写操作结束之后需要把原始数组指向新的复制数组。

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    final void setArray(Object[] a) {
        array = a;
    }
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
    

    2. 适用场景

    CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。

    但是 CopyOnWriteArrayList 有其缺陷:

    • 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;
    • 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。

    所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。

    LinkedList

    概览

    基于双向链表实现,使用 Node 存储链表节点信息。

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
    }
    

    每个链表存储了 first 和 last 指针:

    transient Node<E> first;
    transient Node<E> last;
    

    结构图

    Java面试3——LinkedList存储结构.png

    与 ArrayList 的比较

    ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。ArrayList 和 LinkedList 的区别可以归结为数组和链表的区别:

    • 数组支持随机访问,但插入删除的代价很高,需要移动大量元素;
    • 链表不支持随机访问,但插入删除只需要改变指针。

    关注【星辰学院】 http://xingchenxueyuan.com 更多知识和内容,一起打怪升级!

    相关文章

      网友评论

          本文标题:Java面试3——Java8List源码解析

          本文链接:https://www.haomeiwen.com/subject/jauumhtx.html