【Java零基础·第4章】数组 - 详解

数组是Java中最基础也最常用的数据结构之一,它就像一个有序的容器,可以存储多个相同类型的数据。本文将从数组的基本概念讲起,逐步深入到各种实用的数组操作技巧和算法,帮助你全面掌握Java数组的使用。

一、数组基础概念

1.1 什么是数组?

数组是用来管理一组相同数据类型变量的数据结构。想象一下,如果你需要记录一个班级50名学生的成绩,使用单个变量需要声明50个变量,而使用数组只需要一个数组变量即可,极大地简化了代码。

数组的核心特点:

  • 使用[]作为标志性符号
  • 包含多个相同数据类型的元素
  • 每个元素通过"下标"(index)来访问,下标从0开始
  • 数组长度固定,一旦创建无法更改
  • 数组长度通过数组名.length获取

1.2 数组的基本操作

数组的"遍历"是最基本也最重要的操作,即逐个访问数组中的元素,通常使用for循环实现:

public class ArrayTraversal {
public static void main(String[] args) {
// 定义一个存储成绩的数组
int[] scores = {86, 98, 75, 88, 93};
// 遍历数组(快捷键:itar 或 数组名.fori)
for (int i = 0; i < scores.length; i++) {
System.out.println("第" + (i + 1) + "个同学的成绩:" + scores[i]);
}
}
}

⚠️ 注意:数组下标范围是[0, 数组名.length - 1],如果访问超出这个范围的下标,会抛出ArrayIndexOutOfBoundsException(数组下标越界异常)。

二、数组的声明与初始化

数组的使用分为两步:声明和初始化。初始化就是确定数组的长度和元素值。

2.1 静态初始化

当你已经知道数组中每个元素的值时,可以使用静态初始化:

// 方式一:声明和初始化同时完成(推荐)
元素类型[] 数组名 = {元素1, 元素2, 元素3, ...};
// 方式二:声明和初始化分开
元素类型[] 数组名;
数组名 = new 元素类型[]{元素1, 元素2, 元素3, ...};

示例:

public class StaticInitialization {
public static void main(String[] args) {
// 存储一周七天的英文名称
String[] weekDays = {"Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"};
// 存储平年每个月的天数
int[] daysInMonth;
daysInMonth = new int[]{31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
// 遍历月份天数
for (int i = 0; i < daysInMonth.length; i++) {
System.out.println((i + 1) + "月有" + daysInMonth[i] + "天");
}
}
}

2.2 动态初始化

当你只知道数组长度,不知道具体元素值时,使用动态初始化。此时数组元素会被赋予默认值:

元素类型[] 数组名 = new 元素类型[长度];

不同数据类型的默认值:

数据类型默认值
byte, short, int0
long0L
float0.0F
double0.0
char‘\u0000’(空字符)
booleanfalse
引用类型(如String)null

示例:

import java.util.Scanner;
public class DynamicInitialization {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入班级人数:");
int count = input.nextInt();
// 动态初始化数组,存储学生成绩
int[] scores = new int[count];
// 从键盘输入成绩
for (int i = 0; i < scores.length; i++) {
System.out.print("请输入第" + (i + 1) + "个学生的成绩:");
scores[i] = input.nextInt();
}
// 显示所有成绩
System.out.println("班级成绩如下:");
for (int i = 0; i < scores.length; i++) {
System.out.print(scores[i] + " ");
}
input.close();
}
}

三、数组的内存理解

理解数组在内存中的存储方式,有助于更好地掌握数组的特性。

  • 数组名存储的是数组在内存中的首地址(引用)
  • 数组元素在内存中连续存储
  • 访问元素时,通过"首地址 + 元素宽度 × 下标"计算元素位置

在Java中,数组的内存分配与管理是理解数组特性的核心,以下从内存模型角度详细详细说明:

1、数组的内存分配机制

Java中数组是引用数据类型,其内存分配涉及两种关键内存区域:

  1. 栈内存(Stack):存储局部变量(包括数组引用变量),特点是内存自动分配和释放,速度快。
  2. 堆内存(Heap):存储数组的实际元素数据,特点是动态分配内存,需要JVM的垃圾回收机制回收,速度较慢。

2、一维数组的内存解析

int[] arr = new int[3];为例,内存分配过程如下:

  1. 声明数组引用int[] arr在栈内存中创建一个引用变量arr,此时它还未指向任何对象(值为null)。
  2. 创建数组对象new int[3]在堆内存中开辟一块连续的内存空间,用于存储3个int类型元素。
    • 数组长度一旦确定不可修改(因为堆内存空间大小固定)。
    • 元素会被赋予默认值(int类型默认值为0)。
  3. 关联引用与对象:堆内存会为数组分配一个唯一的内存地址,栈中的arr变量存储该地址,从而指向堆中的数组对象。
栈内存          堆内存
+--------+     +--------+
|  arr   | --> | [0] 0  |  // 索引0
+--------+     +--------+
               | [1] 0  |  // 索引1
               +--------+
               | [2] 0  |  // 索引2
               +--------+
               (地址:0x0012)

3、二维数组的内存解析

二维数组本质是“数组的数组”,即外层数组的元素是内层数组的引用。以int[][] arr = new int[2][3];为例:

  1. 外层数组分配new int[2][3]先在堆内存中创建外层数组(长度为2),其元素是两个int[]类型的引用(默认值为null)。
  2. 内层数组分配:自动为外层数组的每个元素创建内层数组(长度为3),并将内层数组的地址赋值给外层数组的对应元素。
  3. 引用关联:栈中的arr变量指向外层数组的地址。
栈内存          堆内存(外层数组)         堆内存(内层数组)
+--------+     +--------------+         +--------+
|  arr   | --> | [0] 0x1122   | -->     | [0] 0  |
+--------+     +--------------+         +--------+
                                        | [1] 0  |
                                        +--------+
                                        | [2] 0  |
                                        +--------+
               +--------------+         +--------+
           --> | [1] 0x3344   | -->     | [0] 0  |
               +--------------+         +--------+
                                        | [1] 0  |
                                        +--------+
                                        | [2] 0  |
                                        +--------+
注意:不规则二维数组

int[][] arr = new int[2][]; arr[0] = new int[2]; arr[1] = new int[4];,内层数组长度可不同,内存分布如下:

堆内存(外层数组)         堆内存(内层数组)
+--------------+         +--------+
| [0] 0x1122   | -->     | [0] 0  |
+--------------+         +--------+
                         | [1] 0  |
+--------------+         +--------+
| [1] 0x3344   | -->     | [0] 0  |
+--------------+         +--------+
                         | [1] 0  |
                         +--------+
                         | [2] 0  |
                         +--------+
                         | [3] 0  |
                         +--------+

4、数组赋值与引用的内存表现

  1. 数组引用赋值:如int[] arr2 = arr;,此时arr2arr指向堆中同一个数组对象,修改arr2[0]会影响arr[0]

    栈内存          堆内存            栈内存
    +--------+     +--------+       +--------+
    |  arr   | --> | [0] 10 |  <--  |  arr2  |
                   +--------+       +--------+
                   | [1] 20 |  // 同一块内存
                   +--------+
  2. 数组拷贝(新对象):如int[] arr2 = Arrays.copyOf(arr, arr.length);,会在堆中创建新数组,并复制原数组元素,此时arrarr2指向不同对象,修改互不影响。

5、数组的生命周期

  • 当数组引用变量超出作用域(如方法执行结束),栈内存中的引用被释放。
  • 若堆中的数组对象不再被任何引用指向,会被JVM的垃圾回收器(GC)标记为“可回收”,最终释放内存。

总结

  • 数组的引用存储在栈内存,实际元素存储在堆内存。
  • 一维数组是连续的单一内存块,二维数组是“数组的数组”,内层数组可独立分配内存。
  • 数组长度固定的本质:堆内存一旦分配,空间大小不可改变。
  • 引用赋值与对象拷贝的区别:前者共享内存,后者独立内存。

数组引用特性

  1. 两个变量指向同一个数组:修改一个变量所指的元素,另一个变量也会看到变化
public class ArrayReference1 {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = a;  // b和a指向同一个数组
b[0] = 100;   // 修改b所指的数组元素
System.out.println(a[0]);  // 输出100,因为a和b指向同一个数组
}
}
  1. 两个变量指向不同数组:修改一个数组的元素,不影响另一个数组
public class ArrayReference2 {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = {4, 5, 6};  // b指向新的数组
b[0] = 100;
System.out.println(a[0]);  // 输出1,a和b是不同的数组
}
}

四、二维数组

二维数组可以理解为"数组的数组",适合存储表格形式的数据,如矩阵、成绩单等。

4.1 二维数组的概念

  • 二维数组的标志性符号是[][]
  • 二维数组中的每个元素都是一个一维数组
  • 例如:int[][] scores可以存储多个班级的成绩,每个班级的成绩是一个一维数组

4.2 二维数组的初始化

静态初始化
// 直接指定每个元素(每个元素都是一个一维数组)
元素类型[][] 数组名 = {
{元素1, 元素2, ...},
{元素1, 元素2, ...},
...
};

示例:

public class TwoDStaticInit {
public static void main(String[] args) {
// 存储3个班级的成绩
int[][] classScores = {
{89, 85, 86, 75},   // 一班成绩
{99, 98, 93, 92, 91}, // 二班成绩
{63, 76}            // 三班成绩
};
// 遍历二维数组
for (int i = 0; i < classScores.length; i++) {
System.out.println("第" + (i + 1) + "班成绩:");
for (int j = 0; j < classScores[i].length; j++) {
System.out.print(classScores[i][j] + " ");
}
System.out.println();
}
}
}
动态初始化
  1. 规则的二维数组(每行长度相同):
元素类型[][] 数组名 = new 元素类型[行数][每行元素数];

示例:

public class TwoDRectangular {
public static void main(String[] args) {
// 创建3行4列的二维数组(规则矩阵)
int[][] matrix = new int[3][4];
// 给数组赋值
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
matrix[i][j] = i * 10 + j;
}
}
// 打印数组
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
System.out.print(matrix[i][j] + "\t");
}
System.out.println();
}
}
}
  1. 不规则的二维数组(每行长度可以不同):
// 先确定行数,再分别确定每行的长度
元素类型[][] 数组名 = new 元素类型[行数][];
数组名[0] = new 元素类型[1行长度];
数组名[1] = new 元素类型[2行长度];
...

示例:

import java.util.Scanner;
public class TwoDIrregular {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// 创建3行的二维数组,每行长度不确定
int[][] scores = new int[3][];
scores[0] = new int[5];  // 第1组有5人
scores[1] = new int[3];  // 第2组有3人
scores[2] = new int[4];  // 第3组有4人
// 输入成绩
for (int i = 0; i < scores.length; i++) {
for (int j = 0; j < scores[i].length; j++) {
System.out.print("请输入第" + (i + 1) + "组第" + (j + 1) + "人的成绩:");
scores[i][j] = input.nextInt();
}
}
// 显示成绩
for (int i = 0; i < scores.length; i++) {
System.out.print("第" + (i + 1) + "组成绩:");
for (int j = 0; j < scores[i].length; j++) {
System.out.print(scores[i][j] + " ");
}
System.out.println();
}
input.close();
}
}

五、一维数组常用算法

掌握这些算法,能帮你解决大部分数组操作问题。

5.1 求数组的最大值和最小值

思路:

  1. 假设数组第一个元素是最值
  2. 用后面的每个元素与当前最值比较
  3. 如果发现更大/更小的元素,更新最值
public class ArrayMaxMin {
public static void main(String[] args) {
int[] numbers = {5, 6, 2, 7, 8, 1, 3};
// 求最大值
int max = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i];
}
}
// 求最小值
int min = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] < min) {
min = numbers[i];
}
}
System.out.println("最大值:" + max);  // 输出8
System.out.println("最小值:" + min);  // 输出1
}
}

5.2 查找最值及其下标

不仅要找到最值,还要记录它在数组中的位置:

public class ArrayMaxIndex {
public static void main(String[] args) {
int[] numbers = {5, 6, 2, 7, 8, 1, 3};
// 记录最大值的下标
int maxIndex = 0;
// 遍历数组,找到最大值的下标
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > numbers[maxIndex]) {
maxIndex = i;
}
}
System.out.println("最大值是:" + numbers[maxIndex]);  // 输出8
System.out.println("最大值下标是:" + maxIndex);      // 输出4
System.out.println("最大值是第" + (maxIndex + 1) + "个元素");  // 输出第5个
}
}

如果数组中有重复的最值元素:

public class ArrayMaxDuplicate {
public static void main(String[] args) {
int[] numbers = {5, 9, 2, 9, 8, 1, 9};
// 1. 先找到最大值
int max = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i];
}
}
// 2. 再找出所有等于最大值的元素下标
System.out.println("最大值是:" + max);
System.out.println("最大值的下标有:");
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == max) {
System.out.print(i + " ");  // 输出1 3 6
}
}
}
}

5.3 查找目标元素

顺序查找

逐个比较数组中的元素,直到找到目标或遍历完数组:

import java.util.Scanner;
public class SequentialSearch {
public static void main(String[] args) {
int[] numbers = {5, 6, 2, 7, 8, 1, 3};
Scanner input = new Scanner(System.in);
System.out.print("请输入要查找的数字:");
int target = input.nextInt();
boolean found = false;
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == target) {
found = true;
System.out.println("找到了,下标是:" + i);
break;  // 找到后退出循环
}
}
if (!found) {
System.out.println("没找到这个数字");
}
input.close();
}
}

如果数组是有序的,可以优化顺序查找:

public class OptimizedSearch {
public static void main(String[] args) {
int[] sortedArray = {2, 4, 6, 8, 12, 24};  // 有序数组(从小到大)
int target = 7;
boolean found = false;
for (int i = 0; i < sortedArray.length; i++) {
if (sortedArray[i] == target) {
found = true;
break;
} else if (sortedArray[i] > target) {
// 因为数组有序,后面的元素只会更大,所以可以提前退出
break;
}
}
System.out.println(found ? "找到了" : "没找到");  // 输出没找到
}
}
二分查找

对于有序数组,二分查找是更高效的查找方式,时间复杂度为O(logn):

public class BinarySearch {
public static void main(String[] args) {
int[] sortedArray = {2, 4, 6, 8, 12, 24};  // 必须是有序数组
int target = 8;
int left = 0;                  // 左边界
int right = sortedArray.length - 1;  // 右边界
int index = -1;                // 记录找到的下标,初始为-1表示没找到
while (left <= right) {
// 计算中间位置
int mid = left + (right - left) / 2;  // 避免溢出,等价于(left + right) / 2
if (sortedArray[mid] == target) {
index = mid;
break;  // 找到目标,退出循环
} else if (sortedArray[mid] < target) {
// 目标在右侧,调整左边界
left = mid + 1;
} else {
// 目标在左侧,调整右边界
right = mid - 1;
}
}
if (index != -1) {
System.out.println("找到了,下标是:" + index);  // 输出3
} else {
System.out.println("没找到");
}
}
}

5.4 数组反转

将数组元素的顺序颠倒过来:

public class ArrayReverse {
public static void main(String[] args) {
int[] numbers = {5, 6, 7, 2, 1};
System.out.print("反转前:");
for (int num : numbers) {
System.out.print(num + " ");  // 输出5 6 7 2 1
}
// 方案一:对称位置交换(推荐,节省空间)
for (int left = 0, right = numbers.length - 1; left < right; left++, right--) {
// 交换left和right位置的元素
int temp = numbers[left];
numbers[left] = numbers[right];
numbers[right] = temp;
}
/*
// 方案二:使用新数组
int[] newArray = new int[numbers.length];
for (int i = 0; i < numbers.length; i++) {
newArray[numbers.length - 1 - i] = numbers[i];
}
numbers = newArray;
*/
System.out.print("\n反转后:");
for (int num : numbers) {
System.out.print(num + " ");  // 输出1 2 7 6 5
}
}
}

5.5 数组排序

冒泡排序

冒泡排序的基本思想是重复地走访数组,一次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来:

public class BubbleSort {
public static void main(String[] args) {
int[] numbers = {2, 8, 6, 3, 1, 7, 4};
System.out.print("排序前:");
for (int num : numbers) {
System.out.print(num + " ");
}
// 冒泡排序
for (int i = 1; i < numbers.length; i++) {  // 控制轮数
boolean swapped = false;  // 标记本轮是否发生交换
// 每轮比较次数递减
for (int j = 0; j < numbers.length - i; j++) {
if (numbers[j] > numbers[j + 1]) {
// 交换元素
int temp = numbers[j];
numbers[j] = numbers[j + 1];
numbers[j + 1] = temp;
swapped = true;  // 发生了交换
}
}
// 如果本轮没有交换,说明数组已经有序,可以提前退出
if (!swapped) {
break;
}
}
System.out.print("\n排序后:");
for (int num : numbers) {
System.out.print(num + " ");  // 输出1 2 3 4 6 7 8
}
}
}
选择排序

选择排序的基本思想是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完:

public class SelectionSort {
public static void main(String[] args) {
int[] numbers = {5, 6, 3, 4, 1};
System.out.print("排序前:");
for (int num : numbers) {
System.out.print(num + " ");
}
// 选择排序
for (int i = 0; i < numbers.length - 1; i++) {
// 找到从i开始的最小值及其下标
int minIndex = i;
for (int j = i + 1; j < numbers.length; j++) {
if (numbers[j] < numbers[minIndex]) {
minIndex = j;
}
}
// 如果最小值不在i位置,交换
if (minIndex != i) {
int temp = numbers[i];
numbers[i] = numbers[minIndex];
numbers[minIndex] = temp;
}
}
System.out.print("\n排序后:");
for (int num : numbers) {
System.out.print(num + " ");  // 输出1 3 4 5 6
}
}
}

5.6 数组扩容

由于数组长度固定,当需要存储更多元素时,需要进行扩容:

import java.util.Scanner;
public class ArrayGrow {
public static void main(String[] args) {
// 初始数组长度为5
int[] numbers = new int[5];
Scanner input = new Scanner(System.in);
int count = 0;  // 记录已存入的元素个数
while (true) {
System.out.print("请输入一个整数(输入0结束):");
int num = input.nextInt();
if (num == 0) {
break;  // 输入0,结束循环
}
// 存入数组
numbers[count] = num;
count++;
// 如果数组已满,进行扩容
if (count >= numbers.length) {
// 创建一个更大的数组(原长度的1.5倍)
int[] newArray = new int[numbers.length + (numbers.length >> 1)];
// 复制原数组元素到新数组
for (int i = 0; i < numbers.length; i++) {
newArray[i] = numbers[i];
}
// 让numbers指向新数组
numbers = newArray;
System.out.println("数组已扩容,新长度:" + numbers.length);
}
}
System.out.println("最终数组元素:");
for (int i = 0; i < count; i++) {
System.out.print(numbers[i] + " ");
}
input.close();
}
}

5.7 数组元素的插入和删除

插入元素
import java.util.Scanner;
public class ArrayInsert {
public static void main(String[] args) {
int[] numbers = {10, 20, 30, 40, 50};
Scanner input = new Scanner(System.in);
System.out.print("请输入要插入的元素:");
int newNum = input.nextInt();
// 1. 先扩容数组(增加一个位置)
int[] newArray = new int[numbers.length + 1];
for (int i = 0; i < numbers.length; i++) {
newArray[i] = numbers[i];
}
numbers = newArray;
// 2. 将要插入位置及其后面的元素右移
// 这里插入到下标为1的位置
for (int i = numbers.length - 1; i > 1; i--) {
numbers[i] = numbers[i - 1];
}
// 3. 插入新元素
numbers[1] = newNum;
// 4. 显示结果
System.out.println("插入后数组:");
for (int num : numbers) {
System.out.print(num + " ");
}
input.close();
}
}
删除元素
public class ArrayDelete {
public static void main(String[] args) {
int[] numbers = {10, 20, 30, 40, 50};
System.out.print("删除前:");
for (int num : numbers) {
System.out.print(num + " ");  // 输出10 20 30 40 50
}
// 删除下标为1的元素(值为20)
// 1. 把要删除位置后面的元素左移
for (int i = 1; i < numbers.length - 1; i++) {
numbers[i] = numbers[i + 1];
}
// 2. 清空最后一个位置(可选)
numbers[numbers.length - 1] = 0;
System.out.print("\n删除后:");
for (int num : numbers) {
System.out.print(num + " ");  // 输出10 30 40 50 0
}
}
}

六、数组的实际应用案例

统计学生成绩

import java.util.Scanner;
public class ScoreStatistics {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入学生人数:");
int count = input.nextInt();
// 存储学生成绩
int[] scores = new int[count];
// 输入成绩
for (int i = 0; i < scores.length; i++) {
System.out.print("请输入第" + (i + 1) + "个学生的成绩:");
scores[i] = input.nextInt();
}
// 计算总分
int sum = 0;
for (int score : scores) {
sum += score;
}
// 计算平均分
double average = (double) sum / scores.length;
// 找最高分
int max = scores[0];
for (int score : scores) {
if (score > max) {
max = score;
}
}
// 找最低分
int min = scores[0];
for (int score : scores) {
if (score < min) {
min = score;
}
}
// 统计及格人数(60分及以上)
int passCount = 0;
for (int score : scores) {
if (score >= 60) {
passCount++;
}
}
// 输出统计结果
System.out.println("总分:" + sum);
System.out.println("平均分:" + average);
System.out.println("最高分:" + max);
System.out.println("最低分:" + min);
System.out.println("及格人数:" + passCount);
System.out.println("及格率:" + (double) passCount / scores.length * 100 + "%");
input.close();
}
}

总结

数组是Java编程的基础,掌握数组的操作是学好Java的关键一步。本文介绍了数组的基本概念、声明初始化方式、内存模型以及常用算法,包括查找、排序、扩容等操作。

通过大量的示例代码,我们学习了如何:

  • 创建和初始化一维数组和二维数组
  • 遍历数组并访问元素
  • 查找数组中的最值和目标元素
  • 对数组进行排序和反转
  • 动态扩容数组以及插入删除元素

这些知识不仅适用于Java,也适用于其他编程语言,是计算机科学的基础。多加练习,熟练掌握这些操作,将为你的编程之路打下坚实的基础。

posted on 2025-11-08 21:25  blfbuaa  阅读(7)  评论(0)    收藏  举报