🎐 前情提要

✨本节目标:

  1. 理解数组基本概念
  2. 掌握数组的基本用法
  3. 数组与方法互操作
  4. 熟练掌握数组相关的常见问题和代码


👩🏻🏫作者: 初入编程的菜鸟哒哒

文章目录

  • 1. 数组的基本概念
  • 1.1 为什么要使用数组
  • 1.2 什么是数组
  • 1.3 数组的创建及初始化
  • 1.4 如何访问数组当中的元素
  • 1.5 如何求数组长度:
  • 1.5 如何遍历数组:
  • 2. 数组是引用类型
  • 2.1 初始JVM的内存分布
  • 2.2 再谈引用变量
  • 2.3 认识 null
  • 3. 数组拷贝
  • 4.数组练习
  • 4.1求数组中元素的平均值
  • 4.2 查找数组中指定元素(顺序查找)
  • 最简单的顺序查找:
  • 二分查找:
  • 4.3 数组排序(冒泡排序)
  • 4.4 数组逆序
  • 5. 二维数组
  • 总结



1. 数组的基本概念

1.1 为什么要使用数组

如果我们想定义3个整型数据,你可能会这样定义:

public static void main(String[] args) {
        int a = 8;
        int b = 6;
        int c = 9;
}

当然,这样定义是没有问题的。
但如果我们想定义10个整型数据呢?
很显然一个一个定义就显得有些麻烦…
在java当中有一个构造数据类型:数组
当你想创建10个变量并且他们是相同类型的时候,我们就可以通过数组来进行定义。

1.2 什么是数组

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

数组的特点:

  1. 数组中存放的元素其类型相同
  2. 数组的空间是连在一起的
  3. 每个空间有自己的编号,其实位置的编号为0,即数组的下标。

那在程序中如何创建数组呢?

1.3 数组的创建及初始化

第一种定义方式:

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

这样就定义好了一个数组。 int[]代表一个整型的数组类型,arr是数组名,虽然我们没有指定数组的长度,但编译器在编译时会根据{}中元素个数来确定数组的长度。

第二种定义方式:

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

第三种定义方式:

public static void main(String[] args) {
        int[] arr = new int[5];//这里面的5是数组的长度
}

这三种定义方式在内存中的存储方式都是一样的,第一种只不过是第二种的一种简写,第三种里面没有存储数据,默认为里面全都是0(boolean默认为false)。

java 可变byte 数组 java可变数组定义_java 可变byte 数组


如果不确定数组当中内容时,应该使用动态初始化。

为什么呢?

因为数组是没办法直接更改里面的所有元素的:

比如:

java 可变byte 数组 java可变数组定义_数组_02


这样写是不被允许的。以定义方法一为例,我们直接打印数组名System.out.println(arr); 可以打印出整个数组吗?

可以看见结果不是整个数组:

java 可变byte 数组 java可变数组定义_java 可变byte 数组_03


java 可变byte 数组 java可变数组定义_System_04


这个时候我们可以发现arr虽然是变量,但里面存了一个地址,这个时候就把这个变量叫做引用变量。

所以我们把里面存地址的变量都叫做引用。

关键字new

我们通过new关键字来创建一个对象。
引用里面存储了对象的地址,我们通常说: 引用指向了/引用了一个对象

1.4 如何访问数组当中的元素

数组名[合法的下标]

举个栗子:

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

java 可变byte 数组 java可变数组定义_System_05


也可以修改:

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

java 可变byte 数组 java可变数组定义_java 可变byte 数组_06


那什么叫合法的下标呢?比如如果我把arr[0]改成arr[5](不合法的下标):

这个时候就会报错:

java 可变byte 数组 java可变数组定义_开发语言_07


如果再初始化只能改成动态的:

java 可变byte 数组 java可变数组定义_System_08

1.5 如何求数组长度:

只需要调用length函数:数组名.length

public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        int len = arr.length;//这个就是数组长度
        System.out.println(len);
}

执行结果:

java 可变byte 数组 java可变数组定义_开发语言_09

1.5 如何遍历数组:

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

第一种打印方式:

使用for循环

java 可变byte 数组 java可变数组定义_开发语言_10


第二种打印方式:

使用for each

java 可变byte 数组 java可变数组定义_System_11

for(int x : arr) {
            System.out.println(x);
}

上面代码的意思是把arr的每项都存在x中,存一个打印一个,存一个打印一个。

第三种打印方式:

调用Arrays中的toString函数,将传进去的数组转化为字符串。

java 可变byte 数组 java可变数组定义_开发语言_12


java 可变byte 数组 java可变数组定义_java_13

2. 数组是引用类型

2.1 初始JVM的内存分布

JVM一共有五块内存:
我们平时嘴边上说的(接触到的)栈都是Java虚拟机栈,堆也是我们现阶段经常用到的。

java 可变byte 数组 java可变数组定义_java_14


那什么叫线程隔离的数据区呢?

在多线程中,每一个线程都有方法区,程序方法区、程序计算器(单独的线程隔离的数据区),而Java数据栈、堆(所有线程共享的数据区)是各个线程共用的。

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

举例讲解一下:

java 可变byte 数组 java可变数组定义_java 可变byte 数组_15


这块代码在内存中的分配情况是怎么样的呢?

java 可变byte 数组 java可变数组定义_开发语言_16


在执行main函数的时候我们会在栈中给main函数开辟一片栈帧。

java 可变byte 数组 java可变数组定义_开发语言_17


这个时候我们就说arr这个引用指向那个堆上的对象。

到这里就能看出来这个通过new(省略了)创建的对象其实存储在堆上。

2.2 再谈引用变量

看这个代码:

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]);
        }
    }
    public static void main(String[] args) {
        func();
    }

在执行arrar1 = array2 语句之前,java虚拟机的数据区是这样的。

java 可变byte 数组 java可变数组定义_java 可变byte 数组_18


执行了之后,array1转为指向array2

java 可变byte 数组 java可变数组定义_开发语言_19


所以下面的array1[2]array1[3]指向的其实就是array2[2]array2[3],array2的指向没有发生改变,所以array2[4]还是指向array2[4]

赋值之后堆上数据变成这样:

java 可变byte 数组 java可变数组定义_java 可变byte 数组_20


array2打印出来:

java 可变byte 数组 java可变数组定义_开发语言_21

2.3 认识 null

java中的局部变量我们在使用的时候一定要初始化。

java 可变byte 数组 java可变数组定义_数组_22

那如果这个变量是一个引用变量我们怎样给它初始化呢?

int[] arr3 = null;

意思是:array3这个引用不指向任何对象。

那这样写有问题吗?

int[] arr3 = null;
System.out.println(arr3.length);

答案是不可以的:

java 可变byte 数组 java可变数组定义_数组_23


编译器给我们报了一个空指针异常。

有一个很棒的方法排查空指针异常:
就是在编译器指向的那一行找.号,一般空指针异常都是用空指针.了一个东西。

或者:

java 可变byte 数组 java可变数组定义_java 可变byte 数组_24


这样也会报空指针异常。

因为arr3这个引用是没有指向对象的

接下来大家做一道题:
下面这个代码的执行结果是什么呢?

public static void func1(int[] array) {
        array = new int[]{1,2,3};
    }
    public static void func2(int[] array) {
        array[0] = 99;
    }

    public static void main(String[] args) {
        int[] array = {9,8,7};
        func1(array);
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+ " ");
        }
        System.out.println();
        System.out.println("===========");
        func2(array);
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+ " ");
        }
        System.out.println();

    }

答案是:

java 可变byte 数组 java可变数组定义_开发语言_25


为什么func1()没有改变array引用所指向对象里面的值,而func2()就改变了呢?

因为

public static void func1(int[] array) {
        array = new int[]{1,2,3};
    }

这个fun1()函数形参array一开始确实存的是实参array的值,但是array = new int[]{1,2,3};这段代码改变了形参array的指向,是在堆中又建立了一个{1,2,3}这个数组,形参array指向了这个新数组,并且出函数就销毁了。
fun2()中的形参array没有改变,所以就直接改变实参array指向的数组中的数据了。

3. 数组拷贝

拷贝:

  1. 要有原来的
  2. 拷贝出来一个新的

非常简单:一个一个放进去就可以了

java 可变byte 数组 java可变数组定义_java_26


第一种方法:

public class TestDemo {
    public static int binarySearch(int[] arr,int num) {
        int left = 0;
        int right = arr.length;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid]<num) {
                left = mid+1;
            }else if(arr[mid]>num) {
                right = mid - 1;
            }else {
                return mid;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        int ret = binarySearch(arr,6);
        System.out.println(ret);
    }
}

难道以后拷贝都用这个方法吗?
不是的。

第二种方法:

可以使用java当中提供的工具类:

copyOf方法在进行数组拷贝时,创建了一个新的数组。

java 可变byte 数组 java可变数组定义_System_27

全部拷贝:

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

拷贝一部分:

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

java 可变byte 数组 java可变数组定义_java_28


扩容:

java 可变byte 数组 java可变数组定义_System_29

public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        int[] copy = Arrays.copyOf(arr,arr.length*2);
        System.out.println(copy.length);
    }

java 可变byte 数组 java可变数组定义_java_30


第三种方法:

也可以使用Arrays.copyOfRange()

可以拷贝一部分:

在Java中一般使用from都是左闭右开的。

java 可变byte 数组 java可变数组定义_java 可变byte 数组_31


第四种方法:

System.arraycopy();

java 可变byte 数组 java可变数组定义_java_32

System.arraycopy();是native方法。

native : C/C++实现的方法。
优点: 快!

System.arraycopy()参数:

  1. 你要拷贝的数组
  2. 你要从这个数组的哪个下标开始访问
  3. 你要拷贝到哪个数组
  4. 你要拷贝到这个数组的哪个位置开始
  5. 你要拷贝多大

java 可变byte 数组 java可变数组定义_java 可变byte 数组_33

下面这个代码叫拷贝吗?

int[] arr = {1,2,3,4,5,6}; 
int[] copy = arr; 
System.out.println(Arrays.toString(copy));

java 可变byte 数组 java可变数组定义_java_34


根本没有发生拷贝,

只是copy这个引用指向了arr这个引用指向的对象。

简单介绍一下浅拷贝深拷贝:

如果能够做到修改拷贝之后的数组不影响原来的数组,就说这个拷贝是深拷贝

如果arr指向的对象里面的每一个元素都是一个引用,那copy所指向的对象里面的每一个元素也都是一个引用。这时我们写这个代码:copy[0].a = 9;就影响了原来的数组,这就是浅拷贝

深拷贝浅拷贝不是说拷贝基本数据类型还是引用类型,具体需要看代码的实现。

拿上面的例子来说如果拷贝arr的时候把arr所指对象 指向的对象也全部拷贝下来了的话,改变copy还是不会改变arr,这就变成了深拷贝。

4.数组练习

4.1求数组中元素的平均值

public static double avg(int[] array) {
        int sum = 0;
        for(int x:array) {
            sum+=x;
        }
        return sum*1.0/array.length;
    }
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,7};
        System.out.println(avg(arr));
    }

java 可变byte 数组 java可变数组定义_java_35

4.2 查找数组中指定元素(顺序查找)

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

最简单的顺序查找:

public static int findKey(int key,int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] == key) {
                return i;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        int index = findKey(3,array);
        System.out.println(index);
    }

java 可变byte 数组 java可变数组定义_java 可变byte 数组_36

二分查找:

public class TestDemo {
    public static int binarySearch(int[] arr,int num) {
        int left = 0;
        int right = arr.length;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid]<num) {
                left = mid+1;
            }else if(arr[mid]>num) {
                right = mid - 1;
            }else {
                return mid;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        int ret = binarySearch(arr,6);
        System.out.println(ret);
    }
}

java 可变byte 数组 java可变数组定义_java_37

4.3 数组排序(冒泡排序)

import java.util.Arrays;

public class TestDemo {
    public static void bubbleSort(int[] arr) {
        int len = arr.length;
        for (int i = 0; i < arr.length-1; i++) {
            int flag = 0;
            for (int j = 0; j < arr.length-1-i; j++) {
                if(arr[j]>arr[j+1]) {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    flag = 1;
                }
            }
            if(0 == flag) {
                break;
            }
        }
    }
    public static void main(String[] args) {
        int[] arr = {2,3,4,1,7,5};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

java 可变byte 数组 java可变数组定义_数组_38


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

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

4.4 数组逆序

给定一个数组, 将里面的元素逆序排列

public static void main(String[] args) { 
    int[] arr = {1, 2, 3, 4}; 
    reverse(arr); 
    System.out.println(Arrays.toString(arr)); 
}
public static void reverse(int[] arr) { 
    int left = 0; 
    int right = arr.length - 1; 
    while (left < right) { 
        int tmp = arr[left]; 
        arr[left] = arr[right]; 
        arr[right] = tmp; 
        left++;
        right--; 
    }
}

5. 二维数组

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

也可以理解为二维数组每行都存的是一个数组的引用。

java 可变byte 数组 java可变数组定义_java_39

定义二维数组的三种方法:

int[][] array = {{1,2,3},{4,5,6}};
        int[][] array2 = new int[2][3];
        int[][] array3 = new int[][] {{1,2,3},{4,5,6}};

第一种遍历方式:

int[][] array = {{1,2,3},{4,5,6}};
        int[][] array2 = new int[2][3];
        int[][] array3 = new int[][] {{1,2,3},{4,5,6}};

java 可变byte 数组 java可变数组定义_java_40

二维数组是特殊的一维数组。
第二种遍历方式:

for (int [] tmp:array) {
            for (int x:tmp) {
                System.out.print(x+" ");
            }
System.out.println();
}

java 可变byte 数组 java可变数组定义_开发语言_41


第三种遍历方式:

System.out.println(Arrays.deepToString(array));

java 可变byte 数组 java可变数组定义_java 可变byte 数组_42


二维数组的用法和一维数组并没有明显差别, 因此我们不再赘述。