ArrayList与顺序表
1. 线性表
线性表(linear list)是由n个具有相同特性的数据元素组成的有限序列。作为一种实际应用中广泛使用的数据结构,常见的线性表包括:顺序表、链表、栈、队列等。
在逻辑结构上,线性表呈现为连续的线性结构,即一条直线排列。然而在物理存储结构上,元素并不一定连续存储。线性表通常采用两种存储方式:数组(顺序存储)和链式结构(链式存储)。

2. 顺序表
顺序表是一种采用数组存储的线性结构,其特点是数据元素在物理地址上连续存储。通过数组实现顺序表,可以高效地进行数据的增删查改操作。
2.1 接⼝的实现
框架:
java
public class SeqList {
private int[] array;
private int size;
// 默认构造⽅法
SeqList(){ }
// 将顺序表的底层容量设置为initcapacity
SeqList(int initcapacity){ }
// 新增元素,默认在数组最后新增
public void add(int data) { }
// 在 pos 位置新增元素
public void add(int pos, int data) { }
// 判定是否包含某个元素
public boolean contains(int toFind) { return true; }
// 查找某个元素对应的位置
public int indexOf(int toFind) { return -1; }
// 获取 pos 位置的元素
public int get(int pos) { return -1; }
// 给 pos 位置的元素设为 value
public void set(int pos, int value) { }
//删除第⼀次出现的关键字key
public void remove(int toRemove) { }
// 获取顺序表⻓度
public int size() { return 0; }
// 清空顺序表
public void clear() { }
// 打印顺序表,注意:该⽅法并不是顺序表中的⽅法,为了⽅便看测试结果给出的
public void display() { }
}
参考代码:
java
package com.demo1;
public class SeqList {
private int[] array;
private int size;
// 默认构造⽅法
SeqList(){
size = 0;
array = new int[5];
}
// 将顺序表的底层容量设置为initcapacity
SeqList(int initcapacity){
array = new int[initcapacity];
size = 0;
}
public boolean isFull(){
if(size == array.length){
return true;
}
return false;
}
public boolean isEmpty(){
if(size == 0){
return true;
}
return false;
}
// 新增元素,默认在数组最后新增
public void add(int data) {
if(!isFull()) {
array[size] = data;
size++;
}
}
public void moveBack(int start,int end){
for(int i = end;i >= start; i--){
array[i+1] = array[i];
}
}
public void moveFront(int start,int end){
for(int i = start; i <= end; i++){
array[i - 1] = array[i];
}
}
// 在 pos 位置新增元素
public boolean add(int pos, int data) {
if(!isFull()){
if(pos > size || pos < 0){
System.out.println("插入位置不对,请重新选择插入位置");
return false;
}else if(pos == size) {
array[pos] = data;
size++;
}else {
moveBack(pos, size - 1);
array[pos] = data;
size++;
}
}
return true;
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
if(!isEmpty()) {
for (int i = 0; i < size; i++) {
if (array[i] == toFind) {
return true;
}
}
return false;
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind) {
if(!isEmpty()) {
for (int i = 0; i < size; i++) {
if (array[i] == toFind) {
return i;
}
}
}
return -1;
}
// 获取 pos 位置的元素
public int get(int pos) {
if(!isEmpty()) {
if (pos < 0 || pos > size - 1) {
System.out.println("该位置没有元素");
return -1;
} else {
return array[pos];
}
}
return -1;
}
// 给 pos 位置的元素设为 value
public boolean set(int pos, int value) {
if(!isEmpty()) {
if (pos < 0 || pos >= size) {
return false;
} else {
array[pos] = value;
return true;
}
}
return false;
}
//删除第⼀次出现的关键字key
public boolean remove(int toRemove) {
if(!isEmpty()){
for(int i = 0; i < size; i++){
if(array[i] == toRemove){
if(i == size-1){
size--;
return true;
}else{
moveFront(i+1,size-1);
size--;
return true;
}
}
}
}
return false;
}
// 获取顺序表⻓度
public int size() {
return size;
}
// 清空顺序表
public void clear() {
size = 0;
}
// 打印顺序表,注意:该⽅法并不是顺序表中的⽅法,为了⽅便看测试结果给出的
public void display() {
for(int i = 0; i < size; i++){
System.out.print(array[i]+" ");
}
System.out.println();
}
}
3. ArrayList简介
在集合框架中,ArrayList是⼀个普通的类,实现了List接⼝,具体框架图如下:

【说明】
- ArrayList采用泛型实现,使用时需先进行实例化
- ArrayList实现了RandomAccess接口,表明其支持随机访问特性
- ArrayList实现了Cloneable接口,具备克隆能力
- ArrayList实现了Serializable接口,支持序列化操作
- 与Vector不同,ArrayList非线程安全,适用于单线程环境;多线程场景可选用Vector或CopyOnWriteArrayList
- ArrayList底层基于连续存储空间实现,具有动态扩容特性,属于动态顺序表结构
4. ArrayList使⽤
4.1 ArrayList的构造
| 方法 | 说明 |
|---|---|
| ArrayList() | 创建一个空的 ArrayList |
| ArrayList(Collection<? extends E> c) | 通过现有集合创建 ArrayList |
| ArrayList(int initialCapacity) | 创建具有指定初始容量的 ArrayList |
java
public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造⼀个空的列表
List<Integer> list1 = new ArrayList<>();
// 构造⼀个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
// list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素⼀致
ArrayList<Integer> list3 = new ArrayList<>(list2);
// 避免省略类型,否则:任意类型的元素都可以存放,使⽤时将是⼀场灾难
List list4 = new ArrayList();
list4.add("111");
list4.add(100);
}
4.2 ArrayList常⻅操作
ArrayList虽然提供的⽅法⽐较多,但是常⽤⽅法如下所⽰,需要⽤到其他⽅法时,同学们⾃⾏查看
ArrayList的帮助⽂档。
| 方法 | 说明 |
|---|---|
boolean add(E e) |
在列表末尾添加元素 e |
void add(int index, E element) |
在指定索引位置插入元素 e |
boolean addAll(Collection<? extends E> c) |
在列表末尾添加集合 c 中的所有元素 |
E remove(int index) |
删除并返回指定索引位置的元素 |
boolean remove(Object o) |
删除列表中首次出现的元素 o |
E get(int index) |
获取指定索引位置的元素 |
E set(int index, E element) |
将指定索引位置的元素替换为 e |
void clear() |
清空列表中的所有元素 |
boolean contains(Object o) |
判断列表中是否包含元素 o |
int indexOf(Object o) |
返回元素 o 首次出现的索引 |
int lastIndexOf(Object o) |
返回元素 o 最后一次出现的索引 |
List<E> subList(int fromIndex, int toIndex) |
获取指定索引范围内的子列表 |
java
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,否则返回false
if(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可以通过三种方式遍历:
- for循环配合下标访问
- 增强for循环(foreach)
- 使用迭代器(Iterator)
java
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(5);
list.add(7);
list.add(9);
//使用下标+for遍历
for(int i = 0; i < list.size(); i++){
System.out.print(list.get(i) + " ");
}
System.out.println();
//借助foreach遍历
for(Integer integer : list){
System.out.print(integer + " ");
}
System.out.println();
Iterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}
System.out.println();
}
注意:
-
ArrayList最常用的遍历方式有两种:通过for循环结合下标访问,以及使用foreach语法。
-
迭代器是设计模式的一种,后续在大家接触更多容器类型时,我们会进一步讲解这个概念。
4.4 ArrayList的扩容机制
下⾯代码有缺陷吗?为什么?
java
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(i);
}
}
ArrayList是⼀个动态类型的顺序表,即:在插⼊元素的过程中会⾃动扩容。以下是ArrayList源码
(JDK17)中扩容⽅式:
java
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) //第⼀次存储的时候 elementData.length=0,s = 0
elementData = grow();
elementData[s] = e;
size = s + 1;
}
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA ) {
int newCapacity = ArraysSupport. newLength (oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */ );
return elementData = Arrays. copyOf (elementData, newCapacity);
} else {
return elementData = new Object[Math. max ( DEFAULT_CAPACITY ,
minCapacity)];
}
}
//oldLength: 当前数组的⻓度
//minGrowth: 最⼩需要增加的⻓度
//prefGrowth: ⾸选的增⻓⻓度(通常是当前⼤⼩的⼀半)
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// preconditions not checked because of inlining
// assert oldLength >= 0
// assert minGrowth > 0
//当前⻓度加上 minGrowth 和 prefGrowth 中的较⼤值。
int prefLength = oldLength + Math. max (minGrowth, prefGrowth); // might
overflow
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH ) {
return prefLength;
} else {
// put code cold in a separate method
return hugeLength (oldLength, minGrowth);
}
}
private static int hugeLength(int oldLength, int minGrowth) {
int minLength = oldLength + minGrowth;
if (minLength < 0) { // overflow
throw new OutOfMemoryError(
"Required array length " + oldLength + " + " + minGrowth + " is
too large");
; else if (minLength <= SOFT_MAX_ARRAY_LENGTH ) {
return SOFT_MAX_ARRAY_LENGTH ;
} else {
return minLength;
}
}
- 如果调⽤不带参数的构造⽅法,第⼀次分配数组⼤⼩为10
- 后续扩容为1.5倍扩容
5. ArrayList的具体使⽤
5.1 简单的洗牌算法
java
package com.demo2;
public class Card {
public String rank;//牌面值
public String suit;//花色
@Override
public String toString() {
return String.format("[%s %s]",suit,rank);
}
}
java
package com.demo2;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class CardDemo {
public static final String[] SUITS = {"♠", "♥", "♣", "♦"};
public static final String[] RANK = {"3","4","5","6","7","8","9","10",
"J","Q","K","A","2"};
//买一副牌
private static List<Card> buyDeck(){
List<Card> deck = new ArrayList<>(52);
for(int i = 0; i < 4; i++){
for(int j = 0; j < 13; j++){
String suit = SUITS[i];
String rank = RANK[j];
Card card = new Card();
card.rank = rank;
card.suit = suit;
deck.add(card);
}
}
return deck;
}
private static void swap(List<Card> deck, int i, int j){
Card t = deck.get(i);
deck.set(i,deck.get(j));
deck.set(j,t);
}
private static void shuffle(List<Card> deck){
Random random = new Random(20190905);
for(int i = deck.size() - 1; i > 0; i--){
int r = random.nextInt(i);
swap(deck,i,r);
}
}
public static void main(String[] args) {
List<Card> deck = buyDeck();
System.out.println("刚买回来的牌:");
System.out.println(deck);
shuffle(deck);
System.out.println("洗过的牌");
System.out.println(deck);
//三个人,每人轮流抓5张牌
List<List<Card>> hands = new ArrayList<>();
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
for(int i = 0; i < 5; i++){
for(int j = 0; j < 3; j++){
hands.get(j).add(deck.remove(0));
}
}
System.out.println("剩余的牌:");
System.out.println(deck);
System.out.println("玩家A手中的牌:");
System.out.println(hands.get(0));
System.out.println("玩家B手中的牌:");
System.out.println(hands.get(1));
System.out.println("玩家C手中的牌:");
System.out.println(hands.get(2));
}
}
5.2 杨辉三⻆
6. ArrayList的问题及思考
-
ArrayList底层采用连续存储空间,在任意位置插入或删除元素时,需要移动后续所有元素,导致时间复杂度为O(N)。
-
扩容操作涉及申请新内存、数据迁移和释放旧空间,会产生较大的性能开销。
-
扩容通常采用2倍增长策略,可能导致空间浪费。例如当前容量100,扩容到200后仅插入5个元素,就会造成95个空间的浪费。
思考:如何优化上述问题?
感谢您的观看!