第3章 Java数组

第3章 数组

目录

  1. 一、数组的概述
  2. 二、一维数组的使用
  3. 三、多维数组的概念
  4. 四、二维数组的使用
  5. 五、扩展
  6. 六、数组中常用的算法
  7. 七、Arrays 工具类的使用
  8. 八、数组中常见的异常

一、数组的概述

  1. 数组,是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。

  2. 数组的常见概念

    • 数组名
    • 下标(索引)
    • 元素
    • 数组的长度:元素的个数
  3. 数组的特点

    • 元素排列是有序的
    • 数组属于引用数据类型的变量。元素即可是基本数据类型、也可是引用数据类型
    • 创建数组对象会在内存中开辟一整块连续的空间
    • 数组的长度一旦确定,就不能修改
  4. 数组的分类

    • 按维数:一维数组、二维数组、...
    • 按元素的类型:基本数据类型元素的数组、引用数据类型元素的数组

二、一维数组的使用

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

    • 声明:基本数据类型[ ] 数组名;
    • 初始化
      • 静态初始化:数组的初始化和数组元素的赋值操作同时进行
        • 例:int[ ] ids; ids = new int[ ]{1001, 1002, 1003, 1004};
      • 动态初始化:数组的初始化和数组元素的赋值操作分开进行
        • 例:String[ ] names = new String[5];
    • 错误写法:
      • int[] arr1 = new int[];
      • int[2] arr2 = new int[2];
      • int[] arr3 = new int[3]{1,2,3};
    • 总结:数组一旦初始化完成,其长度就确定了(初始化完成后需要开辟内存空间)
  2. 如何调用数组指定位置的元素:通过角标的方式调用

    • 数组的角标(或索引)从0开始,到数组长度-1结束
    • 如需获得String元素类型数组的每个元素中的单个字符,只需对元素调用charAt方法
  3. 如何获取数组的长度:属性——length

    • array.length
  4. 如何遍历数组: for循环

  5. 数组元素的默认初始化值

    • 在分配了数组内存空间后,在没有对数组元素显式赋值时,元素具有默认值
    • 数组元素是整型时:默认值为0
    • 数组元素是浮点型时:默认值为0.0
    • 数组元素是char型时:默认值为0
    • 数组元素是boolean型:默认值为false
    • 数组元素是引用数据类型时:null(关键字)
  6. 数组的内存解析(内存结构在JVM规范中有讲解)

    • 内存简化结构结构有栈(stack)、堆(heap)、方法区(又分为常量池、静态域)

      • 栈中主要存放局部变量(放在方法中的变量都是局部变量)
      • 堆中主要存放new出来的结构:对象、数组
      • 常量池主要存放String
      • 静态域主要存放static
    • 对于以下代码

      1
      2
      3
      4
      5
      int[] arr = new int[]{1, 2, 3};
      String[] arr1 = new String[4];
      arr1[1] = "刘德华";
      arr1[2] = "张学友";
      arr1 = new String[3];

    • 第1行代码执行时内存中的变化为:

      • 因为方法中的变量都是局部变量,因此在main方法中声明并初始化的arr变量首先加入到栈中
      • 再经过初始化,就需要给数组分配内存空间。因为数组主要存放在堆中,因此会在堆中开辟一定长度的连续空间,并将这段空间的首地址存入栈中的arr变量,且空间中各元素均为0;之后再将初始化值赋给各元素。
    第1行代码执行时内存中的变化
    • 第2行代码执行时内存中的变化为:
      • 基本和第1行代码执行时变化相同
    第2行代码执行时内存中的变化
    • 第3行代码执行时内存中的变化为:
      • 根据arr1在栈中的值找到堆中的一块空间,在该空间中从首地址开始寻找角标个元素空间即是要调用的值空间。
      • 然后将该空间的值改为指定值即可
    第3行代码执行时内存中的变化
    • 第4行代码执行时内存中的变化为:
      • 栈中的变量仍为arr1,只是需在堆中再次新开辟一块空间,并将新开辟空间的首地址赋值给arr1。因此相当于只更改了栈中arr1的值
      • 此时垃圾回收器将会在之后的一个不确定的时间把之前的数组空间进行回收。垃圾回收使用的算法为引用计数算法,即判断堆中的一块空间是否还有栈空间中的引用指过来。(当main函数执行完后,局部变量都将会依此出栈,垃圾回收器判断堆中的空间是否还有栈中空间引用它,对于出完栈的变量将会把它之前引用的空间释放)
  7. 练习
    升景坊单间短期出租4个月 ,550 元/月(水电煤公摊,网费 35 元/月),空调、卫生间厨房齐全。屋内均是 IT 行业人士,喜欢安静。 所以要求来租者最好是同或刚毕的年轻爱干净、行业人士,喜欢安静。所以要求来租者最好是同或刚毕的年轻爱干净、

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 采用执行程序的方式筛选IT人士
    public class ArrayTest{
    public static void main(String[] args) {
    int[] arr = new int[] { 8, 2, 1, 0, 3 };
    int[] index = new int[] { 2, 0, 3, 2, 4, 0, 1, 3, 2, 3, 3 };
    String tel = "";
    for (int i = 0; i < index.length; i++) {
    tel += arr[index[i]];
    }
    System.out.println(" 联系方式: " + tel);
    }
    }
    从键盘输入学生的成绩,找出最高分,并输出学生的成绩等级:
    成绩>=最高分-10分 等级为A
    成绩>=最高分-20分 等级为B
    成绩>=最高分-30分 等级为C
    其他 等级为D

    题目程序实现

    请输入学生人数:5
    请输入5个学生的成绩:
    15
    20
    25
    50
    70
    最高分为:70
    student 0 score is 15 grade is D
    student 1 score is 20 grade is D
    student 2 score is 25 grade is D
    student 3 score is 50 grade is B
    student 4 score is 70 grade is A

三、多维数组的概念

因为数组为引用数据类型,而数组元素可以是基本数据类型、也可是引用数据类型,引用数据类型的元素可以是引用数据类型,即可看成一维数组arr1作为另一个一维数组arr2的元素就是二维数组。其实,从数组底层的运行机制来看,是没有多维数组的,只是数组名指向的数组中的元素指向的又是一个一维数组。

四、二维数组的使用

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

    • 二维数组初始化
      • 静态初始化:int[][] arr1 = new int[][]{ {1, 2, 3}, {4, 5}, {6, 7, 8} };
      • 动态初始化:String[][] arr2 = new String[3] [2];或String[][] arr2 = new String[3] [];
      • 其他正确写法:int[] arr1[] = new int[][]{ {1, 2, 3}, {4, 5}, {6, 7, 8} };int arr1[][] = new int[][]{ {1, 2, 3}, {4, 5}, {6, 7, 8} };int arr1[][] = { {1, 2, 3}, {4, 5}, {6, 7, 8} };(叫做类型推断,该用法只能将声明和初始化写在一行时有用,分开写时是错误的)
  2. 如何调用数组指定位置的元素:通过角标的方式调用

    • 当数组初始化时没有给内层数组初始化,如String[][] arr2 = new String\[3][];若在后面需要调用arr2[1][0]时,需在调用之前再次初始化内层数组:arr2[1] = new String[4];
  3. 如何获取数组的长度:属性——length

    • array.length输出第一维数组的长度
    • array[0].length输出第二维(第1行)的数组长度
  4. 如何遍历数组 两层for循环:

    1
    2
    3
    4
    5
    for(int i=0; i < arr.length; i++){
    for(int j=0; j < arr[i].length; j++){
    System.out.print(arr[i][j] + " ");
    }
    }

  5. 数组元素的默认初始化值 规定:二维数组分为外层数组元素和内层数组元素,且初始化值一般都说的是动态初始化的元素值

    • 外层数组元素
      • 在动态初始化后,默认值为地址值:结构为“[类型缩写@地址”,例如int型外层数组元素的值为[I@1586135,int型二维数组名的值为[[I@15618641
      • 当数组在初始化时并没有对内层数组初始化,则外层数组的默认值为null。因为引用数据类型的默认值都为null
    • 内层数组元素
      • 初始化值为声明和初始化时类型的默认值(与一维数组初始化情况相同)
      • 若数组在初始化时并没有对内层数组初始化,则调用内层数组元素时报错(空指针异常)
  6. 数组内存解析

    1
    2
    3
    4
    int[][] arr1 = new int[4][];
    arr1[1] = new int[]{1, 2, 3};
    arr[2] = new int[4];
    arr[2][1] = 30;
    数组内存解析示意图

  7. 复习

  • 写出一维数组初始化的两种方式
    • 答:分为静态初始化和动态初始化
      • 静态初始化:int[] arr1 = new int[]{1, 2, 3};
      • 动态初始化:int[] arr1 = new int[3]; 数组一旦初始化,其长度就是确定的;数组长度一旦是确定的,就不可修改
  • 写出二维数组初始化的两种方式
    • 答:分为静态初始化和动态初始化
      • 静态初始化:int[] [] arr1 = new int\[][]{ {1, 2, 3}, {4, 5}, {7} };
      • 动态初始化:int[] [] arr1 = new int[[3] [3];或int[] [] arr1 = new int[3] [];
  • 如何遍历如下的二维数组
    1
    int[] arr = new int[][]{ {1, 2, 3}, {4, 5}, {6, 7, 8} };
    • 答:
      1
      2
      3
      4
      5
      for(int i=0; i < arr.length; i++){
      for(int j=0; j < arr[i].length; j++){
      System.out.print(arr[i][j] + " ");
      }
      }
  • 不同类型的一维数组元素的默认初始化值各是多少
    • 答:分为基本数据类型和引用数据类型
      • 对于基本数据类型:byte:0、short:0、char:0、int:0、long:0、boolean:false、float:0.0、double:0.0
      • 对于引用数据类型:(类、数组、接口)都是null
  • 一维数组的内存解析
    1
    2
    3
    String[] strs = new String[5];
    strs[2] = "Tom";
    strs = new String[3];
  1. 练习
  • 使用二维数组打印一个10行杨辉三角

五、扩展

  1. 数据结构:
    • 数据与数据之间的逻辑关系:集合、一对一(链表)、一对多(树形结构)、多对多(网络、图)
    • 数据的存储结构:
      • 线性表(一对一):顺序表、链表、栈、队列(堆)
      • 树形结构(一对多):二叉树、...
      • 图形结构(多对多):有向、无向图
  2. 算法:
    • 排序算法
    • 搜索算法

六、数组中常用的算法

  1. 数组元素的赋值(杨辉三角、回形数等)【笔试考查】:

    【拓展之笔试题】:

    • 创建一个长度为6的int型数组,要求数组元素的值都在1-30之间,且是随机赋值。同时,要求元素的值各不相同

    • 回形数格式方针的实现

  2. 求数值型数组中元素的最大值、最小值、平均值、总和等

    • 定义一个int型的一维数组,包含10个元素,分别赋一些随机整数,然后求出所有元素的最大值、最小值、和、平均值,并输出。要求:所有随机数都是两位数

    随机数生成:(int)(Math.random() * (99 - 10 + 1) + 10);

  3. 数组的复制、反转、查找(线性查找、二分查找)【笔试考查】

    • 复制
      使用简单数组
      (1)创建一个名为ArrayTest的类,在main()方法中声明array1和array2两个变量,他们是int[]类型的数组
      (2)使用大括号{},把array1初始化为8个素数:2, 3, 5, 7, 11, 13, 17, 19
      (3)显示array1的内容
      (4)赋值array2变量等于array1,修改array2中偶索引元素,使其等于索引值,打印出array1
      思考:array1和array2是什么关系:array1和array2存储的地址值相同,都指向堆空间中的唯一一个数组实体
      拓展:修改题目,实现array2对array1数组的复制

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
        //数组的复制
      public class Exerc6 {

      public static void main(String[] args){
      //声明array1和array2数组
      int[] array1, array2;
      //初始化array1
      array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19};
      //显示array1的内容
      for(int i = 0; i < array1.length; i++){
      System.out.print(array1[i] + "\t");
      }
      System.out.println();
      //赋值array2变量等于array1
      array2 = array1;
      //修改array2中偶索引元素
      for(int i = 0; i < array2.length; i++){
      if(i%2 == 0){
      array2[i] = i;
      }
      }
      //显示array1的内容
      for(int i = 0; i < array1.length; i++){
      System.out.print(array1[i] + "\t");
      }
      System.out.println();
      //array2对array1数组的复制
      array2 = new int[array1.length];
      for(int i = 0; i < array2.length; i++){
      array2[i] = array1[i];
      }
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      public class ArrayTest {
      public static void main(String[] args){
      String[] arr = new String[]{"AA", "BB", "CC", "DD", "EE", "FF"};
      for(int i = 0; i < arr.length; i++){
      System.out.print(arr[i] + "\t");
      }
      System.out.println();

      //数组的复制
      String[] arr1 = new String[arr.length];
      for(int i = 0; i < arr1.length; i++){
      arr1[i] = arr[i];
      }

      //数组的反转
      //方法一
      for(int i = 0; i < arr.length / 2; i++){
      String temp = arr[i];
      arr[i] = arr[arr.length - i - 1];
      arr[arr.length - i - 1] = temp;
      }
      //方法二
      for(int i = 0, j = arr.length - 1; i < j; i++, j--){
      String temp = arr[i];
      arr[i] = arr[j];
      arr[j] = temp;
      }
      //遍历
      for(int i = 0; i < arr.length; i++){
      System.out.print(arr[i] + "\t");
      }
      System.out.println();

      //查找/搜索
      //线性查找
      String dest = "BB";
      boolean isFlag = true;
      for(int i = 0; i < arr.length; i++){
      if(dest.equals(arr[i])){
      System.out.println("找到了,位置是:" + i);
      isFlag = false;
      break;
      }
      }
      if(isFlag){
      System.out.println("没找到");
      }
      //二分查找
      //前提:所要查找的数组须是有序的
      int[] arr2 = new int[]{-98, -34, 2, 34, 54, 66, 79, 105, 210, 333};
      int dest1 = -34;
      int head = 0, end = arr2.length - 1;
      boolean isFlag1 = true;

      while(head <= end){
      int mid = (head + end) / 2;
      if(arr2[mid] == dest1){
      System.out.println("找到了,位置为:" + mid);
      isFlag = false;
      break;
      }
      else if(arr2[mid] < dest1){
      head = mid + 1;
      }
      else{
      end = mid - 1;
      }
      }
      if(isFlag){
      System.out.println("没找到");
      }
      }
      }

  4. 数组元素的排序算法【笔试考查】

    • 排序算法

      • 排序:假设含有n个记录的序列为{R1, R2, ..., Rn},其相应的关键字序列为{K1, K2, ..., Kn}。将这些记录重新排序为{Ri1, Ri2, ..., Rin},使得相应的关键字值满足Ki1 <= Ki2 <= ... <= Kin,这样的一种操作称为排序。
      • 通常来说排序的目的是快速查找
      • 衡量排序算法的优劣
        • 时间复杂度:关键字的比较次数和记录的移动次数
        • 空间复杂度:算法中需要多少辅助内存
        • 稳定性(专指排序算法):若两个记录A和B的关键字值相等,但排序后A、B的先后次序保持不变,则称这种排序算法是稳定的。
      • 排序算法分类:
        • 内部排序:整个排序过程不需要借助外部存储器,所有排序操作都在内存中完成
        • 外部排序:参与排序的数据非常多、数据量大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器,外部排序最常见的是多路归并排序,可认为外部排序是由多次内部排序组成的。
      • 十大内部排序算法:
        • 选择排序:直接选择排序,堆排序【了解思想】
        • 交换排序:冒泡排序快速排序【需会手写】
        • 插入排序:直接插入排序、折半插入排序、Shell排序(希尔排序)
        • 归并排序【了解思想】
        • 桶式排序
        • 基数排序

      冒泡排序(O(\(n^2\)))

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      public class BubbleSortTest {
      public static void main(String[] args){
      int[] arr = new int[]{43, 32, 76, -98, 0, 64, 33, -21, 32, 99};
      int[] arr1 = arr.clone();
      //遍历
      for(int i = 0; i < arr.length; i++){
      System.out.print(arr[i] + "\t");
      }
      System.out.println();
      //排序
      //先找出最大的数,并从小到大排序
      for(int i = arr.length - 1; i > 0; i--){
      boolean isFlag = true;
      for(int j = 0; j < i -1; j++){
      if(arr[j] > arr[j+1]){
      int temp = arr[j];
      arr[j] = arr[j+1];
      arr[j+1] = temp;
      isFlag = false;
      }
      }
      if(isFlag){
      break;
      }
      }
      //遍历
      for(int i = 0; i < arr.length; i++){
      System.out.print(arr[i] + "\t");
      }
      System.out.println();

      // 先找出最小的数,并从小到大排序
      for(int i = 0; i < arr1.length - 1; i++){
      boolean isFlag = true;
      for(int j = arr1.length - 1; j > i; j--){
      if(arr1[j] < arr1[j-1]){
      int temp = arr1[j];
      arr1[j] = arr1[j-1];
      arr1[j-1] = temp;
      isFlag = false;
      }
      }
      if(isFlag){
      break;
      }
      }
      //遍历
      for(int i = 0; i < arr.length; i++){
      System.out.print(arr1[i] + "\t");
      }
      System.out.println();
      }
      }
      快速排序(O(\(nlog_{2}(n)\)))
      1
      //大话数据结构中的快速排序算法思想(递归章节)和讲到的算法思想区别

七、Arrays工具类的使用

java.util.Arrays:操作数组的工具类,其中有许多操作数组的方法。具体可查看API文档。

序号API作用
1boolean equals(int[] a, int[] b)判断两个数组是否相等
2String toString(int[] a)输出数组信息
3void fill(int[] a, int val)将指定值填充到数组之中
4void sort(int[] a)对数组进行排序
5int binarySearch(int[] a, int key)对排序后的数组进行二分法检索指定的值

八、数组中常见的异常

  1. 数组角标越界异常:\(ArrayIndexOutOfBoundsException\)

    • 索引超过数组索引范围的就属于角标越界异常,负数也是角标越界(python有些区别)
  2. 空指针异常:\(NullPointerException\)

    • 情况1:
    • 情况2:
    • 情况3:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class ArrayException {
      public static void main(String[] args){
      //情况1
      int[] arr1 = new int[]{1, 2, 3};
      arr1 = null;
      System.out.println(arr1[0]);

      //情况2
      int[][] arr2 = new int[4][];
      System.out.println(arr2[0][0]);

      //情况3
      String[] arr3 = new String[]{"AA", "BB", "CC"};
      arr3[0] = null;
      System.out.println(arr3[0].toString());
      }

      }