List集合解析:ArrayList、Vector、LinkedList

ArrayList

ArrayList是基于数组的实现,支持随机访问,可以动态扩容

初始化:

  • 可以用带参的构造函数指定初始值,如果传入的参数是正数就创建指定容量的数组,如果是0就创建一个空数组,如果是负数,就抛出异常

  • 默认大小是10

添加元素:

  • 添加元素时使用ensureCapacityInternal() 方法来保证容量足够,如果不够需要使用grow扩容,扩容的时候每扩容原数组容量的1.5倍

  • 每次添加的时候都会修改modCount的值。modCount++

  • 使用Arrays.copyof()把原数组的元素复制到新的数组中。

  • 扩容1.5倍

    • 如果新的容量小于需要的最小容量size+1,新容量就取最小容量size++(这种情况就是旧容量只有1,新容量1.5向下取模是1,小于添加元素后的最小容量2,这个时候新容量是size+1=2)

    • 如果新的容量大于MAX_ARRAY_SIZE=Integer.MAX_VALUE-8

      • 再判断需要的最小容量是否大于MAX_ARRAY_SIZE,如果是,新容量取值:Integer.MAX_VALUE,否则,新的容量取值:MAX_ARRAY_SIZE

    总结:

    最小容量(a) 1.5倍扩容后的新容量(b) MAX_ARRAY_SIZE(c) Integer.MAX_VALUE(d)

    if b<a 新容量=a;

    if b>c if a>c 新容量=d

    else 新容量=c

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

  

删除元素:

  • 按照下标删除元素

    • 如果下标超出size范围,抛出异常IndexOutOfBoundsException

    • modCount++;

    • 判断要删除的元素是否在数组的结尾,如果在数组的结尾,就不需要调用System.arraycopy(),否则就需要调用System.arraycopy,通过numMoved=size-index-1判断

      • numMoved==0,是在数组结尾,elementData[--size] = null;

      • numMoved>0不是在结尾System.arraycopy(elementData,index+1,elementData,index,numMoved);

    • 返回删除的元素

    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;
    }
  • 根据元素进行删除

    • 遍历element数组,把第一个用equals判断相等的元素删除(如果要删除的元素是null,使用==判断),确定待删除元素的index,然后调用私有方法fastremove()跳过边界检查直接删除index位置上的元素,并且返回true;如果元素不存在返回false.

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

System.arraycopy()和Arrays.copyOf()的区别

  • System.arraycopy():native方法,使用System.arraycopy()进行数组的拷贝非常灵活, 可以选择拷贝的原数组的起点和长度, Arrays.copyOf()就是使用System.arraycopy()实现的

    arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

  • Arrays.copyof():Arrays.copyOf(original, length) 将源数组original中的元素拷贝到一个新数组中并返回这个新数组, 新数组的长度是length。copyOf(int[] original, int newLength)

modCount的作用:

  • modeCount用来记录ArrayList结构性变化的次数,包括添加元素和删除元素,在进行序列化或迭代的时候,需要比较操作前后的modCount是否改变,如果改变了就会抛出ConcurrentModificationException,这种迭代称为Fail-fast模式,常见的的使用fail-fast方式遍历的容器有HashMapArrayList等。

  • Fail-safe模式:这种遍历基于容器的一个克隆。因此,对容器内容的修改不影响遍历。常见的的使用fail-safe方式遍历的容器有ConcerrentHashMapCopyOnWriteArrayList

Vector

  • 使用synchronized同步,单步操作是线程安全的

  • 可以通过构造函数传入初始容量和每次扩容的倍数,如果传入0和默认情况都是两倍扩容

  • Vector与ArrayList的比较

    • Vector 是同步的,开销就比 ArrayList 大,访问速度更慢,最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;

    • Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。

    • 替代:可以使用 concurrent 并发包下的CopyOnWriteArrayList 类替代Vector实现同步

CopyOnWriteArrayList:

通过读写分离实现了并发操作的安全性

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

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

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

  • 适用场景:适用于读多写少的场景

  • 性能分析:写操作每次都要拷贝数组,耗费内存;不能用于实时读的场景,因为写操作复制数组新增元素需要耗费时间,虽然能到达最终一致性,但是读写同时发生的时候读到的还是旧的数据。

LinkedList

  • 基于双向链表的实现,使用Node表示链表节点信息。不支持随机访问,但是新增节点和删除节点的效率比ArrayList高

  • 与 ArrayList 的比较

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

    • 数组支持随机访问,但插入删除的代价很高,需要移动大量元素;

    • 链表不支持随机访问,但插入删除只需要改变指针。

 

posted @ 2021-06-17 15:22  keepkeep  阅读(51)  评论(0编辑  收藏  举报