模拟实现顺序表ArrayList
一、定义接口
无论是顺序表还是链表,它们都是线性表,都需要进行增删改查操作。
所以首先,定义一个线性表接口List,包含线性表的操作
/**
* 线性表接口
* 和存储结构无关(顺序表,链表)
*/
public interface List
{
//返回线性表的大小,即元素的个数
public int size();
//返回线性表中序号为i 的数据元素
public Object get(int i);
//如果线性表为空,返回true,否则返回false
public boolean isEmpty();
//判断线性表中是否包含数据元素e
public boolean contains(Object e);
//返回数据元素e 在线性表中的序号
public int indexOf(Object e);
//将数据元素e 插入到线性表中i 号位置
public void add(int i, Object e);
//将数据元素e 插入到线性表末尾
public void add(Object e);
//将数据元素e 插入到元素obj 之前
public boolean addBefore(Object obj, Object e);
//将数据元素e 插入到元素obj 之后
public boolean addAfter(Object obj, Object e);
//删除线性表中序号为i 的元素,并返回之
public Object remove(int i);
//删除线性表中第一个与e 相同的元素
public boolean remove(Object e);
//替换线性表中序号为i 的数据元素为e, 返回原数据元素
public Object replace(int i, Object e);
}
二、创建顺序表类ArrayList并实现接口List
1、顺序表底层采用的是数组,长度可以动态变化
private Object[] elementData; //底层是一个数组,目前还没有确定长度
private int size; //不是数组分配了几个空间,而是数组中元素的个数
2、编写构造方法指定数组初始长度
public ArrayList(int initalCapacity) {
//给数组分配指定数量的空间
elementData = new Object[initalCapacity];
//指定顺序表的元素个数,默认是0
// size = 0;
}
public ArrayList() {
//没有指定长度,默认长度是4
this(4);
}
以上代码写完,就可以创建顺序表了,但是里面的操作方法还没有实现
3、实现add方法,将数据元素e 插入到线性表中i 号位置,代码如下
public void add(int i, Object e) {
//i 的位置要正确
if (i<0 || i > size){
throw new MyArrayIndexOutOfBoundsException("数组索引越界异常:"+i);
}
//数组元素个数等于数组长度时,需要扩容
if(size == elementData.length){
//扩容方法
grow();
}
//后移i 及其后面的元素,从最后一个元素开始
for (int j = size; j > i ; j--) {
elementData[j] = elementData[j-1];
}
//给数组第i 个位置赋值
elementData[i] = e;
//数组元素个数+1
size++;
}
首先判断索引是否正确,不正确就抛出一个自定义异常
public class MyArrayIndexOutOfBoundsException extends RuntimeException {
public MyArrayIndexOutOfBoundsException() {
}
public MyArrayIndexOutOfBoundsException(String message) {
super(message);
}
}
然后当数组元素个数等于数组长度时,调用grow()方法进行扩容,grow()方法有两种实现
//基本思路是这样
private void grow(){
//创建一个新数组,长度是旧数组2倍
Object[] newArr = new Object[elementData.length * 2];
//将旧数组数据拷贝到新数组
for (int i = 0; i < size; i++) {
newArr[i] = elementData[i];
}
//让旧数组 指向 新数组
elementData = newArr;
}
//一步到位
private void grow(){
//扩容 拷贝 赋值
elementData = Arrays.copyOf(elementData, elementData.length * 2);
}
最后实现添加操作:需要从数组尾部元素开始,将第i 位及其之后的元素向后移一位,然后把e 赋值给第i 个位置的元素,最后数组元素个数+1
4、把元素添加到线性表末尾,是add()方法的特殊情况
public void add(Object e) {
this.add(size, e);
}
5、get()方法,返回线性表中序号为i 的元素,即数组中下标为i 的元素
public Object get(int i) {
if (i < 0 || i >= size) {//i < 0 或者 i >= size
throw new MyArrayIndexOutOfBoundsException("数组索引越界异常:" + i);
}
return elementData[i];
}
判断索引是否正确,错误抛出自定义异常
6、remove(int i)方法,删除线性表中序号为i 的元素,并返回之
public Object remove(int i) {
if (i < 0 || i >= size) {
throw new MyArrayIndexOutOfBoundsException("数组越界异常:"+ i);
}
Object empt = elementData[i];
for (int j = i; j < size; j++) {
elementData[j] = elementData[j+1];
}
elementData[size-1] = null;
size--;
return empt;
}
三、重写toString()方法
public String toString() {
if (size == 0){
return "[]";
}
StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < size; i++) {
if (i != size - 1){
builder.append(elementData[i]+",");
}else {
builder.append(elementData[i]);
}
}
builder.append("]");
return builder.toString();
}
扩展
StringBuffer和StringBuilder非常类似,均代表可变的字符序列。 这两个类都是抽象类AbstractStringBuilder的子类,方法几乎一模一样。
StringBuffer 线程安全,做线程同步检查, 效率较低。
StringBuilder 线程不安全,不做线程同步检查,因此效率较高。 建议使用该类。
四、核心
数组扩容、元素前移、后移
ArrayList源码中扩容为50%
int newCapacity = oldCapacity + (oldCapacity >> 1);