java数据结构--ArrayList与顺序表
一、线性表
线性表是一种最基本的数据结构,它是由 n 个具有相同特性的数据元素组成的有限序列。线性表的特点是:除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继。
生活中很多场景都可以抽象为线性表,比如排队的人群(每个人前面只有一个人,后面也只有一个人)、电话号码簿(按顺序排列的联系人)等。
二、顺序表
顺序表是线性表的一种实现方式,它用一段物理地址连续的存储单元依次存储线性表的数据元素。简单来说,就是在内存中开辟一块连续的空间,把数据按顺序存进去。
2.1.接口的实现
public interface MyList {// 新增元素void add(int data);// 在指定位置插入元素void add(int index, int data);// 删除指定位置元素int remove(int index);// 获取指定位置元素int get(int index);// 修改指定位置元素void set(int index, int data);// 查找元素位置int indexOf(int data);// 判断是否包含元素boolean contains(int data);// 获取元素个数int size();// 判断是否为空boolean isEmpty();// 清空所有元素void clear();
}
顺序表的实现依靠数组完成,通过维护一个数组和当前元素个数来管理数据。其优势是随机访问效率高(通过索引直接访问),但插入和删除操作可能需要移动大量元素,效率较低。
三、ArrayList简介
ArrayList 是 Java 集合框架中 List 接口的一个实现类,本质上是一个动态数组。与普通数组相比,它的容量可以动态增长,无需在创建时就确定大小,极大地提升了使用的灵活性。
ArrayList 的底层是用数组实现的,当元素数量超过当前容量时,会自动进行扩容操作。它允许存储 null 值,也允许存储重复元素,并且是有序的(存储顺序与插入顺序一致)。
需要注意的是,ArrayList 不是线程安全的,在多线程环境下使用需要额外处理线程安全问题(可以使用Collections.synchronizedList()
方法包装,或使用CopyOnWriteArrayList
)。
四、ArrayList使用
4.1.ArrayList的构造
ArrayList 提供了三种常用的构造方法:
1.无参构造:
创建一个初始容量为 0 的 ArrayList,首次添加元素时会扩容到默认容量(10)
List<String> list1 = new ArrayList<>();
2.指定初始容量的构造:
创建一个指定初始容量的 ArrayList,适合提前知道大致元素数量的场景,可减少扩容次数
List<Integer> list2 = new ArrayList<>(20);
3.用已有集合创建:
将其他集合中的元素初始化到 ArrayList 中
List<Character> list3 = new ArrayList<>(Arrays.asList('a', 'b', 'c'));
4.2.ArrayList常见操作
ArrayList 提供了丰富的操作方法,以下是常用的一些:
方法 | 解释 |
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List<E> subList(int fromIndex, int toIndex) | 截取部分 list |
public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("JavaSE");list.add("JavaWeb");list.add("JavaEE");list.add("JVM");list.add("测试课程");System.out.println(list);// 获取list中有效元素个数System.out.println(list.size());// 获取和设置index位置上的元素,注意index必须介于[0, size)间System.out.println(list.get(1));list.set(1, "JavaWEB");System.out.println(list.get(1));// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置list.add(1, "Java数据结构");System.out.println(list);// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置list.remove("JVM");System.out.println(list);// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常list.remove(list.size()-1);System.out.println(list);// 检测list中是否包含指定元素,包含返回true,否则返回falseif(list.contains("测试课程")){list.add("测试课程");}// 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找list.add("JavaSE");System.out.println(list.indexOf("JavaSE"));System.out.println(list.lastIndexOf("JavaSE"));// 使用list中[0, 4)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组List<String> ret = list.subList(0, 4);System.out.println(ret);list.clear();System.out.println(list.size());
}
4.3.ArrayList的遍历
ArrayList 有三种常用的遍历方式:
1.普通 for 循环:适合需要索引的场景
for (int i = 0; i < fruits.size(); i++) {System.out.println(fruits.get(i));
}
2.增强 for 循环(foreach):简洁,无需关心索引
for (String fruit : fruits) {System.out.println(fruit);
}
3.迭代器(Iterator):支持在遍历中安全删除元素
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {String fruit = iterator.next();if ("apple".equals(fruit)) {iterator.remove(); // 安全删除}
}
4.4.ArrayList的扩容机制
ArrayList 的扩容机制是其核心特性之一,理解它有助于更好地使用 ArrayList:
- 初始容量:无参构造创建的 ArrayList 初始容量为 0,首次添加元素时会扩容到 10
- 扩容触发:当元素数量达到当前容量时,触发扩容
- 扩容规则:扩容后的新容量 = 旧容量 + 旧容量 / 2(即 1.5 倍扩容)
- 特殊情况:如果计算的新容量小于最小需求容量,则直接使用最小需求容量
public static void main(String[] args) {List<Integer> list = new ArrayList<>();for (int i = 0; i < 100; i++) {list.add(i);}
}
五、ArrayList的具体使用
5.1.简单的洗牌算法
利用 ArrayList 可以实现一个简单的洗牌算法,将一组牌随机打乱顺序:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class ShuffleDemo {public static void main(String[] args) {// 初始化一副牌(1-54,代表54张牌)List<Integer> cards = new ArrayList<>(54);for (int i = 1; i <= 54; i++) {cards.add(i);}// 洗牌:从最后一张牌开始,与前面随机位置的牌交换Random random = new Random();for (int i = cards.size() - 1; i > 0; i--) {// 生成0到i之间的随机索引int j = random.nextInt(i + 1);// 交换元素int temp = cards.get(i);cards.set(i, cards.get(j));cards.set(j, temp);}System.out.println("洗牌后:" + cards);}
}
洗牌算法的核心思想是从最后一个元素开始,依次与前面随机位置的元素交换,这样可以保证每个元素出现在每个位置的概率相等。
5.2.杨辉三角
杨辉三角是中国古代数学的杰出研究成果之一,其特点是每行的第一个和最后一个元素都是 1,其余元素等于它上方两元素之和。使用 ArrayList 可以方便地生成杨辉三角:
import java.util.ArrayList;
import java.util.List;public class YanghuiTriangle {public static void main(String[] args) {int numRows = 5; // 生成5行杨辉三角List<List<Integer>> triangle = generate(numRows);// 打印杨辉三角for (List<Integer> row : triangle) {System.out.println(row);}}public static List<List<Integer>> generate(int numRows) {List<List<Integer>> result = new ArrayList<>();// 第一行始终是[1]result.add(new ArrayList<>());result.get(0).add(1);// 生成后续行for (int i = 1; i < numRows; i++) {List<Integer> prevRow = result.get(i - 1);List<Integer> currRow = new ArrayList<>();// 每行的第一个元素是1currRow.add(1);// 中间元素 = 上一行相邻两元素之和for (int j = 1; j < i; j++) {currRow.add(prevRow.get(j - 1) + prevRow.get(j));}// 每行的最后一个元素是1currRow.add(1);result.add(currRow);}return result;}
}
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
通过嵌套的 ArrayList 结构,我们可以很自然地表示杨辉三角的行与列关系,每行的元素个数与行号(从 0 开始)相等,实现起来非常直观。
ArrayList 作为 Java 中最常用的集合类之一,基于顺序表的思想实现,提供了动态扩容的功能,兼顾了数组的随机访问效率和链表的动态容量特性。
六、总结
在实际开发中,合理使用 ArrayList 需要注意:
- 根据元素数量预估初始容量,减少扩容开销
- 遍历方式根据场景选择(需要索引用普通 for,删除元素用迭代器)
- 多线程环境下注意线程安全问题
掌握 ArrayList 的原理和使用技巧,能帮助我们更高效地处理数据集合,写出更优质的代码。