java 动态数组_使用java语言实现一个动态数组(详解)(数据结构)

废话不多说,上代码

1.从类名开始(我真是太贴心了,给自己点个赞)

public class Array

首先数组类需要带有泛型,这个不多说。需要注意的是在java中,数组只能存放同一个类型的。

2.成员变量

private int size; //数组中元素的个数

private E[] data; //数组声明

插个题外话:

关于size和索引,最开始学数组时让我很伤神,首先数组的索引是从0开始,而size是指数组中元素的

的个数,假设数组中有3个元素,那么size=3,而索引则为0,1,2。它们是差一位的,这个神奇的设计让我每次在写循环的界限条件时,

总要换算一下。

比如,遍历出数组的所有元素

for (int i = 0; i < size; i++) { }

我的心路历程是这样的:

首先,第一步想,从0开始,到最后一位元素的索引位置结束,那么最后一位元素的索引是应该进来for循环的,那么i就应该

小于最后一位元素的索引的下一位,那么最后一位元素的索引的下一位是谁呢,对哦,size比索引大一位,那么应该是size,

所以应该i

如果每次写for循环的界限时,都要这么想一下,白白消耗脑力阿。是不是只有我这么笨。

最后我的办法是转化成图来记在脑海里。每次用到的时候,直接脑海里浮现出这个图。

4d107505a71ecbaa0b973c3e1082a087.png

学习的本质就是将复杂的东西简单化。

3.构造方法

一种用户指定初始数组容量

一种用户不指定初始数组容量

48304ba5e6f9fe08f3fa1abda7d326ab.png

public Array (int capacity) {

data = (E[])new Object[capacity];

size = 0;

}

public Array () {

this(10); //调用另一个构造方法,并默认初始容量为10

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

4.居家必备的基本方法

48304ba5e6f9fe08f3fa1abda7d326ab.png

//获得数组元素个数

public int getSize () {

return size;

}

//获得数组长度

public int getCapacity () {

return data.length;

}

//获得数组是否为空

public boolean isEmpty () {

return size == 0;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

5.添加方法

数组添加的本质就是:从后往前到指定索引位置,每个元素向后移一个格,给新来的腾出个地方。

重点:从后往前

48304ba5e6f9fe08f3fa1abda7d326ab.png

//向数组指定位置添加元素,index为指定索引位置,e为添加的值

public void add (int index, E e) {

//索引位置不能让它瞎插,索引为负数,或者跳格子插,不可以。

if (index < 0 || index > size) {

throw new IllegalArgumentException("add is fail, require index < 0 || index > size");

}

//当数组容量满了的时候,调用扩容方法,此处给它扩当前数组长度的两倍。

if (data.length == size) {

this.resize(data.length * 2);

}for (int i = size - 1; i >= index; i--) {

data[i+1] = data[i];

}

//新来的进坑

data[index] = e;

//维护size

size ++;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

//向数组第一位添加元素

public void addFirst (E e) {

//直接复用上一个add方法

this.add(0, e);

}

//向数组最后一位添加元素

public void addLast (E e) {

//同理

this.add(size, e);

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

6.删除方法(我个人分为两种,一种根据索引删除,一种根据值删除)

删除的本质:和添加相反,从要删除的索引位置的下一位开始,到最后一位元素索引位置结束,依次向前占一个坑。

重点:遍历的时候从前往后

48304ba5e6f9fe08f3fa1abda7d326ab.png

//根据索引删除某个元素 返回删除的元素

public E remove (int index) {

if (index < 0 || index >= size) {

throw new IllegalArgumentException("remove is fail,require index < 0 || index >= size");

}

//先把要删除的元素存起来,不然等会就给覆盖了。

E value = data[index];

for (int i = index + 1; i < size; i++) {

data[i-1] = data[i];

}

//维护size

size --;

//此处为什么设置为null呢,因为泛型的原因,传进来的都是类对象,数组中存的是引用地址,引用不断开的话,垃圾回收器没办法回收。

data[size] = null;

//此处缩容,当数组元素个数等于数组长度四分之一时,进行缩容

if (size == data.length/4 && data.length / 2 != 0) {

//缩容为数组长度的二分之一

this.resize(data.length /2);

}

return value;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

问题来了,为什么不在二分之一时就进行缩容呢?而是四分之一呢?

此处涉及到复杂度震荡问题,比较极端的一个情况是:

比如容量为10的一个数组,

此时该数组满了,此时要进来个元素,然后数组进行扩容,那么添加完元素此时数组的情况为容量为20,

内部有11个元素。

此时我再对数组进行删除一个元素,删除之后,数组元素个数变为10个,恰好为数组长度的二分之一,

那么自动进行缩容,以此类推,反复操作,每次扩容缩容的时间复杂度为O(n),所以此处应用了lazy的解决方案

就是等到数组元素个数为数组长度的四分之一时,再进行缩容,就可以避免这个问题。

48304ba5e6f9fe08f3fa1abda7d326ab.png

//根据值删除某个元素

public void removeByValue (E e) {

//复用根据值查找元素的方法,返回索引(此方法在下面)

int index = this.getByElement(e);

if (index != -1) {

//复用根据索引删除的方法

this.remove(index);

}

}

//删除第一个元素

public E removeFirst () {

return this.remove(0);

}

//删除最后一个元素

public E removeLast () {

return this.remove(size - 1);

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

7.查找方法(同样分为两种,一种根据索引,一种根据值)

48304ba5e6f9fe08f3fa1abda7d326ab.png

//根据索引查找数组某个元素,返回值

public E getByIndex (int index) {

if (index < 0 || index >= size) {

throw new IllegalArgumentException("get is fail, require index < 0 || index >= size");

}

return data[index];

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

//根据值查找数组某个元素,返回索引

public int getByElement (E e) {

//本质:遍历数组进行比对

for (int i = 0; i < size; i++) {

if (data[i].equals(e) ) {

return i;

}

}

return -1;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

48304ba5e6f9fe08f3fa1abda7d326ab.png

//是否包含该元素

public boolean contains (E e) {

//本质:遍历数组进行比对

for (int i = 0; i < size; i++) {

if (data[i].equals(e)) {

return true;

}

}

return false;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

8.修改方法

48304ba5e6f9fe08f3fa1abda7d326ab.png

//修改数组某个元素

public void set (int index, E e) {

if (index < 0 || index >= size) {

throw new IllegalArgumentException("set is fail, require index < 0 || index >= size");

}

data[index] = e;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

9.扩容方法

扩容的本质:就是开辟个新数组,把旧数组的内容复制过去

48304ba5e6f9fe08f3fa1abda7d326ab.png

private void resize (int newCatacity) {

E[] newData = (E[])new Object[newCatacity];

for (int i = 0; i < size; i++) {

newData[i] = data[i];

}

//给成员变量data重新赋值新引用(后面有内存图介绍)

data = newData;

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

画个扩容引用转换的内存图

测试代码

48304ba5e6f9fe08f3fa1abda7d326ab.png

public static void main(String[] args) {

Array array = new Array(5);

array.addLast(1);

array.addLast(2);

array.addLast(3);

array.addLast(4);

array.addLast(5);

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

2985b23418462c32834111c733dd98ca.png

10.toString方法

本质就是:创建一个StringBuilder对象,然后通过append方法,将数组的内容遍历,添加进StringBuilder对象。

48304ba5e6f9fe08f3fa1abda7d326ab.png

@Override

public String toString () {

StringBuilder stringBuilder = new StringBuilder();

//Format(String, Object, Object) 将指定的 String 中的格式项替换为两个指定的 Object 实例的值的文本等效项。

stringBuilder.append(String.format("size =%d ,capacity =%d\n ", size, data.length));

stringBuilder.append('[');

for (int i = 0; i < size; i++) {

stringBuilder.append(data[i]);

if (i != size -1) {

stringBuilder.append(',');

}

}

stringBuilder.append(']');

return stringBuilder.toString();

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

最后,

浮于表面看千遍,

不如自己思一遍,

希望这篇文章能够对你起到帮助。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值