由浅到深带你详谈Java实现数组扩容的三种方式

 更新时间:2023年06月05日 11:12:33   作者:程序yang  
这篇文章主要详细介绍了Java实现数组扩容的三种方式,新建一个数组,把原来数组的内容搬到新数组中,使用system.arraycopy(),使用java.util.Arrays.copyOf()这三种方式,具有一定的参考价值,需要的朋友可以借鉴一下

1.新建一个数组,把原来数组的内容搬到新数组中。

这种方法实现的思路是:先新建一个数组(前提条件是长度得比原来的长),然后把原来数组的内容搬到新数组中.

案例分析:

public static void main(String[] args) {
	// 利用函数的方法进行数组的扩充
	// 定义一个小型的数组
	int[] a = { 1, 2, 3, 4, 5 };
	// 调用扩容函数
	a= expand1(a);
	// 测试是否扩容完成,输出此时数组a中的值
	for (int i = 0; i < a.length; i++) {
		System.out.println("数组元素:" + a[i]);
	}
}
// 扩容函数1,
public static int[] expand1(int a[]) {
	// 定义一个新数组b,并为其赋值长度为数组a的二倍
	int b[] = new int[a.length * 2];
	// 将数组a的元素循环遍历到数组b中
	for (int i = 0; i < a.length; i++) {
		b[i] = a[i];
	}
	System.out.println("数组扩容方法1:" + Arrays.toString(b));
	return b;
}

输出结果:

数组扩容方法1:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
数组元素:1
数组元素:2
数组元素:3
数组元素:4
数组元素:5
数组元素:0
数组元素:0
数组元素:0
数组元素:0
数组元素:0

2.使用system.arraycopy()

常用作数组的扩容,如ArrayList底层数组的扩容

方法解析:

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

通过上面的源码可以看到,arraycopy它是一个静态本地方法,由虚拟机实现,效率自然比用java一个个复制高。

将指定源数组中的数组从指定位置开始复制到目标数组的指定位置。 阵列组件的一个子序列被从通过引用的源阵列复制src被引用的目标阵列dest 。 复制的组件数等于length参数。 在位置的部件srcPos通过srcPos+length-1源阵列中的被复制到的位置destPos通过destPos+length-1分别,目的地阵列。

大白话总结:
从源数组src取元素,范围为下标srcPos到srcPos+length-1,取出共length个元素,存放到目标数组中,存放位置为下标destPos到destPos+length-1。简单说,就是数组间的复制。

参数解释:

  • src 源数组;
  • srcPos 源数组中的起始位置;
  • dest 目标数组;
  • destPos 目标数组中的起始位置;
  • length 要复制的数组元素的数量

注意事项:

  •  1.若src和dest参数引用相同的数组对象,则执行复制,就好像位置 srcPos 到 srcPos + length - 1 的组件首次复制到具有 length 组件的临时数组,然后临时数组的内容被复制到位置 destPos 通过目标数组的 destPos + length - 1 。
  •  2.若 src 是 null ,则抛java.lang.NullPointerException异常,并且不修改目标阵列。案例演示如下:
public static void main(String[] args) {
	int[] src = { 1, 2, 3, 4 };
	int[] dest = null;
	System.arraycopy(src, 0, dest, 0, 4);//抛异常:java.lang.NullPointerException
	for (Object o : dest) {
		System.out.println(o);
	}
}

解析:
src源数组共有4个元素,索引是0-3,
dest目标数组为null,
当执行System.arraycopy(src, 0, dest, 0, 4);时,
第一步:由于dest为null,arraycopy方法检查到null操作,所以就会抛空指针异常。(如果你想深入理解,可以去了解一下arraycopy的底层源码。)

  • 3.srcPos 的设置主要看 destPos 和 length ,若 srcPos 该位置往后的元素少于要复制的数组元素的数量(即 srcPos + length 的长度已经超过源数组元素的数量/个数,不是长度/索引;或者 destPos + length 的长度已经超过目标数组元素的数量/个数,不是长度/索引;),则会抛java.lang.ArrayIndexOutOfBoundsException异常。案例演示如下:
//src.length < srcPos + length
public static void main(String[] args) {
	String[] src = { "aa", "bb", "cc", "dd" };
	String[] dest = new String[] { "a", "b", "c", "d", "e" };
	System.arraycopy(src, 1, dest, 1, 4);//抛异常java.lang.ArrayIndexOutOfBoundsException
	for (Object o : dest) {
		System.out.println(o);
	}
}

解析:
src源数组共有4个元素,索引是0-3,
dest目标数组共有5个元素,索引是0-4,
当执行System.arraycopy(src, 1, dest, 1, 4);时,
第一步:srcPos为1,即从源数组(src)中,从下标1开始取,
第二步:length为4,即取4个元素,也就是src[1]-src[4],即从“bb”之后要取4个元素,这时源数组(src)只有“bb”、“cc”、“dd” 3个元素,明显不够4个元素,所以就会抛数组索引越界异常。

//dest.length < destPos + length
public static void main(String[] args) {
	String[] src = { "aa", "bb", "cc", "dd" };
	String[] dest = new String[] { "a", "b", "c", "d", "e" };
	System.arraycopy(src, 0, dest, 2, 4);//抛异常java.lang.ArrayIndexOutOfBoundsException
	for (Object o : dest) {
		System.out.println(o);
	}
}

解析:
src源数组共有4个元素,索引是0-3,
dest目标数组共有5个元素,索引是0-4,
当执行System.arraycopy(src, 0, dest, 2, 4);时,
第一步:srcPos为0,即从源数组(src)中,从下标0开始取,
第二步:length为4,即取4个元素,也就是src[0]-src[3],即从“aa”之后要取4个元素;分别是“aa”、“bb”、“cc”、“dd” 4个元素
第三步:把从源数组(src)取出的数(“aa”、“bb”、“cc”、“dd” 4个元素),按顺序,存放到目标数组(dest)中,从下标2开始存,存4个,也就是dest[2]-dest[5],由于dest目标数组最大索引为4,只能存放“aa”、“bb”、“cc”,存不下“dd”,即不够容量存放4个数,所以就会抛数组索引越界异常。

  • 4.若 dest 是 null ,则抛java.lang.NullPointerException异常,同上/参考2。
  •  5.destPos 的设置主要看 srcPos 和 length ,若 destPos 该位置容纳不下要复制的数组元素(即 destPos + length 的长度已经超过目标数组元素的数量/个数,不是长度/索引;或者 srcPos + length 的长度已经超过源数组元素的数量/个数,不是长度/索引;),则会抛java.lang.ArrayIndexOutOfBoundsException异常。同上/参考3。
  •  6.length 的设置主要看 srcPos 和 destPos,若 length 超出源数组中的起始位置之后的元素数量(即 srcPos 往后的元素数量/个数,不是长度/索引 )或者 length 超出目标数组中的起始位置的元素数量(即 destPos 往后的元素数量/个数,不是长度/索引),则会抛java.lang.ArrayIndexOutOfBoundsException异常。
// length > srcPos.length - srcPos[index]
public static void main(String[] args) {
	String[] src = { "aa", "bb", "cc", "dd" };
	String[] dest = new String[] { "a", "b", "c", "d", "e" };
	System.arraycopy(src, 2, dest, 2, 3);// 抛异常java.lang.ArrayIndexOutOfBoundsException
	for (Object o : dest) {
		System.out.println(o);
	}
}

解析:
src源数组共有4个元素,索引是0-3,
dest目标数组共有5个元素,索引是0-4,
当执行System.arraycopy(src, 2, dest, 2, 3);时,
第一步:srcPos为2,即从源数组(src)中,从下标2开始取,
第二步:length为3,即取3个元素,也就是src[2]-src[4],即从“cc”之后要取3个元素,这时源数组(src)只有“cc”、“dd” 2个元素,明显不够3个元素,所以就会抛数组索引越界异常。

// length > destPos.length - destPos[index]
public static void main(String[] args) {
	String[] src = { "aa", "bb", "cc", "dd" };
	String[] dest = new String[] { "a", "b", "c", "d", "e" };
	System.arraycopy(src, 2, dest, 4, 2);// 抛异常java.lang.ArrayIndexOutOfBoundsException
	for (Object o : dest) {
		System.out.println(o);
	}
}

解析:
src源数组共有4个元素,索引是0-3,
dest目标数组共有5个元素,索引是0-4,
当执行System.arraycopy(src, 2, dest, 4, 2);时,
第一步:srcPos为2,即从源数组(src)中,从下标2开始取,
第二步:length为2,即取2个元素,也就是src[2]-src[3],即从“cc”之后要取2个元素,分别是“cc”、“dd” 2个元素;
第三步:把从源数组(src)取出的数(“cc”、“dd” 2个元素),按顺序,存放到目标数组(dest)中,从下标4开始存,存2个,也就是dest[4]-dest[5],由于dest目标数组最大索引为4,只能存放“cc”,存不下“dd”,即不够容量存放2个数,所以就会抛数组索引越界异常。

  • 7.当srcPos 、destPos 和 length 任意一个参数小于0时,则会抛java.lang.ArrayIndexOutOfBoundsException异常。
//srcPos < 0 || destPos < 0 || length < 0
public static void main(String[] args) {
	String[] src = { "aa", "bb", "cc", "dd" };
	String[] dest = new String[] { "a", "b", "c", "d", "e" };
	System.arraycopy(src, -1, dest, 1, 1);//抛异常java.lang.ArrayIndexOutOfBoundsException
	for (Object o : dest) {
		System.out.println(o);
	}
}

解析:
src源数组共有04个元素,索引是0-3,
dest目标数组共有5个元素,索引是0-4,
当执行System.arraycopy(src, -1, dest, 1, 1);时,
第一步:srcPos为-1,不在索引0-3范围内,所以就会抛数组索引越界异常。

📌补充点:

  •  8.当源数组和目标数组两组数据的元素类型不匹配时,则会抛java.lang.ArrayStoreException异常,并且不修改目标数组。
public static void main(String[] args) {
	int[] src = { 1, 2, 3, 4 };
	Object[] dest = new Object[3];
	System.arraycopy(src, 0, dest, 0, 3);//抛异常java.lang.ArrayStoreException
	for (Object o : dest) {
		System.out.println(o);
	}
}

public static void main(String[] args) {
	Object[] src = { 1, 2, 3, 4 };
	int[] dest = new int[3];
	System.arraycopy(src, 0, dest, 0, 3);// 抛异常java.lang.ArrayStoreException
	for (Object o : dest) {
		System.out.println(o);
	}
}

若源数组src的元素为目标数组dest的元素的子类时,是可以复制的,特殊地,如int不是Object的子类(或者说,不存在继承的概念),所以,上述代码中,如果把int[] src = {1, 2, 3, 4};改为Integer[] src = {1, 2, 3, 4};,是不会报错的。

public static void main(String[] args) {
	Integer[] src = { 1, 2, 3, 4 };
	Object[] dest = new Object[3];
	System.arraycopy(src, 0, dest, 0, 3);
	for (Object o : dest) {
		System.out.println(o);
	}
}

输出结果:

1
2
3

public static void main(String[] args) {
	Object[] src = { 1, 2, 3, 4 };
	Integer[] dest = new Integer[3];
	System.arraycopy(src, 0, dest, 0, 3);
	for (Object o : dest) {
		System.out.println(o);
	}
}

输出结果:

1
2
3

总结:

java.lang.ArrayStoreException异常(由于类型不匹配, src阵列中的元素无法存储到 dest阵列中),常见的就是:

  • src参数引用的对象不是数组。
  • dest参数引用的对象不是数组。
  • src参数和dest参数引用其组件类型为不同基元类型的数组。
  • src参数引用具有基本组件类型的数组,而dest参数引用具有引用组件类型的数组。
  • src参数引用具有引用组件类型的数组,而dest参数引用具有基本组件类型的数组。

java.lang.ArrayIndexOutOfBoundsException异常(如果复制会导致访问数组边界外的数据),常见的就是:

  • srcPos论点是否定的。
  • destPos参数为负数。
  • length参数为负数。
  • srcPos+length大于src.length ,源数组的长度。
  • destPos+length大于dest.length ,目标数组的长度。

java.lang.NullPointerException异常,常见的就是:

  • src或 dest 是 null 。

案例分析:

public static void main(String[] args) {
	// 利用函数的方法进行数组的扩充
	// 定义一个小型的数组
	int[] a = { 1, 2, 3, 4, 5 };
	// 调用扩容函数
	a= expand2(a);
	// 测试是否扩容完成,输出此时数组a中的值
	for (int i = 0; i < a.length; i++) {
		System.out.println("数组元素:" + a[i]);
	}
}
// 数组扩容方法2,利用系统函数arraycopy进行扩容
public static int[] expand2(int a[]) {
	int[] b = new int[a.length * 2];
	// 系统函数进行扩容,将a[]的值赋值到b[]中,共a.length个长度。
	System.arraycopy(a, 0, b, 0, a.length);
	System.out.println("数组扩容方法2:" + Arrays.toString(b));
	return b;
}

输出结果:

数组扩容方法2:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
数组元素:1
数组元素:2
数组元素:3
数组元素:4
数组元素:5
数组元素:0
数组元素:0
数组元素:0
数组元素:0
数组元素:0

3.使用java.util.Arrays.copyOf()

方法解析:

Arrays.copyof是用于数组进行复制时常使用的方法,本身在Arrays类里面,而之所以能这么使用而不用创建对象在于该方法本身由static修饰,被static修饰的方法可以在该类创建对象前载入jvm。

Arrays.copyof有众多的重载方法,以最典型的Arrays.copyOf(srcArray, length);为例进行分析,源码如下:

public static long[] copyOf(long[] original, int newLength) {
    long[] copy = new long[newLength];
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

通过上面的代码可以看出,其本质是调用了System.arraycopy方法。

  • 1.先产生一个新的数组
  • 2.调用arraycopy方法
  • 3.返回产生的新数组

参数解释:

  • original 源数组
  • newLength 新扩容长度

注意事项:

当我们将原来的数组进行扩容的时候,调用该方法产生了一个新的数组,而将扩容后的数组赋值给原来的数组的时候,原数组指向新产生的数组,但其原数组的内容依然在内存中,等待jvm回收,在这段时间中其实是造成了内存的浪费,所以使用该方法尽管简便,实际上有一定的不足。

案例分析:

public static void main(String[] args) {
	// 利用函数的方法进行数组的扩充
	// 定义一个小型的数组
	int[] a = { 1, 2, 3, 4, 5 };
	// 调用扩容函数
	a= expand3(a);
	// 测试是否扩容完成,输出此时数组a中的值
	for (int i = 0; i < a.length; i++) {
		System.out.println("数组元素:" + a[i]);
	}
}
// 数组扩容方法3,利用系统函数copyOf进行扩容
public static int[] expand3(int a[]) {
	int[] b = java.util.Arrays.copyOf(a, a.length * 2);
	System.out.println("数组扩容方法3:" + Arrays.toString(b));
	return b;
}

输出结果:

数组扩容方法3:[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
数组元素:1
数组元素:2
数组元素:3
数组元素:4
数组元素:5
数组元素:0
数组元素:0
数组元素:0
数组元素:0
数组元素:0

Java数组扩容的原理:
  •  Java数组对象的大小是固定不变的,数组对象是不可扩容的。
  •  利用数组复制方法可以变通的实现数组扩容。
  • System.arraycopy()可以复制数组。
  •  Arrays.copyOf()可以简便的创建数组副本。
  •  创建数组副本的同时将数组长度增加就变相的实现了数组的扩容。

总结:

只有数组为一维数组,并且元素为基本类型或String类型时,才是深拷贝,其它都属于浅拷贝。

拷贝的两层含义,对应了浅拷贝和深拷贝的概念,做了第一层,就是浅拷贝,做到第二层,就是深拷贝。

  • 浅拷贝:将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。
  • 深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”。

很容易可以想到,浅拷贝比深拷贝要更快,但是,从拷贝的意义上来看,浅拷贝相较于深拷贝,要欠缺一点,即一个是效率问题,一个是内存占用问题。

以上就是由浅到深带你详谈Java实现数组扩容的三种方式的详细内容,更多关于Java实现数组扩容的三种方式的资料请关注脚本之家其它相关文章!

相关文章

  • Java多线程Atomic包操作原子变量与原子类详解

    Java多线程Atomic包操作原子变量与原子类详解

    这篇文章主要介绍了Java多线程Atomic包操作原子变量与原子类详解,简单介绍了Atomic,同时涉及java.util.concurrent中的原子变量,Atomic类的作用等相关内容,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Druid(新版starter)在SpringBoot下的使用教程

    Druid(新版starter)在SpringBoot下的使用教程

    Druid是Java语言中最好的数据库连接池,Druid能够提供强大的监控和扩展功能,DruidDataSource支持的数据库,这篇文章主要介绍了Druid(新版starter)在SpringBoot下的使用,需要的朋友可以参考下
    2023-05-05
  • Spring Aop基本流程原理示例详解

    Spring Aop基本流程原理示例详解

    这篇文章主要给大家介绍了关于Spring Aop基本流程原理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Springboot整合freemarker 404问题解决方案

    Springboot整合freemarker 404问题解决方案

    这篇文章主要介绍了Springboot整合freemarker 404问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • JavaWeb中Servlet的生命周期及线程安全问题详解

    JavaWeb中Servlet的生命周期及线程安全问题详解

    这篇文章主要介绍了JavaWeb中Servlet的生命周期及线程安全问题详解,Servlet 生命周期可被定义为从创建直到毁灭的整个过程,Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的,需要的朋友可以参考下
    2024-01-01
  • SpringController返回值和异常自动包装的问题小结

    SpringController返回值和异常自动包装的问题小结

    今天遇到一个需求,在不改动原系统代码的情况下,将Controller的返回值和异常包装到一个统一的返回对象中去,下面通过本文给大家介绍SpringController返回值和异常自动包装的问题,需要的朋友可以参考下
    2024-03-03
  • JDK14的新特性:instanceof模式匹配的使用

    JDK14的新特性:instanceof模式匹配的使用

    这篇文章主要介绍了JDK 14的新特性:instanceof模式匹配的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • 深入理解Java并发编程之LinkedBlockingQueue队列

    深入理解Java并发编程之LinkedBlockingQueue队列

    本文主要介绍了Java并发编程之LinkedBlockingQueue队列,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • 微信支付java版本之Native付款

    微信支付java版本之Native付款

    这篇文章主要为大家详细介绍了微信支付java版本之Native付款,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • Java中Spring获取bean方法小结

    Java中Spring获取bean方法小结

    Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,如何在程序中获取Spring配置的bean呢?下面通过本文给大家介绍Java中Spring获取bean方法小结,对spring获取bean方法相关知识感兴趣的朋友一起学习吧
    2016-01-01

最新评论