简介
ArrayList 是 java 集合框架中比较常用的数据结构了。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。
成员变量:
从上图我们可以看到,ArrayList的初始长度为10,我们要注意下ArrayList真实结构上的这段注释
/**
* 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.
*/
这段注释讲了四点
1、这个Object[]elementData是ArrayList存储元素的数组缓冲区
2、ArrList的长度就是这个数组缓冲区的长度
3、任何初始的空ArrList的初始时默认使用Object[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组
4、只有当第一个元素添加的时候才会被扩容至默认的10长度的数组
ArrayList的构造方法
/**
* 构造具有指定初始容量的空列表。
* @param initialCapacity 列表的初始容量
* @throws IllegalArgumentException
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 构造一个初始容量为10的空列表。
* MARK:
* 这里其实是赋值了一个共享的空数组,数组在第一次添加元素时会判断elementData是否等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,假如等于则会初始化容量为DEFAULT_CAPACITY 也就是10
* 符合 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的注释说明
* private static int calculateCapacity(Object[] elementData, int minCapacity) 这个方法里可以看出
*
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定元素的列表集合
* @param c 要将其元素放入此列表的集合
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
// 这里说明所有的 Collection 都可以用数组来承载
// 这个步骤可能会抛空指针 NullPointerException
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//必须是Object数组
if (elementData.getClass() != Object[].class)
// 然后再copy一份到 elementData 并不是引用 所有改变不会影响到原先的Collection
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 有参构造函数 当初始化为空数组时 赋值为 EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
/**
* 这里属于针对初始化时的扩容判断 当为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时说明是无参构造函数创建的,则可以直接扩容为DEFAULT_CAPACITY也就是10为初始容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ArrayList存在两种构造函数:
- 无参构造,直接默认的空数组赋值给到了elementData
- 有参构造,根据传入的容量大小来创建数组,不一样的是,这样初始化长度为0的时赋值的不是默认的空数组,而是另一个空数组
这里有个小疑问,为什么两者都用空数组,为什么要用两个,大家都共享一个空数组不就行了,为什么要不同?我查阅了一些资料,下面来详解一下这个问题
从上面3个构造函数里可以看出,无参构造函数的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值,有参构造函数的空数组会用EMPTY_ELEMENTDATA赋值,在增加元素时会先做扩容判断调用calculateCapacity方法,假如elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA就会直接扩容为默认容量10。
这个意思我理解,这是当使用无参构造创建实例,并且向这个实例中添加元素,会自动触发扩容到10的操作。如果构造函数都共享一个空数组时,就会全部触发扩容到10这个机制,但是这未必是我们想要的,也许我们只需要5个容量呢,所以这里使用了两个空数组来对应无参构造函数和有参构构造函数!下面用代码去验证一下,所以还是我想的太多,用两个不同的空数组还是有其道理的,可以有效应对不同的需求
import java.lang.reflect.Field;
import java.util.*;
public class Solution {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<Integer> list1 = new ArrayList<Integer>(0);
list.add(1);//会触发自动扩容机制,扩容到10
System.out.println(getArrayListCapacity(list));
list1.add(1);//不会触发上面的扩容机制,容量应该为1
System.out.println(getArrayListCapacity(list1));
for(int i=0;i<10;i++){//当容量容纳不下元素,会自动扩容原数组长度的0.5倍,新数组长度为原来的1.5倍,将旧数组元素拷贝到新数组
list.add(0);
}
System.out.println(getArrayListCapacity(list1));
}
//利用反射获取私有的element数组的长度,即ArrayList的容量
public static int getArrayListCapacity(ArrayList<?> arrayList) {
Class<ArrayList> arrayListClass = ArrayList.class;
try {
Field field = arrayListClass.getDeclaredField("elementData");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(arrayList);
return objects.length;
} catch (NoSuchFieldException e) {
e.printStackTrace();
return -1;
} catch (IllegalAccessException e) {
e.printStackTrace();
return -1;
}
}
}
输出:
10
1
15
这里还学习了ArrayList的扩容机制!!!
常用方法解析
- add()方法,向容器中插入元素
public class Solution {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
System.out.println(list);
}
}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- get(int index):ArrayList无法直接使用下标来访问对应的元素,通过get()方法,来获取下标对应的元素
public class Solution {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
System.out.println(list);
System.out.println(list.get(0));
}
}
输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
0
- size()方法:容器中元素个数
public class Solution {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
System.out.println(list.size());
}
}
输出:
10
- clear():清空容器中所有元素
public class Solution {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
System.out.println(list);
list.clear();
System.out.println(list);
}
}
输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
- contains()方法,判断容器中是否含有某个元素
public class Solution {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
boolean isExist=list.contains(1);
boolean isExist1=list.contains(100);
System.out.println(isExist);
System.out.println(isExist1);
}
}
输出:
true
false
- remove()方法:移除某个元素
remove方法有两种重载的方式:①移除某个元素值,②移除下标为index的元素
public class Solution {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
boolean success=list.remove((Integer)1);
System.out.println(list);
Integer removeNumber=list.remove(1);
System.out.println(list);
System.out.println(success);
System.out.println(removeNumber);
}
}
输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 2, 3, 4, 5, 6, 7, 8]
true
1
重点:
- ArrayList底层是基于数组实现容量大小动态可变。当触发扩容机制时候,新数组的容量为原数组的1.5倍,再将原数组拷贝到新数组。
总结平常遇到的问题和知识,如果有什么不对的地方请指赐教!
文章内容参考了一下博客:
https://blog.csdn.net/m0_53121042/article/details/110464378
https://blog.csdn.net/lucky1521/article/details/79966743
https://blog.csdn.net/jy317358306/article/details/111633841