Java小试牛刀—数组

目录

1、前言

2、数组的基本概念

2.1 为什么使用数组

2.2 什么是数组

2.3 数组的创建及初始化

2.3.1 数组的创建

2.3.2 数组的初始化

2.4 数组的使用 

2.4.1 数组中元素访问

2.4.2 遍历数组

3. Java中的内存分布

3.1 JVM的内存分布

3.2 引用类型变量

3.3 一个简单的例子

3.4 初识 null

4、数组的应用场景

4.1 保存数据

4.2 作为函数的参数

4.3 作为函数的返回值

5、数组操作相关代码

5.1数组转字符串

5.2 数组拷贝 

5.3 求数组中元素的平均值 

5.4 查找数组中指定元素(二分查找)

6、二维数组

6.1 基本语法

6.1.1二维数组的定义 

6.2 二维数组的遍历 

7、总结


1、前言

经过前几节Java基本语法的洗礼,相信大家对Java有了初步的了解,今天我们接着分享下一个知识点—数组,其中包括数组的基本概念、数组的初始化、数组的使用等内容,干货满满呦。

2、数组的基本概念

2.1 为什么使用数组

如果现在我们要存储5位同学的考试成绩,我们当然可以用如下代码:

public static void main(){
    int a = 90;
    int b = 80;
    int c = 70;
    int d = 95;
    int d = 98;
    System.out.println(a);
    System.out.println(b);
    System.out.println(c);
    System.out.println(d);
    
}
  • 这样创建局部变量的方法当然可以,但是如果存储的是500个同学的成绩呢,要创建500个变量吗?。仔细观察这些学生成绩发现:所有成绩的类型都是相同的,那Java中存在可以存储相同类型多个数据的类型吗?这就是本次blog需要详细讲解的数组。

2.2 什么是数组

  • 数组:可以看成是相同类型元素的一个集合。

注意

  1. 数组中存放的元素其类型相同。
  2. 数组的空间是连续的(无论是一维数组还是二维数组)。
  3. 每个数据的空间有自己的编号,起始位置的编号为0,即数组的下标。

那么我们该如何创建数组呢?

2.3 数组的创建及初始化

2.3.1 数组的创建

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

其中:

  • T:表示数组中存放元素的类型(如int、double等);
  • T[]:表示数组的类型;
  • N:表示数组的长度。
  • 举例(我们用array数组这个英文单词表示数组名(下同),这个可以根据实际需求进行调整):
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new double[3]; // 创建一个可以容纳3个字符串元素的数组

2.3.2 数组的初始化

1、引用变量与对象:

  • 这里涉及到了引用变量和对象的概念,我们放在类与对象的blog中进行详细讲解,这里只需要了解:array就是一个引用变量,他里面存储的是数组的地址,并且指向数组这个对象,对象中存储我们想要存储的数据即可。

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

  • 动态初始化:

在创建数组时,指定数组中元素的个数,而不指定数组的内容,内容会被默认初始化为0

int[] array = new int[3];

注意区别于C语言,Java中引用变量存放在虚拟机栈区中,数组的内容存放在堆区中,在本次blog中的2.1节会简单介绍下内存中各个分区,看到这里还不点个赞嘛!

  • 下面的图更方便大家理解动态初始化,引用变量array存放的是数组的地址,array指向在堆区开辟的对象,对象中存储的内容默认都是0

  • 静态初始化: 

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

  • 语法格式:
T[] 数组名称 = new T[]{data1, data2, data3, ..., datan};
int[] array = new int[]{1,2,3};

 或者它的简写形式:

T[] 数组名称 = {data1, data2, data3, ..., datan};
  • 两种形式等价,相当于创建了一个对象。
  • 代码举例:
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", "!!!"};
  • 下面的图更方便大家理解,我们定义了一个引用变量数组,指向一个对象,对象里面存放的内容是:1、2、3。

 3、注意事项

  1. 静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度
  2. 静态初始化时, {}中数据类型必须与[]前数据类型一致
  3. 静态初始化可以简写,省去后面的new T[]。
  4. 如果没有对数组进行初始化,数组中元素有其默认值。
  5. 如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值,比如:

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

2.4 数组的使用 

2.4.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]);

注意

  1. 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素
  2. 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
int[] array = {1, 2, 3};
System.out.println(array[3]); // 数组中只有3个元素,下标一次为:0 1 2,array[3]下标越界
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.main(Test.java:4)
  • 抛出了 java.lang.ArrayIndexOutOfBoundsException 也就是数组越界异常. 使用数组一定要下标谨防越界

2.4.2 遍历数组

  • 所谓 "遍历" 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作,比如:打印。 
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,修改起来非常麻烦
  4. 通过观察代码可以发现,对数组中每个元素的操作都是相同的,则可以使用循环来进行打印。
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 来获取数组的长度

int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < array.length; i++){
    System.out.println(array[i]);
}
  • 也可以使用 for-each 遍历数组:左边是要存储的变量x :右边是数组名(array);会遍历整个数组,存到x当中,遍历一个,存储一个。
int[] array = {1, 2, 3};
for (int x : array) {
    System.out.println(x);
}
  •  for-each 是 for 循环的另外一种使用方式. 能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错。
  • 如果你在循环里面,需要通过下标判断数据,就不适合用for each

3. Java中的内存分布

3.1 JVM的内存分布

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

现在我们只简单关心堆 和 虚拟机栈这两块空间(在2.3.2节中已经介绍过),后序JVM中还会更详细介绍。

3.2 引用类型变量

  • 基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
  • 而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。如下代码:
public static void func() {
    int a = 10;
    int b = 20;
    int[] array = new int[]{1,2,3};
}
  1. 在上述代码中,a、b、array,都是函数内部的变量,因此其空间都在main方法对应的栈帧中分配
  2. a、b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值
  3. array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址,并指向数组这个对象。如下图所示:

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

3.3 一个简单的例子

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]);
    }
}
  •  代码逻辑(配图讲解)
  • 首先分别在堆区和虚拟机栈建立两个空间array1指向地址为0x0012ff7c的对象,默认初始化为0,后将里面的元素改为10、20、30; 
  • array2指向地址为0x0012ffE0的对象,初始化为1、2、3、4、5,将第0和第1位元素改为100、200,现在存储的就是100、200、3、4、5; 

  • array1 = array2;是将array2的地址给到了array1,那么array现在指向的对象就是array2指向的对象,地址是0x0012ffE0,空间里存放的是100、200、3、4、5。 
  • 现在改变array1[]下标为2、3、4的元素,相当于改变原来array2中的元素因为现在两个引用指向同一个对象。所以现在地址为0x0012ffE0的空间被改为100、200、300、400、500。 

  •  无论打印array1还是array2都是100、200、300、400、500。 
  • array1原来所指向的对象(0x0012ff7c)的空间如果没人用的话,会被Java的垃圾回收器自己回收,不用free(区别于C)

3.4 初识 null

  1. null 在 Java 中表示 "空引用" , 也就是一个不指向对象的引用
  2. 在Java中局部变量必须赋值,基本数据类型就赋值对应的“0”就可以了; 但是引用型要用null来赋值,否则就会报错
  3. null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作, 一旦尝试读写, 就会抛出 NullPointerException(空指针异常)
int[] arr = null;
System.out.println(arr[0]);

// 执行结果
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:6)

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

4、数组的应用场景

4.1 保存数据

 public static void main4(String[] args) {
        int[] array = {1,2,3,4};
        int sum = 0;
        for (int x:array) {
            System.out.println(x);
        }
  }

4.2 作为函数的参数

public static void main(String[] args) {
    int[] array = {1, 2, 3};
    func(array);
    System.out.println("arr[0] = " + arr[0]);
}
public static void func(int[] array) {
    a[0] = 10;
    System.out.println("a[0] = " + a[0]);
}
// 执行结果
a[0] = 10
arr[0] = 10
  • 此时我们发现,原来array数组中的1被改为10了,但是改变的操作是在func()函数中实现的;
  • 这是因为所谓的 "引用" 本质上只是存了一个地址. Java 将数组设定成引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入到函数形参中,这样通过地址我们就可以修改数组中的内容。

4.3 作为函数的返回值

 public static void main1(String[] args) {
        int[] ret = test();
        System.out.println(Arrays.toString(ret));
    }
    public static int[] test(){
        int[] tmp = new int[]{1,2,3};
        return tmp;
    }
  • 解决了C语言中返回参数只能返回1个的问题。 

5、数组操作相关代码

5.1数组转字符串

  • 通过Arrays.toString来实现基本类型转成字符串类型的操作,Java在这方面很便捷。
  • Java 中提供了 java.util.Arrays 包, 其中包含了一些操作数组的常用方法。
import java.util.Arrays
int[] arr = {1,2,3,4,5,6};
String newArr = Arrays.toString(arr);
System.out.println(newArr);
// 执行结果
[1, 2, 3, 4, 5, 6]

5.2 数组拷贝 

  • Arrays包中为我们提供了相应的数组拷贝函数。
    public static void main3(String[] args) {
        double[] array = {1.0,56.0,66.0,89.0};
        double[] copyArray = Arrays.copyOf(array,array.length+2);
        double[] copyArray1 = Arrays.copyOfRange(array,0,4);
        System.out.println(Arrays.toString(copyArray));
        System.out.println(Arrays.toString(copyArray1));
    }
  • Arrays.copyOf中的参数分别为:源数组,以及要拷贝的长度。长度可以分为以下几种:
拷贝源数组的长度,相当于拷贝源数组中所有元素
double[] copyArray = Arrays.copyOf(array,array.length);
//输出结果:
 {1.0,56.0,66.0,89.0};
拷贝源数组的二倍的长度,多余没有数据的部分用0补齐
double[] copyArray = Arrays.copyOf(array,array.length*2);
//输出结果:
 {1.0,56.0,66.0,89.0,0.0,0.0,0.0,0.0};
拷贝源数组前两个元素
double[] copyArray = Arrays.copyOf(array,2);
//输出结果:
 {1.0,56.0};
  •  当然,如果你想指定拷贝的范围,可以调用 Arrays.copyOfRange
  •  Arrays.copyOfRange中的参数分别是:源数组,from(起始拷贝的位置,为数组下标的形式),to(终止拷贝的位置,为数组下标的形式)
  • 注意:Java中遇到from和to的范围,一般都是左闭又开的形式)比如如下代码,最终拷贝的就是array[0]~array[2]的数据,因为给的范围是[0,3),不包括array[3]
    public static void main3(String[] args) {
        double[] array = {1.0,56.0,66.0,89.0};
        double[] copyArray1 = Arrays.copyOfRange(array,0,3);
        System.out.println(Arrays.toString(copyArray1));
    }
    //打印出来{1.0,56.0,66.0}

5.3 求数组中元素的平均值 

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

5.4 查找数组中指定元素(二分查找)

  • 首先我们要明确,二分查找是针对有序的数组进行查找,无序的数组要先用Arrays.sort()先排序
  • 以升序数组为例, 二分查找的思路是先取中间位置的元素, 然后使用待查找元素与数组中间元素进行比较:
  1. 如果和待查找元素相等,即找到了返回该元素在数组中的下标;
  2. 如果小于待查找元素,将left = mid + 1;缩小一半范围,进行下一次查找
  3. 如果大于待查找元素,以类似方式到数组右半侧查找(right = mid - 1;)。
  • 实现代码如下:
  //模拟实现二分查找
    public static void main6(String[] args) {
        int[] array = {1,2,3,4};
        int ret = myBinarySearch(array,3);
        System.out.println(ret);
    }
    public static int myBinarySearch(int[] array,int key){
        int left = 0;
        int right = array.length - 1;
        while(left <= right){
            int mid = left+(right - left)/2;
            if(array[mid] == key){
                return mid;
            }else if(array[mid] < key){
                left = mid + 1;
            }else{
                right = mid -1;
            }
        }
        return -1;
    }
  • 随着数组元素个数越多, 二分的优势就越大,查找的次数相对来说较少。

6、二维数组

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

6.1 基本语法

  • 数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };

6.1.1二维数组的定义 

public static void main8(String[] args) {
    int[][] array = {
   
   {1,2,3},{4,5,6}};
    int[][] array1 = new int[][] {
   
   {1,2,3},{4,5,6}};
    int[][] array2 = new int[2][3];
       
}

6.2 二维数组的遍历 

 //二维数组的遍历
    public static void main8(String[] args) {
        int[][] array = {
   
   {1,2,3},{4,5,6}};
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[0].length; j++) {
                System.out.print(array[i][j] + " ");
            }
            System.out.println();
        }

    }
  • 二维数组的用法和一维数组并没有明显差别, 因此我们不再赘述。
  • 同理,还存在 "三维数组", "四维数组" 等更复杂的数组,只不过出现频率都很低,这里就不再赘述了。

7、总结

至此,Java中的数组就告一段落了,其中数组的冒泡排序算法我会单独写一篇blog来详细说明,请大家移步到Java代码实现—冒泡排序的blog即可,感谢大家的支持!!!

猜你喜欢

转载自blog.csdn.net/weixin_44580000/article/details/124602440