【JavaSE笔记】数组

一、前言

数组被广泛应用于各种应用场景中。

在这篇文章中,我们将深入探讨Java数组的概念、定义、基本操作、多维数组以及常见的应用场景。

二、数组的基本概念

1、什么是数组

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

在java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个车位,从上图中可以看到:

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

  • 数组的空间是连在一起的

  • 每个空间有自己的编号,其实位置的编号为0,即数组的下标。

2、数组的创建

java 复制代码
T[] 数组名 = new T[N];

T:表示数组中存放元素的类型

T[ ]:表示数组的类型

N:表示数组的长度

值得注意的是,一旦创建了数组,就不能再改变它的大小!

在 Java中数组是对象,不是基本数据类型(原生类),大小不可变且连续存储,因为是对象所以存储在堆空间中。数组长度在创建时就确定了,要改变数组长度只能重新创建新的数组,将原有数组复制到新的数组之中。这也是ArrayList扩容时候的效率低于LinkedList的原因。

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

3、数组的初始化

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

Ⅰ、动态初始化

动态初始化:在创建数组时,直接指定数组中元素的个数

java 复制代码
int[] array = new int[10];

此时,没有给数组赋值,所以开辟的数组空间中的初始值全部为0

java 复制代码
 public static void main(String[] args){
        int[] array = new int[5];
        for(int i = 0;i<5;i++){
            System.out.println(array[i]);
        }
    }

Ⅱ、静态初始化

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

java 复制代码
T[] 数组名称 = {data1, data2, data3, ..., datan};
java 复制代码
int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hell", "Java", "!!!"};

【注意事项】

  • 静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。

  • 静态初始化时, {}中数据类型必须与[]前数据类型一致。

  • 静态初始化可以简写,省去后面的new T[]。

java 复制代码
// 注意:虽然省去了new T[], 但是编译器编译代码时还是会还原
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", "!!!"};
  • 静态和动态初始化也可以分为两步,但是省略格式不可以。
java 复制代码
int[] array1;
array1 = new int[10];
int[] array2;
array2 = new int[]{10, 20, 30};
// 注意省略格式不可以拆分, 否则编译失败
// int[] array3;
// array3 = {1, 2, 3};
  • 如果没有对数组进行初始化,数组中元素有其默认值
类型 默认值
byte 0
short 0
int 0
long 0
float 0.0f
double 0.0
char /u0000
boolean false
  • 如果数组中存储元素类型为引用类型,默认值为null

4、数组的使用

Ⅰ、数组中元素的访问

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

java 复制代码
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]);

注意事项

  • 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素

  • 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常

Ⅱ、for循环遍历

"遍历"是指将数组中的所有元素都访问一遍,访问是指对数组中的元素进行某种操作,比如:打印。

java 复制代码
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]);

上述代码可以起到对数组中元素遍历的目的,但问题是:

​ 1.如果数组中增加了一个元素,就需要增加一条打印语句。

​ 2.如果输入中有100个元素,就需要写100个打印语句。

​ 3.如果现在要把打印修改为给数组中每个元素加1,修改起来非常麻烦。

通过观察代码可以发现,对数组中每个元素的操作都是相同的,则可以使用循环来进行打印

java 复制代码
int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < 5; i++){
	System.out.println(array[i]);
}

改成循环之后,上述三个缺陷可以全部2和3问题可以全部解决,但是无法解决问题1。那能否获取到数组的长度呢?

【注意】在数组中可以通过 数组对象.length 来获取数组的长度

java 复制代码
int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < array.length; i++){
System.out.println(array[i]);
}

Ⅲ、for-each遍历

也可以使用for-each 遍历数组

java 复制代码
int[] array = {1, 2, 3};
for (int x : array) {
	System.out.println(x);
}

for-each循环(也被称为增强型for循环)是一种简化迭代集合(如数组、ListSet等)

for-each循环提供了一种更简洁、易读的方式来遍历集合元素。

java 复制代码
for (Type Name : collection) {
 // 代码块
}
//Type:表示集合中元素的类型。
//Name:是一个临时变量,用于在每次迭代中存储集合中的当前元素。
//collection:要遍历的集合。

如果需要处理一个集合中的所有元素,可以用for each循环语句对传统循环语句进行改进。

Ⅳ、数组的拷贝

Java 中Arrays.copyOf 可以用于复制原数组,返回一个新的数组,并且这个新数组可以选择一个新的长度。

java 复制代码
int new_array = Arrays.copyOf(array,new_length);
  • array:原数组。
  • new_length:新数组的长度。
  • 如果数组元素是数值型,那么多余的元素将被赋值为0;如果数组元素是布尔型,那么多余元素将被赋值为false
java 复制代码
    public static void main(String[] args) {
        int[] array1 = new int[5];
        for (int i = 0; i < array1.length; i++) {
            array1[i] = i;
        }
        int[] array2 = Arrays.copyOf(array1,array1.length*2);
        for (int x:array2) {
            System.out.print(x+" ");
        }
    }

三、数组是引用类型

1、初试JVM的内存分布

内存是一段连续的存储空间,主要用来存储程序运行时数据的。

  • 程序运行时代码需要加载到内存

  • 程序运行产生的中间数据要存放在内存

  • 程序中的常量也要保存

  • 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁

如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。因此JVM也对所使用的内存按照功能的不同进行了划分:

程序计数器 (PC Register) : 只是一个很小的空间, 保存下一条执行的指令的地址

虚拟机栈(JVM Stack) : 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧 ,栈帧中包含有:局部变量表操作数栈动态链接返回地址 以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了

本地方法栈(Native Method Stack) : 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的。

堆(Heap) : JVM所管理的最大内存区域。使用new创建的对象都是在堆上保存堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。

方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的的字节码就是保存在这个区域。

2、基本类型与引用类型

基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值。

引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。

java 复制代码
public static void func() {
    int a = 10;
    int b = 20;
    int[] arr = new int[]{1,2,3};
}

在上述代码中,a、b、arr,都是函数内部的变量,因此其空间都在main方法对应的栈帧中分配。

a、b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值。

array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址。

从上图可以看到,引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象

这个有点类似C语言中的指针,但是Java中引用要比指针的操作更简单。

3、引用变量

java 复制代码
public static void func() {
    int[] array1 = new int[3];
    array1[0] = 10;
    array1[1] = 20;
    array1[2] = 30;
    int[] array2 = new int[]{1,2,3,4,5};
    array2[0] = 100;
    array2[1] = 200;
    array1 = array2;
    array1[2] = 300;
    array1[3] = 400;
    array2[4] = 500;
    for (int i = 0; i < array2.length; i++) {
		System.out.println(array2[i]);
	}
}

引用变量其实就是一个变量,不过他存储的是一个对象的内存地址。

4、认识null

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

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

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

注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联

四、数组的应用

1、保存数据

java 复制代码
public static void main(String[] args) {
    int[] array = {1, 2, 3};
    for(int i = 0; i < array.length; ++i){
    	System.out.println(array[i] + " ");
    }
}

2、作为函数的参数

Ⅰ、参数传数组类型

java 复制代码
 public static void print(int[] array){
        array[0] = 8;
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
    }


    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        print(array);
    }

发现在print方法内部修改数组的内容, 方法外部的数组内容也发生改变。因为数组是引用类型,按照引用类型来进行传递,是可以修改其中存放的内容的。

总结: 所谓的 "引用" 本质上只是存了一个地址。Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实只是将数组的地址传入到函数形参中。这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大)

Ⅱ、作函数的返回值

java 复制代码
    public static int[] func(){
        int a = 111;
        int b = 222;
        return new int[]{a,b};
    }
    public static void main(String[] args){
        int[] array = func();
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
    }

例如:写一个方法myToString,传入一个数组的时候以字符串的方式进行打印

java 复制代码
    public static String myToString(int[] array) {
        if(array==null)
            return "null";
        String s = "[";
        for (int i = 0; i < array.length; i++) {
            s+=array[i];
            if(i!= array.length-1)
                s+=",";
        }
        s+="]";
        return s;
    }

    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5, 6};
        int[] array2 = null;
        String ret = myToString(array);
        String ret2 = myToString(array2);
        System.out.println(ret);
        System.out.println(ret2);
    }

五、二维数组

二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组

java 复制代码
数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
java 复制代码
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int row = 0; row < arr.length; row++) {
	for (int col = 0; col < arr[row].length; col++) {
		System.out.printf("%d\t", arr[row][col]);
	}
System.out.println("");
}
// 执行结果
1 2 3 4
5 6 7 8
9 10 11 12

二维数组的用法和一维数组并没有明显差别。

同理, 还存在 "三维数组", "四维数组" 等更复杂的数组, 只不过出现频率都很低。

六、数组练习

1、求数组元素的平均值

题目:给定一个整型数组, 求平均值

java 复制代码
    public static double ave(int [] array){
        int sum = 0;
        for (int i = 0; i < array.length; i++) {
            sum+=array[i];
        }
        return (double)sum /(double)array.length;
    }

    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7,8,9,9,7,5};
        double ret = ave(array);
        System.out.println(ret);
    }

2、查找数组中的指定元素

给定一个数组, 再给定一个元素, 找出该元素在数组中的位置

java 复制代码
    public static int find (int[] array,int n){
        for (int i = 0; i < array.length; i++) {
            if(array[i]==n)
                return i;
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7,8,9,9,7,5};
        int ret = find(array,3);
        System.out.println("下标为:"+ ret);
    }

3、二分查找

针对有序数组, 可以使用更高效的二分查找

以升序数组为例, 二分查找的思路是先取中间位置的元素, 然后使用待查找元素与数组中间元素进行比较:

  • 如果相等,即找到了返回该元素在数组中的下标

  • 如果小于,以类似方式到数组左半侧查找

  • 如果大于,以类似方式到数组右半侧查找

java 复制代码
    public static int binSearch(int[] array,int val) {
        int l = 0, r = array.length - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (val > array[mid]) {
                l = mid + 1;
            } else if (val < array[mid]) {
                r = mid - 1;
            }
            else {
                return mid;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] array = {1,2,2,3,4,5,6,7,8};
        System.out.println(binSearch(array,3));
    }

Java自带的也有二分查找,可以直接使用。

Arrays.binarySearch方法是Java中的java.util.Arrays类提供的一个实用方法,用于在已排序的数组中搜索指定元素。

Arrays.binarySearch有许多重载版本,可以处理不同类型的数组,如int[]long[]float[]double[]short[]char[]byte[]Object[]等。

java 复制代码
    public static void main(String[] args) {
        int[] array = {1,2,2,3,4,5,6,7,8};
        int ret = Arrays.binarySearch(array,3);
        System.out.println(ret);
    }

4、冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。

比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

java 复制代码
public static void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = 1; j < arr.length-i; j++) {
                if (arr[j-1] > arr[j]) {
                    int tmp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = tmp;
                }
            }
        } 
    } 
public static void main(String[] args) {
        int[] array = {1,9,2,3,4,5,8,7,8};
        bubbleSort(array);
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
    }

冒泡排序性能较低, Java 中内置了更高效的排序算法

Arrays.sort 是 Java 中 java.util.Arrays 类提供的一组实用方法,用于对数组进行排序。

java 复制代码
 public static void main(String[] args) {
        int[] arr = {12, 3, 6, 8, 2, 15, 1, 10};
        // 对数组进行排序
        Arrays.sort(arr);
        // 输出排序后的数组
        System.out.println("排序后: " + Arrays.toString(arr));
    }

5、数组的拷贝

java 复制代码
    public static int[] cope (int[] array){
        int [] copearray = new int[array.length];
        for (int i = 0; i < array.length; i++) {
            copearray[i] = array[i];
        }
        return copearray;
    }
    public static void main(String[] args) {
        int[] array = {1,9,2,3,4,5,8,7,8};
        int[] ret = cope(array);
        System.out.println(Arrays.toString(ret));
        System.out.println("------------------");
        int[] ret2 = Arrays.copyOf(array,array.length);
        System.out.println(Arrays.toString(ret2));

    }

注意:数组当中存储的是基本类型数据时,不论怎么拷贝基本都不会出现什么问题,但如果存储的是引用数据类型,拷贝时需要考虑深浅拷贝的问题。

相关推荐
你是狒狒吗16 分钟前
HttpServletRequest是什么
java
你们补药再卷啦28 分钟前
springboot 项目 jmeter简单测试流程
java·spring boot·后端
菜鸡且互啄6940 分钟前
sql 向Java的映射
java·开发语言
顾林海1 小时前
深度解析HashMap工作原理
android·java·面试
cwtlw1 小时前
PhotoShop学习09
笔记·学习·其他·photoshop
雷渊1 小时前
介绍一下RocketMQ的几种集群模式
java·后端·面试
菜菜的后端私房菜1 小时前
深入解析Java SPI🌟从使用到原理的全面之旅🚀
java·后端·设计
AronTing1 小时前
01-Spring Cloud Alibaba 微服务架构总览与核心组件详解
java·微服务·架构
一棵开花的树,枝芽无限靠近你1 小时前
【CodeMirror】系列(二)官网示例(六)自动补全、边栏
前端·笔记·学习·编辑器·codemirror