java数组创建使用拷贝超全详解


一、数组基本用法

1.什么是数组

数组就是一组相同类型的元素的集合
当我们需要定义多个变量时

	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	int e = 50;

如果定义几十甚至几百个变量,那么就不能这样创建变量了,这个时候我们就需要用到数组了

	int[] array = {10,20,30,40,50};

这就是创建了一个简单的整形数组

2.数组的创建

在这里插入图片描述
C语言的创建数组方式

int array[] = {1,2,3,4,5};

java的创建数组方式

int[] array = {1,2,3,4,5};

其实java中也可以使用C语言的创建数组方式,但是我们还是更推荐写成 int[] arr 的形式. int和 [] 是一个整体。这也是是一种语法规范吧!

3.定义数组的方式

定义数组共有三种方式
第一种:静态初始化

	int[] array = {1,2,3,4,5};

第二种:动态初始化

	int[] array = new int[]{1,2,3,4,5};

第三种

	int[] array = new int[5];

注意:
1.静态初始化的时候, 数组元素个数和初始化数据的格式是一致的。
2.第一种和第二种定义,等号右边的 [ ] 的里面不可以出现数字。
3.第一种和第二种数组的大小就是里面数据的个数。

4.数组的使用

4.1 获取数组的长度

通过 数组名.length 就可以获取数组的长度
注意:数组的下标是从0开始的,使用 [ ] 就可以访问数组的元素,技能读取数据也能修改数据。
在这里插入图片描述

public class Test{
    public static void main(String[] args){

        int[] array = {1,2,3,4,5};
        array[0] = 10;
        System.out.println(array[0]);

    }
}

运行结果
在这里插入图片描述

4.2 遍历数组的两种方式

1.for循环遍历

public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
    }
}

2.foreach遍历

for (int x:array) {
	System.out.print(x+" ");
}

4.3 for循环和foreach(增强for循环)的区别

for循环是可以拿到下标的
foreach是拿不到下标的
所以以后要看情况使用for循环和foreach

foreach的遍历原理:遍历array数组里面的每一个元素,把每一个元素取出来,然后赋值给x,最后打印x,直到array数组全部遍历完。

两种遍历的运行结果

public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
        System.out.println();
        for (int x:array) {
            System.out.print(x+" ");
        }
    }
}

在这里插入图片描述

4.4 以字符串打印数组

数组的工具类:import java.util.Arrays
Arrays.toString(数组名)
把你需要打印的数组放到toString函数里面,那么就会把当前的数组转变为字符串进行输出!所以调用这个函数后,它的返回值就是一个字符串。

来看一个例子
在这里插入图片描述
运行结果:可以看到数组被连同方括号都被转换成字符串了
在这里插入图片描述

4.5 数组下标越界访问异常

当你创建的数组长度只有5,你非要访问5下标(数值的下标是从0开始) 。那就会出现下标越界访问异常。
在这里插入图片描述
在这里插入图片描述
就比如这里数组只有5个元素最大下标是4,而而要去访问下标5就是出现数组越界访问异常。

5.数组在内存中的存储

在JVM(JAVA虚拟机)中共有5块内存,而我们平常所说的栈就是JAVA虚拟栈。局部变量和数组名都是在栈上开辟内存的。
在这里插入图片描述
数组变量是一个引用
引用一般指向一个对象(当前你可以理解为,等号右边的就是数组对象)
在这里插入图片描述
来看一段代码

public class Test{
    public static void main(String[] args){

        int[] array = {1,2,3,4,5};
        int[] array2 = new int[5];
        System.out.println(array);
        System.out.println(array2);
    }
}

运行结果
在这里插入图片描述
运行后打印出两个地址
注意:这两个地址不是真正的地址,他是通过正式的地址hash得到的
但是你可以把它当做一个真实的地址,这两个地址是唯一的(安全性)

二、数组作为方法的参数

1.基本用法

代码如下(示例):

public class Test{
    public static void prints(int[] arr){
        for (int x:arr) {
            System.out.print(x+" ");
        }
    }
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        prints(array);
    }
}

运行结果
在这里插入图片描述
我这里用的是foreach遍历整个数组

  • int[ ] arr 是函数的形参, int[ ] array 是函数实参.
  • 如果需要获取到数组长度, 同样可以使用 a.length

2.理解引用类型

2.1 传内置类型

代码示例如下(传内置类型):

public class Test{
    public static void exchange(int n){
        n = 200;
        System.out.println("n= "+n);
    }
    public static void main(String[] args){
        int a = 100;
        exchange(a);
        System.out.println("a= "+a);
    }
}

运行结果:
在这里插入图片描述

我们发现修改形参n的值,不影响实参a的值
在这里插入图片描述

2.2 传引用类型

代码示例(传数组):

public class Test{
    public static void exchange(int[] arr){
        int tmp = arr[0];
        arr[0] = arr[1];
        arr[1] = tmp;
    }
    public static void main(String[] args){
        int[] array = {10,20,30,40};
        System.out.println("交换前");
        System.out.println("array[0] = "+array[0]+" array[1] = "+array[1]);
        exchange(array);
        System.out.println("交换后");
        System.out.println("array[0] = "+array[0]+" array[1] = "+array[1]);
    }
}

运行结果:

在这里插入图片描述
我们发现数组组为参数传参的话就能修改变量的值,我们前面说过array存储的是地址,也就是说我们传的是引用
什么是引用?

  • 引用相当于一个 “别名”, 也可以理解成一个指针.
  • 创建一个引用只是相当于创建了一个很小的变量, 这个变量保存了一个整数,
    这个整数表示内存中的一个地址

我们再通过图来了解一下为社么传引用就能修改实参呢?
在这里插入图片描述
传引用实际上就是传的地址,通过地址找到同一块空间。修改同一块地址上的数据,自然就修改了。

总结:

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

2.3 认识 null

null在java中表示 ”空引用“ ,表示不指向任何对象,也就是一个无效的引用。
引用类型的0值就是null
来看一个代码:

public class Test{
    public static void exchange(int[] arr){
        int tmp = arr[0];
        arr[0] = arr[1];
        arr[1] = tmp;
    }
    public static void main(String[] args){
        int[] array = {10,20,30,40};
        exchange(null);
    }
}

运行结果:
在这里插入图片描述

如果在传引用时传的时null就会报出异常, NullPointerException。null 的作用类似于 C 语言中的 NULL
(空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作。

那如果我们把数组赋一个 null

public class Test{
    public static void main(String[] args){
        int[] array = null;
        System.out.println(array);
    }
}

运行结果
在这里插入图片描述
我们发现并没有打印0,而是打印null

那null的数组能不能求长度呢?

public class Test{
    public static void main(String[] args){
        int[] array = null;
        System.out.println(array.length);
    }
}

运行结果
在这里插入图片描述
我们发现这里也是一个空引用异常,从这我们可以知道,把null 赋给了 array,表示array没有指向任何对象,所以它没在堆上开辟空间,报出空引用异常。所以null.任何东西都会报出异常
总结:
当我们以后遇到 NullPointerException 都是空引用异常

2.4 初识 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 开始, 运行时常量池在堆上)

总结:

1.JVM是多线程的,多线程就意味着高效率。
2.局部变量和引用保存在栈上, new 出的对象保存在堆上.
3.堆的空间非常大, 栈的空间比较小.
4.堆是整个 JVM 共享一个, 而栈每个线程具有一份(一个 Java 程序中可能存在多个栈)

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

假设我们要实现一个方法,把数组里的每个元素都加上1

import java.util.Arrays;

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

这种方法当然可以,但这会破坏原有的数组,有时候我们不希望破坏原数组, 就需要在方法内部创建一个新的数组, 并由方法返回出来.
来看一下代码:

import java.util.Arrays;

public class Test{
    public static int[] addOne(int[] arr){
        int[] array2 = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            array2[i] = arr[i] + 1;
        }

        return array2;
    }
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        int[] ret = addOne(array);
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(ret));
    }
}

运行结果:
在这里插入图片描述
这样的话就不会破坏原有数组了.
另外由于数组是引用类型, 返回的时候只是将这个数组的首地址返回给函数调用者, 没有拷贝数组内容, 从而比较高效。

四、数组的拷贝

1.什么是包

Java 中提供了 java.util.Arrays 包, 其中包含了一些操作数组的常用方法.
那什么是包呢?简单简单点说,假设你煮面吃是不是要自己调配料,而方便面直接就有调料包,拿起来就用,不用像自己煮面一样还要想怎么放才好吃。
像我们很多程序写的过程中不必把所有的细节都自己实现, 已经有大量的标准库(JDK提供好的代码)和海量的第三方库(其他机构组织提供的代码)供我们直接使用. 这些代码就放在一个一个的 “包” 之中.

有了这个包,我们只需要 Arrays. 就能使用JAVA提供的很多和数组相关的方法。

1. for循环拷贝

新创建一个数组,再利用for循环把以前的数组拷贝到新数组里。
注意:新开辟的数组大小一定要大于或者等于原数组的大小。

import java.util.Arrays;
public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        int[] arr = new int[array.length];
        for (int i = 0; i < array.length; i++) {
            arr[i] = array[i];
        }
        System.out.println(Arrays.toString(arr));
    }
}

2. copyof拷贝

这是JAVA提供给我们的一种拷贝数组的方法,先来看一下这个方法的源代码
在这里插入图片描述
这个方法需要两个参数,一个要拷贝的数组和新数组的长度。
代码示例:

import java.util.Arrays;

public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        int[] arr = Arrays.copyOf(array,10);
        System.out.println(Arrays.toString(arr));//以字符串方式打印数组
    }
}

运行结果:
在这里插入图片描述
我们发现如果新数组的长度大于要拷贝数组的长度,多余的位置默认为0.

3. copyOfRange拷贝

同样这也是Arrays提供的一种拷贝数的方法,源代码如图
在这里插入图片描述

再来看一个例子

import java.util.Arrays;

public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        int[] arr = Arrays.copyOfRange(array,1,4);
        System.out.println(Arrays.toString(arr));
    }
}

运行结果
在这里插入图片描述
因为拷贝范围是左闭右开的,所以并没有拷贝到5.
注意:如果超出拷贝数组的范围则超出的元素默认为0,且范围是不能为负数的。

4. arraycopy拷贝

来看一下源代码
在这里插入图片描述
使用例子

import java.util.Arrays;

public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5,6};
        int[] arr = new int[array.length];
        System.arraycopy(array,0,arr,0,6);
        System.out.println(Arrays.toString(arr));
    }
}

运行结果
在这里插入图片描述
注意:这里的拷贝和上面的两种方式是不一样的,这种拷贝方式是不能越界的!如果你的数组要拷贝的长度从你的起始下标起大于了数组的长度(无论是起始数组还是目的地数组都是不能越界的),就会出现数组下标越界异常。

4.1 native:本地方法

还有一个重点不知道大家注意没,arraycopy这个方法的源代码是没有具体实现过程的。而且这个方法中有 native 这么一个关键字。这属于本地方法
那么 native 这样的方法有什么特点呢?

1.运行在本地方法栈上
2.底层是由C/C++写的
3.这样的方法速度是非常快的

所以我们以后拷贝数组优先使用的是这种方法,因为它速度快。

5. clone拷贝

数组名.clone直接产生一个数组的副本

import java.util.Arrays;

public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        int[] arr = array.clone();
        System.out.println(Arrays.toString(arr));
    }
}

在这里插入图片描述

6.深拷贝和浅拷贝

深拷贝:修改拷贝之后的数组不会影响原数组
浅拷贝:修改拷贝之后的数组会影响原数组

如何达到深拷贝:对对象本身进行拷贝
我们刚刚的例子都属于深拷贝
因为JAVA以后是面向对象,数组里一般放的都是对象很少放整形。如果要细说,那么就分基本类型和引用类型。
如果数组里放的是基本数据类型那就是深拷贝,如果数组里放的是引用类型那就是浅拷贝。

五、数组的练习(经典题型)

1.二分查找(折半查找)

二分查找只能用于一组有序数组(升序或者降序)
以升序数组为例, 二分查找的思路是先取中间位置的元素, 看要找的值比中间元素大还是小. 如果小, 就去左边找; 否则就去右边找.

import java.util.Scanner;
public class Test{
    public static int binarySearch(int[] array,int n){
        if(array == null) return -1;

        int left = 0;
        int right = array.length-1;
        while(left<=right){
            int mid = left+(right-left)/2;
            if(array[mid]>n){//当要找的数比中间那个数小
                right = mid-1;
            }else if(array[mid]<n){//当要找的数比中间那个数大
                left = mid+1;
            }else{
                return mid;//找到了返回下标
            }
        }

        return -1;//没找到时
    }
    public static void main(String[] args){
        int[] array = {1,2,3,4,5,6,7,8,9};
        System.out.println("请输入年哟查找的数字");
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int ret = binarySearch(array,n);
        if(ret == -1){
            System.out.println("没找到");
        }else{
            System.out.println("找到了下标为"+ret);
        }
    }
}

JAVA还提供了一个二分查找的方法binarySearch
源代码就不看了,和上面的实现方法类似
同样是两个参数,一个是要查找的数组,一个则是要查找的数字。
如果找到了就放回该数字的下标,没找到就放回一个负数。
来看一下代码

import java.util.Arrays;
import java.util.Scanner;
public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5,6,7,8,9};
        System.out.println("请输入年哟查找的数字");
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int ret = Arrays.binarySearch(array,n);//调用方法
        if(ret < 0){
            System.out.println("没找到");
        }else{
            System.out.println("找到了下标为"+ret);
        }
    }
}

2.数组排序(冒泡排序)

给定一个数组把它排列成升序或者降序
思路:两两相比较交换位置,把最大或者最小的数字放到最后。

import java.util.Arrays;
import java.util.Scanner;
public class Test{
    public static void bubbleSort(int[] array){
        if(array == null) return;
        boolean flag = false;//定义个flag判断数组是否有序
        for (int i = 0; i < array.length-1; i++) {
            for (int j = 0; j < array.length-1-i; j++) {
                if(array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    flag = true;
                }
            }
            if(flag != true){//当数组已经是有序时直接跳出循环
                break;
            }
        }
    }
    public static void main(String[] args){
        int[] array = {2,3,1,5,6,4,3,2,1};
        System.out.println(Arrays.toString(array));
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    }
}

3.判断两个数组是否相等

JAVA中提供了一个方法 equals 用来判断两个数组是否相等
在这里插入图片描述
可以看到equals的返回类型是布尔类型,相等返回true不相等返回false
代码示例:

import java.util.Arrays;
public class Test{
    public static void main(String[] args){
        int[] array = {1,2,3,4,5};
        int[] arr = {1,2,3,4,5};
        boolean bool = Arrays.equals(array,arr);
        System.out.println(bool);

    }
}

4.数组填充

JAVA中还提供整体填充的方法 fillc ,把数组里的元素替换成指定元素。可适用各种类型。

import java.util.Arrays;
public class Test{
    public static void main(String[] args){
        int[] arr = {1,2,3,4,5};
        Arrays.fill(arr,6);
        System.out.println(Arrays.toString(arr));
    }
}

运行结果:
在这里插入图片描述
该函数还能指定下标替换,记住下标是不能越界的。
在这里插入图片描述
运行结果:

在这里插入图片描述

六、二维数组

1.定义二维数组

在JAVA中常见的定义数组方式,JAVA中二维数组可以省略列,不能省略行。

		int[][] array = {{1,2},{3,4},{5,6}};
        int[][] array2 = new int[3][2];
        int[][] array3 = new int[][]{{1,2},{3,4},{5,6}};

2.二维数组在内存中的存储

我们知道二维数组其实就是一个特殊的一维数组,那么它在内存中到底是怎么存储的呢?
在这里插入图片描述
每一行构成了一个一维数组,数组里的每一个元素存的都是指向每一列一维数组的地址。
每一列的一维数组存的则是指向数据的地址。

3.二维数组的打印方式

3.1 for循环打印

我们知道数组名.length求的是数组的长度,而二维数组名.length求的则是拿到得是每一列的地址,那么求每一列的长度则是 数组名[i].length。

public class Test{
    public static void main(String[] args){
        int[][] array = {{1,2,3},{3,4,5},{5,6,7}};
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j]+" ");
            }
            System.out.println();
        }
    }
}

3.2foreach打印

第一层foreach是遍历整个array数组,因为二维数组的数组名都是得地址,拿到每一列得地址也就是相当于拿到了一个一维数组,通过第二次foreach循环就可以拿到每一个元素

public class Test{
    public static void main(String[] args){
        int[][] array = {{1,2,3},{3,4,5},{5,6,7}};
        for (int[] arr:array) {
            for (int x:arr) {
                System.out.print(x+" ");
            }
            System.out.println();
        }
    }
}

3.3 deepToString(深度打印)

改方法是用来把二维数组转换为字符串的,它的返回类型为String

import java.util.Arrays;
public class Test{
    public static void main(String[] args){
        int[][] array = {{1,2,3},{3,4,5},{5,6,7}};
        System.out.println(Arrays.deepToString(array));
    }
}

运行结果:

在这里插入图片描述

4.不规则的二维数组

我们前面说过二维数组的列是可以省略的,但如果省略那这个二维数组能打印吗?

public class Test{
    public static void main(String[] args){
        int[][] array = new int[3][];
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.println(array[i][j]);
            }
            System.out.println();
        }
    }
}

运行结果
在这里插入图片描述
看到了我们熟悉的空引用异常,那这是什么原因呢?
我们知道二维数组名.length拿到的是地址,而现在数组没有指定列,所以里面存的是null,当我们array[ i ].length 的时候就出现了异常,前面也说过 null.任何东西都会出现异常。

在这里插入图片描述

1.不规则二维数组的创建

因为二维数组一般都是行和列都已经写好了,而不规则的二维数组在定义时把列给省略掉了,所以没一行的列数可以有自己定。

public class Test{
    public static void main(String[] args){
        int[][] array = new int[3][];
        array[0] = new int[1];
        array[1] = new int[2];
        array[2] = new int[4];
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j]+" ");
            }
            System.out.println();
        }
    }
}

运行结果

在这里插入图片描述

public class Test{
    public static void main(String[] args){
        int[][] array = new int[3][];
        array[0] = new int[1];
        array[1] = new int[]{2,2,2};
        array[2] = new int[]{1,2,3,4};
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j]+" ");
            }
            System.out.println();
        }
    }
}

运行结果:
在这里插入图片描述

2.不规则的二维数组在数组中的存储

不规则的二维数组和普通的二维数组没什么区别
在这里插入图片描述

数组详解就到这里,记得点个赞再走!

完结!

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的三毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值