写点什么

Java 数组在内存中的结构是怎样的?数组访问、遍历、复制、扩容、缩容如何编写代码?

  • 2023-05-02
    江苏
  • 本文字数:5914 字

    阅读完需:约 19 分钟

Java 是一门面向对象的编程语言,数组是其中的重要数据结构之一。在 Java 中,数组是一种固定长度、有序的数据结构,可以存储一组相同数据类型的元素。在本文中,我们将详细介绍 Java 数组在内存中的结构。


Java 数组的定义

在 Java 中,数组是一种对象,可以用关键字new创建。Java 数组可以是一维的,也可以是多维的,如二维数组、三维数组等。


Java 数组的定义格式如下:


数据类型[] 数组名 = new 数据类型[数组长度];
复制代码


其中,数据类型表示数组中存储的元素的类型,数组名是数组的标识符,数组长度表示数组中元素的个数。


例如,定义一个长度为 5 的整型数组,可以使用以下代码:


int[] arr = new int[5];
复制代码

Java 数组的内存结构

Java 数组在内存中的结构是连续的存储空间。数组中的每个元素在内存中占据相同的空间,并且存储顺序是从数组的第一个元素开始依次存储。


下图是一个长度为 5 的整型数组在内存中的结构示意图:


|-----------------------------------------------------||                 |                 |                 ||  arr[0] 的值     |  arr[1] 的值     |  arr[2] 的值     |  arr[3] 的值     |  arr[4] 的值     ||                 |                 |                 ||-----------------------------------------------------|
复制代码


上图中,每个数组元素占据 4 个字节的空间,因为整型数据类型在 Java 中占据 4 个字节的空间。在内存中,数组的首地址指向第一个元素的地址,数组的最后一个元素存储在数组末尾的位置。

Java 数组的访问

在 Java 中,数组的元素可以通过数组下标访问。数组下标从 0 开始,依次递增,直到数组的长度减 1。下面是访问数组元素的示例代码:


int[] arr = new int[5];arr[0] = 1;arr[1] = 2;arr[2] = 3;arr[3] = 4;arr[4] = 5;
System.out.println(arr[0]); // 输出1System.out.println(arr[1]); // 输出2System.out.println(arr[2]); // 输出3System.out.println(arr[3]); // 输出4System.out.println(arr[4]); // 输出5
复制代码


在上面的示例代码中,我们首先创建了一个长度为 5 的整型数组,然后分别给数组的前 5 个元素赋值。最后,通过数组下标访问数组的元素,并将元素的值打印出来。


需要注意的是,如果访问数组中不存在的元素,将会抛出ArrayIndexOutOfBoundsException异常。

多维数组的内存结构

在 Java 中除了一维数组,Java 还支持多维数组。多维数组可以看作是一维数组的扩展,它可以是二维、三维,甚至可以是更高维度的数组。


对于二维数组,可以将其看作是一组一维数组的集合,每个一维数组中存储着相同的元素类型。在内存中,二维数组按行存储,即每行的元素是连续存储的。


下面是一个二维数组在内存中的结构示意图:


|-----------------------------------------------------||                 |                 |                 ||  arr[0][0] 的值  |  arr[0][1] 的值  |  arr[0][2] 的值  |  |                 |                 |                 ||-----------------------------------------------------||                 |                 |                 ||  arr[1][0] 的值  |  arr[1][1] 的值  |  arr[1][2] 的值  ||                 |                 |                 ||-----------------------------------------------------||                 |                 |                 ||  arr[2][0] 的值  |  arr[2][1] 的值  |  arr[2][2] 的值  ||                 |                 |                 ||-----------------------------------------------------|
复制代码


上图中,arr是一个 3 行 3 列的整型数组,每个元素占据 4 个字节的空间。在内存中,二维数组的每个元素都可以通过两个下标访问。例如,访问数组中第 2 行第 3 列的元素可以使用以下代码:


int[][] arr = new int[3][3];arr[1][2] = 3;System.out.println(arr[1][2]); // 输出3
复制代码


在 Java 中,多维数组的定义和访问方式与一维数组类似。例如,定义一个 3 行 4 列的二维数组可以使用以下代码:


int[][] arr = new int[3][4];
复制代码


在定义多维数组时,可以只指定其中一维的长度,例如以下代码定义了一个长度为 3 的一维数组和一个长度为 5 的二维数组:


int[] arr1 = new int[3];int[][] arr2 = new int[5][];
复制代码


需要注意的是,在定义二维数组时,每个一维数组的长度可以不同。例如,以下代码定义了一个长度为 3、4、5 的三个一维数组组成的二维数组:


int[][] arr = new int[3][];arr[0] = new int[3];arr[1] = new int[4];arr[2] = new int[5];
复制代码

Java 数组的拷贝

Java 数组的拷贝操作可以将一个数组的元素复制到另一个数组中。Java 中提供了两种数组拷贝方式:浅拷贝和深拷贝。


浅拷贝是指将一个数组的引用赋给另一个数组,这样两个数组引用同一块内存空间。当修改其中一个数组的元素时,另一个数组的对应元素也会被修改。以下是一个浅拷贝的示例:


int[] arr1 = {1, 2, 3};int[] arr2 = arr1;arr2[0] = 4;System.out.println(Arrays.toString(arr1)); // 输出 [4, 2, 3]System.out.println(Arrays.toString(arr2)); // 输出 [4, 2, 3]
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3 的一维数组arr1,然后将其引用赋给arr2。接着,我们修改了arr2的第一个元素为 4,最后输出了arr1arr2的元素值,发现两个数组的第一个元素都变成了 4,这说明浅拷贝操作修改了两个数组的元素。


深拷贝是指将一个数组的所有元素逐个复制到另一个数组中,这样两个数组在内存中占据不同的空间。当修改其中一个数组的元素时,另一个数组的对应元素不会受到影响。以下是一个深拷贝的示例:


int[] arr1 = {1, 2, 3};int[] arr2 = Arrays.copyOf(arr1, arr1.length);arr2[0] = 4;System.out.println(Arrays.toString(arr1)); // 输出 [1, 2, 3]System.out.println(Arrays.toString(arr2)); // 输出 [4, 2, 3]
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3 的一维数组arr1,然后使用Arrays.copyOf()方法将其复制到arr2中。接着,我们修改了arr2的第一个元素为 4,最后输出了arr1arr2的元素值,发现只有arr2的第一个元素变成了 4,arr1没有受到影响,这说明深拷贝操作没有修改原始数组。


需要注意的是,对于多维数组,数组拷贝操作只会复制数组的第一维。如果需要对多维数组进行深拷贝,可以使用循环或递归方式逐个复制所有元素。

Java 数组的排序

Java 中提供了多种数组排序算法,常用的有冒泡排序、插入排序、选择排序、快速排序、归并排序等。以下是 Java 中常用的几种排序算法:

冒泡排序

冒泡排序是一种简单的排序算法,它重复地遍历数组,每次比较相邻的两个元素,如果顺序错误则交换它们的位置,直到遍历完整个数组。


以下是一个冒泡排序的示例:


int[] arr = {5, 2, 8, 1, 4};for (int i = 0; i < arr.length - 1; i++)```java    for (int j = 0; j < arr.length - i - 1; j++) {        if (arr[j] > arr[j + 1]) {            int temp = arr[j];            arr[j] = arr[j + 1];            arr[j + 1] = temp;        }    }}System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 4, 5, 8]
复制代码


在上面的示例中,我们首先定义了一个包含元素 5、2、8、1、4 的一维数组arr,然后使用两个嵌套的 for 循环遍历整个数组。内部循环每次比较相邻的两个元素,如果前面的元素比后面的元素大,则交换它们的位置。外部循环控制了循环次数,每一轮循环都会把最大的元素交换到数组的最后一个位置。最后输出排序后的数组,得到[1, 2, 4, 5, 8]。


冒泡排序的时间复杂度为 O(n^2),虽然它的实现简单,但在处理大量数据时效率较低。

快速排序

快速排序是一种高效的排序算法,它基于分治的思想,通过选定一个基准元素,将数组分成两个部分,其中一个部分的所有元素都比基准元素小,另一个部分的所有元素都比基准元素大。然后递归地对两个部分进行排序。


以下是一个快速排序的示例:


public static void quickSort(int[] arr, int left, int right) {    if (left < right) {        int pivotIndex = partition(arr, left, right);        quickSort(arr, left, pivotIndex - 1);        quickSort(arr, pivotIndex + 1, right);    }}
private static int partition(int[] arr, int left, int right) { int pivot = arr[left]; int i = left + 1; int j = right; while (i <= j) { while (i <= j && arr[i] <= pivot) { i++; } while (i <= j && arr[j] > pivot) { j--; } if (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[left]; arr[left] = arr[j]; arr[j] = temp; return j;}
复制代码


在上面的示例中,我们定义了一个快速排序的方法quickSort()和一个分区的方法partition()quickSort()方法接收一个一维数组和数组的左右边界,首先判断左边界是否小于右边界,如果小于,则选定数组的第一个元素为基准元素,使用partition()方法将数组分成两个部分,然后递归地对两个部分进行排序。partition()方法接收一个一维数组和数组的左右边界,首先选定数组的第一个元素为基准元素,使用两个指针 i 和 j 从左右两端向中间扫描,将比基准元素小的元素交换到数组的左边,比基准元素大的元素交换到数组的右边。最后将基准元素交换到它正确的位置上,并返回它的下标。


以下是一个对数组arr进行快速排序的示例:


int[] arr = {5, 2, 8, 1, 4};quickSort(arr, 0, arr.length - 1);System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 4, 5, 8]
复制代码


在上面的示例中,我们首先定义了一个包含元素 5、2、8、1、4 的一维数组arr,然后调用quickSort()方法对数组进行排序,最后输出排序后的数组,得到[1, 2, 4, 5, 8]。


快速排序的时间复杂度为 O(nlogn),虽然它的实现比冒泡排序要复杂一些,但在处理大量数据时效率更高。

数组的遍历

数组的遍历是指按顺序访问数组中的每个元素。在 Java 中,可以使用 for 循环或 foreach 循环对数组进行遍历。


以下是使用 for 循环对数组进行遍历的示例:


int[] arr = {1, 2, 3, 4, 5};for (int i = 0; i < arr.length; i++) {    System.out.print(arr[i] + " ");}// 输出 1 2 3 4 5
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3、4、5 的一维数组arr,然后使用 for 循环遍历整个数组,每次输出当前元素的值。


以下是使用 foreach 循环对数组进行遍历的示例:


int[] arr = {1, 2, 3, 4, 5};for (int num : arr) {    System.out.print(num + " ");}// 输出 1 2 3 4 5
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3、4、5 的一维数组arr,然后使用 foreach 循环遍历整个数组,每次输出当前元素的值。


无论是 for 循环还是 foreach 循环,对于一维数组的遍历都是非常简单的。

数组的复制

Java 提供了多种方式对数组进行复制。其中,使用Arrays.copyOf()方法和System.arraycopy()方法是最常见的两种方式。


以下是使用Arrays.copyOf()方法对数组进行复制的示例:


int[] arr1 = {1, 2, 3, 4, 5};int[] arr2 = Arrays.copyOf(arr1, arr1.length);System.out.println(Arrays.toString(arr2)); 
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3、4、5 的一维数组arr1,然后使用Arrays.copyOf()方法将该数组复制到另一个数组arr2中,最后输出复制后的数组arr2


以下是使用System.arraycopy()方法对数组进行复制的示例:


int[] arr1 = {1, 2, 3, 4, 5};int[] arr2 = new int[arr1.length];System.arraycopy(arr1, 0, arr2, 0, arr1.length);System.out.println(Arrays.toString(arr2)); // 输出 [1, 2, 3, 4, 5]
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3、4、5 的一维数组arr1,然后创建了一个长度与arr1相同的一维数组arr2,最后使用System.arraycopy()方法将arr1中的所有元素复制到arr2中,最后输出复制后的数组arr2


无论是Arrays.copyOf()方法还是System.arraycopy()方法,它们都可以方便地对数组进行复制,从而在编写代码时提高了效率。

数组的扩容和缩容

在 Java 中,一维数组的长度是不可变的,一旦定义了数组的长度,就无法再改变它。如果需要在运行时增加或减少数组的长度,可以使用其他的数据结构,例如 ArrayList。


对于需要频繁进行扩容或缩容操作的情况,ArrayList 是比较适合的一种数据结构。但是,如果只需要偶尔进行扩容或缩容操作,也可以考虑使用新数组来代替旧数组,从而实现数组的扩容和缩容。


以下是一个数组扩容的示例:


int[] arr1 = {1, 2, 3, 4, 5};int[] arr2 = new int[arr1.length + 1];System.arraycopy(arr1, 0, arr2, 0, arr1.length);arr2[arr2.length - 1] = 6;System.out.println(Arrays.toString(arr2)); // 输出 [1, 2, 3, 4, 5, 6]
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3、4、5 的一维数组arr1,然后创建了一个长度比arr1大 1 的一维数组arr2,使用System.arraycopy()方法将arr1中的所有元素复制到arr2中,最后将新元素 6 添加到arr2的最后一个位置。最终输出扩容后的数组arr2


数组缩容的实现方式与数组扩容类似,只需要将新数组的长度设置为原数组的长度减 1,然后使用System.arraycopy()方法将原数组的元素复制到新数组中即可。以下是一个数组缩容的示例:


int[] arr1 = {1, 2, 3, 4, 5};int[] arr2 = new int[arr1.length - 1];System.arraycopy(arr1, 0, arr2, 0, arr2.length);System.out.println(Arrays.toString(arr2)); // 输出 [1, 2, 3, 4]
复制代码


在上面的示例中,我们首先定义了一个包含元素 1、2、3、4、5 的一维数组arr1,然后创建了一个长度比arr1小 1 的一维数组arr2,使用System.arraycopy()方法将arr1中的前 4 个元素复制到arr2中,最后输出缩容后的数组arr2


需要注意的是,当进行数组扩容或缩容操作时,原数组中的元素可能会被拷贝到新数组中。如果原数组中的元素是对象类型,那么拷贝时实际上只是复制了对象的引用,而不是对象本身。因此,如果修改新数组中的某个元素,可能会影响原数组中相应元素的值。为了避免这种情况,可以使用 Arrays.copyOf()方法或 System.arraycopy()方法来创建新数组,并将原数组的元素复制到新数组中,这样就可以确保新数组中的元素不会影响原数组中的元素。

结论

本文介绍了 Java 数组在内存中的存储方式,以及数组的基本操作,包括访问数组元素、遍历数组、数组的复制、数组的扩容和缩容等。数组是 Java 中最基本的数据结构之一,它可以用于存储一组相关数据,并且可以方便地进行操作。在实际开发中,需要根据实际情况选择不同的数据结构,以便更好地实现所需的功能。

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2023-04-15 加入

还未添加个人简介

评论

发布
暂无评论
Java 数组在内存中的结构是怎样的?数组访问、遍历、复制、扩容、缩容如何编写代码?_Java_Java架构历程_InfoQ写作社区