JavaSE · 数组的基本用法 · 数组作为方法的参数 · 数组作为方法的返回值 · 二分查找 · 冒泡排序 · 数组逆序

本文详细介绍了Java中数组的基本用法,包括创建、初始化、访问和遍历数组,以及数组作为方法参数和返回值的处理。文章还探讨了引用类型、null的概念以及JVM内存区域划分,并通过实例展示了数组的拷贝、查找、排序等操作。同时,提供了数组转换为字符串、查找最大元素、计算平均值等实用技巧。最后,文章讲解了二维数组的使用,强调其本质仍是一维数组的组合。
摘要由CSDN通过智能技术生成

一、数组基本用法

什么是数组

数组,能让我们批量地创建相同类型的变量.

例如:
如果需要表示两个数据, 那么直接创建两个变量即可  int a; int b
如果需要表示五个数据, 那么可以创建五个变量  int a1; int a2; int a3; int a4; int a5;
但是如果需要表示一万个数据, 那么就不能创建一万个变量了. 这时候就需要使用数组, 帮我们批量创建.

注意事项:在 Java 中,数组中包含的变量必须是 相同类型.


创建数组

基本语法

// 动态初始化
数据类型[] 数组名称 = new 数据类型 [] { 初始化数据 };

// 静态初始化
数据类型[] 数组名称 = { 初始化数据 };

代码示例

	int[] arr1 = new int[]{1, 2, 3};
	int[] arr2 = {1, 2, 3};

注意事项:静态初始化的时候,数组元素个数和初始化数据的格式一致.

其实数组也可以写成
int arr[] = {1, 2, 3};

这样就和 C 语言更相似了. 但是我们还是更推荐写成 int[] arr 的形式. int和 [] 是一个整体.

数组的使用

代码示例: 获取长度 & 访问元素

        int[] arr = {1,2,3};
        //获取数组长度 -> 3
        System.out.println("length:" + arr.length);

        //访问数组中的元素
        System.out.println(arr[1]); //2
        System.out.println(arr[0]); //1
        arr[1] = 199;
        System.out.println(arr[1]); //199

注意事项

  1. 使用 arr.length 能够获取到数组的长度. “ . ” 这个操作为成员访问操作符. 后面在面向对象中会经常用到.
  2. 使用 [ ] 按下标取数组元素. 需要注意, 下标从 0 开始计数
  3. 使用 [ ] 操作既能读取数据, 也能修改数据.
  4. 下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围, 会出现下标越界异常

代码示例: 下标越界

int[] arr = {1, 2, 3};
System.out.println(arr[100]);

// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.main(Test.java:4)

抛出了 java.lang.ArrayIndexOutOfBoundsException 异常. 使用数组一定要下标谨防越界.

代码示例: 遍历数组
所谓 “ 遍历 ” 是指将数组中的所有元素都访问一遍,通常需要搭配循环语句.

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

代码示例: 使用 for-each 遍历数组

    public static void main(String[] args) {
        int[] arr = {1,2,3,4};
        //传递数组名,之后操作使用 x 来代替
        for (int x : arr) {
        	//直接输出 x 即可遍历数组
            System.out.println(x);
        }
    }

//执行结果
1
2
3
4

for-each 是 for 循环的另一种使用方式. 能够更加方便的完成对数组的遍历. 可以避免循环条件和更新语句写错.


二、数组作为方法的参数

基本用法

代码示例: 打印数组内容

    public static void printArr(int[] arr) {
        for (int x : arr) {
            System.out.println(x);
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        printArr(arr);
    }

//输出结果
1 
2
3

数组可以直接作为方法的参数传递过去使用,传递的性质是传址


理解引用类型(重点 / 难点)

我们尝试以下代码

代码示例1 参数传内置类型

    public static void main(String[] args) {
        int num = 0;
        func(num);
        System.out.println(num);
    }
    
    public static void func(int x){
        x = 10;
        System.out.println(x);
    }
    
//执行结果
x = 10
num = 0

我们发现,修改形参 x 的值,不影响实参的 num 值.

代码示例2 参数传数组类型

    public static void main(String[] args) {
        int[] arr = {1,2,3};
        func2(arr);
        System.out.println(arr[0]);
    }
    public static void func2(int[] a){
        a[0] = 10;
        System.out.println(a[0]);
    }
    
//执行结果
10
10

我们发现,在函数内部修改数组内容,函数外部也能发生改变.
此时数组名 arr 是一个“引用”. 当传参的时候,是按照引用传参.

所谓的 “引用” 本质上只是存了一个地址.
Java 将数组设定成引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入到函数形参中.
这样可以避免对整个数组的拷贝(数组可能比较长,那么拷贝开销就会很大).


认识 null

null 在 Java 中表示“空引用”,也就是一个无效的引用.

int[] arr = null;
System.out.println(arr[0]);

// 执行结果
Exception in thread "main" java.lang.NullPointerException

null 的作用类似于 C 语言中的 NULL,都是表示一个无效的内存位置.
因此不能对这个内存进行任何读写操作. 一旦尝试读写,就会抛出 NullPointerException.

Java 中没有约定 null 和 0 号地址的内存有关系.


初始 JVM 内存区域划分(重点)

在学校,一栋宿舍楼会划分成不同区域:大一、大二…计算机专业,通信专业…
内存也是类似,被划分成不同的部分,每个区域存放不同的数据.

JVM 的内存被划分成了几个区域,如:

  • 程序计数器(PC Register):只是一个很小的空间,保存下一条执行的指令地址.
  • 虚拟机栈(JVM Stack):重点是存储局部变量表(也有其它信息). 我们刚才创建的数组 int[] arr这样的存储地址的引用就是在这里保存.
  • 本地方法栈(Native Method Stack):本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是 Native 方法的局部变量. 在有些版本的 JVM 实现中(HotSpot),本地方法栈和虚拟机栈是在一起的.
  • 堆(Heap):JVM 所管理的最大内存区域,使用 new 创建的对象都是在堆上保存(例如前面的new int[]{1,2,3}).
  • 方法区(Method Area):用于存储已经被虚拟机加载的类信息、常量、静态常量、即使编译器编译后的代码等数据. 方法编译后的字节码就保存在这个区域.
  • 运行时常量池(Runtime Constant Pool):是方法区的一部分,存放字面量(字符串常量)与符号引用. (从JDK1.7开始,运行时常量池在堆上).
ps.
Native 方法:
JVM 是一个基于 C++ 实现的程序. 
在 Java 程序执行过程中,本质也需要调用 C++ 提供的一些函数运行和操作系统底层交互. 
因此在 Java 开发中也会调用到一些 C++ 实现的函数.

Native 方法指的是这些 C++ 实现的,再由 Java 来调用的函数.

小结
总的来说,局部变量和引用保存在栈上new 出来的对象保存在堆上.

堆的空间非常大,栈的空间比较小.

堆是整个 JVM 共享一个,而栈是每个线程具有一份(一个 Java 程序中可能存在多个栈).


三、数组作为方法的返回值

代码示例 :写一个方法,将数组中的每个元素都 *2.

    public static void main(String[] args) {
        int[] arr = {1,2,3};
        transform(arr);
        printArray(arr);
    }
    
    public static void transform(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] *= 2;
        }
    }
    
    public static void printArray(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }

这个代码固然可行, 但是破坏了原有数组. 有时候我们不希望破坏原数组, 就需要在方法内部创建一个新的数组, 并由方法返回出来.

// 返回一个新的数组
class Test {
	public static void main(String[] args) {
		int[] arr = {1, 2, 3};
		int[] output = transform(arr);
		printArray(output);
	}
	
	public static void printArray(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}

	public static int[] transform(int[] arr) {
		int[] ret = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			ret[i] = arr[i] * 2;
		}
		return ret;
	}
}

这样的话就不会破坏原有数组了.

另外由于数组是引用类型, 返回的时候只是将这个数组的首地址返回给函数调用者, 没有拷贝数组内容, 从而比较高效.


四、数组练习

数组转字符串

代码示例

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

//执行结果
[1, 2, 3, 4, 5, 6]

使用这个方法后续打印数组就更方便一些.

自己实现一个数组转字符串方法.

	//纯纯字符串拼接整型
    public static String my_toString(int[] arr) {
        String str = "[";
        for (int i = 0; i < arr.length - 1; i++) {
            str += (arr[i] + ", ");
        }
        str += (arr[arr.length - 1] + "]");
        return str;
    }
    
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6};
        String string = my_toString(arr);
        System.out.println(string);
    }

数组拷贝

代码示例 :字符串拷贝

Arrays 包下的 copyOf 方法.
从 arr1 数组中拷贝,arr1.length 长度的数据到新数组.
        int[] arr1 = {1,2,3,4,5,6};
        int[] arr2 = Arrays.copyOf(arr1, arr1.length);
        System.out.println("arr2:" + Arrays.toString(arr2));

        arr1[0] = 199;
        System.out.println("arr1:" + Arrays.toString(arr1));
        System.out.println("arr2:" + Arrays.toString(arr2));

代码示例 :拷贝某个范围

Arrays.copyOfRange(arr, n1, n2);
从数组 arr 中拷贝数据,数据范围是 n1 到 n2.
        int[] arr1 = {1,2,3,4,5,6,7,8,9};
        int[] arr2 = Arrays.copyOfRange(arr1, 2,4);
        System.out.println(Arrays.toString(arr2));

//运行结果
[3, 4]

注意事项
相比于newArr = arr这样的赋值,copyOf 是将数组进行了深拷贝,即又创建了一个数组对象,拷贝原有数组中的所有元素到新数组中.
因此,修改原数组,不会影响到新数组.

模拟实现数组拷贝

    public static int[] my_copyOf(int[] arr, int len) {
        int[] newArr = new int[len];
        for (int i = 0; i < len; i++) {
            newArr[i] = arr[i];
        }
        return newArr;
    }

找数组中的最大元素

给定一个整型数组,找到其中最大的元素.

代码示例

    public static int getMax(int[] arr){
        int max = arr[0];
        for (int i = 1; i < arr.length; i++){
            if (max < arr[i]){
                max = arr[i];
            }
        }
        return max;
    }
    
    public static void main(String[] args) {
        int[] arr = {1,5,9,3,5,7,4,6,8,2};
        int max = getMax(arr);
        System.out.println(max);
    }

类似于”打擂台“,假设数组的第一个元素是最大,然后让其余元素依次与之对比。

求数组中元素的平均值

给定一个整型数组,求平均值

    public static double getAvg(int[] arr){
        int sum = 0;
        for (int i = 0; i < arr.length; i++){
            sum += arr[i];
        }
        return sum/arr.length;
    }
    public static void main(String[] args) {
        int[] arr = {1,5,9,3,5,7,4,6,8,2};
        double avg = getAvg(arr);
        System.out.println(avg);
    }

平均值有可能是小数,需要使用 double 来表示.

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

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

    public static int findPost(int[] arr, int num){
        for (int i = 0; i < arr.length; i++){
            if (num == arr[i]){
                return i;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] arr = {1,5,9,3,7,4,6,8,2};
        int post = findPost(arr,2);
        System.out.println(post);
    }

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

针对有序数组,才能使用二分查找.

二分查找的思路就是:
先取中间位置的元素,与要找的数据对比;
元素大了,表示数据在中间元素的左侧;
元素小了,表示数据在中间元素的右侧。

代码示例

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

感受二分查找的效率

    public static int[] makeBigArray() {
        int[] arr = new int[10000];
        for (int i = 0; i < 10000; i++){
            arr[i] = (i + 1);
        }
        return arr;
    }
    
    public static int binarySearch(int[] arr, int num) {
        int left = 0;
        int mid = 0;
        int right = arr.length - 1;
        while (left <= right) {

            count++;

            mid = (left + right) / 2;
            if (arr[mid] > num) {
                right = mid - 1;
            }else if (arr[mid] < num) {
                left = mid + 1;
            }else {
                return mid;
            }
        }
        return -1;
    }
    
    public static void main(String[] args) {
        int[] arr = makeBigArray();
        int post = binarySearch(arr, 9999);
        System.out.println(post);
        System.out.println(count);
    }

//运行结果
9998
14

创建一个静态变量 count 来记录。
我们可以看到,一个长度为 10000 的数组,使用二分查找只需要循环 14 次。
可见数组越大,二分查找的优势越大

检查数组的有序性

给定一个整型数组,判断是否该数组有序(升序).

    public static boolean isSorted(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            if (arr[i] > arr[i + 1]) {
                return false;
            }
        }
        return true;
    }
    
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6};
        System.out.println(isSorted(arr));
    }

数组排序(冒泡排序)

给定一个数组,让数组升序(降序)排序.

算法思路
每次尝试找当前待排序区间中最小(最大)的元素,放在数组最前面(最后面).

代码示例

    public static void bubbleSort(int[] arr) {
    	//外层循环,长度减一,因为最后一个元素不需要排序;
        for (int i = 0; i < arr.length-1; i++) {
			//内层循环,走完一趟循环需要减少一个元素,因为已经排序好了;
			//担心接下来使用数组变量来交换会越界,所以数组长度减一;
            for (int j = 0; j < arr.length-1-i; j++) {
            	//使用内层循环变量来对比
                if (arr[j] > arr[j+1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                }
            }
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {1,5,9,3,7,4,6,8,2};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
    }

冒泡排序是比较简单的排序,性能不高.
Java 中内置了更高效的排序算法,我们可以直接使用.

    public static void main(String[] args) {
        int[] arr = {1,7,9,3,4,6,8,2};
        Arrays.sort(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--;
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7,8,9};
        reverse(arr);
        System.out.println(Arrays.toString(arr));
    }

数组数字排列

给定一个整型数组,将所有的偶数放在前半部分,将所有奇数放在数组后半部分.

{1, 2, 3, 4}
调整后
{2, 4, 1, 3}

基本思路
设定两个下标,分别是数组最前面和最后面.

最前面找第一个奇数,最后面找第一个偶数,彼此交换,依次循环.

代码示例

    public static void transform(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        while (left < right) {
            //找第一个奇数
            if (left < right && arr[left] % 2 != 1) {
                left++;
            }
            //找第一个偶数
            if (left < right && arr[right] % 2 != 0) {
                right--;
            }
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6};
        transform(arr);
        System.out.println(Arrays.toString(arr));
    }

五、二维数组

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

基本语法

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

代码示例

    public static void main(String[] args) {
        int[][] arr = {{1,2,3}, {4,5,6}, {7,8,9}};
        for (int row = 0; row < arr.length; row++) {
            for (int col = 0; col < arr[row].length; col++) {
                System.out.print(arr[row][col] + " ");
            }
            System.out.println();
        }
    }

//执行结果
1 2 3 
4 5 6 
7 8 9 

二维数组的用法和一维数组并没有明显差别. 和C语言差不多.
同理,还有“三维数组”,“四维数组”等更加复杂的数组,只不过出现的频率都很低

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值