transient
Object[] elementData :见 transient关键字
transient 关键字
trimToSize()
ArrayList<Integer> list = newArrayList<>(5);
for(inti=0;i<6;i++){
list.add(i);
}
list.trimToSize();
System.out.println(list);
第一个断点:
第二个断点:可以看到,数组的容量从_7=>6;所以可以推出该方法的作用,是将数组的容量改为了和list的size,节省了内存,内存紧张的时候可以用到,贴一下_trimToSize()的源码:_ ```java /**
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
<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);
断点一下:如下。
所以,要进行比较。懂了吧
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++;
单线程执行这段代码完全没问题,可是到多线程环境下可能就有问题了。可能一个线程会覆盖另一个线程的值。
- 列表为空 size = 0。
- 线程 A 执行完
elementData[size] = e;
之后挂起。A 把 “a” 放在了下标为 0 的位置。此时 size = 0。- 线程 B 执行
elementData[size] = e;
因为此时 size = 0,所以 B 把 “b” 放在了下标为 0 的位置,于是刚好把 A 的数据给覆盖掉了。- 线程 B 将 size 的值增加为 1。
- 线程 A 将 size 的值增加为 2。
这样子,当线程 A 和线程 B 都执行完之后理想情况下应该是 “a” 在下标为 0 的位置,”b” 在标为 1 的位置。而实际情况确是下标为 0 的位置为 “b”,下标为 1 的位置啥也没有。
- 其二:
ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。
- 线程 A 执行完 add 函数中的
ensureCapacityInternal(size + 1)
挂起了。- 线程 B 开始执行,校验数组容量发现不需要扩容。于是把 “b” 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。
- 线程 A 接着执行,尝试把 “a” 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 9,所以会抛出数组越界异常
ArrayIndexOutOfBoundsException