第六讲 Java语法基础——数组

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yerenyuan_pku/article/details/81639241

数组的概念

同一种类型数据的集合,其实数组就是一个容器。数组既可以存储基本数据类型,也可以存储引用数据类型。

数组的定义格式

格式一:

元素类型[] 数组名 = new 元素类型[元素个数或数组长度];

例,需求:想定义一个可以存储3个整数的容器。

int[] x = new int[3];

格式二:

元素类型[] 数组名 = new 元素类型[]{元素, 元素, ……}; 

例,

int[] arr = new int[] {3, 1, 6, 5, 4};

以上数组又可简写成:

int[] arr = {3, 1, 6, 5, 4};

Java中的内存分配

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

  • 栈内存:用于存储局部变量(只要是在方法中定义的变量都是局部变量),一旦变量的生命周期结束,该变量就被释放。
  • 堆内存:
    1. 数组和对象,通过new建立的实例都存放在堆内存中。
    2. 每一个实体都有内存地址值。
    3. 堆内存的变量都有默认初始化值。不同类型不一样,int为0,double为0.0,boolean为false,char为’\u0000’(即空格)。
    4. 实体不再被使用,会在不确定的时间内被垃圾回收器回收。
  • 方法区:又叫方法和数据共享区。运行时期,class文件进入的地方。
  • 本地方法区:和系统底层的方法相关,JVM调用了系统中的功能。
  • 寄存器:给CPU使用,不多说。

Java中数组的内存结构

这一节举例说明在Java中数组的内存结构。
例一,定义一个数组,然后将其指向null,如下:

int[] arr = new int[3];
arr = null;

对于以上代码,数组在内存中的结构为:
这里写图片描述
例二,试看如下代码,你能不能画出数组在内存中的结构图呢?

int[] arr = new int[3];
int[] xxx = new int[3];
arr[1] = 34;
xxx[0] = 89;
System.out.println(arr[0]);

这里写图片描述
例三,最后看一个示例代码,希望你能画出数组在内存中的结构图。

int[] arr = new int[3];
int[] xxx = new int[3];
arr = xxx;
arr[1] = 34;
xxx[0] = 89;
System.out.println(arr[0]);

这里写图片描述

数组操作常见问题

数组操作有两个常见的小问题:

  1. 数组脚标越界异常(ArrayIndexOutOfBoundsException)

    int[] arr = new int[3];
    System.out.println(arr[3]); // ArrayIndexOutOfBoundsException: 3:操作数组时,访问到了数组中不存在的角标
  2. 空指针异常(NullPointerException)

    int[] arr = new int[3];
    arr = null;
    System.out.println(arr[1]); // NullPointerException:空指针异常,当引用没有任何指向,值为null的情况,该引用还在用于操作实体。

数组的常见操作

数组遍历

获取数组中的元素,通常会用到遍历,数组中有一个属性可以直接获取到数组的元素个数:length,使用方式:数组名称.length。 下面我会用两个例子来讲解数组是如何遍历的。
例一,观察如下程序,你觉得是不是可以获取到数组中的每一个元素。

int[] arr = {3, 6, 5, 1, 8, 9, 67};

System.out.println("length: "+arr.length);
for (int x = 0; x < arr.length; x++) {
    System.out.println("arr["+x+"]="+arr[x]+";"); 
}

此时,直接输出变量arr:

System.out.println(arr);

会得到诸如[I@139a55的值,分析该值:

  • [:表示是一个一维数组。
  • I:表示数组中的元素类型是int。
  • 139a55:用哈希算法算出来的地址值。

例二,定义一个功能,用于打印数组中的元素,元素间用逗号隔开。

public static void printArray(int[] arr) {
    System.out.print("[");
    for (int x = 0; x < arr.length; x++) {
        if(x != arr.length - 1)
            System.out.print(arr[x] + ", ");
        else
            System.out.println(arr[x]+"]");
    }
}

获取数组最值

例,给定一个数组,如{5, 1, 6, 4, 2, 8, 9},获取数组中的最大值,以及最小值。
获取最大值的原理图(获取最小值同理):
这里写图片描述
用文字描述即为:

  1. 获取最值需要进行比较。每一次比较都会有一个较大的值,因为该值不确定,通过一个变量进行存储。
  2. 让数组中的每一个元素都和这个变量中的值进行比较,如果大于了变量中的值,就用该变量记录较大值。
  3. 当所有的元素都比较完成,那么该变量中存储的就是数组中的最大值了。

步骤如下:

  1. 定义变量,初始化为数组中的任意一个元素即可。
  2. 通过循环语句对数组进行遍历。
  3. 在遍历过程中定义判断条件,如果遍历到的元素比变量中的元素大,就赋值给该变量。

此时,我们需要定义一个功能来完成,以便提高复用性。首先须明确两点:

  1. 明确结果:数组中的最大元素,int。
  2. 未知内容:一个数组,int[]。

    public static int getMax(int[] arr) {
        int max = arr[0];
    
        for (int x = 1; x < arr.length; x++) {
            if(arr[x] > max) 
                max = arr[x];
        }
        return max;
    }

    获取最大值的另一种方式,可不可以将临时变量初始化为0呢?可以,这种方式其实是在初始化为数组中的任意一个角标。

    public static int getMax_2(int[] arr) {
        int max = 0;
    
        for (int x = 1; x < arr.length; x++) {
            if(arr[x] > arr[max]) 
                max = x;
        }
        return arr[max];
    }

    同理,获取数组中的最小值,代码如下:

    public static int getMin(int[] arr) {
        int min = 0;
    
        for (int x = 1; x < arr.length; x++) {
            if(arr[x] < arr[min]) 
                min = x;
        }
        return arr[min];
    }

选择排序

虽然Java中的排序算法有很多,但在这里我只讲其中两种,即选择排序和冒泡排序。在这一小节中我用一个例子来详细讲解选择排序。
例,对给定数组进行排序,如{5, 1, 6, 4, 2, 8, 9}。
选择排序的原理图:
这里写图片描述

/*
对给定数组进行排序,如{5, 1, 6, 4, 2}。

0-1 0-2 0-3 0-4
1-2 1-3 1-4
2-3 2-4
3-4
*/
public static void selectSort(int[] arr) {
    for (int x = 0; x < arr.length - 1; x++) {
        for (int y = x + 1; y < arr.length; y++) {
            if(arr[x] > arr[y]) {
                int temp = arr[x];
                arr[x] = arr[y];
                arr[y] = temp;
            }
        }
    }
}

冒泡排序

在这一小节中我用一个例子来详细讲解冒泡排序。例,对给定数组进行排序,如{5, 1, 6, 4, 9, 2, 8}。
冒泡排序的原理图:
这里写图片描述

/*
对给定数组进行排序,如{5, 1, 6, 4, 2}。

0-1 1-2 2-3 3-4
0-1 1-2 2-3
0-1 1-2 
0-1
*/
public static void bubbleSort(int[] arr)
{
    for (int x = 0; x < arr.length - 1; x++)
    {
        for (int y = 0; y < arr.length - 1 - x; y++)
        {
            if (arr[y] > arr[y + 1])
            {
                int temp = arr[y];
                arr[y] = arr[y + 1];
                arr[y + 1] = temp;
            }
        }
    }
}

发现无论什么排序,都需要对满足条件的元素进行位置置换,所以可以把这部分相同的代码提取出来,单独封装成一个函数。

public static void swap(int[] arr, int a, int b) {
    int temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
}

那么,冒泡排序可以写为:

public static void bubbleSort(int[] arr) {
    for (int x = 0; x < arr.length - 1; x++) {
        for (int y = 0; y < arr.length - x - 1; y++) { // -x:让每一次比较的元素减少,-1:避免角标越界
            if(arr[y] > arr[y+1]) {
                swap(arr, y, y+1);
            }
        }
    }
}

同理,选择排序也可以写为:

public static void selectSort(int[] arr) {
    for (int x = 0; x < arr.length - 1; x++) {
        for (int y = x + 1; y < arr.length; y++) {
            if(arr[x] > arr[y]) {
                swap(arr, x, y);
            }
        }
    }
}

折半查找

折半查找也叫二分查找,虽然可以提高效率,但是必须要保证该数组是有序的数组。折半查找的原理可以这样简单理解:

  1. 通过角标先获取中间角标上元素。
  2. 让该元素和要找的数据比较。
  3. 如果要找的数大了,那就应该缩小范围,要找的范围应该是中间的角标+1—尾角标;如果要找的数小了,要找的范围就是头角标—中间的角标-1。
  4. 不断地如此重复,就可以找到元素对应的角标。

折半查找有两种写法,你任选其中一个即可,个人更倾向于第二种写法。

  1. 第一种写法。

    public static int binarySearch(int[] arr, int key)
    {
        //1.定义三个变量,记录头角标、尾角标、中间角标。
        int max, min, mid;
        min = 0;
        max = arr.length - 1;
        mid = (max + min) >> 1;
    
        while (arr[mid] != key)
        {
            if (key > arr[mid])
                min = mid + 1;
            else if (key < arr[mid])
                max = mid - 1;
            //判断元素是否存在
            if (max < min)
                return -1;
    
            mid = (max + min) >> 1;
        }
    
        return mid;
    }
  2. 第二种写法。

    public static int binarySearch(int[] arr, int key)
    {
        //1.定义三个变量,记录头角标、尾角标、中间角标。
        int max, min, mid;
        min = 0;
        max = arr.length - 1;
    
        while (min <= max)
        {
            mid = (max + min) >> 1;
    
            if (key > arr[mid])
                min = mid + 1;
            else if (key < arr[mid])
                max = mid - 1;
            else
                return mid;
        }
    
        return -1;
    }

折半查找的另一种表现形式

折半查找还有另一种变种形式(个人归纳的),以例子来进行说明——有一个有序的数组,想要将一个元素插入到该数组中,还要保证该数组是有序的,问如何获取该位置?
分析:既然是有序的数组,而且是找位置,必须要想到二分查找法。

public static int binarySearch(int[] arr, int key)
{
    int max, min, mid;
    min = 0;
    max = arr.length - 1;

    while (min <= max)
    {
        mid = (min + max) >> 1;

        if (key > arr[mid])
            min = mid + 1;
        else if (key < arr[mid])
            max = mid - 1;
        else
            return mid;
    }

    return -(1 + min);//min为插入点
}

记住min就是插入点就可以了。

进制转换

如何将一个十进制数转换成十六进制数呢?为解决该问题,我特地引出查表法,查表法可以这样理解:将所有的元素临时存储起来,建立对应关系,这样数据之间存在着对应关系,所以可通过关系中的一方查询另一方。

class ArrayTest 
{
    public static void main(String[] args) 
    {
        int num = 26;
        String str_hex = toHex(num);
        System.out.println("hex: " + str_hex);
    }

    /*
    需求:十进制--->十六进制,终结版

    思路:
    十进制转换成十六进制的每一位都是十六进制元素中的某一个。
    十六进制的元素有很多(固定个数),而且还有对应的编号,所以可以使用传说中的 查表法!
    */
    public static String toHex(int num)
    {
        //1.建立表
        char[] chs = {'0', '1', '2', '3',
                      '4', '5', '6', '7',
                      '8', '9', 'A', 'B',
                      'C', 'D', 'E', 'F'};
        //2.创建临时容器
        char[] arr = new char[8];

        //3.创建操作临时容器的索引
        int index = arr.length;

        //4.通过循环对num进行&和>>>等运算
        while (num != 0)
        {
            //5.对num进行&运算
            int temp = num & 15;

            //6.根据&运算后的结果作为角标查表,获取对应的字符,并将字符存储到临时容器中。
            arr[--index] = chs[temp];

            //7.对num进行右移
            num = num >>> 4;
        }

        return "0x" + toString(arr, index);
    }

    //定义一个功能,将字符数组转成字符串。
    public static String toString(char[] arr, int index)
    {
        String temp = "";
        for (int x = index; x < arr.length; x++)
        {
            temp = temp + arr[x];
        }
        return temp;
    }
}

很显然,接下来就要解决诸如十进制数怎么转换成二进制数、八进制数的问题了,所以我们需要建立一个通用的用于进制转换的功能。

class ArrayTest
{
    public static void main(String[] args) 
    {
        int num = 60;
        String str_bin = toBinary(num);
        String str_oct = toOctal(num);
        String str_hex = toHex(num);
        System.out.println("bin: " + str_bin);
        System.out.println("oct: " + str_oct);
        System.out.println("hex: " + str_hex);

        //Java已经提供的功能
        System.out.println(Integer.toBinaryString(60));
        System.out.println(Integer.toOctalString(60));
        System.out.println(Integer.toHexString(60));
    }

    /*
    十进制--->十六进制。
    */
    public static String toHex(int num)
    {
        return "0x" + trans(num, 15, 4);
    }

    /*
    十进制--->二进制。
    */
    public static String toBinary(int num)
    {
        return trans(num, 1, 1);
    }

    /*
    十进制--->八进制。
    */
    public static String toOctal(int num)
    {
        return "0" + trans(num, 7, 3);
    }

    //用于进制转换的功能。
    public static String trans(int num, int base, int offset)
    {
        if (num == 0)
            return "0";
        //1.建立表
        char[] chs = {'0', '1', '2', '3',
                      '4', '5', '6', '7',
                      '8', '9', 'A', 'B',
                      'C', 'D', 'E', 'F'};
        //2.创建临时容器
        char[] arr = new char[32];

        //3.创建操作临时容器的索引
        int index = arr.length;

        //4.通过循环对num进行&和>>>等运算
        while (num != 0)
        {
            //5.对num进行&运算
            int temp = num & base;

            //6.根据&运算后的结果作为角标查表,获取对应的字符,并将字符存储到临时容器中。
            arr[--index] = chs[temp];

            //7.对num进行右移
            num = num >>> offset;
        }

        return toString(arr, index);
    }

    //定义一个功能,将字符数组转成字符串。
    public static String toString(char[] arr, int index)
    {
        String temp = "";
        for (int x = index; x < arr.length; x++)
        {
            temp = temp + arr[x];
        }
        return temp;
    }
}

二维数组

二维数组的定义格式

二维数组有三种定义格式,分别如下:

  • 格式一

    int[][] arr = new int[3][2]; 

    定义了一个名称为arr的二维数组,二维数组中有3个一维数组,每一个一维数组中有2个元素,一维数组的名称分别为arr[0]、arr[1]、arr[2],给第一个一维数组1脚标位赋值为78写法是:arr[0][1] = 78;。例,

    int[][] arr = new int[2][3];
    arr[1][2] = 8;
    arr[0][3] = 90;
    
    System.out.println(arr); // [[I@139a55(二维数组实体)  139a55(哈希值,实体在内存中存放的位置)
    System.out.println(arr[0]); // [I@1db9742(一维数组实体)
    System.out.println(arr[0][1]); // 0(一维数组中的元素)
  • 格式二

    int[][] arr = new int[3][]; 

    二维数组中有3个一维数组,每个一维数组都是默认初始化值null,可以对这三个一维数组分别进行初始化。例,

    int[][] arr = new int[3][];
    System.out.println(arr[0]); // null
    
    arr[0] = new int[3];
    arr[1] = new int[1];
    arr[2] = new int[2];
    
    System.out.println(arr); // [[I@139a55
    System.out.println(arr.length); // 打印的是二维数组的长度:3
    System.out.println(arr[0].length); // 打印二维数组中第1个一维数组的长度
  • 格式三

    int[][] arr = {{3,8,2},{2,7},{9,0,1,6}}; 

下面用一个练习来加深对二维数组的理解。从以下代码可以看出哪个选项正确与否?

int[] x, y[]; 


a.
x[0] = y; 

b.
y[0] = x; 

c.
y[0][0] = x;

d.
x[0][0] = y; 

e.
y[0][0] = x[0]; 

f.
x = y; 

答案:一维数组可以写为:

int[] x; 
int x[];

二维数组可以写为:

int[][] y; 
int y[][]; 
int[] y[]; // 注意这种特殊写法

所以:

a.
x[0] = y; // error

b.
y[0] = x; // yes

c.
y[0][0] = x; // error

d.
x[0][0] = y; // error

e.
y[0][0] = x[0]; // yes

f.
x = y; // error

猜你喜欢

转载自blog.csdn.net/yerenyuan_pku/article/details/81639241