详解Java数组扩容缩容与拷贝的实现和原理

 更新时间:2023年05月16日 09:32:31   作者:一一哥Sun  
这篇文章主要带大家学习数组的扩容、缩容及拷贝,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一. 数组内存分析(重点)

1. 简介

Java的内存,可以分为栈、堆、方法区、本地方法区、程序寄存器等几个核心部分。这一块的内容,以后会专门编写文章进行介绍,对于初学者来说,这还不适合我们学习。但是我们现在要先对以下三个概念有所了解:

栈: 栈中可以存储基本类型的数据和引用类型的地址。特点: 先进后出,一般空间比较小,存取速度较快。

堆: 堆中可以存储引用类型的数据。特点: 空间比较大,存储速度相对较慢。

方法区: 方法区中可以 存储字符串常量池、静态数据、代码和类的元数据。

我们知道,数组属于引用类型,而数组的引用变量(数组名称)只是一个地址引用。 这个引用变量可以指向任何有效的内存空间,只有当这个引用指向有效的空间时,才可以通过引用去操作数组中真正的数据元素。所以数组的引用变量(数组名称)是存储在栈空间中,但真正的数组数据是存储在堆空间中。

2. 代码案例

为了让大家更好地理解数组的内存结构,接下来给大家设计一个代码案例,然后给大家分析一下这个数组的内存结构。

/**
 * @author
 */
public class Demo01 {
    public static void main(String[] args) {
	//使用静态初始化的方式初始化一个数组a
        //a存放在栈中,a的值是数组的地址,数组的真正数据{5,7,20}存放在堆中
        int[] a = {5,7,20}; 
        System.out.println("a的长度为:" + a.length);//3

        //整型变量,存放在栈中
        int num =8;
        System.out.println("num:"+num);

        //定义一个新的数组b
        int[] b=new int[4]; 
        System.out.println("b的长度是:"+b.length);

        //将a赋值给b,是b的指向改变了,但b原先对应的数组依然存在
        b=a; 
        System.out.println("b的长度是:"+b.length);
    }
}

3. 内存分析

为了让各位更好地理解基本类型的数据和数组的内存结构,给大家绘制下面一张图。

根据上面的代码和下面的内存分析图,我们可以得到如下结论:

  • 变量a存放在栈中,a的值是数组的首地址,数组的真正数据{5,7,20}存放在堆中;
  • 整型变量num存放在栈中;
  • 定义新的数组b,数组名称b存放在栈中,b的数据在堆中;
  • 将a赋值给b,此时b的指向改变了,但b原先对应的数组依然存在,此时b指向原先a对应的数组数据。

二. 数组扩容与缩容

1. 扩容简介

在前面给大家说过,数组一旦创建初始化后,其长度就不能被改变。但是有的小伙伴就说了,”不对啊,我看别人的文章说,可以往数组中增加很多新数据啊......“。那如果是这样,假如我们一开始定义一个长度为5的数组,然后想把10个数据元素都插进去,这能不能实现?

大家想一下,你能把10升水装到5升的瓶子中吗?肯定不行!如果你非要把10升水都装到瓶子里,肯定需要换一个新的更大的瓶子!

所以今天跟大家说的”数组扩容“,其实并不是将这些多余的数据装到原有的数组中,而是创建一个新的更大的数组,再把原有数组中的内容都复制到新数组中来!

2. 扩容与缩容流程(重点)

在Java中,数组的”扩容“和”缩容“,并不是真的改变原有数组的大小,而是创建一个新的数组,然后再进行操作,具体流程如下:

  • 步骤1: 定义一个新数组,新数组的长度要比原数组增加或者减小;
  • 步骤2: 将原数组中的元素拷贝到新数组中;
  • 步骤3将原数组的名称变量指向新数组。

3. 代码实现

接下来就按照上面的流程,来带大家实现一下数组的扩容和缩容。

3.1 扩容代码

以下代码是进行数组扩容的案例。

/**
 * @author
 */
public class Demo05 {
    public static void main(String[] args) {
	// 数组扩容
	// 原数组
	int[] oldArr = { 1, 3, 46, 22, 11 };

	// 1.定义一个新数组,长度比原数组的长度多1,用于扩容
	int[] newArr = new int[oldArr.length + 1];

	// 2.数组拷贝
	for (int i = 0; i < oldArr.length; i++) {
            //数组拷贝,将原来数组的元素拷贝到新数组中
            newArr[i] = oldArr[i];
	}

	// 3.将原数组的名称变量指向新数组
	oldArr = newArr;
	System.out.println("数组长度="+oldArr.length);
        
	//4.遍历数组
	for (int i = 0; i < oldArr.length; i++) {
            //最后一个元素的值是默认值0
            System.out.println(oldArr[i]);
	}
    }
}

这里我们使用newArr[i] = oldArr[i];这样的语句,将旧数组中的元素拷贝到新数组中

3.2 缩容代码

以下代码是进行数组缩容的案例,供大家参考:

/**
 * @author
 */
public class Demo06 {
    public static void main(String[] args) {
	// 数组缩容
	//定义一个原数组
	int[] oldArr = {1,3,46,22,11};
	//1.定义一个新数组,新数组的长度比原数组长度少1个
	int[] newArr = new int[oldArr.length-1];

	//2.进行数组拷贝,将旧数组中的元素拷贝到新数组中
	for (int i = 0; i < newArr.length; i++) {
            newArr[i] = oldArr[i];
	}

	//3.将原数组的名称变量指向新数组
	oldArr = newArr;
	for (int i = 0; i < newArr.length; i++) {
            System.out.println(oldArr[i]);
	}
    }
}

三. 数组拷贝

在给大家讲解数组扩容时,涉及到了数组中数据元素的拷贝复制。那么除了上面的拷贝方式之外,数组还有哪些拷贝方式呢?

1. 拷贝方式

在Java中,数组的拷贝主要有三种实现方式:

通过循环语句,将原数组中的各个元素拷贝到新数组中(即数组扩容案例中使用的方法);

System类提供的数组拷贝方法;

Arrays类提供的数组拷贝方法。

接下来就设计几个案例,来给大家展示这几种方式都是怎么进行数组拷贝的。因为第一种数组拷贝方式,我们已经在数组扩容的案例中给大家演示了,这里就不再重复展示相关代码了。

2. System.arraycopy方法

2.1 简介

System.arraycopy()是Java提供的一个本地静态方法,用于将数据元素从源数组复制到目标数组。

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

arraycopy()方法有5个核心参数,其含义如下:

  • src: 源数组,即被复制的旧数组;
  • srcPos: 源数组中开始复制的索引位置;
  • dest: 目标数组,即要复制到的新数组;
  • destPos: 要复制到目标数组中的索引位置;
  • length: 要复制的元素个数。

另外我们还要注意,arraycopy()方法在有些情况下有可能会发生如下异常:

  • NullPointerException: if source or destination array is null.NullPointerException :如果源或目标数组为null时,就会产生NullPointerException异常。
  • ArrayStoreException: if the source and destination array type doesn’t match or they are not array.ArrayStoreException :如果源和目标数组类型不匹配或不是数组,会产生该异常;
  • ArrayIndexOutOfBoundsException: if the data overflow occurs because of index values or they are negative.ArrayIndexOutOfBoundsException :如果由于索引值导致数据溢出,或它们为负数时会产生该异常。

2.2 案例

我们先来看看下面这个数组拷贝的案例:

/**
 * @author
 */
public class Demo07 {
    public static void main(String[] args) {
	// 数组拷贝
	//1.源数组
	int[] srcArr = {1,3,46,22,11};

	//2.目标数组
	int[] destArr = new int[srcArr.length + 5];
        /**
	* src:原数组
	* srcPos:原数组的起始拷贝位置
	* dest:目标数组
	* destPos:目标数组的起始拷贝位置
	* length:拷贝的长度
	*/
	//3.调用arraycopy方法进行复制
	System.arraycopy(srcArr, 1, destArr, 3, 4);

	//对新数组进行遍历
	for (int i = 0; i < destArr.length; i++) {
            System.out.print(destArr[i]+"\t");
	}
    }
}

3. Arrays.copyOf方法

3.1 简介

Arrays.copyOf()可以复制数组中指定范围的元素。该方法会返回一个新的数组对象,且改变新数组中的元素值,不会影响原来的数组。我们还可以利用Arrays.toString方法将赋值后的数组输出。

该方法支持的参数可以是long、float、double、int、boolean、byte、Object等类型的数组。

public static int[] copyOf(int[] original, int newLength);

copyOf()方法有2个核心参数,其含义如下:

  • original: 源数组,即被复制的旧数组;
  • newLength: 表示新数组的长度。如果新数组的长度超过源数组的长度,会采用数组元素类型的默认值。

3.2 案例

以下是Arrays.copyOf()方法的实现案例:

/**
 * @author
 */
public class Demo08 {
    public static void main(String[] args) {
	// 数组拷贝
	//1.源数组
	int[] srcArr = {1,3,46,22,11};
	/**
	* original:原数组
	* newLength:新数组的长度
	* 返回值:返回新数组 
	*/
	//2.调用copyOf方法进行数组拷贝
	int[] destArr = Arrays.copyOf(srcArr, srcArr.length+1);
		
	//3.遍历新数组
	for (int i = 0; i < srcArr.length; i++) {
            System.out.print(destArr[i]+"\t");
	}
    }
}

四. 结语

至此,就把数组的扩容、缩容及拷贝等内容给大家介绍完毕了,现在你明白数组的扩容原理了吗?

以上就是详解Java数组扩容缩容与拷贝的实现和原理的详细内容,更多关于Java数组扩容缩容与拷贝的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot2.0整合Shiro框架实现用户权限管理的示例

    SpringBoot2.0整合Shiro框架实现用户权限管理的示例

    这篇文章主要介绍了SpringBoot2.0整合Shiro框架实现用户权限管理的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • springboot整合持久层的方法实现

    springboot整合持久层的方法实现

    本文主要介绍了springboot整合持久层的方法实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • 简单了解java获取类的3种方式

    简单了解java获取类的3种方式

    这篇文章主要介绍了java获取类的3种方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • spring整合redis以及使用RedisTemplate的方法

    spring整合redis以及使用RedisTemplate的方法

    本篇文章主要介绍了spring整合redis以及使用RedisTemplate的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • Java之MultipartFile和File类型互转方式

    Java之MultipartFile和File类型互转方式

    这篇文章主要介绍了Java之MultipartFile和File类型互转方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • 详解JVM中的GC调优

    详解JVM中的GC调优

    我们经常会听到甚至需要自己动手去做GC调优。那么GC调优的目的到底是什么呢?让程序跑得更快?让GC消耗更少的资源?还是让程序更加稳定?带着这些疑问来读一下这篇文章,将会得到一个系统的甚至是不一样的结果。
    2021-06-06
  • Java超详细讲解多线程中的Process与Thread

    Java超详细讲解多线程中的Process与Thread

    进程process:在一定的环境下,把静态的程序代码运行起来,通过使用不同的资源,来完成一定的任务;线程thread:是程序中一个单一的顺序控制流程。在单个进程中同时运行多个线程完成不同的工作,称为多线程
    2022-05-05
  • 使用Springboot处理跨域的方式

    使用Springboot处理跨域的方式

    这篇文章主要介绍了使用Springboot处理跨域的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-09-09
  • Springboot之整合Socket连接案例

    Springboot之整合Socket连接案例

    这篇文章主要介绍了Springboot之整合Socket连接案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • java的GUI实现简单切换界面

    java的GUI实现简单切换界面

    这篇文章主要为大家详细介绍了java的GUI实现简单切换界面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04

最新评论