当前位置: 首页 > news >正文

Java:数组的定义和使用(万字解析)

目录

1. 数组的概念

2. 数组的基础知识

2.1 数组的创建

\1. 基础创建格式:

\2. 类似C语言的创建格式:

【错误的创建(初始化)格式】

2.2 数组的数据类型

2.3 数组的初始化 —— 两种方式

\1.动态初始化:(完全默认初始化)

\2. 静态初始化:

【注意事项】

3. 数组的使用

3.1 数组元素的下标访问

3.2 计算数组长度

3.3 循环遍历数组的2种方式

\1. for语句

\2. for - each语句

【两种遍历方式的区别】

4. 数组是引用类型(内存空间的底层知识)

4.1 JVM的内存区域划分

4.2 内存分配:基础类型的变量 和 引用类型的变量

4.3 数组大小可变吗?

4.4 “数组1 = 数组2”会发生什么?

4.5 空引用null

5. 数组 与 方法

5.1 数组作为方法参数

5.2 数组作为方法返回类型

6. Arrays的一些实用工具方法

6.1 数组转字符串

6.2 数组的拷贝

6.3 数组的排序

6.4 二分查找指定元素

6.5 判断两个数组是否相等

6.6 填充数组元素


1. 数组的概念

数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间

1. 数组中存放的元素其类型相同

2. 数组的空间是连在一起的

3. 每个空间有自己的编号,起始位置的编号为0,即数组的下标。

2. 数组的基础知识

首先要明确一点,数组在Java中属于引用类型,引用类型变量的创建都需要new出来。

2.1 数组的创建

\1. 基础创建格式:

T[ ] 数组名 = new T[N]; 

  • T:表示数组中存放元素的类型
  • T[ ]:表示数组的类型
  • N:表示数组的长度

例如:

  1. int[] array1 = new int[10];       // 创建一个可以容纳10个int类型元素的数组
  2. double[] array2 = new double[5];  // 创建一个可以容纳5个double类型元素的数组
  3. String[] array3 = new String[3];  // 创建一个可以容纳3个字符串元素的数组

数组的创建也可以类似C语言那样,把方括号[]写在变量名的右边。

\2. 类似C语言的创建格式:

T 数组名[ ] = new T[N];

  • T:表示数组中存放元素的类型
  • T[ ]:表示数组的类型
  • N:表示数组的长度

例如:

  1. int array1[] = new int[10]; // 创建一个可以容纳10int类型元素的数组
  2. double array2[] = new double[5]; // 创建一个可以容纳5double类型元素的数组
  3. String array3[] = new String[3]; // 创建一个可以容纳3个字符串元素的数组

【错误的创建(初始化)格式】

  • 没有new一个数组对象。
  • 数组长度的定义写在了第一个方括号[ ]里面

例如:

2.2 数组的数据类型

注意:java中数组的数据类型是T[],而不是T[n]

【在C语言中,数组的数据类型是T[n]】

比如现在有1个元素个数为3的整型数组“int[] arr = new int[3]”,那么该数组arr的数据类型是int[ ],而不是int[3]

2.3 数组的初始化 —— 两种方式

数组的初始化主要分为动态初始化以及静态初始化

\1.动态初始化(完全默认初始化)

在创建数组时,直接指定数组中元素的个数N。创建后,从0到N-1的数组元素都被默认初始化

语法格式:T[ ] 数组名 = new T[N];

例如:int[] array = new int[10];

  • 如果数组中存储元素类型为基础类型,默认值为基础数据类型对应的默认值,比如:

  • \u 是用来表示Unicode转义字符的前缀。它的格式为 \u 后面跟着四个十六进制数字,用于表示一个特定的Unicode字符。

例如:\u0041 表示字符 A,因为 0041A 的Unicode编码。

  • 如果数组中存储元素类型为引用类型默认值为null

\2. 静态初始化:

在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定

(完全格式)语法格式①:T[ ] 数组名 = new T[ ]{data1, data2, data3, ..., datan};   (左边也可以写成“T 数组名[ ]”)

(省略格式)语法格式②:T[ ] 数组名 = {data1, data2, data3, ..., datan};    (左边也可以写成“T 数组名[ ]”)

【这写法类似C语言的数组完全初始化,虽然看上去省去了new T[ ],但是编译器编译代码时还是会还原

例如:

格式1的静态初始化:

  • int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
  • String[] array3 = new String[]{"hell", "Java", "!!!"};

格式2的静态初始化:

  • int[] array1 = {0,1,2,3,4,5,6,7,8,9};
  • double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
  • String[] array3 = {"hell", "Java", "!!!"};

【注意事项】

  • 1.静态和动态初始化也可以分为两步,但是省略格式不可以。【类似C语言的初始化格式不能分成两步,而且这在C语言中也是不可以的
int[] array1;array1 = new int[10];int[] array2;array2 = new int[]{10, 20, 30};// 注意省略格式不可以拆分, 否则编译失败
// int[] array3;// array3 = {1, 2, 3};
  • 2. 在使用省略格式的静态初始化时,浮点数数组不能用float[ ]类型接收,整数无限制。(所以不太建议使用省略格式)

  • 3. 数组的初始化不能既是动态初始化,又是静态初始化。(同时也说明:Java中不能像C语言那样不完全初始化。)

这里既直接指定了数组的大小是5(动态初始化),又用大括号来指定数组大小(静态初始化),所以报错了。

3. 数组的使用

3.1 数组元素的下标访问

数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过 下标访问其任意位置的元素。比如:

int[]array = new int[]{10, 20, 30, 40, 50};System.out.println(array[0]);System.out.println(array[1]);System.out.println(array[2]);System.out.println(array[3]);System.out.println(array[4]);// 也可以通过[]对数组中的元素进行修改
array[0] = 100;System.out.println(array[0]);

tips:在Java中,方括号 [] 确实用于访问数组中的元素,但它并不被称为“下标访问运算符”或者索引运算符。这与其他支持运算符重载的语言有所不同。Java将 [] 视为数组访问的直接语法仅用于数组,并没有定义为运算符或允许重载。

【注意事项】

  1. 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素。 
  2. 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。(java.lang.ArrayIndexOutOfBoundsException异常代表数组越界访问)

3.2 计算数组长度

在数组中可以通过数组对象.length来获取数组的长度。

例如:

输出:


为什么可以这样获得数组的长度?(简单了解)

  • 我们注意到,我们用到了一个符号".",这个符号是成员访问运算符,用法就类似C语言中的结构体。Java中的对象就类似C语言的结构体(但会多了很多复杂的语法,比如对象中可以有方法)
  • 其实在Java中使用数组时,会自动导入“java.lang.reflect.Array”类。我们new的int[10]或String[4]等等,其实是new了(创建了)一个Array类型的对象
  • 而Array类中有一个成员变量length,它记录着数组的长度。【可以简单得理解为C语言中的顺序表

3.3 循环遍历数组的2种方式

\1. for语句

这是最简单的方法,例如:

int[] arr = new int[10];
for(int i = 0; i < arr.length; i++){System.out.println(arr[i]);
}

\2. for - each语句

for-each 是 for 循环的另外一种使用方式。

语法格式:

for (元素类型 变量名 : 数组名) {

        // 循环体

}

  1. “元素类型”是数组中元素的类型
  2. “变量名”是在每次迭代中用来引用当前元素的变量
  3. “数组名”是要遍历的数组

注意:迭代变量与数组名之间有一个冒号“ : ”

例如:

int[] arr = new int[10];
for(int x: arr){System.out.println(x);
}
  • arr数组中的每个元素都是int型,所以创建的变量x是int类型的。
  • 第一次循环中,x等于arr[0];第二次循环中,x等于arr[1]……以此类推。

【两种遍历方式的区别】

循环条件控制:

  • for语句中的循环条件由程序员编写,可以完成更复杂的要求,但也容易出错。
  • for-each语句不需要手动管理索引,使得代码的意图更加明确,减少了出错的可能性,但循环的条件不能手动改变。

元素访问方式:

  • for循环中通过访问数组下标会直接对数组元素进行操作
  • for-each循环通过迭代变量间接访问数组元素,即使发生错误操作也不会改变原数组的数据

4. 数组是引用类型(内存空间的底层知识)

4.1 JVM的内存区域划分

JVM对所使用的内存按照功能的不同进行了划分,主要分为5的区域:

  • 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址。
  • 虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一 些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。局部变量的存储、方法栈帧空间的开辟和销毁就在这里)
  • 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似。只不过保存的内容是Native方法的局部变量在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的。
  • 堆区(Heap): JVM所管理的最大内存区域。使用 new 创建的对象都是在堆上保存 ,堆是随着程序开始运行时而创建,随着程序的退出而销毁。在运行时,堆中的数据只要还有在使用,就不会被销毁
  • 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的的字节码就是保存在这个区域。

补充一点:字节码是与平台无关的中间代码表示形式。字节码是Java平台核心特性之一,它的存在使得Java程序具备了高度的可移植性和灵活性。

4.2 内存分配:基础类型的变量 和 引用类型的变量

基本数据类型创建的变量,该变量空间中直接存放的是其所对应的数值; 而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址

例如:

public static void func() {int a = 10;int b = 20;int[] arr = new int[]{1,2,3};}
  • 在上述代码中,a、b、arr,都是函数内部的局部变量,因此其空间都在func方法对应的栈帧中分配。(即它们的空间在栈区上)
  • a、b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值
  • array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址,而数组的本体在堆区连续存放

引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该 地址,引用变量便可以去操作对象。有点类似C语言中的指针,但是Java中引用要比指针的操作更简单。

4.3 数组大小可变吗?

先说结论:数组的大小是固定的,一旦创建无法改变其大小

public static void main(String[] args) {int arr[] = new int[10];System.out.println(arr);arr = new int[5];System.out.println(arr);}

可以发现,虽然我们用new的方式把数组arr的大小从10改变成了5,但前后两次arr存储的地址值不同了。

这说明数组的大小是无法改变的,每次使用new时都会重新创建一个新的数组

4.4 “数组1 = 数组2”会发生什么?

  1. arr1 = arr2,相当于把arr2所引用的地址交给了arr1,所以现在arr1和arr2都指向了同一个数组
  2. arr1现在改变了引用的对象,如果arr1原来所引用的数组没有被其他引用变量引用,则系统会自动回收该堆上的空间

【C语言中不支持“数组1 = 数组2”的语法】

例如:

public static void func() {int[] array1 = new int[]{10,20,30};int[] array2 = new int[]{1,2,3,4,5};int[] t = array1;array1 = array2;array2 = t;for(int x:array1){System.out.print(x+" ");}System.out.println();for (int x:array2){System.out.print(x+" ");}}

运行结果:

可以发现array1和array2所引用的对象成功被交换。

【注意事项】

在对象交换的过程中,数组array1、数组array2和临时变量 t 都必须是同类型的。(刚刚的例子中它们都是int[]类型的变量)

4.5 空引用null

null 在 Java 中表示 "空引用" , 也就是一个不指向对象的引用.

 int[] arr = null;System.out.println(arr[0]);// 执行结果
Exception in thread "main" java.lang.NullPointerExceptionat Test.main(Test.java:6)

null 的作用类似于 C 语言中的 NULL (空指针),都是表示一个无效的内存位置。 因此不能对这个内存进行任何读写操作。 一旦尝试读写,就会抛出 NullPointerException。

注意事项:

1. 当引用类型的变量没有new一个对象的时候,必须要赋值null。

2. null不能写成NULL,这是因为:

  • NULL是“ (void*)0 ”的宏定义,在Java中既没有宏定义的语法,也没有指针的语法。

5. 数组 与 方法

5.1 数组作为方法参数

数组作为方法参数时,形参数组要与实参数组的数组类型一致

看一个例子:

public static void main(String[] args) {int[] arr = {1, 2, 3};func(arr);System.out.println("arr[0] = " + arr[0]);}public static void func(int[] a) {a[0] = 10;System.out.println("a[0] = " + a[0]);}// 执行结果
a[0] = 10
arr[0] = 10

可以发现,在func方法内部修改数组的内容, 方法外部的数组内容也发生改变。

与C语言类似:数组作为参数传递的也是数组的地址

但不同的是:

  • C语言中的形参数组是一个“伪数组”,本质上是一个指针变量,所以形参的类型可以用指针类型来代替
  • 而Java中的形参数组是一个“真数组”,本质上是数组类型的引用变量,所以形参的类型不能用其他引用类型来代替

将数组的地址传入到函数形参中,这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大)。

5.2 数组作为方法返回类型

数组作为返回值,一般都是在方法内部new了一个数组对象,然后返回该对象的地址。

例如:

 public static int[] makeIntArray(int n){int[] arr = new int[n];return arr;}public static void main(String[] args) {int[] array = makeIntArray(10);}

这种用法就类似C语言中,给顺序表中的数组用malloc函数或colloc函数创建数组一样。

6. Arrays的一些实用工具方法

Java为我们提供了一个专门处理数组的工具类Arrays,里面有一系列实用的方法。

【注意】我们本章使用的工具类是Arrays类,不是Array类:

  • 数组类型:是Java语言的核心部分,没有特定的包路径。
  • Array:位于 java.lang.reflect 包中,主要用于反射机制中的数组操作。
  • Arrays:位于 java.util 包中,提供了更多实用的数组操作方法,适用于日常开发中的数组处理需求。

它们三者都是引用类型,前者是数组类型,后两者属于类类型;三者均无包含关系

  • 即使没有显式导入 Array 类,你仍然可以创建和操作数组,这是因为数组是Java语言的核心部分。
  • 不过Arrays需要显式导入“import  java.util.Arrays

6.1 数组转字符串

toString方法可以把数组的信息转换为字符串。

  • 参数是要被转化数组
  • 返回类型是字符串。
  • 原数组的信息不会被修改

例如:

public static void main(String[] args) {int[] arr = new int[]{1,2,3,4,5};String str = Arrays.toString(arr);//把数组转换为字符串,由字符串str接收//打印数组被转换后的结果System.out.println(str);//打印原数组for(int x: arr){System.out.print(x+" ");}}

输出结果:


模拟实现Arrays.toString

public static String myToString(int[] arr){String str = "[";for(int i = 0; i < arr.length; i++){str += arr[i];if(i != arr.length - 1){  //最后一个元素不要加逗号str += ",";}}str += "]";return str;}
public static void main(String[] args) {int[] arr1 = new int[]{1,2,3,4,5};String str = myToString(arr1);System.out.println(str);}

输出:

6.2 数组的拷贝

1. 两个参数的copyOf (数组名arr,拷贝长度N)

  • 在方法中新创建一个长度为N的数组,把arr中的数据拷贝到该方法中的数组,最后返回该数组的地址。
  • 从序号0一直拷贝到下标N-1处。
  • 如果长度N大于arr的大小,则用默认值填充
public static void main(String[] args) {int[] arr1 = new int[]{1,2,3,4,5};int[] arr2 = Arrays.copyOf(arr1,7);for(int x: arr2){System.out.print(x+" ");}}

输出:


2. 三个参数的copyOfRange(数组名arr,整数1,整数2)

  • 使用方式与上面的方法类似,区别是该方法是范围拷贝。
  • 拷贝范围是 [ 整数1,整数2 )(左闭右开)
  • 拷贝的范围如果超出arr数组的右边,则用用默认值填充

例如:

public static void main(String[] args) {int[] arr1 = new int[]{1,2,3,4,5};int[] arr2 = Arrays.copyOfRange(arr1,3,7); //拷贝区间[3,7)for(int x: arr2){System.out.print(x+" ");}}

输出:

6.3 数组的排序

使用sort方法可以为数组排序

例如:

public class Blog {public static void main(String[] args) {int[] arr = {9, 5, 2, 7};Arrays.sort(arr);System.out.println(Arrays.toString(arr));}
}

6.4 二分查找指定元素

1. 两个参数的binarySearch (数组名arr,  要查找的元素key)

  • 返回值 是key所在的序号
  • 如果找不到该元素,则会返回一个负数

例如:

public static void main(String[] args) {int[] arr = {0,1,2,3,4,5,6,7,8,9};int pos = Arrays.binarySearch(arr,5);System.out.println("要查找的元素位于序号"+pos);}


二分查找方法的模拟

 public static int myBinarySearch(int[] arr, int key){int left = 0;int right = arr.length-1;while(left <= right) {  //如果left不能等于right的话,那么当key在最右边时,会误以为找不到该元素int mid = (left + right) / 2;if(arr[mid] > key){right = mid - 1;//此时要找的数据在mid左边,right变成mid-1,则区间[left,right]就位于mid左边了}else if(arr[mid] < key){left = mid + 1;//此时要找的数据在mid右边,left变成mid+1则区间[left,right]就位于mid右边了}else{return mid;}}return -1;}
public static void main(String[] args) {int[] arr = {0,1,2,3,4,5,6,7,8,9};int pos = myBinarySearch(arr,9);System.out.println("要查找的元素位于序号"+pos);}


2. 四个参数的binarySearch (数组名arr, 左边界k1, 右边界k2, 要查找的元素key)

  • 效果与上一个方法类似,区别是在规定范围内查找,区间[ k1, k2 )(左闭右开)

例如:

public static void main(String[] args) {int[] arr = {0,1,2,3,4,5,6,7,8,9};int pos = Arrays.binarySearch(arr,6,9,9);//区间[6,9)System.out.println("要查找的元素位于序号"+pos);pos = Arrays.binarySearch(arr,6,10,9);//区间[6,10)System.out.println("要查找的元素位于序号"+pos);}

6.5 判断两个数组是否相等

使用equals(数组1,数组2)可以判断两个数组是否相等。

  • 相等返回true,不相等返回false。

例如:

public static void main(String[] args) {int[] arr = new int[]{1,2,3,4};int[] arr1 = new int[]{1,2,3,4};int[] arr2 = new int[]{1,2,3,3};System.out.println(Arrays.equals(arr, arr1));System.out.println(Arrays.equals(arr, arr2));}

6.6 填充数组元素

1. 两个参数的 fill (数组名arr,填充值m)

  • 用填充值m对数组arr中的所有元素进行赋值。
  • 该方法会直接操作数组arr

例如:

 public static void main(String[] args) {int[] arr = new int[]{1,2,3,4};Arrays.fill(arr,5);System.out.println(Arrays.toString(arr));}


2. 四个参数的 fill (数组名arr,左边界k1,右边界k2,填充值m)

  • 范围填充,区间是[ k1, k2 )

例如:

public static void main(String[] args) {int[] arr = new int[]{1,2,3,4};Arrays.fill(arr,1,3,6);System.out.println(Arrays.toString(arr));}


总的来说,数组作为Java编程中最基本且常见的数据结构之一,其重要性不言而喻。通过深入了解数组的特点、用法以及在实际应用中的优势,开发者可以更好地应用它来解决问题,提高代码的效率和可读性。


本期分享完毕,谢谢大家的支持Thanks♪(・ω・)ノ


http://www.mrgr.cn/news/66039.html

相关文章:

  • 【HarmonyOS Next】数据本地存储:@ohos.data.preferences
  • isaac sim 14 物理学习
  • Android沙箱
  • 【已解决】群晖docker无法删除容器 “Error response from daemon: container” 终极解决办法
  • 曼切斯特编码原理以及FPGA实现
  • MyBatis 第二章
  • 企业平台生态嵌入数据集(2000-2023年)
  • 应用层协议-FTP协议
  • 电商邮件营销策略:提升邮件转化率的关键!
  • 宁德时代Java面试题及参考答案
  • 应用层内网代理
  • 【真题笔记】16年系统架构设计师要点总结
  • 人工智能理论之Opencv图像预处理(2)
  • 【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统
  • 手机号加密脱敏还原方案
  • 铠侠CD8系列产品对比 KCD81PUG3T20 KCD81PJE3T20 KCD81VUG3T20
  • 数据库范式设计
  • Stable Diffusion(2024)Ai绘画AIGC最新安装包资源下载+自学教程
  • w020基于Java的免税商品优选购物商城设计与实现
  • 103 - Lecture 2 Table and Data Part 1
  • 瑞芯微RK3568开发板Linux编译报错404怎么办?触觉智能教你轻松解决
  • 利士策分享,青年暴富难守,因何在?
  • Visitor 访问者模式
  • 【喂饭级AI教程】手把手教你在本机安装Stable Diffusion秋包【附带全套资源】
  • uniapp中echarts的正确集成方式:Vue2与Vue3组合式API双重视角
  • Java 序列化:深入探索 Serializable 接口