ArrayList详解

1. 作为方法内部变量时,并发添加修改时未出现异常

局部变量存在于jvm虚拟机栈中,是线程私有的。每个⽅法在执⾏时都会创建⼀个栈帧,⽤于局部变量存在于局部变量表中。

2.作为类成员变量时

1.多线程并发添加数据时会出现数组越界的异常
private int size;
public boolean add(E e) {
 ensureCapacityInternal(size + 1); 
 elementData[size++] = e;
 return true;
}

从源码可以看出size 表示当前数组的⼤⼩,类型为int,没有使⽤volatile修饰,⾮线程安全的,并发时,size++就可能造成数据越界异常。

2. 多线程并发删除数据时⽆法移除完全部元素
public E remove(int index) {
 // 检查是否索引是否超出数组⼤⼩,超出抛出IndexOutOfBoundsException
 rangeCheck(index);
 // 在remove元素时,确保容量的⽅法中modCount累加
 modCount++;
 // 记录待删除元素的值
 E oldValue = elementData(index);
 // numMoved表示删除index位置的元素后,需要从index后移动多少个元素到前⾯去
 int numMoved = size - index - 1;
 if (numMoved > 0)
 //这行会发生数组元素的移动,多线程下,会导致出现删不掉元素。
 System.arraycopy(elementData, index+1, elementData, index,
 numMoved);
 // 置空引⽤最后⼀位元素,让更好地垃圾回收
 elementData[--size] = null; 
 // 返回删除元素的值
 return oldValue;
}

因为线程A移除某元素后 数组的元素会重新排列,导致线程B移除元素时找不到要移除的那个元素了。

3. 扩容为什么是1.5倍不是2倍

扩容的源码

public boolean add(E e) {
 // 确保数组⼤⼩是否⾜够,超出容量后执⾏扩容,size为当前数组的⼤⼩
 ensureCapacityInternal(size + 1); 
// 当前数组数据是否给定了初始值,如果没有判断是否使⽤默认容量10,还是预期容量
 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 }
}
private void ensureCapacityInternal(int minCapacity) {
// 当前数组数据是否给定了初始值,如果没有判断是否使⽤默认容量10,还是预期容量
 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;
 // oldCapacity >> 1 扩容后的⼤⼩是原来容量的1.5倍
 int newCapacity = oldCapacity + (oldCapacity >> 1);
 // 如果扩容后的值⼩于我们的期望值,已预期值为准
 if (newCapacity - minCapacity < 0)
 newCapacity = minCapacity;
 // 如果扩容后的值⼤于jvm所能分配的数组的最⼤值,就⽤Integer的最⼤值
 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);
}
private static int hugeCapacity(int minCapacity) {
 // 扩容后数组的⼤⼩下界不能⼩于0,上界不能⼤于Integer的最⼤值
 if (minCapacity < 0) // overflow
 throw new OutOfMemoryError();
 return (minCapacity > MAX_ARRAY_SIZE) ?
 Integer.MAX_VALUE :
 MAX_ARRAY_SIZE;
}

扩容是通过Arrays.copyOf(elementData, newCapacity) 实现的(native⽅法),本质是数组之间的拷⻉,扩容是会先新建⼀个符合我们预期容量的新数组,然后把⽼数组的数据拷⻉过去。
一般扩容因⼦最适合范围为(1, 2),
如果k >= 2,新容量刚刚好永远⼤于过去所有废弃的数组容量,无法重新利用原来的已经释放的空间。
那为什么不是1.7或者1.8呢 而是1.5呢?
因为扩容时使用了位运算,原来的容量向右移了一位 这样做的目的是速度快,
机器码运算速度>字节码运算速度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值