1、 ArrayList容器类
ArrayList 是 List 接口的实现类。是 List 存储特征的具体实现。
特点:查询快,增删慢,主要用于查询遍历数据,为最常用集合之一;
数据结构:数组;
底层分析:数组结构是有序的元素序列,在内存中开辟一段连续的空间,在空间中存放元素,每个空间都有编号,通过编号可以快速找到相应元素,因此查询快;数组初始化时长度是固定的,要想增删元素,必须创建一个新数组,把源数组的元素复制进来,随后源数组销毁,耗时长,因此增删慢。
1.1 构造器
List<String> list = new ArrayList<>();
List<String> list1 = new ArrayList<>(list);
List<String> list2 = new ArrayList<>(20);
第二种构造器List<String> list1 = new ArrayList<>(list);
源码
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
elementData = EMPTY_ELEMENTDATA;
}
}
参数的源码
public interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
}
第三种构造器List<String> list1 = new ArrayList<>(20);
System.out.println(list1.size());//0
看到了没,list1还是一个空列表,只不过她的初始容量是20
1.2 添加元素
List<String> list = new ArrayList<>();
boolean a = list.add("a");
list.add(1,"b");
System.out.println(list);//[a, b]
list.add(3,"b");//IndexOutOfBoundsException
注意:第二种方法,索引不能大于元素的个数。
1.3 获取元素
String s1 = list.get(0);
String s2 = list.get(1);
String s3 = list.get(2);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
1.4 删除元素
List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"d");
System.out.println("列表:"+list);
String remove = list.remove(2);
System.out.println("删除的元素"+remove);
System.out.println("列表:"+list);
list.remove("d");
输出:
列表:[a, b, c, d]
删除的元素c
列表:[a, b, d]
1.5 替换元素
List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"d");
System.out.println("列表:"+list);
String hello = list.set(0, "Hello");
System.out.println("替换的元素"+hello);
System.out.println("列表:"+list);
输出:
列表:[a, b, c, d]
替换的元素a
列表:[Hello, b, c, d]
1.6 清空元素
List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"d");
System.out.println("列表:"+list);
list.clear();
System.out.println("列表:"+list);
输出:
列表:[a, b, c, d]
列表:[]
1.7 判空
List<String> list2 = new ArrayList<>();
boolean empty = list2.isEmpty();
System.out.println(empty);//true
1.8 判断容器是否包含指定元素
List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"a");
System.out.println("列表:"+list);
boolean is = list.contains("a");
System.out.println(is);//true
输出:
列表:[a, b, c, a]
true
1.9 查找元素的位置
List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"a");
System.out.println("列表:"+list);
int index = list.indexOf("a");
System.out.println("a在列表中的第一个位置 "+index);
int lastIndex = list.lastIndexOf("a");
System.out.println("a在列表中的最后一个位置 "+lastIndex);
输出:
列表:[a, b, c, a]
a在列表中的第一个位置 0
a在列表中的最后一个位置 3
1.10 将单例集合转换成数组
转换为 Object 数组
Object[] array = list.toArray();
for (Object o : array) {
System.out.print(o + " ");
}
输出:
列表:[a, b, c, a]
a b c a
注意:不可以将转换后的结果强制类型转换(Object可以强转为String,但是你见过将Object数组强转为String数组吗?除非你一个一个的来强转)。想要实现该效果,请看下一知识
String[] arr = (String[]) list.toArray();
for (String s : arr) {
System.out.print(s+" ");
}
输出:
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String; are in module java.base of loader 'bootstrap')
转换泛型类型数组
List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"a");
System.out.println("列表:"+list);
String[] array = list.toArray(new String[list.size()]);
for (String s : array) {
System.out.print(s+" ");
}
输出:
列表:[a, b, c, a]
a b c a
1.11 容器的并集操作
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
System.out.println("列表list1:"+list1);
List<String> list2 = new ArrayList<>();
list2.add("b");
list2.add("c");
list2.add("d");
System.out.println("列表list2:"+list2);
boolean b = list1.addAll(list2);
System.out.println(b);
System.out.println("列表list1:"+list1);
System.out.println("列表list2:"+list2);
输出:
列表list1:[a, b, c]
列表list2:[b, c, d]
true
列表list1:[a, b, c, b, c, d]
列表list2:[b, c, d]
1.12 容器的交集操作
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
System.out.println("列表list1:"+list1);
List<String> list2 = new ArrayList<>();
list2.add("b");
list2.add("c");
list2.add("d");
System.out.println("列表list2:"+list2);
boolean b = list1.retainAll(list2);
System.out.println(b);
System.out.println("列表list1:"+list1);
System.out.println("列表list2:"+list2);
输出:
列表list1:[a, b, c]
列表list2:[b, c, d]
true
列表list1:[b, c]
列表list2:[b, c, d]
Process finished with exit code 0
1.13 容器的差集操作
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
System.out.println("列表list1:"+list1);
List<String> list2 = new ArrayList<>();
list2.add("b");
list2.add("c");
list2.add("d");
System.out.println("列表list2:"+list2);
boolean b = list1.removeAll(list2);//相当于list1-list2
System.out.println(b);
System.out.println("列表list1:"+list1);
System.out.println("列表list2:"+list2);
输出:
列表list1:[a, b, c]
列表list2:[b, c, d]
true
列表list1:[a]
列表list2:[b, c, d]
Process finished with exit code 0
2、 ArrayList底层的初始化(使用空参构造)
前置知识:ArrayList的重要字段和方法
/**
* 默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于默认大小的空实例的共享空阵列实例。我们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素
* 时要膨胀多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储 ArrayList 元素的数组缓冲区。ArrayList 的容量是此数组缓冲区的长度。添加第一个元素时,任何带
* 有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为
* DEFAULT_CAPACITY
*/
transient Object[] elementData;
/**
* ArrayList 的大小(它包含的元素数)
*/
private int size;
//空参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
问题的引出
现在有一个代码,问:list1列表是如何初始化的呢?
List<String> list1 = new ArrayList<>();
list1.add("a");
先说结论:
-
JDK1.7时采用立即初始化,也就是说list1列表会在一开始的时候直接
elementData = new Object[10]
此时elementData的长度为10。 -
JDK1.8之后采用延迟初始化,底层的elementData会被直接初始化为{}(空数组)只有当向列表中添加元素的时候才会将列表的长度改为10(DEFAULT_CAPACITY)
源码分析
追踪代码中的List list1 = new ArrayList<>();
public ArrayList() {//初始化为空数组{}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
追踪代码中的add方法
add底层:
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
add(e, elementData, size)底层调用了grow方法,grow方法的底层(其实是底层的底层):
private Object[] grow(int minCapacity) {
//...
return elementData = new Object[DEFAULT_CAPACITY];
}
现在你明白了吧,JDK1.8之后的延迟初始化就是在第一次添加元素的时候,进行初始化长度10
现在问你一个问题:此时我们list2.size()的结果是多少呢?答案是1。想一想为什么
3、 ArrayList的扩容机制
目标代码
List<String> list1 = new ArrayList<>();
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
list1.add("5");
list1.add("6");
list1.add("7");
list1.add("8");
list1.add("9");
list1.add("10");
list1.add("11");
追踪list1.add(“1”);
- 调用list接口方法
boolean add(E e);
- 实际上调用ArrayList类的方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);//size 0
return true;
}
- 然后调用ArrayList类的方法
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)//满足条件
elementData = grow();
elementData[s] = e;
size = s + 1;
}
- 此时,满足条件(s == elementData.length)
private Object[] grow() {
return grow(size + 1);
}
- grow方法底层,又调用了另一个grow方法
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity =
ArraysSupport.newLength(oldCapacity,
minCapacity -oldCapacity,oldCapacity >> 1);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
-
此时,是第一次添加元素,所以不满足if条件,执行else语句
else语句的本质就是将elementData初始化为DEFAULT_CAPACITY长度
追踪list1.add(“11”);
- 调用list接口方法
boolean add(E e);
- 实际上调用ArrayList类的方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);//size 10
return true;
}
- 然后调用ArrayList类的方法
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)//elementData.length == 10
elementData = grow();//这是重点
elementData[s] = e;
size = s + 1;
}
- 此时,满足条件(s == elementData.length)
private Object[] grow() {
return grow(size + 1);//size == 10
}
- grow方法底层,又调用了另一个grow方法
private Object[] grow(int minCapacity) {//minCapacity == 11
int oldCapacity = elementData.length;//oldCapacity == 10
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity =
ArraysSupport.newLength(oldCapacity,
minCapacity -oldCapacity,oldCapacity >> 1);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
- 此时,满足if条件,执行ArraysSupport类中的newLength方法
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
int prefLength = oldLength + Math.max(minGrowth, prefGrowth);
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
return hugeLength(oldLength, minGrowth);
}
}
- 计算得出newCapacity = 15,然后执行 Arrays.copyOf 。最后实现将字符串"11"插入列表,并进行扩容1.5倍的操作
扩容机制总结
当添加一个元素时,检查数组的长度是否足够。如果不够则扩容,够了就不扩容。ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右
注意:ArrayList不存在”当数组剩余容量多少时,我们扩容“