java知识体系梳理:数组

知识体系梳理流程图

一维数组

1.1 数组概述

  • 数组是指一组数据的集合,数组中的每个数据被称作元素。在数组中可以存放任意类型的元素,但同一个数组里存放的元素类型必须一致。
  • 数组的好处

    • 可以自动给数组中的元素从0开始编号,方便操作这些元素。

1.2 数组的定义

在java中,可以使用以下2种格式来定义一维数组。

  • 定义的格式1

    • 在定义数组时只指定数组的长度,由系统自动为元素赋初值的方式称作动态初始化。
    • 元素类型[] 数组名 = new 元素类型[元素个数或者数组长度];
`int[] arr = new int[3];`
* 上述语句就相当于在内存中定义了3个int类型的变量,第一个变量的名称为arr[0],第二个变量的名称为arr[1],以此类推,第三个变量的名称为arr[2],这些变量的初始值都是0。为了更好地理解数组的这种定义方式,可以将上面的一句代码分成两句来写,具体如下:
   ``` 
    int[] arr; // 声明一个int[]类型的变量
    arr = new int[3]; // 创建一个长度为3的数组
    ```
  • 定义的格式2

    • 在定义数组的同时就为数组的每个元素赋值称为静态初始化。
    • 元素类型[] 数组名 = new 元素类型[]{元素1, 元素2, 元素3, ......};

      int[] arr = new int[]{3, 5, 1} or
      int[] arr = {3, 5, 1};
    • 需要注意,以下写法是错误的:int[] arr = new int[4]{3, 5, 1}; 因为编译器会认为数组限定的元素个数[4]与实际存储的元素{3, 5, 1}个数有可能不一致,存在一定的安全隐患。

1.3 数组的内存结构

  • 内存结构简介

    • Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,有对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
    • JVM对内存划分5个区域

      1. 寄存器
      2. 本地方法区
      3. 方法区
          * 运行时存放class文件
      4. 栈内存
          * 用于存取局部变量,当局部变量的作用域一旦结束,所占的内存空间就会自动释放。
      5. 堆内存
          * 数组和对象,通过new创建的实例都存放在堆内存中;
          * 每一个实体都有内存地址值;
          * 堆内存中每一个变量都有默认的初始化值,根据元素类型的不同,默认初始化的值也是不一样的,具体如下表所示:
        ![图片描述][2]
        * 实体不在使用时,会在不确定的时间内被垃圾回收器回收  
  • 数组内存结构

    • 内存图
![图片描述][3]
* 通过上面内存图来详细的说明数组在创建过程中内存的分配情况。
    * 第一行代码 int[] arr; 声明一个变量arr, 该变量的数据类型为int[],即一个int类型的数组。变量arr会在栈内存中占用一块内存单元,它没有被分配初始化值。
    * 第二行代码 arr = new int[3];会在堆内存中分配一块内存单元,数组首地址为0x7852e922,数组中每一个元素初始化值为0,并将首地址赋值给变量arr,在程序运行的过程中可以使用变量arr来引用数组。
       

1.4 数组中元素的访问,赋值,遍历

  • length属性获取数组的长度

    • 我们可以通过数组名.length的方式来获取数组的长度,即数组中元素的个数
  • 通过索引访问数组中的元素

    • 数组中的每一个元素都有一个索引(也可称为角标),想要访问数组中的元素可以通过arr[索引]形式,值得注意的是:数组中最小的索引是0,最大的索引是数组的长度-1。

       public class ArrayDemo{    
          public static void main(String[] args){        
              int[] arr = new int[3]; //创建一个长度为3的数组
              System.out.println("arr[0] = " + arr[0]); // 访问数组中第一个元素
              System.out.println("arr[1] = " + arr[1]); // 访问数组中第二个元素
              System.out.println("arr[2] = " + arr[2]); // 访问数组中第三个元素
              System.out.println("数组的长度length = " + arr.length); // 获取数组的长度
          }
         }
运行结果如下图:
       ![图片描述][4]
    从打印结果可以看出,数组中的三个元素初始值都为0,这是因为当数组被成功创建后,数组中元素会被自动赋予一个默认值,根据元素类型的不同,默认初始化的值也是不一样的。
  • 为数组中的元素赋值

    • 如果在使用数组时,不想使用这些默认初始值,也可以显式地为这些元素赋值。

      public class ArrayDemo{    
          public static void main(String[] args){        
              int[] arr = new int[4]; //创建一个长度为4的int类型数组
              arr[0] = 3; // 为第一个元素赋值为3
              arr[1] =  5; // 为第二个元素赋值为5
               System.out.println("arr[0] = " + arr[0]); // 访问数组中第一个元素
              System.out.println("arr[1] = " + arr[1]); // 访问数组中第二个元素
              System.out.println("arr[2] = " + arr[2]); // 访问数组中第三个元素
              System.out.println("arr[3] = " + arr[3]); // 访问数组中第三个元素
              System.out.println("数组的长度length = " + arr.length); // 获取数组的长度
          }
      }

      运行结果如下图:

      `在上述代码中,定义一个长度为4的int类型数组,此时数组中每个元素都为默认初始值0。通过赋值语句将数组中的元素arr[0]和arr[1]分别赋值为3和5,而元素arr[2]和arr[3]没有赋值,其值仍为0,因此打印结果中四个元素的值依次为3、5、0、0。`
  • 数组遍历

在操作数组时,经常需要依次访问数组中的每个元素,这种操作称作数组的遍历。接下来通过一个案例来学习如何使用for循环来遍历数组。如下所示。ArrayDemo02.java

    public class ArrayDemo02{
   public static void main(String[] args){
       int[] arr = new int[]{3, 6, 9, 5, 1}; // 创建一个数组
       // 使用for循环遍历数组中每一个元素
       for(int index = 0; index < arr.length; index++){
           // 通过索引(角标)来访问数组中的元素
           System.out.print("arr["+ index +"]" + arr[index] + ", ");
       }
   }
    }
 运行结果如下图所示:
![图片描述][6]
* 上述代码中,定义一个长度为5的数组arr,数组的角标为0~4。由于for循环中定义的变量index的值在循环过程中为0~4,因此可以作为索引,依次去访问数组中的元素,并将元素的值打印出来。
  

1.4 数组中常见问题分析

  • 数组索引越界异常
    每个数组的索引都有一个范围,即0~length-1。在访问数组的元素时,索引不能超出这个范围,否则程序会报ArrayIndexOutOfBoundsException,如下所示。ArrayDemo03.java

    public class ArrayDemo03{
        public static void main(String[] args){
            int[] arr = new int[]{3, 6, 9, 5, 1};
            System.out.println(arr[5]);         
        }
    }

运行结果如下:

    上图运行结果中所提示的错误信息是数组角标越界异常ArrayIndexOutOfBoundsException,出现这个异常的原因是数组的长度为5,其索引范围为0~4。
  • 空指针异常

在使用变量引用一个数组时,变量必须指向一个有效的数组对象,如果该变量的值为null,则意味着没有指向任何数组,此时通过该变量访问数组中的元素会出现空指针异常NullPointerException。接下来通过一个案例来演示这种异常,如下所示。ArrayDemo04.java

    public class ArrayDemo04{
  public static void main(String[] args){        
      int[] arr = new int[3]; // 创建一个长度为3的int类型数组
      arr[0] = 23; // 给数组中的第一个元素赋值为23
      System.out.println("arr[0] = " + arr[0]); // 访问数组中第一个元素
      arr = null; // 将变量arr置位null
      System.out.println("arr[0] = " + arr[0]); // 访问数组中第一个元素
  }
    }
  运行的结果如下图:
  ![图片描述][8]
通过上图所示的运行结果可以看出,代码中将变量arr置为null,再次次访问数组时就出现了空指针异常NullPointerException。

1.5数组中常见操作

  • 获取最值

在操作数组时,经常需要获取数组中元素的最值(最大值或者最小值)。

    // int[] arr = {5,1,12,4,6,8,0,3};
    // 第一种写法
    public static int getMax(int[] arr){
        if(arr == null)
            return -1;
         // 定义一个变量记录最大的值,默认数组中第一个元素
        int maxElement = arr[0];
        for(int index = 1; index < arr.length; index++){
            // 如果arr[index]大于maxElement
            if(arr[index] > maxElement)
                maxElement = arr[index]; //条件成立, 将arr[index]赋值给maxElement
        }
        return maxElement;
    }
    
    // 第二种写法
    public static int getMax_2(int[] arr){
        if(arr == null)
            return -1;
        // 定义一个变量记录索引值,默认数组中第一个索引0
        int maxIndex = 0;
        for(int index = 1; index < arr.length; index++){
            // 如果arr[index]大于arr[maxElement]
            if(arr[index] > arr[maxIndex])
                maxIndex = index;//条件成立, 将index赋值给maxIndex
        }        
        return arr[maxIndex];
    }
运行结果:maxElement = 12
   上述代码中,定义一个临时变量maxElement用于记录最大值,通过for循环遍历数组中的每一个元素arr[index]与maxElement比较获取最大值,并赋值给maxElement。
   首先假设数组中第一个元素arr[0]为最大值,使用for循环对数组进行遍历,在遍历的过程中只要遇到比maxElement还要大的元素,就讲该元素赋值给maxElement,变量maxElement就能够在for循环结束时记录数组中的最大值。值得注意的是,在for循环中的变量index是从1开始的,这样写的原因是程序已经假设数组中第一个元素为最大值,for循环中只需要从数组中第二个元素开始遍历,从而可以提高程序的运行效率。
  
  • 定义打印数组元素方法,按照给定的格式打印[11, 33, 44, 22, 55]

    1. 题目分析:通过观察发现,要实现按照指定格式,打印数组元素操作。

      • 通过循环,我们可以完成数组中元素的获取,数组名[索引]
      • 观察发现,每个数组元素之间加入了一个逗号”,”进行分隔;并且,整个数组的前后有一对中括号”[]”包裹数组所有元素。
    2. 解题步骤

      • 使用输出语句完成打印 左边的中括号”[”
      • 使用循环,输出数组元素值。输出元素值分为两种情况,如下:

        • 最后一个数组元素,加上一个右边的中括号”]”
        • 非最后一个数组元素,加上一个逗号”,”
//按照指定的格式打印数组
public static void toArray(int[] arr){
        System.out.print("[");
        for(int index = 0; index < arr.length; index++){
            if(index == arr.length - 1)
                System.out.print(arr[index] + "]");
            else
                System.out.print(arr[index] + ", ");
        }
    }
  • 数组元素的逆序

    1. 图解
![clipboard.png](/img/bVXxlL)
2. 题目分析:通过观察发现,要实现原数组元素倒序存放操作。即原数组存储元素为{23,34,45,56,67,78,89},逆序后为原数组存储元素变为{89,78,67,56,45,34,23}。
    * 通过图解发现,想完成数组元素逆序,其实就是把数组中索引为start与end的元素进行互换;
    * 每次互换后,start索引位置后移,end索引位置前移,再进行互换;
    * 直到start位置超越了end位置,互换结束,此时,数组元素逆序完成。
3. 解题步骤
    * 定义两个索引变量start值为0,变量end值为数组长度减去1(即数组最后一个元素索引);
    * 使用循环,完成数组索引start位置元素与end位置元素值互换;
    * 在循环换过程中,每次互换结束后,start位置后移1,end位置前移1;
    * 在循环换过程中,最先判断start位置是否超越了end位置,若已超越,则跳出循环
```
    // 数组元素的逆序
    public static void reverseArray(int[] arr){
        for(int start = 0, end = arr.length - 1; start < end; start++, end--){
            int temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;
        }
    }
```
  • 数组元素选择排序

    1. 图解
![图片描述][9]
2. 题目分析:通过观察发现,要实现把数组元素{13,46,22,65,3}进行排序。
    * 提到数组排序,就要进行元素值大小的比较,通过上图发现,我们想完成排序要经过若干次的比较才能够完成;
    * 上图中用每圈要比较的第一个元素与该元素后面的数组元素依次比较到数组的最后一个元素,把小的值放在第一个数组元素中,数组循环一圈后,则把最小元素值互换到了第一个元素中;
    * 3数组再循环一圈后,把第二小的元素值互换到了第二个元素中。按照这种方式,数组循环多圈以后,就完成了数组元素的排序。这种排序方式我们称为选择排序。
3. 解题步骤
    * 使用for循环(外层循环),指定数组要循环的圈数(通过图解可知,数组循环的圈数为数组长度 - 1);
    * 在每一圈中,通过for循环(内层循环)完成数组要比较的第一个元素与该元素后面的数组元素依次比较到数组的最后一个元素,把小的值放在第一个数组元素中;
    * 在每一圈中,要参与比较的第一个元素由第几圈循环来决定。如上图所示
        * 进行第一圈元素比较时,要比较的第一个元素为数组第一个元素,即索引为0的元素;
        * 进行第二圈元素比较时,要比较的第一个元素为数组第二个元素,即索引为1的元素;
        * 依次类推,得出结论:进行第n圈元素比较时,要比较的第一个元素为数组第n个元素,即数组索引为n-1的元素
    ```
    // 选择排序
        public static void selectSort(int[] arr){
            // 外层for循环控制数组循环的次数
            for(int i = 0; i < arr.length - 1; i++){
                // 内层for循环用来比较元素值,将较小的值转换到要比较的第一个元素中
                for(int j = i+1; j < arr.length; j++){
                    if(arr[i] > arr[j]){
                        int temp = arr[i];
                        arr[i] = arr[j];
                        arr[j] = temp;
                    }
                }
            }
        }
        
        // 这种方式可以减置换的次数,提高程序的性能
        public static void selectSort_2(int[] arr){
            for(int i = 0; i < arr.length - 1; i++){
                // 定义变量index和element分别记录要比较的第一个元素的索引和值
                int index = i;
                int element = arr[i];
                for(int j = i + 1; j < arr.length; j++){
                    // 比较获取最小值的索引和值
                    if(element > arr[j]){
                        index = j;
                        element = arr[j];
                    }
                }
                // 当第一个要比较的元素索引不等于最小值的索引置换位置
                if(index != i){
                    int temp = arr[i];
                    arr[i] = arr[index];
                    arr[index] = temp;
                }
            }
        }
    ```
  • 数组元素冒泡排序

    1. 图解
![clipboard.png](/img/bVXxrW)

2. 题目分析:通过观察发现,要实现把数组元素{13,46,22,65,3}进行排序
    * 提到数组排序,就要进行元素值大小的比较,通过上图发现,我们想完成排序要经过若干次的比较才能够完成;
    * 上图中相邻的元素值依次比较,把大的值放后面的元素中,数组循环一圈后,则把最大元素值互换到了最后一个元素中。数组再循环一圈后,把第二大的元素值互换到了倒数第二个元素中。按照这种方式,数组循环多圈以后,就完成了数组元素的排序。这种排序方式我们称为冒泡排序。
3. 解题步骤
    * 使用for循环(外层循环),指定数组要循环的圈数(通过图解可知,数组循环的圈数为数组长度 - 1);
    * 在每一圈中,通过for循环(内层循环)完成相邻的元素值依次比较,把大的值放后面的元素中;
    * 每圈内层循环的次数,由第几圈循环来决定。如上图所示
        * 进行第一圈元素比较时,内层循环次数为数组长度 - 1;
        * 进行第二圈元素比较时,内层循环次数为数组长度 - 2;
        * 依次类推,得出结论:进行第n圈元素比较时,内层循环次数为数组长度 - n
    // 冒泡排序
        public static void bubbleSort(int[] arr){
            // 外层for循环控制数组循环的次数
            for(int i = 0; i < arr.length - 1; i ++){
                // arr.length - 1 为了避免角标越界
                // arr.length - 1 - i 为了比较效率,减少重复比较
                // 内层for循环用来比较元素值,把大的元素置换到后面
                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;
                    }
                }
            }
        }
  • 数组元素普通查找

    1. 图解
![clipboard.png](/img/bVXxsc)
2. 题目分析:通过观察发现,要实现查找指定数值第一次在数组中存储的位置(索引),返回该位置(索引)
    * 我们可以通过遍历数组,得到每个数组元素的值;
    * 在遍历数组过程中,使用当前数组元素值与要查找的数值进行对比
        * 数值相等,返回当前数组元素值的索引;
        * 整个循环结束后,比对结果数值没有相等的情况,说明该数组中没有存储要查找的数值,此时,返回一个索引值-1,来表示没有查询到对应的位置。(使用 -1来表示没有查询到,是因为数组的索引没有负数)。
3. 解题步骤
    * 使用for循环,遍历数组,得到每个数组元素值;
    * 在每次循环中,使用if条件语句进行当前数组元素值与要查找的数值进行对比,若比较结果相等,直接返回当前数组元素的索引值;
    * 若整个循环结束后,比对结果数值没有相等的情况,说明该数组中没有存储要查找的数值,此时,返回一个索引值-1
```
// 普通查找
    public static int getArrayIndex(int[] arr, int key){
        // 把数组中的元素依次与指定的值比较
        for(int i = 0; i < arr.length - 1; i++){
            // 如果相等,找到了直接返回
            if(key == arr[i])
                return i;
        }
        return -1;
    }
```
  • 数组元素二分查找(折半查找)

    1. 图解
![clipboard.png](/img/bVXxsK)
2. 题目分析:通过观察发现,要实现查找指定数值在***元素有序的数组***中存储的位置(索引),返回该位置(索引)。
 * 我们使用数组最中间位置的元素值与要查找的指定数值进行比较,若相等,返回中间元素值的索引
 * 最中间位置的元素值与要查找的指定数值进行比较,若不相等,则根据比较的结果,缩小查询范围为上次数组查询范围的一半;再根据新的查询范围,更新最中间元素位置,然后使用中间元素值与要查找的指定数值进行比较。
     * 比较结果相等,返回中间元素值的索引
     * 比较结果不相等,继续缩小查询范围为上次数组查询范围的一半,更新最中间元素位置,继续比较,依次类推。
   * 当查询范围缩小到小于0个元素时,则指定数值没有查询到,返回索引值-1。
3. 解题步骤
 * 定义3个用来记录索引值的变量,变量min记录当前范围最小索引值,初始值为0;变量max记录当前范围最大索引值,初始值为数组长度-1;变量mid记录当前当前范围最中间元素的索引值,初始值为(min+max) / 2;
 * 使用循环,判断当前范围下,最中间元素值与指定查找的数值是否相等
    * 若相等,结束循环,返回当前范围最中间元素的索引值mid;
    * 若不相等,根据比较结果,缩小查询范围为上一次查询范围的一般
         * 中间元素值 比 要查询的数值大,说明要查询的数值在当前范围的最小索引位置与中间索引位置之间,此时,更新查询范围为:范围最大索引值 = 上一次中间索引位置 -1;
         * 中间元素值 比 要查询的数值小,说明要查询的数值在当前范围的最大索引位置与中间索引位置之间,此时,更新查询范围为:范围最小索引值 = 上一次中间索引位置 +1;
         * 在新的查询范围中,更新中间元素值的位置,再次使用最中间元素值与指定查找的数值是否相等,中间索引值 = (范围最小索引值 +范围最大索引值) / 2;
 * 每次查询范围缩小一半后,使用if语句判断,查询范围是否小于0个元素,若小于0个元素,则说明指定数值没有查询到,返回索引值-1                
```
public static int binarySearch(int[] arr, int key){
        // 定义三个变量分别记录最小、最大和中间索引
        int min, max , mid;
        min = 0;
        max = arr.length - 1;
        while(min <= max){
            mid = (min + max) >>> 2;
            
            if(arr[mid] < key)
                min = mid + 1; // 在右边
            else if(arr[mid] > key)
                max = mid - 1; // 在左边
            else
                return mid;    // 找到了    
        }
        // 没有找到
        return -(min + 1);
    }
```

二维数组

1.1 数组定义

  • 定义的格式1

    • int[][] arr = new int[3][4];
    • 上面的代码相当于定义了一个3*4的二维数组,即二维数组的长度为3,二维数组中的每个元素又是一个长度为4的一维数组,接下来通过一个图来表示这种情况,如下图所示:
  • 定义的格式2

    • int[][] arr = new int[3][];
    • 二维数组中有3个一维数组,每个一维数组都有默认的初始化值为null,可以对这个三个一维数组分别进行初始化,接下来通过一个图来表示这种情况,如下图所示:
![clipboard.png](/img/bVXxDx)
  • 定义的格式3

    • int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};
    • 定义一个名称为arr的二维数组,二维数组中有3个一维数组,每一个一维数组中具体元素都已初始化,接下来通过一个图来表示这种情况,如图所示
![clipboard.png](/img/bVXxEx)

1.2 数组中元素的访问,赋值,遍历

  • 对二维数组中元素的访问也是通过角标的方式,如需访问二维数组中第一个元素数组的第二个元素,具体代码如下:arr[0][1]
  • 获取arr数组中所有元素的和。使用for的嵌套循环即可。

        int sum = 0;
        int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};  
        for(int i =0; i < arr.length; i++){
            for(int j=0; j< arr[i].length; j++){
                sum += arr[i][j];
            }
        }

你可能感兴趣的:(java)