滚雪球学Java(58):玩转Java中的ArrayList:常用操作技巧和方法总结
咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
@[toc]
前言
在Java开发中,ArrayList是一种常见的数据结构。它可以动态地增加或删除元素,十分方便。但是,要想熟练掌握ArrayList的使用,不仅要了解常用的操作技巧和方法,还需要深入理解它的实现原理。本文就旨在帮助读者更好地掌握ArrayList,快速地进行开发。
摘要
本文首先介绍了ArrayList的定义和特点,然后详细描述了它的常用操作技巧和方法,包括添加元素、删除元素、遍历元素、查找元素等。接下来,通过源代码解析让读者了解ArrayList的底层实现原理,进一步理解它的性能和使用限制。进一步,本文通过应用场景案例分析,探讨ArrayList的使用场景和优缺点,以帮助读者在实际开发中更好地选择数据结构。最后,通过测试用例和全文小结,对本文内容进行总结,让读者更好地掌握ArrayList的使用方法和技巧。
ArrayList
概述
ArrayList是Java中的一个类,它继承了AbstractList类,并且实现了List接口。它是一个动态数组,数组长度可以动态增加和缩小,它可以对数组中的元素进行增、删、改、查等操作。ArrayList可以存储任意类型的对象。
特点
ArrayList的特点包括:
- 可变长度:ArrayList的长度是可变的,可以动态增加或缩小。
- 重复元素:ArrayList中可以存储重复的元素。
- 线程不安全:ArrayList不是线程安全的,如果有多个线程同时访问同一个ArrayList实例,可能会出现竞争条件。
- 非同步:ArrayList不是同步的,不保证多个线程并发访问时的安全性。
- 查找元素效率高:通过索引可以快速查找元素,时间复杂度为O(1)。
源代码解析
ArrayList的底层实现是基于数组的,具体来说,它是通过一个Object类型的数组来存储元素的。在第一次添加元素时,会创建一个默认长度为10的数组,当数组空间不足时,会创建一个长度为原来数组长度+原来数组长度/2的新数组,并将原来数组中的元素复制到新数组中。在删除元素时,会将该元素后面的所有元素前移,如果删除的元素位于数组中间,则需要复制这两部分,同时更新数组长度。
ArrayList是Java集合框架中的一种数据结构,它基于数组实现,动态增长。下面是ArrayList的源代码解析:
- 类型参数定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
...
}
可以看到ArrayList实现了List接口、RandomAccess接口以及Cloneable接口,并且继承了AbstractList抽象类。其中,E表示ArrayList中的元素类型。
如下是部分源码截图:
- 成员变量定义:
// 默认容量为10
private static final int DEFAULT_CAPACITY = 10;
// 所有元素
transient Object[] elementData;
// 数组实际数量
private int size;
// 可选的List迭代器
private static final class ListItr<E> extends Itr<E> implements ListIterator<E> {
...
}
elementData数组存放ArrayList中的所有元素,size记录了当前存放元素的数量。
- 构造函数定义:
// 无参构造函数,初始化为默认容量10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 带初始容量参数的构造函数
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);
}
}
// 带集合参数的构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 若集合为空,则初始化为默认容量
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList支持无参、带初始容量参数、带集合参数的构造函数。其中,无参构造函数初始化为默认容量10,带初始容量参数的构造函数会检查参数是否合法,如果小于等于0则抛出IllegalArgumentException异常,带集合参数的构造函数会将集合转化为数组并存放在elementData数组中。
- 常用方法定义:
// 添加元素,支持链式调用
public boolean add(E e) {
// 确保elementData数组可以存放新元素
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
// 获取指定下标的元素
@SuppressWarnings("unchecked")
public E get(int index) {
rangeCheck(index);
return (E) elementData[index];
}
// 删除指定下标的元素
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
// 获取元素数量
public int size() {
return size;
}
其中,ensureCapacityInternal方法用于确保elementData数组容量足够存放新元素,rangeCheck方法用于检查下标是否越界,modCount记录集合结构修改次数。
- 扩容机制
在添加元素时,如果elementData数组容量不足,则需要扩容。扩容过程如下:
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);
// 将原数组元素拷贝到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, 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 static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
grow方法用于扩容,将原数组容量增加一半并重新分配大小,如果新容量小于minCapacity,则将minCapacity赋值给新容量。如果新容量大于MAX_ARRAY_SIZE,则将新容量赋值为MAX_ARRAY_SIZE。然后将原数组元素拷贝到新数组中。ensureCapacityInternal方法用于确保elementData数组容量足够存放新元素。ensureExplicitCapacity方法用于确保elementData数组容量不小于minCapacity。calculateCapacity方法计算出需要的最小容量,如果elementData数组为空,则返回默认容量DEFAULT_CAPACITY和minCapacity中的较大值。
常用操作技巧和方法
添加元素
在ArrayList中添加元素最基本的方法就是add()方法,该方法有两种重载形式,一种是无参的add()方法,一种是有参数的add(int index, E element)方法。无参的add()方法会在ArrayList的最后一位添加一个元素,而有参数的add(int index, E element)方法则可以将元素插入到指定的索引位置。
public boolean add(E e)
public void add(int index, E element)
如下是部分源码截图:
删除元素
在ArrayList中删除元素最常用的方法是remove()方法,该方法也有两种重载形式,一种是删除指定索引位置的元素,另一种是删除指定元素。需要注意的是,如果是使用remove(int index)方法删除元素,则会将该位置后面的所有元素向前移动一位,而如果是使用remove(Object o)方法,则会将第一个匹配到的元素删除。
public E remove(int index)
public boolean remove(Object o)
遍历元素
在ArrayList中遍历元素可以使用for循环和foreach语句,如下所示:
package com.example.javase.se.classes;
import java.util.ArrayList;
/**
* @Author ms
* @Date 2023-11-02 19:13
*/
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("Java");
list.add("Python");
list.add("C++");
// 使用for循环遍历元素
for (int i = 0; i < list.size(); i++) {
String element = list.get(i);
System.out.println(element);
}
// 使用foreach语句遍历元素
for (String element : list) {
System.out.println(element);
}
}
}
测试结果
根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。
查找元素
在ArrayList中查找元素可以使用indexOf()方法或contains()方法。其中,indexOf()方法返回该元素第一次出现的位置的索引值,如果没有找到该元素,则返回-1;contains()方法返回ArrayList中是否存在该元素,返回值为布尔型。
public int indexOf(Object o)
public boolean contains(Object o)
应用场景案例
场景一:存储简单数据类型
在Java中,数组是一种非常基本的数据结构,可以用来存储简单数据类型,例如int、double、boolean等。但是,数组的长度是不可变的,如果需要动态增加或删除元素,就需要使用ArrayList。例如:
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
场景二:实现栈和队列
栈和队列是计算机科学中常见的数据结构。栈是一种LIFO(Last-In-First-Out)的数据结构,队列是一种FIFO(First-In-First-Out)的数据结构。借助ArrayList可以很容易地实现栈和队列。例如:
// 实现栈
ArrayList<Integer> stack = new ArrayList<Integer>();
stack.add(1);
stack.add(2);
stack.add(3);
Integer top = stack.remove(stack.size() - 1);
// 实现队列
ArrayList<Integer> queue = new ArrayList<Integer>();
queue.add(1);
queue.add(2);
queue.add(3);
Integer front = queue.remove(0);
优缺点分析
ArrayList的优点包括:
- 索引快速查找:通过索引可以快速查找元素,时间复杂度为O(1)。
- 可变长度:ArrayList的长度是可变的,可以动态增加或缩小。
- 可存储任意类型的对象:ArrayList可以存储任意类型的对象,灵活性高。
ArrayList的缺点包括:
- 不适合大量元素的添加和删除:由于ArrayList底层是基于数组实现的,因此在进行大量元素的添加或删除时,会存在数组复制的问题,影响性能。
- 线程不安全:ArrayList不是线程安全的,如果有多个线程同时访问同一个ArrayList实例,可能会出现竞争条件。
- 非同步:ArrayList不是同步的,不保证多个线程并发访问时的安全性。
类代码方法介绍
在Java中,ArrayList是一个类,我们可以自己创建ArrayList对象并进行操作。ArrayList中的常用方法已经在前面进行了介绍,这里就不再赘述,但是需要注意的是,ArrayList继承了AbstractList类,并且实现了List接口,因此,它还有一些其他的方法,例如:
public boolean isEmpty()
public void clear()
public boolean equals(Object o)
public Object[] toArray()
测试用例
在本文中,我们介绍了ArrayList的定义和特点,详细描述了它的常用操作技巧和方法,通过源代码解析让读者了解ArrayList的底层实现原理,通过应用场景案例分析,探讨ArrayList的使用场景和优缺点,以帮助读者在实际开发中更好地选择数据结构。下面,我们构建一个测试用例来检验ArrayList的操作是否正确:
package com.demo.javase.day58;
import java.util.ArrayList;
/**
* @Author bug菌
* @Date 2023-11-05 23:45
*/
public class ArrayListTest {
public static void main(String[] args) {
// 创建一个空的ArrayList对象
ArrayList<String> list = new ArrayList<String>();
// 添加元素
list.add("Java");
list.add("Python");
list.add("C++");
// 遍历元素
for (String element : list) {
System.out.println(element);
}
// 插入元素
list.add(1, "JavaScript");
// 删除元素
list.remove("C++");
// 查找元素
boolean contains = list.contains("Java");
int index = list.indexOf("Python");
// 输出结果
System.out.println(list);
System.out.println("List contains Java? " + contains);
System.out.println("Index of Python: " + index);
}
}
运行结果如下所示:
Java
Python
C++
[Java, JavaScript, Python]
List contains Java? true
Index of Python: 2
测试结果
根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。
测试用例中,我们创建了一个空的ArrayList对象,并向其中添加了三个元素。接着,我们使用for-each语句遍历ArrayList中的所有元素,然后插入了一个新元素,并删除了一个元素。最后,我们使用contains()方法和indexOf()方法查找元素,并输出结果。可以看到,ArrayList的操作均正确无误。
测试代码分析
根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。
这是一个用于演示 ArrayList 使用的 Java 类,主要实现了以下功能:
- 创建一个空的 ArrayList 对象;
- 向 ArrayList 中添加元素;
- 遍历 ArrayList 中的元素;
- 插入元素到指定位置;
- 删除元素;
- 查找元素在 ArrayList 中的位置。
在 main 方法中,创建了一个空的 ArrayList 对象 list,并向其中添加了三个元素:Java、Python 和 C++。接着使用 for 循环遍历了 list 中的元素,并对每个元素进行了输出。然后调用了 list.add(1, “JavaScript”) 方法,在 list 中第二个位置插入了一个元素 JavaScript。接着又调用了 list.remove(“C++”) 方法,将 list 中的 C++ 元素删除。最后通过 list.contains(“Java”) 方法查找元素 Java 是否在 list 中,并使用 list.indexOf(“Python”) 方法查找元素 Python 在 list 中的位置,将查找结果输出。
总体来说,这个类演示了 ArrayList 的基本使用方法,包括添加、删除、插入和查找元素等。
总结
本文详细介绍了Java中的ArrayList类,包括其定义和特点、常用操作技巧和方法、源代码解析、应用场景案例分析以及优缺点分析等方面。文章通过测试用例对ArrayList的操作进行了验证,并给读者提供了一些学习建议和推荐。通过本文的学习,读者可以更好地掌握ArrayList的使用方法和技巧,快速地进行开发。
…
好啦,这期的内容就基本接近尾声啦,若你想学习更多,可以参考这篇专栏总结《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。
附录源码
如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。
☀️建议/推荐你
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。
📣关于我
我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。
- 点赞
- 收藏
- 关注作者
评论(0)