Java基础-数组操作与内存全解析

数组是 Java 中用于存储固定大小相同类型元素集合的基础数据结构。

一、核心特性

  1. 固定长度
    创建后长度不可变,需在声明时指定:
    int[] arr = new int[5];
    内存分配:连续存储空间(堆内存)

  2. 索引访问
    元素通过从 0 开始的索引访问:
    arr[0] = 10;
    索引范围:[0, length-1]

  3. 类型约束
    所有元素必须是同一数据类型(基本类型或对象引用)

二、声明与初始化

方式示例代码说明
先声明后初始化int[] arr;arr = new int[3];默认值:数值型为 0,布尔型为 false
声明时初始化int[] arr = {1, 2, 3};编译器自动推导长度
动态初始化String[] names = new String[]{"A", "B"};允许重新赋值
//动态初始化 包含默认初始化
//使用new操作符来创建数组,语法:
dataType[] arrayRefVar = new dataType[arraySize]
int[] a = new int[10]

//静态初始化 创建+赋值
int[] a = {1,2,3};
Man[] men ={new Man(1,1),new Man(2,2)}; 

三、关键操作

  1. 遍历数组

    int[] arr = new int[10];
    // 标准 for 循环
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
    
    // 增强 for 循环(Java 5+) 快捷键:array.for+enter
    for (int num : arr) {
        System.out.println(num);
    }

     

  2. 多维数组

    // 二维数组声明
    int[][] matrix = new int[3][4]; 
    int[][] array = {{1,2},{3,4},{5,6}};
    
    // 不规则数组
    int[][] jagged = {{1}, {2,3}, {4,5,6}};
    

二维数组介绍

在Java中,二维数组是一种数据结构,用于存储表格状的数据(如矩阵)。它本质上是“数组的数组”,即每个元素本身是一个一维数组。二维数组的行数和列数可以灵活定义,但索引必须从0开始。

1. 二维数组的声明和初始化

声明二维数组时,需指定数据类型和维度。基本语法如下:

  • 声明数据类型[][] 数组名;

  • 初始化:可以通过new关键字分配内存,或直接赋初值。

    常见初始化方式:

    • 固定大小初始化:数组名 = new 数据类型[行数][列数];
    • 直接赋初值:数据类型[][] 数组名 = {{值1, 值2, ...}, {值3, 值4, ...}, ...};

    示例:

    // 声明并初始化一个2行3列的整数数组
    int[][] matrix = new int[2][3]; // 所有元素初始化为0
    // 直接赋初值
    int[][] matrix2 = {{1, 2, 3}, {4, 5, 6}}; // 2行3列
    

     

2. 访问和修改元素

二维数组的元素通过行索引i和列索引j访问,索引从0开始。例如:

  • 访问元素:数组名[i][j]

  • 修改元素:数组名[i][j] = 新值;

    数学上,元素位置表示为arr[i][j],其中i是行索引(范围0到行数-1),j是列索引(范围0到列数-1)。
    示例代码:

    int[][] arr = {{10, 20}, {30, 40}};
    int element = arr[0][1]; // 访问第一行第二列元素,结果为20
    arr[1][0] = 50; // 修改第二行第一列元素为50
    

     

3. 常见操作

遍历二维数组

使用嵌套循环遍历所有元素:

  • 外层循环遍历行,内层循环遍历列。

  • 获取数组大小:

    • 数组名.length 获取行数。
    • 数组名[i].length 获取第i行的列数(支持锯齿状数组)。

    示例代码:

    int[][] matrix = {{1, 2, 3}, {4, 5}}; // 锯齿状数组:第一行3列,第二行2列
    for (int i = 0; i < matrix.length; i++) { // i从0到行数-1
        for (int j = 0; j < matrix[i].length; j++) { // j从0到当前行列数-1
            System.out.print(matrix[i][j] + " ");
        }
        System.out.println(); // 换行
    }
    // 输出:
    // 1 2 3 
    // 4 5
    

     

其他操作

  • 获取元素总数:遍历计算所有元素个数。
  • 复制数组:使用System.arraycopy()或循环手动复制。
  • 动态调整大小:Java二维数组大小固定,需创建新数组实现“扩容”。

4. 完整示例程序

以下是一个完整的Java程序,演示二维数组的声明、初始化、遍历和修改:

public class TwoDArrayExample {
    public static void main(String[] args) {
        // 初始化一个3行2列的字符串数组
        String[][] names = new String[3][2];
        names[0][0] = "Alice";
        names[0][1] = "Bob";
        names[1][0] = "Charlie";
        names[1][1] = "Diana";
        names[2][0] = "Eve";
        names[2][1] = "Frank";

        // 遍历并打印所有元素
        System.out.println("二维数组内容:");
        for (int i = 0; i < names.length; i++) {
            for (int j = 0; j < names[i].length; j++) {
                System.out.print(names[i][j] + " ");
            }
            System.out.println(); // 换行
        }

        // 修改一个元素
        names[1][1] = "David";
        System.out.println("修改后第二行第二列: " + names[1][1]);
    }
}

 

运行此程序输出:

二维数组内容:
Alice Bob 
Charlie Diana 
Eve Frank 
修改后第二行第二列: David

 

 

3. 数组工具类-Array类

Arrays类是Java集合框架的核心工具类(位于java.util包),提供静态方法操作数组,涵盖排序、搜索、比较等常见操作。


1. 数组排序

  • 方法sort()
  • 支持类型:所有基本数据类型及对象数组
  • 底层实现:双轴快速排序(基本类型) / TimSort(对象类型)
int[] nums = {5, 2, 9, 1};
Arrays.sort(nums);  // 结果: [1, 2, 5, 9]

 


2. 二分查找

  • 方法binarySearch()
  • 前提:数组必须已排序
  • 返回值:找到返回索引,否则返回负数
int[] sorted = {1, 3, 5, 7};
int index = Arrays.binarySearch(sorted, 5);  // 返回 2
int notFound = Arrays.binarySearch(sorted, 4); // 返回 -3

 


3. 数组填充

  • 方法fill()
  • 作用:将指定值填充到数组所有/部分位置
char[] chars = new char[5];
Arrays.fill(chars, 'A');  // 结果: ['A','A','A','A','A']
Arrays.fill(chars, 1, 3, 'B'); // 部分填充: ['A','B','B','A','A']

 


4. 数组比较

  • 方法equals() / deepEquals()
  • 区别
    • equals():比较一维数组内容
    • deepEquals():递归比较多维数组
int[] a = {1, 2}, b = {1, 2};
boolean isEqual = Arrays.equals(a, b); // true

int[][] matrix1 = {{1,2}, {3,4}};
int[][] matrix2 = {{1,2}, {3,4}};
boolean deepEqual = Arrays.deepEquals(matrix1, matrix2); // true

 


5. 数组转列表

  • 方法asList()
  • 注意:返回的列表是固定长度(不可增删)
String[] names = {"Alice", "Bob"};
List<String> list = Arrays.asList(names); // 固定大小列表
list.set(0, "Carol"); // 允许修改
// list.add("Dave"); // 抛出 UnsupportedOperationException

 


6. 数组复制

  • 方法copyOf() / copyOfRange()
  • 特点:自动处理新数组长度(截断或补默认值)
int[] origin = {10, 20, 30};
int[] copy1 = Arrays.copyOf(origin, 2); // [10, 20]
int[] copy2 = Arrays.copyOf(origin, 5); // [10,20,30,0,0]
int[] rangeCopy = Arrays.copyOfRange(origin, 1, 3); // [20, 30]

 


7. 数组转字符串

  • 方法toString() / deepToString()
  • 作用:快速输出可读的数组内容
int[] arr = {1, 2, 3};
System.out.println(Arrays.toString(arr)); // 输出: [1, 2, 3]

int[][] matrix = {{1,2}, {3,4}};
System.out.println(Arrays.deepToString(matrix)); // 输出: [[1, 2], [3, 4]]

 


关键特性总结

  1. 静态工具类:无需实例化,直接通过类名调用
  2. 泛型支持:对对象数组操作时自动推断类型
  3. 性能优化:排序/搜索等方法针对不同场景高度优化
  4. 空安全:方法内部处理null输入(如toString(null)返回"null"

最佳实践:优先使用Arrays而非手动实现数组操作,可提升代码健壮性和可读性。

import java.util.Arrays;

// 排序
Arrays.sort(arr);

// 二分查找(需先排序)
int index = Arrays.binarySearch(arr, 5);

// 快速填充
Arrays.fill(arr, -1);

具体完整方法可查看Java官方文档。

四、内存分析

Java内存分析是优化应用程序性能和避免内存问题的关键过程。它涉及理解Java虚拟机(JVM)的内存管理机制,包括内存区域划分、对象生命周期和垃圾回收(Garbage Collection, GC)

步骤1: Java内存模型

JVM将内存划分为几个主要区域,每个区域有特定用途:

  • 堆(Heap)存储所有对象实例和数组。堆是GC的主要工作区,分为新生代(Young Generation)和老年代(Old Generation)。新生代又包括Eden区、Survivor区(S0和S1)。堆的大小可通过JVM参数设置,例如初始堆大小用Xms表示,最大堆大小用Xmx表示。
  • 栈(Stack):每个线程私有,存储局部变量、方法调用和基本类型数据。栈帧(Stack Frame)随方法调用入栈,方法结束出栈。栈溢出常见于递归过深。
  • 方法区(Method Area)存储类信息、常量、静态变量等。在Java 8后,通常由元空间(Metaspace)实现,使用本地内存。
  • 程序计数器(PC Register):线程私有,指示当前执行指令的地址。
  • 本地方法栈(Native Method Stack):支持本地方法(如C/C++代码)调用。

内存分析的核心是监控堆的使用,因为对象创建和GC直接影响性能。例如,堆使用率超过阈值可能触发Full GC,导致应用暂停。

步骤2: 使用内存分析工具

Java提供多种工具来可视化内存使用,帮助诊断问题:

  • JConsole:内置工具,监控堆、线程、GC活动。启动命令:jconsole
  • VisualVM:更强大,支持内存快照(Heap Dump)分析。可安装插件如MAT(Memory Analyzer Tool)。
  • 命令行工具:如jstat监控GC统计,jmap生成堆转储文件。
  • 第三方工具:如Eclipse MAT或YourKit,用于深度分析内存泄漏。

使用步骤:

  1. 运行Java应用时添加JVM参数:-XX:+HeapDumpOnOutOfMemoryError,以便在内存溢出时自动生成堆转储。
  2. 启动VisualVM,连接到目标进程,查看“Monitor”标签下的内存图表。
  3. 分析堆转储:识别大对象或无效引用链。

步骤3: 实际示例与代码分析

以下是一个简单Java程序,演示常见内存问题(如内存泄漏)。

import java.util.ArrayList;
import java.util.List;

public class MemoryLeakExample {
    private static List<Object> leakList = new ArrayList<>(); // 静态集合可能导致内存泄漏

    public static void main(String[] args) {
        while (true) {
            Object obj = new Object(); // 创建新对象
            leakList.add(obj); // 对象被静态集合引用,无法被GC回收
            // 模拟业务逻辑
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

  • 内存分析
    • 堆使用:程序运行后,堆内存持续增长,因为leakList持有对象引用,阻止GC回收。使用JConsole观察,堆曲线呈上升趋势,最终导致OutOfMemoryError
    • 问题诊断:静态集合leakList是根因,应改为弱引用(如WeakReference)或及时清理。
    • 优化建议:避免长生命周期对象引用短生命周期对象;使用null显式解除引用。

五、应用场景

  1. 高频访问:需要O(1)时间复杂度随机访问
  2. 固定数据集:如棋盘状态、常量表
  3. 算法基础:排序/查找算法的底层实现

六、代码实例

冒泡排序

package Array;

public class TestSort {
    static void main(String[] args) {
        int[] array = {5,7,3,7,2,9};
        sort(array);
        printArray(array);
    }
    public static void sort(int[] array){
        int temp = 0;
        for (int i = 0; i < array.length - 1; i++) {
            boolean flag = false;
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j] > array[j + 1]) {
                    temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                    flag = true;
                }
            }
            if (!flag) break;
        }
    }

    public static void printArray(int[] array){
        for (int i = 0; i < array.length; i++) {
            if (i == 0){
                System.out.print("[");
            }
            if (i == array.length-1){
                System.out.print(array[i]+"]");
            }else
                System.out.print(array[i]+", ");
        }
    }
}

七、稀疏数组

稀疏数组是一种高效的数据结构,用于存储具有大量零元素(或默认值)的矩阵。它通过只记录非零元素的位置和值来节省内存空间。在Java中,稀疏数组常用于处理大规模稀疏数据,如图像处理、科学计算和机器学习领域。下面我将逐步解释其原理、实现方法和应用。

1. 稀疏数组的概念

  • 为什么需要稀疏数组?
    在标准二维数组中,如果一个矩阵中非零元素很少(例如,只有10%的元素非零),存储所有元素会浪费大量内存。稀疏数组只存储非零元素,形式为三元组:(行号, 列号, 值)。例如,一个矩阵 A 中,非零元素 a_{ij} 存储为 (i, j, a_{ij})

  • 应用场景

    • 图像处理:存储黑白图像中的像素点(非零值代表黑色)。
    • 网络图:表示图的邻接矩阵(非零值代表连接)。
    • 科学数据:处理大型矩阵中的稀疏数据集。

2. Java实现稀疏数组

在Java中,稀疏数组可以通过自定义类实现,核心是使用列表(如ArrayList)存储三元组。每个三元组包含行索引、列索引和值。以下是关键步骤:

  • 数据结构设计
    • 定义一个类来封装三元组。
    • 添加方法:设置值(set)、获取值(get)、转换回标准数组。
  • 优势与局限
    • 优点:节省内存;支持高效插入和查询非零元素。
    • 缺点:随机访问速度较慢(需遍历列表);不适合高密度数据。

下面是一个完整的Java代码示例。

需求:编写五子棋游戏中,有存盘退出和续上盘的功能

package Array;

import java.util.Arrays;

public class ArrayDome06 {
    static void main(String[] args) {
        int[][] array1 = new int[11][11];
        array1[1][2] = 1;
        array1[2][3] = 2;
        //输出原始数组
        for (int i = 0; i < array1.length; i++) {
            for (int j = 0; j < array1[i].length; j++) {
                System.out.print(array1[i][j]+"\t");
            }
            System.out.println();
        }

        System.out.println("================");
        //转换成稀疏数组
        int sum = 0;
        for (int i = 0; i < array1.length; i++) {
            for (int j = 0; j < array1[i].length; j++) {
                if (array1[i][j] != 0){
                    sum++;
                }
            }
        }
        int[][] array2 = new int[sum+1][3];
        array2[0][0] = 11;
        array2[0][1] = 11;
        array2[0][2] = sum;
        int count = 1;
        for (int i = 0; i < array1.length; i++) {
            for (int j = 0; j < array1[i].length; j++) {
                if (array1[i][j] != 0){
                    array2[count][0] = i;
                    array2[count][1] = j;
                    array2[count][2] = array1[i][j];
                    count++;
                }
            }
        }
        for (int i = 0; i < array2.length; i++) {
            System.out.println(array2[i][0]+"\t"
                    +array2[i][1]+"\t"
                    +array2[i][2]);
        }

        //还原为原数组
        System.out.println("=============");
        int[][] array3 = new int[array2[0][0]][array2[0][1]];
        for (int i = 1; i < array2.length; i++) {
            array3[array2[i][0]][array2[i][1]] = array2[i][2];
        }
        for (int[] ints : array3) {
            for (int ints1 : ints) {
                System.out.print(ints1+"\t");
            }
            System.out.println();
        }

    }
}

代码结果:

0	0	0	0	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	0	0	0	
0	0	0	2	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
================
11	11	2
1	2	1
2	3	2
=============
0	0	0	0	0	0	0	0	0	0	0	
0	0	1	0	0	0	0	0	0	0	0	
0	0	0	2	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	
0	0	0	0	0	0	0	0	0	0	0	

八、注意事项

  1. 数组越界:访问 arr[arr.length] 抛出 ArrayIndexOutOfBoundsException
  2. 长度限制:最大长度受 Integer.MAX_VALUE - 5 限制
  3. 对象数组:存储的是对象引用(非对象本身)

最佳实践:当需要动态扩容时,优先使用 ArrayList(基于数组实现)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值