文章目录
📕各位读者好, 我是小陈, 这是我的个人主页
📗小陈还在持续努力学习编程, 努力通过博客输出所学知识
📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
📙 希望我的专栏能够帮助到你:
JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)
前言
本篇将分享Java集合类当中的ArrayList的常见方法和使用,在这之前需要了解 [什么是线性表] ❓
👉线性表 :是 n 个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列…
🙋🏼线性表在逻辑上是连续的 ,也就说是连续的一条直线
🙋🏼但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组 和 链式结构的形式存储。
提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎评论区指点~ 废话不多说,直接发车~
一、模拟实现顺序表
👉认识顺序表:
顺序表是用 一段物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用 数组 存储。在数组上完成数据的增删查改。
为什么要模拟实现:
自己模拟实现 简易版的 顺序表的增删查改等主要功能,大致理解顺序表的设计思想
再对比学习 Java 提供的集合类当中的 ArrayList ,在学习 Java 的 ArrayList 常用方法的同时,也能学习源码的思想
成员属性
Java 中的 ArrayList 是集合框架中的一个类,要模拟实现顺序表,也得自己实现一个类,首先要考虑这个类中的成员属性
已经说过,顺序表底层是基于数组实现的(不完全对,暂时这样理解),那么成员属性就需要:
1️⃣数组 array:来存放数据
2️⃣变量 capacity :来记录数组的容量,当数组满了就需要增大容量
3️⃣变量 useSize:来记录数组已经存放了几个数据
体现封装思想,成员属性全部设置为 private
public class SeqList {
private int[] array;// 数组
private int capacity;// 容量
private int useSize;// (使用过的)长度
}
⚠️模拟实现重在理解理想,为了简便,不使用泛型🙅,数组中存放 int 类型
成员方法
// 新增元素,默认在数组最后新增
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() { }
// 打印顺序表,注意:ArrayList 没有这个方法,为了方便看测试结果给出的
public void display() { }
1,构造方法
构造方法的作用:初始化成员属性,useSize 无需初始化,编译器默认初始化为 0
// 将顺序表的底层容量设置为capacity
public SeqList(int capacity){
this.capacity = capacity;
// 为数组开辟内存空间
this.array = new int[this.capacity];
}
2,add——新增元素,默认在数组末尾新增
❗️❗️在末尾新增数据之前,必须考虑:
顺序表是否已🈵️,如果满了,则需要扩容之后再添加元素
分别写出 判满 和 扩容 这两个方法:
2.1, isFull——判断顺序表是否已满
public boolean isFull() {
return this.useSize == this.capacity;
}
用户也可能 / 可以使用这个方法,所以我们可以设置为 public
2.2, expandCapacity——扩容
利用 copyOf 方法,拷贝数组并指定容量,让 this.array 引用新的数组
private void expandCapacity() {
// 太长了,中间换行
this.array = Arrays.copyOf(this.array,
this.capacity * 2);
}
这个方法不需要用户访问,只在类内使用,所以可以设置为 private
✅所以 add 方法的正确写法为:
public void add(int data) {
// 判满
if (isFull()) {
// 满了就扩容
expandCapacity();
}
this.array[this.useSize] = data;
this.useSize++;
}
最后记得,增加数据之后,**useSize 也要++**❗️❗️
3,add——在 pos 位置新增元素
❗️❗️这个方法是:在指定位置新增元素,新增之前必须考虑:
1️⃣顺序表是否已满
2️⃣pos 位置是否合法
判满的方法以及写过了,现在我们需要补充:判断 pos 位置合法性 的方法
需要思考,pos 在什么位置才是合法呢?
📃分析:
在这种情况下,pos < 0 或 pos > 3 都是不合法❌的
⚠️数组没有负数下标
⚠️数组内数据必须连续
3.1, judgeAddPos——判断 add 时 pos 位置合法性
private void judgeAddPos(int pos) {
if (pos < 0 || pos > this.useSize) {
// 自定义异常
throw new ArrayListIndexOutOfException
(" add 时 pos 位置不合法!");
}
}
这个方法不需要用户访问,只在类内使用,所以可以设置为 private
如果 pos 参数不合法,就不能执行下面的代码,所以我们可以自定义一个异常类:
3.3,ArrayListIndexOutOfException——自定义下标不合法异常
// 继承于运行时异常
public class ArrayListIndexOutOfException
extends RuntimeException {
public ArrayListIndexOutOfException(String str) {
super(str);
}
}
这样,当我们 add 时的 pos 参数不合法时,就会抛出这样的异常并终止程序🈲
“准备工作” 做足之后,我们需要考虑,如何实现在 pos 位置新增,也就是插入呢❓
🚗🚗🚗
就是把 pos 下标以及之后 的数据 向后依次 覆盖,最终把 pos 位置“空出来”,放入新数据:
public void add(int pos, int data) {
if (isFull()) {
// 判满 + 扩容
expandCapacity();
}
// 判断 pos 位置合法性
judgeAddPos(pos);
// 挪动数据
for (int i = this.useSize; i > pos; i--) {
this.array[i] = this.array[i - 1];
}
// 插入数据
this.array[pos] = data;
this.useSize++;
}
最后不要忘记 useSzie++ ❗️❗️
⚠️注意:
要先移动 3,再移动 2 ——从后往前的顺序移动
如果先移动 2 ,则会把 3 覆盖掉,丢失数据
4,contains——判定是否包含某个元素
比较简单,遍历这个数组即可
public boolean contains(int toFind) {
for (int i = 0; i < this.useSize; i++) {
if (this.array[i] == toFind) {
return true;
}
}
return false;
}
因为这里我们存放的是 int 类型的变量,但 ArrayList 当中可以存放引用数据类型的
⚠️⚠️⚠️当表中是引用类型时,就不可以用“等号”比较,应该用 equals 方法
5, indexOf——查找某个元素对应的位置
还是遍历数组
public int indexOf(int toFind) {
for (int i = 0; i < this.useSize; i++) {
if (this.array[i] == toFind) {
return i;
}
}
return -1;
}
因为这里我们存放的是 int 类型的变量,但 ArrayList 当中可以存放引用数据类型的
⚠️⚠️⚠️当表中是引用类型时,就不可以用“等号”比较,应该用 equals 方法
6,get——获取 pos 位置的元素
在获取 pos 之前必须保证 pos 位置合法性,但此时的 pos 判断合法性和 add 时的判断规则不一样咯❗️❗️
🚗🚗🚗
获取 pos 位置的元素,前提是 pos 位置上有数据
此时 pos 的合法性判断规则是:pos 不能小于 0 或 不能大于 useSize - 1
6.1,judgePos——判断 pos 位置合法性
private void judgePos(int pos) {
if (pos < 0 || pos > this.useSize - 1) {
// 自定义异常
throw new ArrayListIndexOutOfException
(" pos 位置不合法!");
}
}
这个方法不需要用户访问,只在类内使用,所以可以设置为 private
如果pos 不合法,我们让程序终止,抛出异常❌
做好 “准备工作” 之后, get 方法就很简单咯
public int get(int pos) {
// 首先判断 pos 位置合法性
judgePos(pos);
return this.array[pos];
}
7, set——给 pos 位置的元素设为 value
老规矩,pos 作为参数时,就要判断合法性❗️❗️
public void set(int pos, int value) {
judgePos(pos);
this.array[pos] = value;
}
8,remove——删除第一次出现的数据
我们前面分析过了 add 方法的执行原理,那么删除的原理恰好是和 add 的操作相反:在数组中要 “删除” 一个数,让后面的数据依次向前覆盖即可:
可以看到最后还剩一个 3,没有必要处理,useSize- - 即可✅
别忘了,怎么找到待删除数据的位置呢❓——调用前面写过的 indexOf 方法❗️
public void remove(int toRemove) {
int pos = indexOf(toRemove);
if (pos == -1) {
// 找不到的情况
System.out.println("不存在该数据");
}else {
// 注意这里的循环条件
for (int i = pos; i < this.useSize - 1; i++) {
this.array[i] = this.array[i + 1];
}
this.useSize--;
}
}
⚠️注意:
i < this.useSize - 1 这里不能写成 <=,当数组正好是满的情况下
this.array[i] = this.array[i + 1]; 这里访问 i+1 下标就会数组越界
9,size——获取顺序表长度
直接返回 useSize 即可
public int size() {
return this.useSize;
}
10,clear——清空顺序表
我们的操作都是利用 useSize 所以直接把 useSize 置为 0 即可
public void clear() {
this.useSize = 0;
}
11,display——打印顺序表
注意:顺序表中不存在该方法,为了方便看测试结果
public void display() {
for (int i = 0; i < this.useSize; i++) {
System.out.print(this.array[i] + " ");
}
System.out.println();
}
二、Java提供的ArrayList
1,ArrayList 的说明
🙋🏼Java官方的集合框架中,ArrayList 是一个普通的类,继承了List接口
⚠️特殊说明:
1️⃣ArrayList 是以泛型的方式实现的类,使用时必须先实例化
2️⃣ArrayList 实现了 RandomAccess 接口,表明 ArrayList 支持随机访问
3️⃣ArrayList 实现了 Cloneable 接口,表明 ArrayList 是可以 clone 的
4️⃣ArrayList 实现了 Serializable 接口,表明 ArrayList 是支持序列化的
5️⃣ArrayList 不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector 或者 CopyOnWriteArrayList
6️⃣ArrayList 底层是一段连续的空间(可以理解为数组),并且可以动态扩容,是一个动态类型的顺序表
2,使用ArrayList
2.1,ArrayList 实例化方式
1️⃣无参构造法
ArrayList<Integer> arrayList1 = new ArrayList<>();
2️⃣有参数——参数表示容量
ArrayList<Integer> arrayList2 = new ArrayList<>(10);
此时的 arrayList2 的容量被指定为 10
3️⃣有参数——参数是其他 Collection
Collection是集合框架中的一个接口,实现了这个接口的类的对象就可以作为参数,说白了就是
可以把其他的顺序表,链表,栈,队列等等 作为参数传参,例如:
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.addLast(1);
linkedList.addLast(2);
ArrayList<Integer> arrayList3 = new ArrayList<>(linkedList);
我先 new 了一个链表对象 linkedList,在这个链表中插入了 “1” “2” 两个数据,链表中的数据是链式存储❗️的
当 linkedList 作为参数传递时,arrayList3 中就有了 linkedList 中的所有数据,arrayList3 中的数据是顺序存储❗️的
2.2,ArrayList 常用方法
ArrayList<Integer> arrayList = new ArrayList<>();
// 1,插入(尾插)
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
arrayList.add(5);
System.out.println("插入数据后:" + arrayList);
// 2,在任意位置插(下标)插入
arrayList.add(0, -1);
System.out.println("在0下标插入-1后:" + arrayList);
// new 一个链表对象并尾插“1”,“2”
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.addLast(1);
linkedList.addLast(2);
// 3,插入 linklist 的所有元素
arrayList.addAll(linkedList);
System.out.println("插入linklist后:" + arrayList);
// 4,删除任意位置(下标)数据
arrayList.remove(0);
System.out.println("删除0下标数据后:" + arrayList);
// 5,删除指定数据
arrayList.remove(new Integer(1));// 参数为Object(类)类型的对象
System.out.println("删除第一个1后:" + arrayList);
// 上面两个remove()方法构成了重载
// remove()方法的参数输入之后会被自动识别为index,
// 因为index是int类型,而顺序表中的数据是Object(类)类型
// 比如在插入数据时,输入的是1,基本类型,但会自动装箱,变成Integer类型
// 所以要删除数据,应该输入类类型的对象,而不是基本类型的数据
// 6,获取任意位置(下标)数据
int ret = arrayList.get(0);
System.out.println("得到0下标的数据:" + ret);
// 7,更改任意位置(下标)数据
arrayList.set(0,100);
System.out.println("把0下标数据改成100后:" + arrayList);
// 8,判断是否存在该数据
boolean bl = arrayList.contains(100);
System.out.println("判断是否存在100这个数据:" + bl);
// 9,返回第一个key的位置(下标)
int index = arrayList.indexOf(2);
System.out.println("第一个2的下标:" + index);
// 10,返回最后一个key的位置(下标)
int lastIndex = arrayList.lastIndexOf(2);
System.out.println("最后一个2的下标:" + lastIndex);
// 11,获取顺序表长度
int size = arrayList.size();
System.out.println("顺序表长度为:" + size);
// 12,截取
List<Integer> list = arrayList.subList(1, 3);// [1,3)左闭右开
System.out.println(list);
// 13,清空顺序表
arrayList.clear();
System.out.println("清空顺序表后:" + arrayList);
总结
以上就是今天分享的关于数据结构中【顺序表】的内容,一方面介绍了如何模拟实现简易的顺序表,一方面介绍了Java集合框架中的【ArrayList】类的基本使用【ArrayList】
如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦🤪🤪🤪
上山总比下山辛苦
下篇文章见