ArrayList

初始化

private static final Object[] EMPTY_ELEMENTDATA = https://www.it610.com/article/{}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; public ArrayList(int initialCapacity) { if (initialCapacity> 0) { this.elementData = https://www.it610.com/article/new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }public ArrayList() { this.elementData = https://www.it610.com/article/DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

初始化目的是为了初始化底层的elementData,但是无参构造会将elementData初始化为一个空数组,当插入,扩容会按默认值重新初始化数组,有参数构造,则会根据次参数来构造数组,这样的做法显然是为了按需分配避免浪费
插入
/** 在元素序列尾部插入 */ public boolean add(E e) { // 1. 检测是否需要扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // 2. 将新元素插入序列尾部 elementData[size++] = e; return true; }/** 在元素序列 index 位置处插入 */ public void add(int index, E element) { rangeCheckForAdd(index); // 1. 检测是否需要扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // 2. 将 index 及其之后的所有元素都向后移一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 3. 将新元素插入至 index 处 elementData[index] = element; size++; }

  • 直接插入(默认插入到尾部)
  1. 检查数组是否有足够的空间
  2. 将新元素插入至尾部

    ArrayList
    文章图片
    image
  • 指定位置插入
  1. 检测是不是有足够的空间
  2. 将index及其后面的所有元素退因为
  3. 将新元素插入
    ArrayList
    文章图片
    image
    注意 可以看到这个操作时间复杂度为O(N),平凡的一定元素坑定为导致效率的问题,所以日常开发中最好别用特别是元素较多的时候
扩容 接下来就讲讲扩容吧
扩容的入口:
ensureCapacityInternal 就是扩容的入口,在插入前我们得检查是否需要扩容从而得到了他。
/** 扩容的入口方法 */ private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }/** 计算最小容量 */ private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData =https://www.it610.com/article/= DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return 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; // newCapacity = oldCapacity + oldCapacity / 2 = oldCapacity * 1.5 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 扩容 elementData = https://www.it610.com/article/Arrays.copyOf(elementData, newCapacity); }private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); // 如果最小容量超过 MAX_ARRAY_SIZE,则将数组容量扩容至 Integer.MAX_VALUE return (minCapacity> MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }

em....比较简单就不讲了,最关键查看到put时会查看这个时候其表elementData =https://www.it610.com/article/=DEFAULTCAPACITY_EMPTY_ELEMENTDATA如果是,才开始初始化数组。
删除
因为删除没有无参的方法,所以难以避免的就是造成了元素的移动。
/** 删除指定位置的元素 */ public E remove(int index) { rangeCheck(index); modCount++; // 返回被删除的元素值 E oldValue = https://www.it610.com/article/elementData(index); int numMoved = size - index - 1; if (numMoved> 0) // 将 index + 1 及之后的元素向前移动一位,覆盖被删除值 System.arraycopy(elementData, index+1, elementData, index, numMoved); // 将最后一个元素置空,并将 size 值减1 elementData[--size] = null; // clear to let GC do its workreturn oldValue; }E elementData(int index) { return (E) elementData[index]; }/** 删除指定元素,若元素重复,则只删除下标最小的元素 */ 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; }/** 快速删除,不做边界检查,也不返回删除的元素值 */ private void fastRemove(int index) { modCount++; 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 }

先来看根据index判断是不是要进行删除。
  1. 获取索引位置的元素值
  2. 将index+1之后的的元素向前移动一位
  3. 最后将最后一个元素置空,size也减去一个1
  4. 然后 返回被删除的值。
如果是对象
  1. 判断是不是传入对象是不是为空,如果是就调用fastRemove删除value为空的
  2. 如果不是就删除,就遍历找到最小的所以同样调用fastRemove进行删除。

    ArrayList
    文章图片
    image
缩容 因为Arraylist的变化超大,扩容后删除会产生大量空闲空间,这个时候就需要锁绒了
/** 将数组容量缩小至元素数量 */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = https://www.it610.com/article/(size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }

ArrayList
文章图片
image 遍历
快速失败机制 当遇到并发修改的时候,的迭代器会快速失败,抛出异常ConcurrentModificationException。
关于遍历时删除 这个得注意,阿里巴巴 Java 开发手册里也有所提及。这里引用一下:
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
List a = new ArrayList(); a.add("1"); a.add("2"); for (String temp : a) { System.out.println(temp); if("1".equals(temp)){ a.remove(temp); } } }

eg代码:执行的逻辑看不出来问题,因为问题“隐藏”得比较深,我们把次打印到控制台发下只有1没有2,为啥捏
先来看看转换的另外一个代码吧:
List a = new ArrayList<>(); a.add("1"); a.add("2"); Iterator it = a.iterator(); while (it.hasNext()) { String temp = it.next(); System.out.println("temp: " + temp); if("1".equals(temp)){ a.remove(temp); } }

【ArrayList】这个时候,我们再去分析一下 ArrayList 的迭代器源码就能找出原因。

    推荐阅读