动态数组原理实现
- 一、数组(Array)
- 二、动态数组
- 三、动态数组的设计
- 四、动态数组的实现
- 1. 添加元素
- 2. 数组扩容
- 3. 删除元素
- 4. 数组缩容
- 5. 清空元素
- 6. 修改元素
- 7. 查询元素
- 8. 插入元素
- 9. 查看元素位置
- 10. 是否包含某个元素
- 11. 元素的数量
- 12. 数组是否为空
- 13. 动态数组打印
- 附上动态数组源码
一、数组(Array)
- 数组是一种顺序存储的线性表,所有元素的内存地址都是连续的。
int[] array = new int[]{11,22,33};
在很多编程语言中,数组有个致命的缺点,无法动态修改容量;
实际开发中,我们希望数组的容量是动态变化的。
二、动态数组
- 可以通过数组实现一个动态数组,动态数组的容量是动态变化的
- 可以对动态数组进行增删改查等操作
// 元素的数量
int size();
// 是否为空
boolean isEmpty();
// 是否包含某个元素
boolean contains(E element);
// 添加元素到最后面
void add(E element);
// 返回index位置对应的元素
E get(int index);
// 设置index位置的元素
E set(int index, E element);
// 往index位置添加元素
void add(int index, E element);
// 删除index位置对应的元素
E remove(int index);
// 查看元素的位置
int indexOf(E element);
// 清除所有元素
void clear();
三、动态数组的设计
- 创建类 ArrayList 如下图,创建 size 属性来管理数组中元素的个数,创建 elements 属性来管理存取的数据。
public class ArrayList<E> {
private int size;
private E[] elements;
}
- 添加初始化方法,创建 elements 数组,并指定 elements 默认的容量
public class ArrayList<E> {
private int size;
private E[] elements;
// 设置elements数组默认的初始化空间
private static final int CAPACITY_DEFAULT = 10;
public ArrayList(int capacity) {
capacity = capacity < CAPACITY_DEFAULT ? CAPACITY_DEFAULT : capacity;
elements = (E[]) new Object[capacity];
}
// 默认情况
public ArrayList() {
this(CAPACITY_DEFAULT);
}
}
四、动态数组的实现
1. 添加元素
- 我们知道,每当数组添加新元素时,都会在数组最后一个元素的后面添加新元素;
- 这个 新元素需要添加到的索引 等于 当前数组元素的个数,在ArrayList 中 size 属性就是 当前数组元素的个数,所以就是将新元素添加到数组的 size 位置上,然后 size 加 1。
public void add(E element) {
elements[size] = element;
size++;
}
2. 数组扩容
- 由于数组elements最大的容量只有10, 所以当数组存满元素时, 就需要对数组进行扩容
- 因为数组是无法动态扩容的,所以需要创建一个新的数组,这个数组的容量要比之前数组的容量大
- 然后在将原数组中的元素存放到新数组中, 这样就实现了数组的扩容
// 扩容
private void ensureCapacity(){
// 获取数组当前容量(长度)
int oldLength = elements.length;
// 如果 当前存储的元素个数(size) < 当前数组容量,直接返回
if(size < oldLength){
return;
}
// 新数组的容量为原数组容量的1.5倍
int newCapacity = oldLength + (oldLength >> 1);
// 创建新数组
E[] newElements = (E[]) new Object[newCapacity];
//原数组中的元素存储到新数组中
for(int i = 0; i < size; i++){
newElements[i] = elements[i];
}
// 引用新数组(新数组的索引要指向旧数组的索引)
elements = newElements;
}
- 此时,add 方法的实现如下,在添加新元素之前,先判断是否需要扩容
public void add(E element){
// 添加新元素之前,先判断是否需要扩容
ensureCapacity();
elements[size] = element;
size++;
}
3. 删除元素
- 删除元素,实际上就是去掉 指定位置的元素,并将后面的元素 向前移动
- 如下图,当删除索引为 2 的元素,只需要将后面的元素向前面移动,然后去掉最后一个元素,size 减 1 即可。
public E remove(int index){
// 取出需要删除的元素
E element = elements[index];
// 通过循环,将index后面的所有元素向前移动一位
for(int i = index; i < size - 1; i++){
elements[i] = elements[i+1];
}
// 删除最后一个元素
elements[--size] = null;
// 将删除的元素返回
return element;
}
- 注意:删除元素时传入的索引不能越界,即 不能小于0,也不能大于等于size
- 所以,我们在删除元素之前需要先进行索引检查
private void rangCheck(int index){
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
}
- 此时,remove方法实现如下
public E remove(int index){
// 判断索引是否越界
rangCheck(index);
// 取出需要删除的元素
E element = elements[index];
// 通过循环,将index后面的所有元素向前移动一位
for(int i = index; i < size - 1; i++){
elements[i] = elements[i+1];
}
// 删除最后一个元素
elements[--size] = null;
// 将删除的元素返回
return element;
}
4. 数组缩容
- 当数组中的元素删除后,数组剩余的空间可能会很大,这样就会造成内存的浪费
- 所以,当数组中元素删除后,我们需要对数组进行缩容
- 实现方法类似于扩容,当数组中容量小于某个值时,创建新的数组,然后将原有的数组中的元素存入新数组即可
// 缩容
private void trim(){
// 获取当前数组的容量(长度)
int capacity = elements.length;
// 当size大于等于当前容量的一半,或者当前容量已经小于默认容量(10)时,就直接返回
if (size >= capacity >> 1 || capacity < CAPACITY_DEFAULT){
return;
}
// 计算新的容量 = 原来容量的一半
int newCapacity = capacity >> 1;
// 创建新数组
E[] newElements = (E[]) new Object[newCapacity];
// 将原数组元素存入新数组
for(int i = 0; i < size; i++){
newElements[i] = elements[i];
}
// 引用新数组(新数组的索引要指向旧数组的索引)
elements = newElements;
}
- 每次删除元素后,判断是否需要缩容
public E remove(int index){
// 判断索引是否越界
rangCheck(index);
// 取出需要删除的元素
E element = elements[index];
// 通过循环,将index后面的所有元素向前移动一位
for(int i = index; i < size - 1; i++){
elements[i] = elements[i+1];
}
// 删除最后一个元素
elements[--size] = null;
// 判断数组是否需要缩容
trim();
// 将删除的元素返回
return element;
}
5. 清空元素
- 清空元素,只需要将所有的元素置为 null,然后 size 置为 0
public void clear(){
// 清空存储的数据
for(int i = 0; i < size; i++){
elements[i] = null;
}
// 将size置为0
size = 0;
}
6. 修改元素
- 修改元素时,只需要将原有位置的元素替换掉即可,只是需要注意一下索引是否越界
public E set(int index, E element){
// 判断索引是否越界
rangCheck(index);
// 取出被替代元素
E oldElement = elements[index];
// 替换新元素
elements[index] = element;
// 返回被替换的元素
return oldElement;
}
7. 查询元素
- 查询元素,只需要将指定索引的元素返回,注意判断索引是否越界即可
public E get(int index){
rangCheck(index);
return elements[index];
}
8. 插入元素
- 插入元素类似于删除元素,只需要将插入位置后面的元素向后移动即可
- 注意:需要从后向前移动元素,如果从前向后移动元素,那么会进行元素覆盖,最后出错
public void add(int index, E element){
// 从后向前的顺序,将元素向后移动
for(int i = size - 1; i >= index; i++){
elements[i + 1] = elements[i];
}
// 插入元素
elements[index] = element;
// size+1
size++;
}
- 需要注意的是,插入元素的索引也不能越界,不过不同于删除和设置元素时,插入的位置可以是所有元素的最后,即size的位置
private void rangCheckForAdd(int index){
// 当索引小于0 或 大于 size时,抛出异常
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
}
- 此时,add 方法如下
public void add(int index, E element){
// 检查索引是否越界
rangCheckForAdd(index);
// 从后向前的顺序,将元素向后移动
for(int i = size - 1; i >= index; i++){
elements[i + 1] = elements[i];
}
// 插入元素
elements[index] = element;
// size+1
size++;
}
9. 查看元素位置
- 可以通过循环,查找元素在数组中的位置
- 注意:数组中可以存储null,而null是不能调用equals方法的,所以需要对传入的元素进行判断,如果查找的元素是null,需要单独处理
- 当元素存在时,返回索引,否则返回变量 ELEMENT_NOT_FOUND 的值
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;
}
10. 是否包含某个元素
- 当元素存在时,只需要判断索引是否等于 ELEMENT_NOT_FOUND 即可
public boolean contains(E element){
// 查看元素的索引是否为ELEMENT_NOT_FOUND
return indexOf(element) != ELEMENT_NOT_FOUND;
}
11. 元素的数量
- 数组元素的数量,就是size的值
public int size(){
return size;
}
12. 数组是否为空
- 判断 size 的值是否为 0 即可
public boolean isEmpty(){
return size == 0;
}
13. 动态数组打印
- 可以重写 toString 方法,来打印 ArrayList 中的元素
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size= ").append(size).append(", [");
for(int i = 0; i < size; i ++){
if(i != 0){
string.append(",");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
附上动态数组源码
package com.aimoyudett;
/**
* @Author: 爱摸鱼的TT~
* @Description: 自己实现ArrayList接口类
* @Date Created in 2022-11-18 21:40
* @Modified By:
*/
public class ArrayList<E> {
private int size;// 元素的个数
private E[] elements;// 数组对象
// 设置elements数组默认的初始化空间
private static final int CAPACITY_DEFAULT = 10;
private static final int ELEMENT_NOT_FOUND = -1;
// 无参构造方法
public ArrayList(){
this(CAPACITY_DEFAULT);
}
// 有参构造方法
public ArrayList(int capacity){
capacity = capacity < CAPACITY_DEFAULT ? CAPACITY_DEFAULT : capacity;
// 创建长度为capacity的数组
elements = (E[]) new Object[capacity];
}
public void add(E element){
// 添加新元素之前,先判断是否需要扩容
ensureCapacity();
elements[size] = element;
size++;
}
public E remove(int index){
// 判断索引是否越界
rangCheck(index);
// 取出需要删除的元素
E element = elements[index];
// 通过循环,将index后面的所有元素向前移动一位
for(int i = index; i < size - 1; i++){
elements[i] = elements[i+1];
}
// 删除最后一个元素
elements[--size] = null;
// 判断数组是否需要缩容
trim();
// 将删除的元素返回
return element;
}
public void add(int index, E element){
// 检查索引是否越界
rangCheckForAdd(index);
// 从后向前的顺序,将元素向后移动
for(int i = size - 1; i >= index; i--){
elements[i + 1] = elements[i];
}
// 插入元素
elements[index] = element;
// size+1
size++;
}
public E get(int index){
rangCheck(index);
return elements[index];
}
public E set(int index, E element){
// 判断索引是否越界
rangCheck(index);
// 取出被替代元素
E oldElement = elements[index];
// 替换新元素
elements[index] = element;
// 返回被替换的元素
return oldElement;
}
public void clear(){
// 清空存储的数据
for(int i = 0; i < size; i++){
elements[i] = null;
}
// 将size置为0
size = 0;
}
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 boolean contains(E element){
// 查看元素的索引是否为ELEMENT_NOT_FOUND
return indexOf(element) != ELEMENT_NOT_FOUND;
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size= ").append(size).append(", [");
for(int i = 0; i < size; i ++){
if(i != 0){
string.append(",");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
// 缩容
private void trim(){
// 获取当前数组的容量(长度)
int capacity = elements.length;
// 当size大于等于当前容量的一半,或者当前容量已经小于默认容量(10)时,就直接返回
if (size >= capacity >> 1 || capacity < CAPACITY_DEFAULT){
return;
}
// 计算新的容量 = 原来容量的一半
int newCapacity = capacity >> 1;
// 创建新数组
E[] newElements = (E[]) new Object[newCapacity];
// 将原数组元素存入新数组
for(int i = 0; i < size; i++){
newElements[i] = elements[i];
}
// 引用新数组(新数组的索引要指向旧数组的索引)
elements = newElements;
}
private void rangCheckForAdd(int index){
// 当索引小于0 或 大于 size时,抛出异常
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
}
// 判断索引位置
private void rangCheck(int index){
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
}
// 扩容
private void ensureCapacity(){
// 获取数组当前容量(长度)
int oldLength = elements.length;
// 如果 当前存储的元素个数(size) < 当前数组容量,直接返回
if(size < oldLength){
return;
}
// 新数组的容量为原数组容量的1.5倍
int newCapacity = oldLength + (oldLength >> 1);
// 创建新数组
E[] newElements = (E[]) new Object[newCapacity];
//原数组中的元素存储到新数组中
for(int i = 0; i < size; i++){
newElements[i] = elements[i];
}
// 引用新数组(新数组的索引要指向旧数组的索引)
elements = newElements;
}
/*// 元素数量
int size();
// 是否为空
boolean isEmpty();
// 是否包含某个元素
boolean contains(E element);
// 添加元素到最后面
void add(E element);
// 返回index位置的元素
E get(int index);
// 设置index位置对应的元素
E set(int index, E element);
// 往index位置添加元素
void add(int index, E element);
// 删除index位置对应的元素
E move(int index);
// 查看元素的位置
int indexOf(E element);
// 清除所有元素
void clear();*/
}