transient

Object[] elementData :见 transient关键字
transient 关键字

trimToSize()

  1. ArrayList<Integer> list = newArrayList<>(5);
  2. for(inti=0;i<6;i++){
  3. list.add(i);
  4. }
  5. list.trimToSize();
  6. System.out.println(list);

第一个断点:
image.png
第二个断点: image.png 可以看到,数组的容量从_7=>6;所以可以推出该方法的作用,是将数组的容量改为了和listsize,节省了内存,内存紧张的时候可以用到,贴一下_trimToSize()的源码:_ ```java /**

  1. * Trims the capacity of this <tt>ArrayList</tt> instance to be the
  2. * list's current size. An application can use this operation to minimize
  3. * the storage of an <tt>ArrayList</tt> instance.
  4. */
  5. public void trimToSize() {
  6. modCount++;
  7. if (size < elementData.length) {
  8. elementData = (size == 0)
  9. ? EMPTY_ELEMENTDATA
  10. : Arrays.copyOf(elementData, size);
  11. }
  12. }
<a name="9E8dt"></a>
## 扩容机制

> 上源码!!!!
> add() 

```java
    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

看到每次添加都调用了ensureCapacityInternal(),我们点进去看

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

内部调用了两个方法,calculateCapacity【计算容量】和ensureExplicitCapacity【确保明确的容量】,先看calculateCapacity

    //入参 当前集合数组,最小容量(上一步传入的是当前集合长度+1,即确保可以够容下当前add的对象)
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //当前集合的数组是空的,那么返回 DEFAULT_CAPACITY和最小容量中比较大的一个
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity); //DEFAULT_CAPACITY=10
        }
        //如果当前数组不是空的,返回当前集合长度+1
        return minCapacity;
    }

再来看ensureExplicitCapacity

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //集合修改的次数++

        // overflow-conscious code 
        //当需要的最小容量比当前数组大了,那么进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

关键的扩容来了 Tip:

  • overflow-conscious code 注释的含义:表示以下代码是为“溢出”考虑(或者准备)的代码
  • MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
    /**
    翻译一下:扩容来保证至少可以放得下当前的元素
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * 希望的最小容量
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code 
        //当前数组的长度
        int oldCapacity = elementData.length;
        //新容量 = 旧容量 + 旧容量/2 (即:旧容量×1.5)
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //新容量小于所需最小容量, 则新容量=所需最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //新容量大于Integer.MAX_VALUE - 8, 则新容量=最大容量
        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) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

clear()

    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

        // clear to let GC do its work
        //数组还在只是里面的元素变为了null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

不同版本的jdk,无参构造ArrayList的区别

JDK 1.6

饿汉式,直接创建一个初始容量为10的数组

/** Constructs an empty list with an initial capacity of ten. */
    public ArrayList() {
        this(10);
    }

JDK1.7

相对1.6版本,引入了一个新的常量EMPTY_ELEMENTDATA,它是一个空数组,因此容量为0。 java集合类在jdk1.7版本基本上都有一种改动:懒初始化。懒初始化指的是默认构造方法构造的集合类,占据尽可能少的内存空间(对于ArrayList来说,使用空数组来占据尽量少的空间,不使用null是为了避免null判断),在第一次进行包含有添加语义的操作时,才进行真正的初始化工作。 1.7开始的ArrayList,默认构造方法构造的实例,底层数组是空数组,容量为0,在进行第一次add/addAll等操作时才会真正给底层数组赋非empty的值。如果add/addAll添加的元素小于10,则把elementData数组扩容为10个元素大小,否则使用刚好合适的大小(例如,第一次addAll添加6个,那么扩容为10个,第一次添加大于10个的,比如24个,扩容为24个,刚好合适);

/** Shared empty array instance used for empty instances. */
private static final Object[] EMPTY_ELEMENTDATA = {};

...

/** Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

JDK1.8

相对1.7版本,又引入了一个新的常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,它也是一个空数组,因此容量也为0

EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的区别和作用:

  • EMPTYELEMENTDATA用在有参构造函数当初始容量 为0时共享赋值用(new ArrayList<>(0)),DEFAULTCAPACITY_EMPTY_ELEMENTDATA 用在无参构造函数赋值用(new ArrayList<>()_)
  • 两者都是用来减少空数组的创建,所有空ArrayList都共享空数组。两者的区别主要是用来起区分用,针对有参无参的构造在扩容时做区分走不通的扩容逻辑,优化性能。
  • 在无参构造函数创建ArrayList时其实创建的是一个容量为0的数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA 标识),只有在第一次新增元素时才会被扩容为10
/** Shared empty array instance used for empty instances. */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/** Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

有参和无参的区别

    List<String> noArgsList = new ArrayList<>(); //容量为0
    noArgsList.add("22"); //容量变为10
    List<String> argsList = new ArrayList<>(0); //容量为0
    argsList.add("22");     //容量变为1

吐槽一下jdk这个类维护者,默认构造方法的行为都改变了,你注释还是用之前的,能不能改改啊!!!

疑问:下面这个方法,既然DEFAULTCAPACITY_EMPTY_ELEMENTDATA 肯定是无参构造创建的,那么为何还要比较DEFAULT_CAPACITY(10)和minCapacity的大小呢?

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

解答:因为当一个无参构造的list,执行addAll的时候,addAll的集合长度是不可控的。例如下面:

        List<String> noArgsList = new ArrayList<>();
        List<String> argsList = new ArrayList<>(0);
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        argsList.add("22");
        noArgsList.addAll(argsList);

断点一下:如下。

image.png

所以,要进行比较。懂了吧

ArrayList有缩容吗?

ArrayList没有缩容。无论是remove方法还是clear方法,它们都不会改变现有数组elementData的长度。但是它们都会把相应位置的元素设置为null,以便垃圾收集器回收掉不使用的元素,节省内存。

ArrayList为啥线程不安全?

add 操作源码

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

ArrayList 的不安全主要体现在两个方面 其一:

elementData[size++] = e;

不是一个原子操作,是分两步执行的。

elementData[size] = e;
size++;

单线程执行这段代码完全没问题,可是到多线程环境下可能就有问题了。可能一个线程会覆盖另一个线程的值。

  1. 列表为空 size = 0。
  2. 线程 A 执行完 elementData[size] = e;之后挂起。A 把 “a” 放在了下标为 0 的位置。此时 size = 0。
  3. 线程 B 执行 elementData[size] = e; 因为此时 size = 0,所以 B 把 “b” 放在了下标为 0 的位置,于是刚好把 A 的数据给覆盖掉了。
  4. 线程 B 将 size 的值增加为 1。
  5. 线程 A 将 size 的值增加为 2。

这样子,当线程 A 和线程 B 都执行完之后理想情况下应该是 “a” 在下标为 0 的位置,”b” 在标为 1 的位置。而实际情况确是下标为 0 的位置为 “b”,下标为 1 的位置啥也没有。

  • 其二:

ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。

  1. 线程 A 执行完 add 函数中的ensureCapacityInternal(size + 1)挂起了。
  2. 线程 B 开始执行,校验数组容量发现不需要扩容。于是把 “b” 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。
  3. 线程 A 接着执行,尝试把 “a” 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 9,所以会抛出数组越界异常 ArrayIndexOutOfBoundsException