✨博客主页: XIN-XIANG荣
✨系列专栏:【Java实现数据结构】
✨一句短话: 难在坚持,贵在坚持,成在坚持!
推荐一款模拟面试、刷题神器 、从基础到大厂面试题:点击跳转刷题网站进行注册学习
ArrayList类又称动态数组, 同时实现了 Collection 和 List 接口,其内部数据结构由数组实现,因此可对容器内元素实现快速随机访问; 具体的框架图如下:
【说明】
ArrayList有三种构造方法:
要学习这些构造方法需要先了解源码中的一些成员变量和常量
// 默认的容量大小(常量)
private static final int DEFAULT_CAPACITY = 10;
// 定义的空数组(final修饰,大小固定为0)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 定义的默认空容量的数组(final修饰,大小固定为0)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 定义的不可被序列化的数组,实际存储元素的数组
transient Object[] elementData;
// 数组中元素的个数
private int size;
// 无参的构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
观察这里给出的无参构造方法, 可以发现当我们直接无参实例化一个ArrayList时, elementData被赋予了默认空容量的数组;
要注意的是, 默认空容量数组是被final修饰的,这个数组是无法进行修改的, 所以此时ArrayList数组是空的、固定长度的,也就是说ArrayList数组此时的容量为0, 元素的个数size为默认值0.
看到这里小伙伴们可能会有一些疑问, 可能大家知道的是实例化ArrayList掉用无参构造方法时不是默认时分配10个空间的容量吗,这里为啥是0呢, 其实这跟ArrayList的扩容机制有关, 在后文中会有解释, 对于无参构造方法默认开辟10个空间这个说法是不准确的.
示例:
实例化一个0容量的顺序表
List list = new ArrayList<>();
// 传容量的构造方法
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);
}
}
示例:
实例化一个容量为5的顺序表
List<Integer> list2 = new ArrayList<>(15);
// 传入Collection元素列表的构造方法
public ArrayList(Collection<? extends E> c) {
// 将列表转化为对应的数组
elementData = c.toArray();
//更新size
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
// 把数组类型变成Object类型
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 赋予空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
传入Collection元素列表后,构造方法首先会将其转化为数组,将其索引赋给elementData;
如果此数组的长度为0,会重新赋予elementData为默认的空数组,此时ArrayList的容量是0,元素个数size为0。
如果此数组的长度大于0,会更新size的大小为其长度,同时将数组的类型转化为Object, 此时ArrayList的容量为传入序列的长度,也就是size的大小,同时元素个数也为size,也就是说,此时ArrayList是中放的就是传进来的元素列表中的元素。
示例:
实例化一个和list2元素一致的顺序表
List<Integer> list2 = new ArrayList<>(15);
List<Integer> list3 = new ArrayList<>(list2);
观察下面的实例化方式有什么问题, 初学时很容易写出这样的代码;
List list = new ArrayList();
这种实例化方式,没有指定泛型的类型,此时顺序表中可以存放任意类型的元素,这样的代码使用和维护都不太方便。
上面在介绍无参构造方法时, 强调了当使用无参构造方法时, 顺序表中其实是一个容量为0的空数组, 那么既然容量为0, 又如何往其中添加元素呢, 这里就引出了它的扩容机制了.
首先看源码中的add方法, 假设我们是第一次往顺序表中添加元素, 那么size此时就是0;
add方法中首先调用 ensureCapacityInternal 这个方法, 此时这里的参数应当为 size +1 = 1, 此时继续往下看这几个方法;
在ensureCapacityInternal方法中又调用了 ensureExplicitCapacity 方法,
ensureExplicitCapacity 方法的参数为 calculateCapacity 方法的返回值,
calculateCapacity 方法中, 如果数组是默认的空数组, 那么返回值为DEFAULT_CAPACITY 和 minCapacity两者中的较大值,
此时的minCapacity为1, 所以返回值当为DEFAULT_CAPACITY, 也就是10, 此时10又作为ensureExplicitCapacity的参数, minCapacity(10) - elementData.length(0) > 0 , 此时就会调用grow方法,
grow方法中首先会获取到数组当前的容量oldCapacity, 然后定义一个新的容量值变量newCapacity, 赋值为oldCapacity + (oldCapacity >> 1),
oldCapacity >> 1的功能是将oldCapacity 进行位运算,右移一位,也就是减半,此时newCapacity 为 oldCapacity 的1.5 倍;
后面的第一个if语句是 如果用户需要扩容大小超过原空间的1.5倍,按照用户所需大小扩容
第二个if语句是 如果需要扩容大小超过MAX_ARRAY_SIZE(等于int的最大值 - 8),那么由于要扩容的空间太大, 需要重新计算容量大小, 调用 hugeCapacity 方法,
此时如果minCapacity小于0,抛出OutOfMemoryError异常 , 超过MAX_ARRAY_SIZE返回
Integer.MAX_VALUE (int 类型的最大值) .
返回后最后进行的就是扩容了, 通过copyOf将原来数组的的内容拷贝过来, 并将新数组空间容量设置为newCapacity , 将新数组的引用赋值给 elementData .
通过对于源码的分析此时我们就知道了当第一次add时, 先执行的操作是将数组的容量是由0扩容为10;
Object[] elementData; // 存放元素的空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认空间
private static final int DEFAULT_CAPACITY = 10; // 默认容量大小
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 获取旧空间大小
int oldCapacity = elementData.length;
// 预计按照1.5倍方式扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用copyOf扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 如果minCapacity小于0,抛出OutOfMemoryError异常
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
【总结】
[扩容可分为两种情况]:
第一种情况,当ArrayList的容量为0时,此时添加元素的话,需要扩容,三种构造方法创建的ArrayList在扩容时略有不同:
第二种情况,当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍。
方法名称 | 方法描述 |
---|---|
public boolean add(E e) | 将指定的元素添加到当前列表的尾部 |
public void add(int index,E element) | 在当前列表的指定位置添加指定元素 |
public boolean addAll(Collection extends E> c) | 将指定的列表添加到此列表的尾部 |
public boolean addAll( int index,Collection extends E e> c ) | 在当前列表的指定位置添加指定的列表 |
[代码演示]:
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("张三");
list1.add("李四");
list1.add(1,"王五");
List<String> list2= new ArrayList<>();
list2.add("赵钱");
list2.add("孙李");
list1.addAll(list2);
list1.addAll(0, list2);
System.out.println(list1);
}
[执行结果]:
方法名称 | 方法描述 |
---|---|
public E get(int index) | 返回此列表指定索引处的元素 |
public E set(int index ,E element) | 用指定的元素替换此列表中指定位置的元素 |
[代码演示]:
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张三");
list.set(0,"李四");
String s2=list.get(0);
System.out.println(list);
}
[执行结果]:
方法名称 | 方法描述 |
---|---|
public boolean contains(Object o) | 如果此列表包含指定的元素,则返回true |
public boolean containsAll(Collection> C) | 如果此列表包含指定的列表,则返回true |
[代码演示]:
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("张三");
list1.add("李四");
list1.add(1,"王五");
List<String> list2= new ArrayList<>();
list2.add("赵钱");
list2.add("孙李");
System.out.println(list1.contains("李四"));
System.out.println(list1.containsAll(list2));
}
[执行结果]:
[注意]:
方法名称 | 方法描述 |
---|---|
public E remove(int index ) | 删除该列表中指定位置的元素 |
public boolean remove(Object o) | 删除该列表中第一个指定的元素 |
public boolean removeAll(Collection> c) | 从此列表中删除指定集合包含的所有元素 |
public boolean retainAll()collection> c | 除了集合c中的元素全部删除 |
[代码演示]:
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("张三");
list1.add("李四");
list1.add("王五");
String s1=list1.remove(1);
System.out.println("删除的元素为:"+s1+"\n删除后集合list1:"+list1);
boolean flag1=list1.remove("王五");
System.out.println("删除结果为:"+flag1+"\n删除后集合list1:"+list1);
List<String> list2=new ArrayList<>();
list2.add("张三");
list2.add("赵钱");
list2.add("孙李");
list2.add("张三");
boolean flag2=list2.removeAll(list1);
System.out.println("删除结果为:"+flag2+"\n删除后集合list2:"+list2);
list2.add("张三");
boolean flag_3=list2.retainAll(list1);
System.out.println("删除结果为:"+flag_3+"\n删除后集合list2:"+list2);
}
[执行结果]:
方法名称 | 方法描述 |
---|---|
public Object[] toArray() | 将集合转换为数组**,** 返回一个包含当前列表所有元素的数组 |
public T[] toArray(T[] a) | 将集合转换为数组**,** 返回一个包含当前列表所有元素的数组,指定类型 |
[代码演示]:
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
Object[] o1=list.toArray();
System.out.println(Arrays.toString(o1));
String[] s1=list.toArray(new String[]{});
System.out.println(Arrays.toString(s1));
}
[执行结果]:
方法名称 | 方法描述 |
---|---|
int indexOf(Object o) | 从前往后找 , 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 从后往前找 , 返回最后一个 o 的下标 |
[代码演示]:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(2);
list.add(1);
list.add(1);
list.add(2);
System.out.println(list.indexOf(1));
System.out.println(list.lastIndexOf(1));
}
[执行结果]:
方法名称 | 方法描述 |
---|---|
List subList(int fromIndex, int toIndex) | 截取部分 list |
[代码演示]:
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("张三");
list1.add("李四");
list1.add("王五");
list1.add("赵钱");
System.out.println("截取之前的list1:"+list1);
List<String> list2 = list1.subList(0,2);
System.out.println("截取到的list2"+list2);
list1.set(1,"孙李");
System.out.println("修改lest2"+list2);
System.out.println("截取之后的list1:"+list1);
}
[执行结果]:
[注意]:
这里的截取并不是获取到列表范围内的元素再拷贝到一个新的列表中 , 也就是说 , 截取所得到的列表和被截取的列表是同一个列表 , 在同一个空间范围内 , 只是指向不同 , 所以上面的代码中修改截取到列表中的元素 , 原来的列表中的内容也会被修改.
方法名称 | 方法描述 |
---|---|
void clear() | 清空顺序表 |
这个就不做演示了
ArrayList 可以使用四种方式遍历:直接输出, for循环+下标, foreach, 使用迭代器
直接输出有一个缺陷 , 它不能修改其中的内容 , 只能一次性全部输出.
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵钱");
list.add("孙李");
System.out.println(list);
}
[执行结果]:
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵钱");
list.add("孙李");
//for循环+下标
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
//foreach
for (String s:list) {
System.out.print(s+" ");
}
}
迭代器是一种设计模式,迭代器可以用于遍历集合,开发人员不必去了解这个集合的底层结构。迭代器封装了数据获取和预处理逻辑,屏蔽了容器的实现细节,无需暴露数据结构内部。在数据量非常庞大时使用迭代器进行数据迭代获取,避免全部取出占用过多的服务器资源,且可以对部分数据进行预加载,提升性能。
下面给出的是Iterator迭代器接口的源码 , Iterator中有三个方法:分别为hasNext() , next() , remove()
package java.util;
import java.util.function.Consumer;
public interface Iterator<E> {
//如果迭代有更多元素,则返回true
boolean hasNext();
//返回迭代中的下一个元素
//如果没有元素则抛出NoSuchElementException异常
E next();
//从底层集合中移除此迭代器已经返回的最后一个元素,只能在next被调用后使用
default void remove() {
throw new UnsupportedOperationException("remove");
}
//对每个剩余元素执行给定的操作,直到处理完所有元素或操作引发异常。
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
集合想要获取一个迭代器可以使用 iterator() 方法返回一个用Iterator接收的对象 , 这个对象所对应的类是ArrayList中的内部类 , 其中重写了Iterator接口中的那些方法。
注意:iterator()方法是java.lang.Iterable接口中的抽象方法 , 被Collection继承; 最终在ArrayList中实现了iterator(); 要使用迭代器, 必须是在实现迭代器接口的前提下.
也可以使用 listIterator() 方法 , 对应的 listIterator 接口其实是继承了Iterator接口.
[代码示例1]:
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵钱");
list.add("孙李");
System.out.println("遍历元素");
Iterator<String> it = list.listIterator();
while (it.hasNext()) {
System.out.print(it.next()+" ");
}
}
[代码示例2]:
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵钱");
list.add("孙李");
Iterator<String> it = list.listIterator();
System.out.println("删除第一个元素");
it.next();
it.remove();
System.out.println(list);
}