一、ArrayList知识点
- 底层数据结构为数组
- 扩容采用的是
Arrays.copyOf()
和System.arraycopy()
Arrays.copyOf()
创建新数组System.arraycopy()
把原数组中的元素复制到新数组中
- 每次扩容后的容量,一般为原容量的1.5倍,特殊情况下为指定容量
- 非线程安全,Vector线程安全,但速度慢,它每个方法都加上了synchronized
- 可以无限添加null,一个null占一个位置
- 查询快,删除慢
二、父类、接口
- 父类:AbstractList
- 接口:List, RandomAccess, Cloneable, java.io.Serializable
三、成员变量
1. 自身的成员变量
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*
* 默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 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.
*
* 用于所有默认空实例的共享空数组实例,区别于EMPTY_ELEMENTDATA,当添加第一个元素时,知道数组扩容多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*
* 集合底层数组引用,真正存放对象的地方,transient修饰,表明elementData不会被序列化
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* 集合元素数量
* @serial
*/
private int size;
2. 继承的成员变量
// 操作计数
protected transient int modCount = 0;
EMPTY_ELEMENTDATA与DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别?
调用无参构造函数时创建容器时,当第一次往该容器添加对象时,执行到ensureCapacityInternal,因为这个时候minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity)形参列表中的minCapacity必定是1,所以初始化出来的elementData必定是长度为10的数组,这就是为什么网传默认的ArrayList的容量为10的由来
当调用剩下两个构造函数初始化时,即便初始化出来是容量为0的容器,在第一次往容器里添加对象时,只会让容量扩充到1
个人猜测这个设计的由来是为了容器容量的缓慢增长,避免浪费太多的空间,所以以后编码遇到容器类需要存储比较少对象的时候,用带参数的构造函数有利于节省内存
主要用于标志这个对象是通过哪个构造函数创建的,DEFAULTCAPACITY_EMPTY_ELEMENTDATA
用于标志无参,EMPTY_ELEMENTDATA
用于标志有参,为什么要作标志呢?是因为,两种创建方法后续的扩容大小不同
四、构造函数
一共三个构造函数
- ArrayList()
- ArrayList(int initialCapacity)
- ArrayList(Collection<? extends E> c)
1. ArrayList():无参构造函数,使用最多
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // transient,修饰符,代表这个字段不会被序列化
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- elementData变量,是Object数组,作为ArrayList存储数据的底层结构。换句话说,ArrayList的数据结构是数组
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA,提供elementData默认初始化时使用
ArrayList list = new ArrayList();
这行代码,得到的就是一个长度为0的Object数组,且所有以这个构造函数构造的对象为同一内存个地址,可以减少内存的消耗(DEFAULTCAPACITY_EMPTY_ELEMENTDATA被static修饰)
2. ArrayList(int initialCapacity):指定初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity]; // 创建给定长度的数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA; // 给定长度为0时,引用指向同一个数组地址,并且长度为0
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity); // 给定长度小于0时,抛出异常
}
}
这个构造函数被使用的比较少
3. ArrayList(Collection<? extends E> c):通过指定集合创建
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
- 将参数集合转为数组,然后赋值给elementData
- 赋值后,进行判断,如果elementData长度为0,则elementData重新指向EMPTY_ELEMENTDATA
- 如果elementData不为0,再elementData人实际类型是否是Object[].class,如果不是,则通过Arrays.copyOf()方法,进行向上转型为Object[].class
关于Arrays.copyOf()方法,可查看Arrays.copyOf()方法详解-jdk1.8
五、扩容
ArrayList类中,所有的扩容,都是通过ensureCapacityInternal
方法
扩容的时机?
- 添加元素时
- readObject
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
- 参数指定集合最小容量
elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
为true
时,也就是通过无参构造函数创建的ArrayList对象,并且是第一次添加元素,所以执行到minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity)
时,minCapacity = DEFAULT_CAPACITY
,也就是10
这里,就造成了无参构造函数创建的集合对象的初始容量为10的错觉
- 执行
ensureExplicitCapacity(minCapacity)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 操作记数加1
- 当指定容量大于当前容量时,不进行扩容
- 当指定容量小于等于当前容量时,执行
grow(minCapacity)
进行扩容
/**
* 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;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
int newCapacity = oldCapacity + (oldCapacity >> 1)
,默认扩容到原容量的1.5倍- 如果扩容后的容量还小于指定容量,则直接取指定容量,作为扩容后的容量
- 如果扩容后的容量大于
MAX_ARRAY_SIZE
,则执行hugeCapacity(minCapacity)
hugeCapacity
确保容量不会超过Integer的最大值
- 否则执行
Arrays.copyOf(elementData, newCapacity)
进行扩容
Arrays.copyOf(elementData, newCapacity)
的使用,可查看Arrays.copyOf()方法详解-jdk1.8
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
minCapacity < 0
,当int值大于Integer.MAX_LALUE
时,呈现为负数- 超出则抛出异常,否则返回
Integer.MAX_VALUE
,所以集合的最大容量为Integer.MAX_VALUE
六、添加
- add(E e)
- add(int index, E element)
- addAll(Collection<? extends E> c)
- addAll(int index, Collection<? extends E> c)
1. add(E e)
将指定的元素追加到该列表的末尾。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal(size + 1)
,保证数组长度大于等于size + 1
,否则进行扩容,才可以存下新元素;操作记数加1- elementData[size++] = e:将指定元素存入数组中,按顺序存;并将集合元素数量加1
- 前两步成功,则返回true
2. add(int index, E element)
在此列表中的指定位置插入指定的元素。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
rangeCheckForAdd(index)
,判断指定位置是否超出集合范围ensureCapacityInternal(size + 1)
,保证数组长度大于等于size + 1
,否则进行扩容,才可以存下新元素;操作记数加1System.arraycopy(elementData, index, elementData, index + 1, size - index)
,通过arraycopy
的数组复制功能,将数组中的元素进行移位,空中指定位置elementData[index] = element
,将指定元素插入指定位置size++
,集合元素数量加1
3. addAll(Collection<? extends E> c)
将指定集合的所有元素,按顺序追加到此列表的末尾。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
- 将指定集合转为Object数组
ensureCapacityInternal(size + numNew)
,保证数组长度大于等于size + numNew
,否则进行扩容,才可以存下新元素;操作记数加1System.arraycopy(a, 0, elementData, size, numNew)
,将指定数组的所有元素复制到原数组末尾- 集合元素数量加
numNew
- 当指定集合大小为0时,返回false
4. addAll(int index, Collection<? extends E> c)
将指定集合中的所有元素插入到此列表中,从指定的位置开始。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
- 判断指定位置是否超出集合范围
- 扩容,操作记数+1
- 判断当前集合元素数量是否大于指定位置,大于即表示要插入到中间位置,需要将后面的元素往后移,执行
System.arraycopy(elementData, index, elementData, index + numNew, numMoved)
System.arraycopy(a, 0, elementData, index, numNew)
,将指定集合中的所有元素插入到此列表中,从指定的位置开始
七、删除元素
- E remove(int index)
- boolean remove(Object o)
- boolean removeAll(Collection<?> c)
- boolean removeIf(Predicate<? super E> filter)
- void removeRange(int fromIndex, int toIndex)
- void fastRemove(int index)
- boolean batchRemove(Collection<?> c, boolean complement)
- void clear()
1. E remove(int index)
删除该列表中指定位置的元素。
public E remove(int index) {
rangeCheck(index); // 判断是否越界
modCount++; // 操作记数+1
E oldValue = elementData(index); // 取出指定索引处的元素
int numMoved = size - index - 1; // 计算指定索引后的元素数,这些元素需要往前移动1位
if (numMoved > 0) // 大于0,表示移除的元素不在最后
System.arraycopy(elementData, index+1, elementData, index,
numMoved); // 将指定索引后的元素全部往前移动1位
elementData[--size] = null; // clear to let GC do its work 其实这一步并不会引起GC工作
return oldValue; // 返回移除的元素
}
// clear to let GC do its work
是源码中的注释,但是,最后一个元素的引用已经前移,就是说,该对象的引用还被数组持有,不会被GC回收
2. boolean remove(Object o)
从列表中删除第一个出现的指定元素(如果存在)。
public boolean remove(Object o) {
if (o == null) { // null和非null处理方式不一样
for (int index = 0; index < size; index++) // 循环所有元素
if (elementData[index] == null) { // ==null时
fastRemove(index); // 移除
return true; // 返回true
}
} else { // 非null
for (int index = 0; index < size; index++) // 循环所有元素
if (o.equals(elementData[index])) { // 指定对象用equals方法与每个元素判断
fastRemove(index); // 移除
return true; // 返回true
}
}
return false; // 如果上面都没有找到,则返回false
}
如果元素在集合最后,就需要遍历并比较所有元素,速度慢
3. boolean removeAll(Collection<?> c)
从此列表中删除指定集合中包含的所有元素。
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c); // 判断指定集合是否为null
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData; // 备份当前数组引用
int r = 0, w = 0; // r为当前数组的索引,w为新数组的索引,但是,执行for循环后,r为已遍历元素数量,w为新数组数量(r++, w++)
boolean modified = false; // 是否移除元素的标记
try {
for (; r < size; r++) // 遍历当前集合
if (c.contains(elementData[r]) == complement) // 形参complement为false,整个判断就是,当指定集合不包含遍历元素时,就执行下面的语句
elementData[w++] = elementData[r]; // 当指定集合不包含遍历元素时,就把当前元素保留,并移动到索引w处,移动后索引w+1
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) { // 当没有异常时,r = size,就不用做处理了;有异常时,数组还没有遍历完,那r肯定是小于size的;这里就是对上面的for循环出现异常时的处理
System.arraycopy(elementData, r,
elementData, w,
size - r); // 已经处理的不管,从出现异常的元素开始,把它和之后的元素移动到新数组的后面
w += size - r; // 新数组的元素数量,就是w加上,出现异常时,还没有处理的元素数量
}
if (w != size) { // 当前集合没有元素在指定集合中时,w = size;也就是说,当出现异常,或有元素在指定集合时,就执行下面的逻辑
// clear to let GC do its work
for (int i = w; i < size; i++) // w为新数组的元素数量,所以从下标为w开始,全部置null
// 新数组不持有被移除的元素的引用,这些元素将可能会被GC回收
// (这里还要考虑指定集合是否还有引用指向,因为指定集合持有被移除元素的引用)
elementData[i] = null;
modCount += size - w; // 移除多少个元素,操作记数就加多少
size = w; // 将新数组的元素数量赋值给size
modified = true; // 当有元素被移除时,标记才为true
}
}
return modified; // 返回是否有元素被移除
}
final Object[] elementData = this.elementData
的用意?
- 访问局部变量比访问成员变量速度快?
- 使用final修饰可以避免变量被重新赋值(引用赋值)?
算法实现
- 遍历当前集合,一一按顺序判断是否存在于指定集合中
- 当不存在时,元素向前移动,移动到索引w处,w从0开始,移到后索引+1(w++)
- 当存在时,不做处理
总的来说,就是把指定集合中没有的元素抽出来,按顺序存放到当前数组
例子:|0|1|2|3|移除|1|2|后,就是|0|3|2|3|,把3复制到了索引为1处
方法分析
假定当前集合元素数量为size,指定集合元素数量为n
- 在
try
里,有一个循环,循环次数为size
,每次循环,都会调用contains()
方法,contains()
又是遍历实现的,所以最多能循环size * n
次 - 数组元素最多能移动
size - 1
次
所以,用这个方法移除元素的效率不高(或者说,数组的增删都慢,查询快)
4. boolean removeIf(Predicate<? super E> filter)
删除此集合中满足给定谓词的元素。
@Override // 重写Collection接口的默认方法
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter); // 判断非null
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
int removeCount = 0; // 记录要移除的数量
final BitSet removeSet = new BitSet(size); // 记录要移除的下标
final int expectedModCount = modCount; // 用局部记录当前操作记数,当其它线程改变操作记数时,就报错
final int size = this.size; // 用局部变量记录当前集合的元素数量
for (int i=0; modCount == expectedModCount && i < size; i++) { // 当操作记数没有被改变时,才进行以下操作
@SuppressWarnings("unchecked")
final E element = (E) elementData[i]; // 操作局部变量比操作成员变量速度快
if (filter.test(element)) { // 通过指定的谓词判断
removeSet.set(i); // 将指定索引处的位设置为true
removeCount++; // 待移除的数量+1
}
}
if (modCount != expectedModCount) { // 当前集合的操作记数被其它线程修改了,则抛出异常
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0; // 当有需要移除的元素时,就返回true
if (anyToRemove) { // 当有需要移除的元素时,才执行下面的操作
final int newSize = size - removeCount; // 移除元素后的集合的元素数量
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) { // i为原数组下标,j为新数组下标
i = removeSet.nextClearBit(i); // 返回在指定的起始索引上或之后第一个设置为false的索引
elementData[j] = elementData[i]; // 把这个元素放到新数组中(实质在同一个数组中操作)
}
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work // newSize为新数组的元素数量,所以从下标为newSize开始,全部置null
// 如果被移除了引用的元素对象,没有被其它引用指向,则可能被GC回收
}
this.size = newSize; // 更新集合对象的元素数量
if (modCount != expectedModCount) { // 再次判断,当前集合的操作记数被其它线程修改了,则抛出异常
throw new ConcurrentModificationException();
}
modCount++; // 操作记数+1
}
return anyToRemove; // 返回是否有元素被移除
}
- 这里使用到
BitSet
,可减少循环次数(就是这句代码i = removeSet.nextClearBit(i)
),需要移除多少元素,就循环多少次即可 - 多次判断操作记数,是为了在其它线程入侵破坏集合时,中断当前操作,并抛出异常
- 这个方法是1.8后新增的,重写Collection接口的默认方法
5. void removeRange(int fromIndex, int toIndex)
从这个列表中删除所有索引在 fromIndex (含)和 toIndex 之间的元素。
protected void removeRange(int fromIndex, int toIndex) {
modCount++; // 操作记数+1
int numMoved = size - toIndex; // toIndex及后面的需要前移的元素的个数
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex); // 删除后剩余的元素个数
for (int i = newSize; i < size; i++) {
elementData[i] = null; // 数组后面的位置置null
}
size = newSize; // 新集合数量
}
图解System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
算法思想:把toIndex后的元素移动到fromIndex及之后的位置。换句话说,就是抽出要删除的元素,然后把后面的元素移动到空出来的位置。
在这个例子中,2 9 0 1 就是要删除的元素,把这几元素抽出后,再把后面的4 6 5 前移
6. void fastRemove(int index)
删除指定下标的元素,私有方法,不对外提供
private void fastRemove(int index) {
modCount++; // 操作记数+1
int numMoved = size - index - 1; // index后的元素都往前移一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved); // 跟上面差不多
elementData[--size] = null; // clear to let GC do its work
}
这个方法,是提供给remove(Object o)
方法使用的,remove(Object o)
在前面已分析。
7. boolean batchRemove(Collection<?> c, boolean complement)
私有方法,可看上面的removeAll(Collection<?> c)
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
8. void clear()
从列表中删除所有元素。
public void clear() {
modCount++; // 操作记数+1
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; // 全部置null
size = 0; // 集合元素数量为0
}
八、查找
- E get(int index)
- int indexOf(Object o)
- int lastIndexOf(Object o)
- int size()
1. E get(int index)
返回此列表中指定位置的元素。
public E get(int index) {
rangeCheck(index); // 是否下标越界
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
elementData(int index)
为default方法,只有同包类才可以使用,也就是不对外开放
2. int indexOf(Object o)
返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
使用equals方法判断是否是相同的元素,由于equals无法判断null,所以分开遍历。
3. int lastIndexOf(Object o)
返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
从最后一个元素开始遍历
4. int size()
返回此列表中的元素数。
public int size() {
return size;
}
九、遍历
- void forEach(Consumer<? super E> action)
- iterator()
- listIterator()
- listIterator(int index)
- spliterator()
1. void forEach(Consumer<? super E> action)
对集合中的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action); // 非null
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
十、修改
1. set(int index, E element)
用指定的元素替换此列表中指定位置的元素。
public E set(int index, E element) {
rangeCheck(index); // 给定下标是否超过集合最大下标
E oldValue = elementData(index); // 取出给定下标的元素,用于返回
elementData[index] = element; // 用指定的元素替换此列表中指定位置的元素
return oldValue; // 返回原来的元素
}
十一、剩余方法
- trimToSize()
- Object clone()
- isEmpty()
- replaceAll(UnaryOperator operator)
- retainAll(Collection<?> c)
- sort(Comparator<? super E> c)
- subList(int fromIndex, int toIndex)
- toArray()
- toArray(T[] a)
1. trimToSize()
/**
* 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.
*
* 修改这个ArrayList实例的容量为列表的当前大小。应用程序可以使用此操作来最小化ArrayList实例的存储。
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
方法作用:减小集合对象占用的内存大小
- 修饰记录加1
- 判断集合实际长度与数组长度,如果前者小于后者,则执行第3步
- 判断实际长度,如果为0,集合对象引用指向EMPTY_ELEMENTDATA;否则,调用Arrays.copyOf()方法进行数组截断
- 集合实际长度为数组存放的元素个数,与数组长度是两个概念
- 关于Arrays.copyOf()方法,可查看Arrays.copyOf()方法详解-jdk1.8
2. Object clone()
返回此 ArrayList实例的浅拷贝。
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone(); // 拷贝最外层,也就是ArrayList对象引用地址
v.elementData = Arrays.copyOf(elementData, size); // 拷贝数组地址
v.modCount = 0; // 操作记数重置为0
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
在这里,底层数组克隆了一份,但是数组里的元素还是同一份,所以是浅拷贝,关于克隆,可看这里java浅克隆与深克隆详解