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

admin 9345 2026-07-31 12:34:43

数组是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, int0long0Lfloat0.0Fdouble0.0char‘\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中数组是引用数据类型,其内存分配涉及两种关键内存区域:

栈内存(Stack):存储局部变量(包括数组引用变量),特点是内存自动分配和释放,速度快。堆内存(Heap):存储数组的实际元素数据,特点是动态分配内存,需要JVM的垃圾回收机制回收,速度较慢。2、一维数组的内存解析以int[] arr = new int[3];为例,内存分配过程如下:

声明数组引用:int[] arr在栈内存中创建一个引用变量arr,此时它还未指向任何对象(值为null)。创建数组对象:new int[3]在堆内存中开辟一块连续的内存空间,用于存储3个int类型元素。

数组长度一旦确定不可修改(因为堆内存空间大小固定)。元素会被赋予默认值(int类型默认值为0)。关联引用与对象:堆内存会为数组分配一个唯一的内存地址,栈中的arr变量存储该地址,从而指向堆中的数组对象。

栈内存 堆内存

+--------+ +--------+

| arr | --> | [0] 0 | // 索引0

+--------+ +--------+

| [1] 0 | // 索引1

+--------+

| [2] 0 | // 索引2

+--------+

(地址:0x0012)

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

外层数组分配:new int[2][3]先在堆内存中创建外层数组(长度为2),其元素是两个int[]类型的引用(默认值为null)。内层数组分配:自动为外层数组的每个元素创建内层数组(长度为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、数组赋值与引用的内存表现数组引用赋值:如int[] arr2 = arr;,此时arr2和arr指向堆中同一个数组对象,修改arr2[0]会影响arr[0]。

栈内存 堆内存 栈内存

+--------+ +--------+ +--------+

| arr | --> | [0] 10 | <-- | arr2 |

+--------+ +--------+

| [1] 20 | // 同一块内存

+--------+ 数组拷贝(新对象):如int[] arr2 = Arrays.copyOf(arr, arr.length);,会在堆中创建新数组,并复制原数组元素,此时arr和arr2指向不同对象,修改互不影响。

5、数组的生命周期当数组引用变量超出作用域(如方法执行结束),栈内存中的引用被释放。若堆中的数组对象不再被任何引用指向,会被JVM的垃圾回收器(GC)标记为“可回收”,最终释放内存。总结数组的引用存储在栈内存,实际元素存储在堆内存。一维数组是连续的单一内存块,二维数组是“数组的数组”,内层数组可独立分配内存。数组长度固定的本质:堆内存一旦分配,空间大小不可改变。引用赋值与对象拷贝的区别:前者共享内存,后者独立内存。数组引用特性两个变量指向同一个数组:修改一个变量所指的元素,另一个变量也会看到变化

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指向同一个数组

}

}

两个变量指向不同数组:修改一个数组的元素,不影响另一个数组

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();

}

}

}

动态初始化规则的二维数组(每行长度相同):

元素类型[][] 数组名 = 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();

}

}

}

不规则的二维数组(每行长度可以不同):

// 先确定行数,再分别确定每行的长度

元素类型[][] 数组名 = 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 求数组的最大值和最小值思路:

假设数组第一个元素是最值用后面的每个元素与当前最值比较如果发现更大/更小的元素,更新最值

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,也适用于其他编程语言,是计算机科学的基础。多加练习,熟练掌握这些操作,将为你的编程之路打下坚实的基础。

上一篇
下一篇
相关文章