数据结构与算法
- 线性表(顺序存储)
- 1. 线性表的定义
- 2. List 接口的定义
- 3. 线性表的顺序存储结构
- 3.1 顺序存储定义
- 3.2 ArrayList 的实现
- 3.2.1 ArrayList 的属性
- 3.2.2 ArrayList 的构造器
- 3.2.3 添加元素的 add() 方法
- 3.2.4 移除元素的 remove() 方法
- 3.2.5 扩缩容问题 resize() 方法
- 3.2.6 获取元素下标值的 indexOf() 方法:
- 3.2.7 获取元素方法 get()
- 3.2.8 修改元素方法 set()
- 3.2.9 判断元素是否存在 contains()
- 3.2.10 判空 isEmpty()
- 3.2.11 清空当前元素 clear()
- 3.2.12 排序方法 sort()
- 3.2.13 获取子表 subList()
- 3.2.14 ArrayList 的 equals()
- 3.2.15 ArrayList 的 toString()
- 3.2.16 获取当前容量 getCapacit()
- 3.2.15 获取迭代器 iterator()
- 3.3 ArrayList 总结
线性表(顺序存储)
1. 线性表的定义
大家好,首先还是要先感谢大家来看我的文章。今天,我们要学习的是数据结构中最常用且很简单的一种结构,也就是线性表,它是一个能存储零个或多个数据元素的有限序列。就像我们生活中排队一样,有头有尾,并且除了首尾两个人外,其他的人都知道自己前面是谁,后面是谁(其实就是线性表中前驱后继的概念),如同有一根线把他们串起来,这样的结构我们把它称之为线性表。如下图:
如上图可知线性表有如下特点,ai - 1 是 ai 的直接前驱;ai + 1 是 ai 的直接后继;并且除了第 1 个元素 a1 之外,其他元素都有唯一的直接前驱;除了第 n 个元素 an 之外,其他元素都有唯一的直接后继;n 表示线性表的长度,当 n = 0 时,称为空表。
2. List 接口的定义
可能有朋友不理解,不是要学习线性表吗,怎么又出来一个 List 接口是什么鬼?别急,听我来给大家解释一下。线性表,从名字都可以看出来它是一个线性结构,在上一章我讲逻辑结构的时候有提到,不了解的可以看上一章节。既然线性表是一个线性结构,我们要将这种逻辑结构存储在计算机当中又有什么存储方式呢?这里又牵扯到了我们讲过的物理结构的问题,我们要将这种线性结构存储到计算机中,可以由顺序存储结构和链式存储结构两种物理结构来实现。既然可以有两种不同的实现方式,那么我们可以对二者相同的操作进行抽取,在 Java 语言中,我们可以将这些有多种实现的方法抽象定义在一个接口当中,然后用不同的结构去实现它,因此我们定义 List 接口。下面来分析一下 List 接口都需要哪些功能?
List 接口实现:
首先我们来声明 List 接口,因为我们并不知道存入线性表的数据是什么类型,所以在这里应该应用泛型来进行声明,并且为了使线性表有迭代功能,我们还让 List 接口继承了 Iterable 接口。
public interface List<E> extends Iterable<E> {
......
}
下面我们来分析一下 List 接口所要实现的功能,首先要有添加元素的方法,我们可以默认向表尾添加元素;为了添加更加灵活,我们还可以设置一个方法,可以在指定角标 index 处添加元素。
//向末尾添加元素 element
public void add(E element);
//向指定角标 index 处添加元素 element
public void add(int index, E element);
有了添加元素的方法,当然要有移除元素的方法,我们需要可以直接移除某个元素,也需要能根据用户传入的角标 index 来移除对应位置的元素,并返回移除元素的值。
//移除元素 element
public void remove(E element);
//移除角标 index 处的元素 element 并返回该元素
public E remove(int index);
接下来,我们还需要有获取角标 index 处的元素和修改角标 index 处的元素的方法。
//获取对应角标 index 处的元素
public E get(int index);
//修改对应角标 index 处的元素为 element 并返回原元素
public E set(int index, E element);
然后还需要有获取有效元素个数、获取某元素从左到右第一次出现的位置角标、判断某元素是否存在于该线性表、判断该线性表是否为空、清空该线性表、获取子线性表等方法。
//获取有效元素的个数
public int size();
//获取元素 element 从左到右第一次出现位置的下标值
public int indexOf(E element);
//判断是否存在元素 element
public boolean contains(E element);
//判空
public boolean isEmpty();
//清空
public void clear();
//获取子线性表,从 [fromIndex, toindex) 左闭右开
public List<E> subList(int fromIndex, int toIndex);
最后还要说一下,我们还需要一个为线性表中的元素排序的方法,那么问题来了,如果该线性表中存的是一些数字,我们可以很容易的对它们进行排序;但是如果是其他的引用数据类型,我们需要有自己的排序规则,所以这里定义排序的方法需要引入比较器 Comparator 的概念。
//排序,需要导入java.util.Comparator包
public void sort(Comparator<E> c);
至此,List 接口定义完成,我们来看一下完整的 List 接口。
import java.util.Comparator;
//线性表的接口定义,继承了迭代接口Iterable
public interface List<E> extends Iterable<E> {
//向末尾添加元素 element
public void add(E element);
//向指定角标 index 处添加元素 element
public void add(int index, E element);
//移除元素 element
public void remove(E element);
//移除角标 index 处的元素 element 并返回该元素
public E remove(int index);
//获取对应角标 index 处的元素
public E get(int index);
//修改对应角标 index 处的元素为 element 并返回原元素
public E set(int index, E element);
//获取有效元素的个数
public int size();
//获取元素 element 从左到右第一次出现位置的下标值
public int indexOf(E element);
//判断是否存在元素 element
public boolean contains(E element);
//判空
public boolean isEmpty();
//清空
public void clear();
//排序
public void sort(Comparator<E> c);
//获取子线性表,从 [fromIndex, toindex) 左闭右开
public List<E> subList(int fromIndex, int toIndex);
}
3. 线性表的顺序存储结构
3.1 顺序存储定义
上文提到,线性表有两种物理存储结构,一种是顺序存储结构,另一种是链式存储结构。那么接下来,我们来看一下线性表的顺序存储结构。线性表的顺序存储结构,指用一段地址连续的存储单元依次存储线性表的数据元素。
3.2 ArrayList 的实现
线性表的顺序存储实现出来的,我们也可以称之为顺序表,既然我们要用一段地址连续的存储单元依次存储线性表的数据元素,那么使用数组作为线性表的容器再合适不过了。下面我们来实现顺序表,我们给它起名为 ArrayList,它是用来具体实现线性表的顺序存储结构,因此首先要让它实现 List 接口,我们来看一下 ArrayList 类的定义。
public class ArrayList<E> implements List<E> {
......
}
同上,我们并不知道存入该结构的数据是什么类型,因此它也要支持泛型。ArrayList 类实现了 List 接口,因此要实现 List 接口中的所有方法,又因为 List 接口继承了 Iterable 接口,所以还要实现 iterator() 方法,我们接下来会逐个讲解。
3.2.1 ArrayList 的属性
当然,我们 ArrayList 类需要有自己的属性。首先,作为一个能存储数据的结构,肯定需要容器,这里我们使用数组;有了容器后,我们还需要一个默认容量来构造默认容器;接下来还需要知道该容器中存储的有效元素的个数。
//默认容量为10
private static int DEFAULT_CAPACITY = 10;
//容器,使用数组
private E[] data;
//记录存储的有效元素的个数
private int size;
3.2.2 ArrayList 的构造器
上面我们已经为 ArrayList 定义了自己的属性,下面我们需要定义它的构造方法,首先默认的构造方法用来构造一个默认容量的 ArrayList,还需要一个能够自定义容量的构造器,最后再定义一个可以将简单的一维数组封装成一个 ArrayList 类型的构造器。如下:
默认构造器: 构造一个默认容量的容器,因为没有元素,所以有效元素个数为 0
//默认构造方法,这里使用 this 关键字调用了接收容量的构造方法
public ArrayList() {
this(DEFAULT_CAPACITY);
}
//如果不想使用 this 关键字,如下
public ArrayList() {
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
可指定容量的构造器: 既然可以指定容量,那首先应该判断该容量是否合法,也就是容量不能小于零,然后再去构造一个指定容量的容器,因为没有元素,所以有效元素个数为 0
//自定义容量的构造方法
public ArrayList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("容量不合法!");
}
data = (E[]) new Object[capacity];
size = 0;
}
转换数组的构造器: 首先,还是判断传入的数组是否合法,该数组不能为空。这里会有一个问题,就是我们能不能直接让 ArrayList 的容器 data 直接指向 传入的数组 arr,显然是不合适的,因为如果直接指向该数组,外界对数组进行操作后,我们 ArrayList 内部也发生了变化,肯定不合适。所以,我们应该先对 arr 数组进行备份,让 data 指向拷贝出来的新数组,这样 arr 在外部是否改变就不会影响我们内部了。所以我们新建一个容器,把数组 arr 的元素复制过来即可。
这里又有一个问题,我们新建的这个数组的容量,到底是用 ArrayList 的默认容量还是用数组 arr 的容量呢?其实没有严格规定,但是我认为使用 ArrayList 的默认容量无疑更合适,虽然我们并不知道默认容量能不能够存储数组 arr 中的所有元素,但是我们这里是使用 ArrayList 的 add() 方法将数组 arr 的元素添加到新容器中,add() 方法会自动扩容的,所以我们并不需要担心,至于扩容问题,我们下面会详细说。
//将一个数组封装成一个 ArrayList 类型的构造方法
public ArrayList(E[] arr) {
if (arr == null || arr.length == 0) {
throw new IllegalArgumentException("传入数组不能为空!");
}
data = (E[]) new Object[DEFAULT_CAPACITY];
for (int i = 0; i < arr.length; i++) {
add(arr[i]);
}
}
3.2.3 添加元素的 add() 方法
在表尾添加元素: 在表尾添加元素,就相当于在最后一个元素后面的位置添加,而这个位置的角标恰好等于有效元素 size 的值,所以我们可以直接调用在指定位置添加元素的方法,将 size 和要添加的元素传过去即可。
add(size, element);
在指定位置处添加元素: 既然要在指定的位置添加元素,那我们首先要判断指定的位置是否合法,这个位置不能是负值,也不能超过 size;然后要考虑添加该元素前,容器是否满了,如果满了我们就需要扩容,这里我们约定将容器容量扩为原先的二倍,扩容操作在下面会详细说,这里只要知道需要扩容就可以。判断完之后我们才可以将该元素添加进去,添加之后有效元素加一。
添加元素的过程,首先确定添加的位置后,让该位置后包括该位置的元素,从表尾开始依次后移一位,然后把元素插入到需要插入的位置。在移动元素的循环中,我们让 i 从 size 或者 size - 1 开始都可以,后面的操作稍作改动即可。请看图:
扩容过程如下:
public void add(int index, E element) {
//判断位置是否合法
if (index < 0 || index > size) {
throw new IllegalArgumentException("添加位置不合法!");
}
//判断容器是否是满的,如果是满的需要扩容
if (size == data.length) {
resize(data.length * 2);
}
//将该位置后的含该位置的所有元素后移,然后将该元素添加进去,有效元素加一
for (int i = size; i > index; i--) {
data[i] = data[i - 1];
}
data[index] = element;
size++;
}
3.2.4 移除元素的 remove() 方法
移除某个元素: 因为要移除一个元素,我们首先应该确定这个元素的位置,然后再去移除该元素,因此引出删除指定角标 index 处的元素的方法,和确定某个元素从左到右第一次出现的位置的方法。
public void remove(E element) {
int index = indexOf(element);
if (index != -1)
remove(index);
}
}
移除指定角标处元素: 首先,判断传入角标是否合法,因为要移除某个有效元素,因此角标的范围应该是大于等于零、小于有效元素个数 size 的。然后,再将 index 位置的元素暂存在 ret 中,因为最后要返回该元素;接下来直接将 index 位置后面的元素依次向前移动一位,然后让有效元素个数 size 减一即可。如下图:
注意,在上面讲添加元素的方法时,我们在添加之前要考虑容量是否需要扩容问题;那么在移除元素的方法中我们也应该考虑缩容的问题,缩容操作应该是在元素成功移除之后。
缩容问题,我们需要在什么情况下进行缩容呢?我们知道,在上面的扩容问题中,我们每次将容器容量扩为原先的二倍,那么我们在有效元素个数为容量的一半的时候是不是也将容量缩为原先的一半呢?这显然是不合适的,因为这时候虽然容量缩为原先的一半了,但是容器是满的,如果接下来是添加元素的操作,那么又要进行扩容,频繁的扩缩是会影响效率的;所以我们可以在有效元素个数为容量的四分之一的时候,将容量缩为原先的一半,这样就解决了顾虑。对于扩缩容问题,并不非要按照我的方法去写,我这只是一种思路,合理即可。对于改变容量的方法 resize() 会在下面详细说明。如下图:
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("删除位置不合法!");
}
E ret = data[index];
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
size--;
if (size < data.length / 4 && data.length > DEFAULT_CAPACITY) {
resize(data.length / 2);
}
return ret;
}
3.2.5 扩缩容问题 resize() 方法
上文中提到的 resize() 方法来了,不管是扩容还是缩容,都是要重新改变容量,因此我们可以抽取出该方法,只要传入新容量即可。最简单粗暴的方法就是重新定义一个容器,其容量就是传入的新容量,然后将原容器中的有效元素复制过来,最后让 data 重新指向这个新的容器即可。具体细节可以查看 add() 和 remove() 中的动图。
private void resize(int newLength) {
E[] newData = (E[]) new Object[newLength];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
3.2.6 获取元素下标值的 indexOf() 方法:
该方法在移除某元素的方法中用到了,在这里给大家讲解一下,由于我们并不知道容器中有多少个该元素,所以我们只返回该元素从左到右第一次出现的位置对应的下标 index。我们对有效元素进行遍历,寻找是否存在当前元素,如果存在则返回其角标,不存在返回 -1 表示异常。
这里比较的时候,我们选择 equals() 方法,为什么不用 “==” 呢?因为我们并不确定存入线性表中的元素类型,如果是基本数据类型,我们还可以正常比较;但如果是对象的引用,我们当然应该去比较指向对象的内容是否相同,但是双等号在比较引用数据类型的时候,比较的是引用指向的地址,这显然是不合适的,我们应该用 equals() 方法进行比较。如下图:
在这里多说一句,equals() 方法是 Object 类的方法,而 Object 类中 equals() 方法底层实现其实还是双等号;因此我们需要在存储的元素所属的类中重写 equals() 方法,如果重写了,则根据自己重写的规则去判断两个对象是否相同,否则还是调用基类 Object 中的 equals() 方法。
public int indexOf(E element) {
for (int i = 0; i < size; i++) {
if (element.equals(data[i])) {
return i;
}
}
return -1;
}
3.2.7 获取元素方法 get()
由于该方法是根据传入的位置,来获取该位置的元素,所以我们应该先判断传入的角标 index 是否合法。如果 index 小于零或者大于等于有效元素个数 size,则不合法。判断完成后直接返回该位置的元素即可。
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("获取位置不合法!");
}
return data[index];
}
3.2.8 修改元素方法 set()
该方法是用来修改指定角标 index 处的元素,并返回原先元素。同上,首先我们还是应该去判断要修改的位置是否合法,然后将要修改的值暂存入 ret 中,然后将传进来的元素赋给该位置,最后返回 ret 的值即可。
public E set(int index, E element) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("修改位置不合法!");
}
E ret = data[index];
data[index] = element;
return ret;
}
3.2.9 判断元素是否存在 contains()
这个比较简单,由于我们已经写过寻找元素的位置,那么只要能找到该元素的位置,即存在,所以直接判断 indexOf() 方法的返回值即可。
public boolean contains(E element) {
return indexOf(element) != -1;
}
3.2.10 判空 isEmpty()
直接判断有效元素个数是否为零即可。
public boolean isEmpty() {
return size == 0;
}
3.2.11 清空当前元素 clear()
简单粗暴的写法,直接创建一个新的容器,然后让 data 指向该容器,size 清零即可。
public void clear() {
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
3.2.12 排序方法 sort()
常见的排序算法有很多种,我们这里使用了插入排序的算法,判断大小的规则由传入的比较器决定。既然要使用比较器进行比较,首先应该判断比较器是否存在,然后是插入排序的逻辑。定义一个变量 i 来遍历有效元素,定义变量 e 来记录当前 i 位置的元素值,定义变量 j 用来记录需要比较位置的值,根据元素大小调整位置。i 从第二个元素开始,将 i 位置的值赋给 e,然后令 j = i 比较 j - 1和 e 的值的大小,并且 j 要大于零,不然 j - 1 会报错。如果 j - 1 的值大于 e 的值,则将 j - 1 的值覆盖 j 位置的值,全部覆盖完毕后,将 e 赋值给 j 位置。这样是升序排序,规则可以自己定。文字描述起来比较抽象,下面给大家画个图。
public void sort(Comparator<E> c) {
if (c == null) {
throw new IllegalArgumentException("比较器非法!");
}
int j = 0;
E e = null;
for (int i = 1; i < size; i++) {
e = data[i];
for (j = i; j > 0 && c.compare(data[j - 1], e) > 0; j--) {
data[j] = data[j - 1];
}
data[j] = e;
}
}
3.2.13 获取子表 subList()
该方法是获取从 fromIndex 开始到 toIndex 结束但不包括 toIndex 位置(左闭右开)的子线性表。首先我们应该判断起始位置和结束位置是否合法,起始位置不能小于 0,结束位置不能大于 size(因为是左闭右开),并且起始位置不能大于结束位置。然后创建子线性表的容器,将范围内获取出的元素添加到子线性表中,最后返回子线性表即可。如下图:
public List<E> subList(int fromIndex, int toIndex) {
if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) {
throw new IllegalArgumentException("范围不合法!");
}
List<E> list = new ArrayList<E>();
for (int i = fromIndex; i < toIndex; i++) {
list.add(data[i]);
}
return list;
}
3.2.14 ArrayList 的 equals()
该方法是重写基类 Object 的,是用来判断两个 ArrayList 内容是否相同。首先判断传入对象是否为空,然后判断传入的是否是当前对象本身,然后判断 o 是否指向一个 ArrayList 类型的对象,如果是的话,判断两个 ArrayList 的有效元素个数是否相同,不同直接返回 false,如果有效元素个数相同,则应该比较存储的元素是否相同,用 for 循环遍历,比较元素时也应该使用当前元素自己的 equals() 方法,因为我们比那个不知道存储的对象是什么类型的,如果存在不相同的元素,直接返回 false,不存在返回 true,排除上面所有条件后,最后返回 false 即可。
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof ArrayList) {
ArrayList<E> other = (ArrayList<E>) o;
if (size != other.size) {
return false;
}
for (int i = 0; i < size; i++) {
if (!data[i].equals(other.data[i])) {
return false;
}
}
return true;
}
return false;
}
3.2.15 ArrayList 的 toString()
该方法也是重写基类 Object 的方法,可以以自定义格式输出该对象的信息。我们因为要频繁操作字符串,我们这里使用 StringBuilder,首先拼接一个 “[”,然后判断表是否为空,如果为空直接拼 “]”,否则遍历所有有效元素并拼接,如果是最后一个元素则拼接 “]”,否则拼接 “,” 即可。最后返回 sb.toString() 即可。
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
if (isEmpty()) {
sb.append(']');
} else {
for (int i = 0; i < size; i++) {
sb.append(data[i]);
if (i == size - 1) {
sb.append(']');
} else {
sb.append(',');
sb.append(' ');
}
}
}
return sb.toString();
}
3.2.16 获取当前容量 getCapacit()
该方法没什么多说的,就是获取容器当前的容量,我们直接返回底层数组的长度即可。
private int getCapacity() {
return data.length;
}
3.2.15 获取迭代器 iterator()
该方法用来获取 ArrayList 的迭代器,可以让该类型支持 foreach 循环,既然要获取迭代器,那么我们还需要创建一个构造 ArrayList 类的迭代器的类 ArrayListIterator,实现迭代器接口 Iterator,定义私用属性 cur 从 0 开始,用来记录迭代位置。然后重写 hashNext() 和 next() 方法,前者用来判断是否存在下一个元素,后者用来获取该元素。如下图:
public Iterator<E> iterator() {
return new ArrayListIterator();
}
class ArrayListIterator implements Iterator<E> {
private int cur = 0;
@Override
public boolean hasNext() {
return cur < size;
}
@Override
public E next() {
return data[cur++];
}
}
3.3 ArrayList 总结
至此,ArrayList 就实现完成了,大家可以自行测试,整体代码如下:
import 自己 List 接口的包
import java.util.Comparator;
import java.util.Iterator;
public class ArrayList<E> implements List<E> {
private static int DEFAULT_CAPACITY = 10;
private E[] data;
private int size;
public ArrayList() {
this(DEFAULT_CAPACITY);
}
public ArrayList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("容量不合法!");
}
data = (E[]) new Object[capacity];
size = 0;
}
public ArrayList(E[] arr) {
if (arr == null || arr.length == 0) {
throw new IllegalArgumentException("传入数组不能为空!");
}
data = (E[]) new Object[DEFAULT_CAPACITY];
for (int i = 0; i < arr.length; i++) {
add(arr[i]);
}
}
@Override
public void add(E element) {
add(size, element);
}
@Override
public void add(int index, E element) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("添加位置不合法!");
}
if (size == data.length) {
resize(data.length * 2);
}
for (int i = size; i > index; i--) {
data[i] = data[i - 1];
}
data[index] = element;
size++;
}
private void resize(int newLength) {
E[] newData = (E[]) new Object[newLength];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
@Override
public void remove(E element) {
int index = indexOf(element);
if (index != -1) {
remove(index);
}
}
@Override
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("删除位置不合法!");
}
E ret = data[index];
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
size--;
if (size < data.length / 4 && data.length > DEFAULT_CAPACITY) {
resize(data.length / 2);
}
return ret;
}
@Override
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("获取位置不合法!");
}
return data[index];
}
@Override
public E set(int index, E element) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("修改位置不合法!");
}
E ret = data[index];
data[index] = element;
return ret;
}
@Override
public int size() {
return size;
}
@Override
public int indexOf(E element) {
for (int i = 0; i < size; i++) {
if (element.equals(data[i])) {
return i;
}
}
return -1;
}
@Override
public boolean contains(E element) {
return indexOf(element) != -1;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void clear() {
data = (E[]) new Object[DEFAULT_CAPACITY];
size = 0;
}
@Override
public void sort(Comparator<E> c) {
if (c == null) {
throw new IllegalArgumentException("比较器非法!");
}
int j = 0;
E e = null;
for (int i = 1; i < size; i++) {
e = data[i];
for (j = i; j > 0 && c.compare(data[j - 1], e) > 0; j--) {
data[j] = data[j - 1];
}
data[j] = e;
}
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) {
throw new IllegalArgumentException("范围不合法!");
}
List<E> list = new ArrayList<E>();
for (int i = fromIndex; i < toIndex; i++) {
list.add(data[i]);
}
return list;
}
@Override
public Iterator<E> iterator() {
return new ArrayListIterator();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof ArrayList) {
ArrayList<E> other = (ArrayList<E>) o;
if (size != other.size) {
return false;
}
for (int i = 0; i < size; i++) {
if (!data[i].equals(other.data[i])) {
return false;
}
}
return true;
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
if (isEmpty()) {
sb.append(']');
} else {
for (int i = 0; i < size; i++) {
sb.append(data[i]);
if (i == size - 1) {
sb.append(']');
} else {
sb.append(',');
sb.append(' ');
}
}
}
return sb.toString();
}
//获取线性表的容量
private int getCapacity() {
return data.length;
}
class ArrayListIterator implements Iterator<E> {
private int cur = 0;
@Override
public boolean hasNext() {
return cur < size;
}
@Override
public E next() {
return data[cur++];
}
}
}