ArrayList

ArrayList和Vector的区别

  1. ArrayList和Vector底层都是 Object数组
  2. Vector是线程安全的,对所有的操作都添加了 synchonized 锁,因此并发效率低
  3. ArrayList 是线程不安全的

ArrayList和LinkedList的区别

  1. ArrayList底层是Object数组,LinkedList底层是双向链表
  2. ArrayList实现了 RandomAccess接口,允许随机访问,LinkedList没有继承,因为底层的空间是不连续的因此不能随机访问
  3. ArrayList和LinkedList都是线程不安全的
  4. ArrayList添加元素需要进行扩容,同时需要进行空间的预留,LinkedList每个节点需要额外分配前后节点的空间
  5. ArrayList添加元素到末尾的时间复杂度是O(1),在其他位置添加元素因为要进行元素的移动,时间复杂度是O(n)。LinkedList在首尾添加元素和查询的时间复杂度是O(1),在其他位置添加和查询元素的时间复杂度是O(n)

ArrayList初始化

  1. 当使用无参构造创建ArrayList时,会将数组初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时数组为空数组,直到添加了第一个元素后才长度会被初始化到10
  2. 当有参构造传入长度是0时,会将数组初始化为 EMPTY_ELEMENTDATA ,与DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行区分,因为都是空数组,因此在判断容量时会根据地址判断,如果是DEFAULTCAPACITY_EMPTY_ELEMENTDATA 容量的默认值是10
  3. 当使用有参构造函数创建ArrayList时,则会直接创建大小为参数的数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
   * 带初始容量参数的构造函数。(用户自己指定容量)
   */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {//初始容量大于0
        //创建initialCapacity大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//初始容量等于0
        //创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {//初始容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

添加元素流程

add

  1. 判断元素是否需要扩容
  2. 在确保容量安全后,将元素赋值到数组中
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //这里看到ArrayList添加元素的实质就相当于为数组赋值
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal与ensureExplicitCapacity

  1. 判断目前所需的最小容量,如果是正常数组则为当前size+1,如果是无参构造的数组则为10
  2. 如果当前的长度小于目前所需的最小容量则需要进行数组的扩容
//获取需要的最小容量
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方法进行扩容,调用此方法代表已经开始扩容了
        grow(minCapacity);
}

grow

  1. 进入到grow方法后,则说明存储元素的数据需要进入扩容流程
  2. 首先需要确定扩容后的大小,默认会扩容为原数组大小的1.5倍,如果该大小仍然小于所需的最小值,则扩容后大小变为所需最小值
  3. 如果长度大于 Integer.MAX_VALUE-8,则数组大小直接扩容到Integer.MAX_VALUE
  4. 在确认好扩容流程后,通过Arrays.copyOf 进行数组的复制,本质上是申请一个新的数组,再将旧数组的数据复制到新数组中
  5. 因此在日常使用中,需要减少ArrayList的扩容,在初始化ArrayList时,尽量赋予初始值
/**
 * ArrayList扩容的核心方法。
 */
private void grow(int minCapacity) {
    // oldCapacity为旧容量,newCapacity为新容量
    int oldCapacity = elementData.length;
    //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
    //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //再检查新容量是否超出了ArrayList所定义的最大容量,
    //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
    //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
    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);
}
//比较minCapacity和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

线程不安全

空指针异常

  1. 当多个线程同时执行到 elementData[size++]=e时,size++后没有同步到主内存。
  2. 多个线程同时执行到 ensureCapacityInternal 判断无需扩容,此时多个线程会设置到size标记,然后再进行size++操作,中间会有一个size永远为null

image.png

数组越界

当多线程同时调用 ensureCapacityInternal方法,判断无需扩容,但在真正添加方法时,当其他线程添加后,会出现数组越界情况

并发修改异常

size没有同步到主内存时,其他的线程会把值覆盖到size下标上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值