数组的自实现ArrayList
一、数组介绍
- 线性表是最基本、最简单、也是最常用的一种数据结构,一个线性表是n个具有相同特性的数据元素的有序数列。其中数组就是一种顺序表,顺序表存储是将数据元素放到一块连续的内存存储空间,相邻数据元素的存放地址也相邻。 生活中的线性表:火车,糖葫芦
- 数组是一种顺序存储的线性表,可以存储多个值,每个元素可以通过索引进行访问,所有元素的内存地址是连续的。
1.Java中数组的三种声明
- 数组类型 [] 数组名 = {em1,em2,em3,…,emN}; 声明数组的时候初始化,一共N个元素,例如:
int[] array = {3,5,4,8,12,5};//一共六个元素
- 数组类型[] 数组名 = new 数组类型[N] 用new关键字声明数组的同时指定数组长度,例如:
String[] str = new String[6]; //数组长度为6,即数组有六个元素
- 数组类型[] 数组名 = new 数组类型[] {em1,em2,em3,…,emN}; 用new关键字声明数组的同时初始化数组,例如:
int[] array = new int[] {2,4,5,6,8,9}; //array数组一共五个元素
注:数组一旦声明,数组长度就已经确定。每个数组都有一个length属性,不可改变。可以改变数组元素。
2. 优点
- 空间利用率高。
- 查询速度高效,通过下标来直接存取。
3. 缺点
- 插入和删除比较慢,比如:插入或者删除一个元素时,整个表需要遍历移动元素来重新排一次顺序。
- 不可以增长长度,有空间限制,当需要存取的元素个数可能多于顺序表的元素个数时,会出现"溢出"问题。当元素个数远少于预先分配的空间时,空间浪费巨大。
二、ArrayList自实现(动态数组)
1. 需求
- 底层采用数组 实现动态扩容数组(ArrayList)
2. 面对的问题
- 数组一旦创建,容量不可变
- 扩容的条件
3.动态数组接口设计
intsize();// 元素的数量
booleanisEmpty();// 是否为空
booleancontains(E element);// 是否包含某个元素
voidadd(E element);// 添加元素到最后面
Eget(intindex);// 返回index位置对应的元素
Eset(intindex,E element);// 设置index位置的元素
voidadd(intindex,E element);// 往index位置添加元素
Eremove(intindex);// 删除index位置对应的元素
intindexOf(E element);// 查看元素的位置
voidclear();// 清除所有元素
4. 具体实现
/**
* 动态可变数组 自动扩容
*/
public class DynamicArray<E> {
private int size = 0;//保存当前元素长度
//定义默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
//查找失败返回值
private static final int ELEMENT_NOt_FOUND = -1;
//用于保存数组元素
private E[] elements = (E[]) new Object[DEFAULT_CAPACITY];
public DynamicArray() {
this(DEFAULT_CAPACITY);
}
/**
* 带参初始化
*
* @param capacity 初始化容量
*/
public DynamicArray(int capacity) {
if (capacity < 10){
elements = (E[]) new Object[DEFAULT_CAPACITY];
} else {
elements = (E[]) new Object[capacity];
}
}
/**
* 检查索引越界
*
* @param index 当前访问索引
*/
private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界" + "允许范围 size:0
=> " + (size - 1) + " 当前索引:" + index);
}
}
/**
* 检查添加索引越界
*
* @param index 添加位置的索引
*/
private void checkAddIndex(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("索引越界" + "允许范围 size:0
=> " + (size) + " 当前索引:" + index);
}
}
/**
* 确保数组容量够用
*/
private void ensureCapacity() {
//扩容1.5倍
E[] newElements = (E[]) new Object[elements.length +
(elements.length >> 1)];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
}
//无参构造方法
public DyTest() {
}
/**
* 带参初始化
*
* @param capacity 初始化容量
*/
public DyTest(int capacity) {
if (capacity < 10) {
elements = (E[]) new Object[DEFAULT_CAPACITY];
} else {
elements = (E[]) new Object[capacity];
}
}
/**
* 返回当前元素的数量
*
* @return 当前元素的个数
*/
public int size() {
return size;
}
/**
* 当前数组是否为空
* 空:true
* 非空:false
*
* @return 返回true | false
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 是否包含某个元素
*
* @param element
* @return 返回true | false
*/
public boolean contains(E element) {
if (element == null) {
for (int i = 0; i < size; i++) {
if (elements[i] == null) return true;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) return true;
}
}
return false;
}
/**
* 添加元素到尾部
*
* @param element 待添加的元素
*/
public void add(E element) {
if (size > elements.length - 1) {
ensureCapacity();
}
elements[size++] = element;
}
/**
* 返回对应索引的值 不存在返回-1
*
* @param index 元素的索引
* @return 对应值 | -1
*/
public E get(int index) {
checkIndex(index);
return elements[index];
}
/**
* 设置index位置元素的值
*
* @param index 需要设置的位置索引
* @param element 设置的值
* @return 返回原先的值
*/
public E set(int index, E element) {
checkIndex(index);//检查索引越界
E old = elements[index];
//
elements[index] = element;
return old;
}
/**
* 向index位置添加元素
*
* @param index 插入位置的索引
* @param element 插入的元素
*/
public void add(int index, E element) {
checkAddIndex(index);//检查索引越界
// 从最后一个往右移是为了避免左边的数据被覆盖
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];//把元素右移
}
elements[index] = element;
size++;
}
/**
* 移除index位置元素
*
* @param index 被移除元素的索引
* @return 返回原先值
*/
public E remove(int index) {
checkIndex(index);
E old = elements[index];
//从移除位置的下一个元素开始,把元素往左移
for (int i = index; i < size; i++) {
elements[i] = elements[i + 1];
}
elements[--size] = null;//清空最后一个元素
return old;
}
/**
* 查找元素
*
* @param element 需要查找的元素(注意)可能为null
* @return 返回该元素索引 | -1
*/
public int indexOf(E element) {
if (element == null) {
for (int i = 0; i < size; i++) {
if (elements[i] == null) {
return i;
}
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) {
return i;
}
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 清空所有元素
*/
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
}
/**
* 返回元素集合size:5, [1, 3, 4 ,5 ,7 ]
*
* @return
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("size:" + size + " => [");
for (int i = 0; i < size; i++) {
if (i != 0) {
sb.append(" ,");
}
sb.append(elements[i]);
}
sb.append("]");
return sb.toString();
}
}
## 代码解释说明:
-
代码第4行尖括号表示泛型,声明当前编写的这个类的类型,E代表宽泛的任意类型
-
代码第7行默认开辟10个容量,是因为Java底层也是开的10个,有需要的可以自己改大或者改小
-
代码第11行(E[]) 表示将类型转换为泛型
-
最好保留无参初始化,因为当后期重新new一个的时候,没有参数用起来很麻烦;无参和带参初始化,这叫做重载,这是面向对象的一个重要特征。
-
在设计是否包含某个元素的时候,做了一个是否为null的判断,因为在java中数组是可以存储null值的,即允许为空,所以当如果要查找的元素是null,就需要进行判断;
-
注意,==和equals是有区别的
- == 对于基本类型来说,就是比较其值是否相等;
例如 a=1,b=1,那么a==b的值为true
- == 对于引用类型来说,就是比较对象内存地址存储的值是否相等;
例如 a、b都为一个对象,其中都包含属性name,a.name = “张三”;b.name = “李四”;那么a.name == b.name 的值为false
- equals方法也是比较对象内存地址是否相等,但由于可以重写,所以比==使用起来更为宽泛
-
实现add在尾部添加元素方法时,要注意判断扩容条件,当 size > element.length - 1 就需要进行扩容。扩容一定是重新开辟一块新的空间,而不是在原来的空间里进行操作。
-
elements.length + (elements.length >> 1)式中;>> 1代表除以2的意思,就是转化为2进制后向右移一位,相当于除以2(箭头向左是除以2的意思,运算速度和效率比使用乘法除法快,一个小技巧罢了),就相当于 elements.length 的0.5倍;前面再加上一个,所以总共是1.5;相当于扩容1.5倍;
-
实现add向index位置添加元素时,重新判断 index必须 > size,而不是 >= size;且是是从最后一个元素向右移,而不是从index位置开始向右移,因为这样会覆盖
-
remove方法中可以考虑缩容,缩容的条件可以自己写,例如当前元素的个数小于数组长度的一半,就进行缩容,但缩容不是必须的;
-
toString方法上方的@Override是重写的意思,因为在Java底层已经提供了一个toString方法,但如果你想改变输出的格式,可以自己写一个方法,用@Override告诉计算机覆盖掉原来的方法,使用自己重写的方法
-
重写的toString方法中调用了Java提供的StringBuilder的API,用来字符串拼接
-
最后的toString方法没有放在接口里面。
三、刷一道力扣题吧~
解析:
- 这题比较简单,有很多种方法,这里写一种我觉得效率比较高的方法:使用双指针,如果遇到需要写双层循环的时候,就可以考虑双指针;
- 大概思路就是,有两个指针 i 和 j ,它们同时往前移动,当指针 i 遇到数字0的时候,就将第 i 个元素的值赋给第 j 个,然后再继续往前走,直到最后一个元素。
- 代码如下:
四、总结
- 线性表数组算是数据结构中最简单和常见的一种结构了,后面还会有更多简单高效的数据结构出现。
- 巩固自己的同时也希望可以帮到大家~