一、顺序表概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组
存储。在数组上完成数据的增删查改。
二、主要功能接口实现
Java顺序表底层就是一个动态数组。其主要功能接口如下:
// 1.打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
public void display() { }
// 2.新增元素,默认在数组最后新增
public void add(int data) { }
// 3.判断当前的顺序表是不是满的
public boolean isFull() { }
// 4.在 pos 位置新增元素
public void add(int pos, int data) { }
// 5.判定是否包含某个元素
public boolean contains(int toFind) { return true; }
// 6.查找某个元素对应的位置
public int indexOf(int toFind) { return -1; }
// 7.获取 pos 位置的元素
public int get(int pos) { return -1; }
// 8.给 pos 位置的元素设为 value
public void set(int pos, int value) { }
// 9.删除第一次出现的关键字 key
public void remove(int toRemove) { }
// 10.获取顺序表长度
public int size() { return 0; }
// 11.清空顺序表
public void clear() { }
下面我们对应上面的功能接口,自己实现一下顺序表(由于这部分比较简单这里就直接给出代码,大家可结合注释参考。另外此处侧重于流程实现,使用了更好理解的int类型,并未使用泛型):
import java.util.Arrays;
public class MyArraylist {
public int[] elem;
// 已使用容量,初始为0
public int usedSize;
// 默认容量
private static final int DEFAULT_SIZE = 5;
// 这里采用无参的构造方法
public MyArraylist() {
this.elem = new int[DEFAULT_SIZE];
}
/**
* 1.打印顺序表
* 注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
* 根据usedSize判断即可
*/
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
}
/**
* 2.新增元素,默认尾插
*/
public void add(int data) {
if (isFull()) {
resize();
}
this.elem[this.usedSize] = data;
this.usedSize++;
}
// 扩容
private void resize() {
this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
}
// 检查坐标合法性
private void checkPosInAdd(int pos) {
if (pos < 0 || pos > this.usedSize) {
// 这里自定义异常类
throw new IndexException("位置不合法,请检查位置的合法性!");
}
}
/**
* 3.判断当前的顺序表是不是满的
*/
public boolean isFull() {
return this.usedSize == this.elem.length;
}
/**
* 4.在 pos 位置新增元素!
*/
public void add(int pos, int data) {
if (isFull()) {
resize();
}
checkPosInAdd(pos);
for (int i = this.usedSize; i > pos; i--) {
this.elem[i] = this.elem[i - 1];
}
this.elem[pos] = data;
this.usedSize++;
}
/**
* 5.判定是否包含某个元素
*/
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return true;
}
}
return false;
}
/**
* 6.查找某个元素对应的位置
*/
public int indexOf(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
/**
* 7.获取 pos 位置的元素
*/
public int get(int pos) {
checkPosInAdd(pos);
return this.elem[pos];
}
// 判空
private boolean isEmpty() {
if (this.usedSize == 0) {
return true;
}
return false;
}
/**
* 8.将 pos 位置的元素设为修改为: value
*/
public void set(int pos, int value) {
checkPosInAdd(pos);
this.elem[pos] = value;
}
/**
* 9.删除第一次出现的关键字key
*/
public boolean remove(int key) {
int index = indexOf(key);
if (index == -1) {
System.out.println("不存在关键字" + key);
return false;
}
for (int j = index; j < this.usedSize - 1; j++) {
this.elem[j] = this.elem[j + 1];
}
this.usedSize--;
//注意:是在这条语句之后
//elem[usedSize] = null;
// 如果里面是引用类型 那么此时就需要手动置空
elem[usedSize] = 0;
return true;
}
/**
* 10.获取顺序表长度
*/
public int size() {
return this.usedSize;
}
/**
* 11.清空顺序表
*/
public void clear() {
this.usedSize = 0;
}
}
上述过程中自定义的异常类:
public class IndexException extends RuntimeException {
// 帮助父类构造
IndexException() {
super();
}
IndexException(String msg) {
super(msg);
}
}
三、Java集合框架中的 ArrayList
在 java.util
包下为我们提供了一个ArrayList
类,ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表,且这里的 ArrayList 是以泛型
方式实现的,使用时必须要先实例化。此外在Java中还对ArrayList有以下这些说明:(现阶段不做解释,大家可以暂时了解即可)
- ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
- ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
- ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
- 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者
CopyOnWriteArrayList
1、ArrayList的构造方法
方法 | 解释 |
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
解释:
- 对于无参构造方法
ArrayList()
,在实例化ArrayList对象时不会分配内存,在add()方法中才进行内存分配。- 对于
ArrayList(Collection<? extends E> c)
,表示可以将实现了Collection接口、并且是E类型或E类型的子类的集合作为参数构造ArrayList。- 相比之下
ArrayList(int initialCapacity)
就比较好理解了,这个构造方法是在实例化ArrayList对象时,分配指定initialCapacity大小的容量。
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 isEmpty() | 如果此列表不包含元素,则返回 true |
int size() | 返回此列表中的元素数 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List subList(int fromIndex, int toIndex) | 截取部分 list |
3、ArrayList 的继承关系
在《Java集合框架中》我们介绍了Java集合中的继承关系,你是否还有印象ArrayList实现了List接口:
对于List
接口来说,里面涵盖了ArrayList
中的方法,也就是说我们可以使用 List 接口对 ArrayList 实例进行操作,因此我们可以写出类似这种调用方式:List<Integer> list = new Arraylist<>();
将ArrayList实例交给List接口,然后直接使用list进行相关操作。
public class OperateTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
list.add(333);
list.add(444);
list.add(555);
System.out.println(list);
// 设定list2->[1,2,3] 便于测试addAll()
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
// 末尾插入list2
list.addAll(list2);
System.out.println(list);
// 获取list中有效元素个数
System.out.println(list.size());
// 获取和设置index位置上的元素,注意index必须介于[0, size)间
System.out.println(list.get(0));
list.set(0, 999);
System.out.println(list.get(0));
// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
list.add(1, 888);
System.out.println(list);
// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
// (这里如果直接写444会默认下标,需要使用引用类型)
list.remove(new Integer(444));
System.out.println(list);
// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
list.remove(list.size()-1);
System.out.println(list);
// 检测list中是否包含指定元素,包含返回true,否则返回false
if(!list.contains(123456)){
list.add(123456);
System.out.println(list);
}
// 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
System.out.println(list.indexOf(333));
System.out.println(list.lastIndexOf(333));
// 使用list中[0, 4)之间的元素构成一个新的SubList返回,
// 但是和ArrayList共用一个elementData数组(对任一部分修改会影响整个部分)
List<Integer> ret = list.subList(0, 2);
System.out.println(ret);
// 清空
list.clear();
System.out.println(list.size());
}
}
4、ArrayList的遍历
(1) 方式一:
使用for
循环+下标遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
(2) 方式二:
由于List、Arraylist
集合实现了iterable
接口,可以使用增强的for循环-foreach
for (Integer x:list) {
System.out.println(x);
}
(3) 方式三:
对于Java而言只要实现了Iterable
接口的集合就可以使用迭代器 iterator
Iterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.println(it.next());
}
总结
本篇文章主要介绍了 Java 集合中的线性结构 ArrayList
,这是一个相对比较简单,且容易理解和实现的数据结构。这里就简单总结一下顺序表 ArrayList的优缺点:
顺序表缺点:
ArrayList
底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈
倍数
增长。假如以2倍扩容,势必会有一定的空间浪费,例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
顺序表优点:
- 当给定下标的时候,查找速度非常快,复杂度为
O(1)