一、什么是顺序表?
顺序表是一种线性存储结构(线性表),其是由一段物理地址连续的存储单元构成的,通常我们采用数组存储,在数组上完成数据的增删改查。
如下图所示:
二、模拟实现ArrayList
-
创建MyArrayList类,并创建类中元素
//使用泛型类,方便存放各种类型的值
public class MyArrayList<T> {
//定义一个静态常量为默认数组初始大小
private static final int DEFAULT_CAPACITY = 10;
//创建一个Object数组
private Object[] element = new Object[DEFAULT_CAPACITY];
//定义一个计数器,方便知道顺序表中已经存放了多少个值
private int usedSize;
}
-
建立构造方法
//有参构造方法,自定义初始容量
public MyArrayList(int initCapacity){
element = new Object[initCapacity];
}
//无参构造方法,初始容量为默认值
public MyArrayList(){
}
-
插入元素(add)
//1、尾插
public void add(T t){
//判断数组是否已满,已满则扩容
if(isFull()){
expandCapacity();
}
//插入元素,计数器加1
element[usedSize] = t;
usedSize++;
}
//2、在指定位置插入元素
public void add(int index, T t){
//判断下标是否越界,越界则引发异常
if(index > usedSize || index < 0){
new IndexOutOfBoundException(index + "下标越界");
}
//判断数组是否已满,已满则扩容
if(isFull()){
expandCapacity();
}
//将需插入下标后的元素后移,再放入待添加元素
for (int i = usedSize - 1; i >= index; i--) {
element[i + 1] = element[i];
}
element[index] = t;
usedSize++;
}
-
获取指定下标元素(get)
//获取所给下标的元素
public T get(int index){
//判断下标是否合法,不合法则引发下标越界异常
if(index >= usedSize || index < 0){
new IndexOutOfBoundException(index + "下标越界");
}
//返回该元素
return (T)element[index];
}
-
更改指定下标元素(set)
//更改指定下标的元素
public void set(int index, T t){
//判断下标是否合法,不合法则引发异常
if(index >= usedSize || index < 0){
new IndexOutOfBoundException(index + "下标越界");
}
//更改该下标元素
element[index] = t;
}
-
判断是否包含指定元素(contain)
//判断是否包含指定元素
public boolean contain(T t){
//for循环逐个对比
for (int i = 0; i < usedSize; i++) {
//找到了返回true
if(element[i].equals(t)){
return true;
}
}
//没找到则返回false
return false;
}
-
获取指定元素下标(IndexOf)
//获取第一个与指定元素相同的元素的下标
public int indexOf(T t){
//循环对比
for (int i = 0; i < usedSize; i++) {
//找到则返回该元素下标
if(element[i].equals(t)){
return i;
}
}
//没找到返回-1(因为-1不可能是任何元素的下标)
return -1;
}
-
移除元素(remove)
//1、去除指定下标元素
public void remove(int index){
//判断下标是否越界,越界则引发下标越界异常
if(index >= usedSize || index < 0){
new IndexOutOfBoundException(index + "下标越界");
}
//将所去除元素后的元素后移,填补空缺
for (int i = index + 1; i < usedSize; i++) {
element[i - 1] = element[i];
}
//将最后下标位置的值置为null,并将计数器-1
element[usedSize - 1] = null;
usedSize--;
}
//2、去除与指定元素相同的第一个元素
public void remove(T t){
//利用indexOf方法找到其下标
int index = indexOf(t);
//若无相同元素则不做任何操作
if(index == -1){
return;
}
//若找到则使用第一个remove去除该元素
remove(index);
}
-
返回已存元素个数(size)
public int size(){
//返回计数器usedSize
return usedSize;
}
-
清空顺序表(clear)
public void clear(){
//循环将所有已存元素置为null
for (int i = 0; i < usedSize; i++) {
element[i] = null;
}
//将计数器置为0
usedSize = 0;
}
-
重写toString方法
//方便可以使用print直接将顺序表打印出来
@Override
public String toString() {
//设置空字符串ret
String ret = "";
//循环将每个元素字符化后添入ret中
for (int i = 0; i < size(); i++) {
if (i == 0){
ret = ret + element[i].toString();
}else {
ret = ret + ", " + element[i].toString();
}
}
//返回ret
return "[" + ret + "]";
}
注意:此处为顺序表的模拟实现,并非与ArrayList类一致。
三、ArrayList的优势和缺陷
-
优势
在获取或更改某个元素时,可以直接使用下标访问,时间复杂度为O(1)
-
缺陷
- 当插入或删除某个元素时,需要将后续所有元素前移或后移,故时间复杂度为O(n)。
- 当扩容时,需要申请新空间,拷贝数据,释放旧空间,会有不少的消耗。
- 一般扩容是以2倍或1.5倍扩容,每次add的个数不多,一般会有多余空间浪费。