java基础—数组

java基础—数组

1、理解数组

  数组也是一种类型,数组既可以存储基本类型的数据, 也可以存储引用类型的数据,只要所有的数组元素具有相同的类型即可。Java的数组要求所有的数组元素具有相同的数据类型。因此,在一个数组中,数组元素的类型是唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多种数据类型的数据。
  但需要注意的是Java语言是面向对象的语言, 而类与类之间可以支持继承关系,这样可能产生一个数组里可以存放多种数据类型的假象。例 如有一个水果数组,要求每个数组元素都是水果,实际上数组元素 既可以是苹果,也可以是香蕉(苹果、香蕉都继承了水果,都是一种特殊的水果),但这个数组的数组元素的类型还是唯一的,只能是水果类型。
  Java数组必须先初始化,然后才可以使用。所谓初始化 就是为数组的数组元素分配内存空间,并为每个数组元素赋初始值,一旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此数组的长度将不可改变。即使把某个数组元素的数据清空, 但它所占的空间依然被保留,依然属于该数组,数组的长度依然不变。

2、数组初始化

  • 静态初始化:初始化时由程序员显式指定每个数组元素的初始
    值,由系统决定数组长度。
    public static void main(String[] args) {
        // 定义一个int数组类型的变量,变量名为intArr.
        int[] intArr;
        // 使用静态初始化,初始化数组时只指定数组元素的初始值,不指定数组长度。
        intArr = new int[]{5, 6, 8, 20};
        // 定义一个Object数组类型的变量,变量名为objArr.
        Object[] objArr;
        // 使用静态初始化,初始化数组时数组元素的类型是
        // 定义数组时所指定的数组元素类型的子类
        objArr = new String[]{"Java", "c#"};
        Object[] objArr2;
        // 使用静态初始化
        objArr2 = new Object[]{"Java", "git"};
        // 数组的定义和初始化同时完成,使用简化的静态初始化写法
        int[] a = {5, 6, 7, 9};
    }
  • 动态初始化:初始化时程序员只指定数组长度,由系统为数组
    元素分配初始值。
/* 
  动态初始化时数组各种类型的默认值如下:
  1、数组元素的类型是基本类型中的整数类型(byte、short、int 和
  	 long),则数组元素的值是0。
  2、数 组 元 素 的 类 型 是 基 本 类 型 中 的 浮 点 类 型 ( float 
  	、 double),则数组元素的值是0.0。
  3、数组元素的类型是基本类型中的字符类型(char),则数组元 素的值
  	 是'\u0000'。 
  4、数组元素的类型是基本类型中的布尔类型(boolean), 则数 组元素的值
  	 是false。 
  5、数组元素的类型是引用类型(类、接口和数组),则数组元素 的值是
  	 null。
*/

 //数组的定义和初始化同时完成,使用动态初始化语法
 int[] prices = new int[5];
 // 数组的定义和初始化同时完成,初始化数组时元素的类型是定义数组时元素类型的子类
 Object[] books = new String[4];

3、内存中数组

  数组引用变量只是一个引用, 这个引用变量可以指向任何有效的 内存,只有当该引用指向有效内存后,才可通过该数组变量来访问数 组元素。
  与所有引用变量相同的是, 引用变量是访问真实对象的根本方 式。也就是说,如果希望在程序中访问数组对象本身,则只能通过这 个数组的引用变量来访问它。
  实际的数组对象被存储在堆(heap)内存中;如果引用该数组对 象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中,如下图所示。
在这里插入图片描述
  只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这种操作 会让人产生数组的长度可变的错觉,

 int[] a = {5, 7, 20};
 int[] b = {0, 0, 0, 0};
 System.out.println("b数组的长度为:" + b.length);
 // 因为a是int[]类型,b也是int[]类型,所以可以将a的值赋给b。
 // 也就是让b引用指向a引用指向的数组
 b = a;
 System.out.println("b数组的长度为:" + b.length);

  运行上面代码后, 将可以看到先输出b数组的长度为4,接着会输出b数组的长度为3。看起来似乎数组的长度是可变的,但这只是一个假象。必须牢记:定义并初始化一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身。下面将结合示意图来说明上 面程序的运行过程。
  当程序定义并初始化了a、b两个数组后, 系统内存中实际上产生 了4块内存区,其中栈内存中有两个引用变量:a和b;堆内存中也有两 块内存区,分别用于存储a和b引用所指向的数组本身。此时计算机内存的存储示意图如下图所示。

  从上图中可以非常清楚地看出a引用和b引用各自所引用的数组对 象,并可以很清楚地看出a变量所引用的数组长度是3,b变量所引用的 数组长度是4。
  当执行上面的粗体字代码b=a;时,系统将会把a的值赋给b,a和b都 是引用类型变量,存储的是地址。因此把a的值赋给b后,就是让b指向 a所指向的地址。此时计算机内存的存储示意图如下图所示。
在这里插入图片描述
  从上图中可以看出,当执行了b=a;之后,堆内存中的第一个数组 具有了两个引用:a变量和b变量都引用了第一个数组。此时第二个数 组失去了引用,变成垃圾,只有等待垃圾回收器来回收它—但它的长 度依然不会改变,直到它彻底消失。

3.1、基本类型数组的初始化内存分配过程

  对于基本类型数组而言, 数组元素的值直接存储在对应的数组元 素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将 数组元素的值存入对应数组元素中。
  下面程序定义了一个int[]类型的数组变量,采用动态初始化的方 式初始化了该数组,并显式为每个数组元素赋值。

    public static void main(String[] args) {
        // 定义一个int[]类型的数组变量
        int[] iArr;
        // 动态初始化数组,数组长度为5
        iArr = new int[5];
        // 采用循环方式为每个数组元素赋值。
        for (var i = 0; i < iArr.length; i++) {
            iArr[i] = i + 10;
        }
    }

  执行第一行代码int[] iArr;时,仅定义一个数组变量,此时内存中的存储示意图如图下所示。
在这里插入图片描述
  执行了int[] iArr;代码后,仅在栈内存中定义了一个空引用(就是iArr数组变量),这个引用并未指向任何有效的内存,当然无法指定数组的长度。
  当执行iArr=new int[5];动态初始化后, 系统将负责为该数组分配内存空间,并分配默认的初始值:所有数组元素都被赋为值0,此时内存中的存储示意图如下图所示。
在这里插入图片描述
  此时iArr数组的每个数组元素的值都是0,当循环为该数组的每个数组元素依次赋值后,此时每个数组元素的值都变成程序显式指定的值。显式指定每个数组元素值后的存储示意图如下图所示。
在这里插入图片描述

3.2、引用类型数组的初始化内存分配过程

  引用类型数组的数组元素是引用,因此情况变得更加复杂。每个数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了有效数据。为了更好地说明引用类型数组的运行过程,下面先定义一个 Person类(所有类都是引用类型)。

class Person {
    public int age;
    public double height;

    Person() {
    }

    public void info() {
        System.out.println("我的年龄是:" + this.age + ",我的身高是:" + this.height);
    }
}

   然后定义一个 Person[] 数组 ,接着动态初始化这个Person[]数组,并为这个数组的每个数组元素指定值。程序代码如下。

    public static void main(String[] args) {
        // 定义一个students数组变量,其类型是Person[]
        Person[] students;
        // 执行动态初始化
        students = new Person[2];
        // 创建一个Person实例,并将这个Person实例赋给zhang变量
        Person zhang = new Person();
        // 为zhang所引用的Person对象的age、height赋值
        zhang.age = 15;
        zhang.height = 158;
        // 创建一个Person实例,并将这个Person实例赋给lee变量
        Person lee = new Person();
        // 为lee所引用的Person对象的age、height赋值
        lee.age = 16;
        lee.height = 161;
        // 将zhang变量的值赋给第一个数组元素
        students[0] = zhang;
        // 将lee变量的值赋给第二个数组元素
        students[1] = lee;
        // 下面两行代码的结果完全一样,因为lee
        // 和students[1]指向的是同一个Person实例。
        lee.info();
        students[1].info();
    }

   执行Person[] students;代码时, 这行代码仅仅在栈内存中定义 了一个引用变量,也就是一个指针,这个指针并未指向任何有效的内 存区。此时内存中存储示意图如下图所示。
在这里插入图片描述
   在如上所示的栈内存中定义了一个students变量,它仅仅是一个引用, 并未指向任何有效的内存。直到执行初始化,本程序对 students数组执行动态初始化, 动态初始化由系统为数组元素分配默 认的初始值:null, 即每个数组元素的值都是null。执行动态初始化后的存储示意图如下图所示。
在这里插入图片描述
   从上图图中可以看出,students数组的两个数组元素都是引用,而 且这个引用并未指向任何有效的内存, 因此每个数组元素的值都是 null。 这意味着依然不能直接使用students数组元素, 因为每个数组 元素都是null, 这相当于定义了两个连续的Person变量, 但这个变量 还 未 指 向 任 何 有 效 的 内 存 区 , 所 以 这 两 个 连 续 的 Person 变 量 (students数组的数组元素)还不能使用。
   接着的代码定义了zhang和lee两个Person实例,定义这两个实例 实际上分配了4块内存,在栈内存中存储了zhang和lee两个引用变量, 还在堆内存中存储了两个Person实例。此时的内存存储示意图如下图所示。
在这里插入图片描述
  此时students数组的两个数组元素依然是null, 直到程序依次将 zhang赋给students数组的第一个元素, 把lee赋给students数组的第 二个元素, students数组的两个数组元素将会指向有效的内存区。 此 时的内存存储示意图如下图所示。
在这里插入图片描述
  从上图中可以看出, 此时zhang和students[0]指向同一个内存 区, 而且它们都是引用类型变量, 因此通过zhang和students[0]来访 问 Person 实 例 的 实 例 变 量 和 方 法 的 效 果 完 全 一 样 , 不 论 修 改 students[0]所指向的Person实例的实例变量, 还是修改zhang变量所 指向的Person实例的实例变量, 所修改的其实是同一个内存区, 所以 必然互相影响。 同理, lee和students[1]也是引用同一个Person对 象,也具有相同的效果。

4、多维数组(以二维数组演示)

  Java语言里提供了支持多维数组的语法。 但本书还是想说, 没有多维数组—如果从数组底层的运行机制上来看。Java语言里的数组类型是引用类型, 因此数组变量其实是一个引 用,这个引用指向真实的数组内存。数组元素的类型也可以是引用, 如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像 多维数组。
  回到前面定义数组类型的语法:type[ ] arrName;,这是典型的一 维数组的定义语法, 其中type是数组元素的类型。 如果希望数组元素 也是一个引用, 而且是指向int数组的引用, 则可以把type具体成 int[](前面已经指出,int[]就是一种类型,int[]类型的用法与普通 类 型 并 无 任 何 区 别 ), 那 么上面定义数组的语法就是 int[][]arrName。 如果把int这个类型扩大到Java的所有类型(不包括数组类型), 则出现了定义二维数组的语法:type[][] arrName;
  Java语言采用上面的语法格式来定义二维数组, 但它的实质还是 一维数组,只是其数组元素也是引用,数组元素里保存的引用指向一 维数组。接着对这个“二维数组”执行初始化, 同样可以把这个数组当成 一维数组来初始化,把这个“二维数组”当成一个一维数组,其元素 的类型是type[]类型,则可以采用如下语法进行初始化:arrname = new type[length][]
  上面的初始化语法相当于初始化了一个一维数组, 这个一维数组 的长度是length。 同样, 因为这个一维数组的数组元素是引用类型 (数组类型)的,所以系统为每个数组元素都分配初始值:null。
  这个二维数组实际上完全可以当成一维数组使用:使用new type[length]初始化一维数组后, 相当于定义了length个type类型的 变量;类似的, 使用new type[length][]初始化这个数组后, 相当于 定义了length个type[]类型的变量, 当然, 这些type[]类型的变量都 是数组类型,因此必须再次初始化这些数组。下面程序示范了如何把二维数组当成一维数组处理。

public static void main(String[] args) {
        // 定义一个二维数组
        int[][] a;
        // 把a当成一维数组进行初始化,初始化a是一个长度为4的数组
        // a数组的数组元素又是引用类型
        a = new int[4][];
        // 把a数组当成一维数组,遍历a数组的每个数组元素
        for (int i = 0, len = a.length; i < len; i++) {
            System.out.println(a[i]);
        }
        // 初始化a数组的第一个元素
        a[0] = new int[2];
        // 访问a数组的第一个元素所指数组的第二个元素
        a[0][1] = 6;
        // a数组的第一个元素是一个一维数组,遍历这个一维数组
        for (int i = 0, len = a[0].length; i < len; i++) {
            System.out.println(a[0][i]);
        }
    }

  上面程序中粗体字代码部分把a这个二维数组当成一维数组处理, 只是每个数组元素都是null, 所以看到输出结果都是null。 下面结合 示意图来说明这个程序的执行过程。
  程序的第一行int[][] a;, 将在栈内存中定义一个引用变量, 这 个变量并未指向任何有效的内存空间,此时的堆内存中还未为这行代 码分配任何存储区。
  程序对a数组执行初始化:a=new int[4][];,这行代码让a变量指 向一块长度为4的数组内存,这个长度为4的数组里每个数组元素都是引用类型(数组类型), 系统为这些数组元素分配默认的初始值: null。此时a数组在内存中的存储示意图如下图所示。
在这里插入图片描述
  从上图来看,虽然声明a是一个二维数组,但这里丝毫看不出它 是一个二维数组的样子,完全是一维数组的样子。这个一维数组的长 度是4,只是这4个数组元素都是引用类型,它们的默认值是null。所以程序中可以把a数组当成一维数组处理,依次遍历a数组的每个元 素,将看到每个数组元素的值都是null。
  由于a数组的元素必须是int[]数组,所以接下来的程序对a[0]元 素执行初始化,也就是让上图右边堆内存中的第一个数组元素指向 一个有效的数组内存,指向一个长度为2的int数组。因为程序采用动 态初始化a[0]数组,因此系统将为a[0]所引用数组的每个元素分配默 认的初始值:0,然后程序显式为a[0]数组的第二个元素赋值为6。此时在内存中的存储示意图如下图所示。
在这里插入图片描述
  从上面程序中可以看出, 初始化多维数组时, 可以只指定最左边 维的大小;当然,也可以一次指定每一维的大小。如下代码所示。

   public static void main(String[] args) {
        // 同时初始化二维数组的2个维数
        int[][] b = new int[3][4];

        // 使用静态初始化的语法来初始化一个二维数组
        String[][] str1 = new String[][]{new String[3],
                new String[]{"hello"}};
        // 使用简化的静态初始化语法来初始化二维数组
        String[][] str2 = {new String[3],
                new String[]{"hello"}};
        System.out.println(str1[1][0]);
        System.out.println(str2[1][0]);
    }

同时初始化二维数组的两个维数后的存储示意图如下。
在这里插入图片描述

采用静态初始化语法初始化二维数组的存储示意图如下。
在这里插入图片描述

5、数组工具类Arrays类常用方法

  Java提供的Arrays类里包含的一些static修饰的方法可以直接操 作数组, 这个Arrays类里包含了如下几个static修饰的方法(static 修饰的方法可以直接通过类名调用)。
   ➢ int binarySearch(type[] a, type key):使用二分法查询 key元素值在a数组中出现的索引;如果a数组不包含key元素 值, 则返回负数。 调用该方法时要求数组中元素已经按升序排 列,这样才能得到正确结果。
  ➢ int binarySearch(type[] a, int fromIndex, int toIndex, type key):这个方法与前一个方法类似, 但它只搜索a数组中 fromIndex到toIndex索引的元素。 调用该方法时要求数组中元 素已经按升序排列,这样才能得到正确结果。
   ➢ type[] copyOf(type[] original, int length):这个方法将 会把original数组复制成一个新数组, 其中length是新数组的 长度。 如果length小于original数组的长度, 则新数组就是原 数组的前面length个元素;如果length大于original数组的长 度, 则新数组的前面元素就是原数组的所有元素, 后面补充 0(数值类型)、false(布尔类型)或者null(引用类型)。 type[] copyOfRange(type[] original, int from, int to):这个方法与前面方法相似,但这个方法只复制original数 组的from索引到to索引的元素。
   ➢ boolean equals(type[] a, type[] a2):如果a数组和a2数组 的长度相等,而且a数组和a2数组的数组元素也一一相同,该方 法将返回true。
  ➢ void fill(type[] a, type val):该方法将会把a数组的所有 元素都赋值为val。
  ➢ void fill(type[] a, int fromIndex, int toIndex, type val):该方法与前一个方法的作用相同, 区别只是该方法仅仅 将a数组的fromIndex到toIndex索引的数组元素赋值为val。
  ➢ void sort(type[] a):该方法对a数组的数组元素进行排序。
  ➢ void sort(type[] a, int fromIndex, int toIndex):该方 法 与 前 一 个 方 法 相 似 , 区 别 是 该 方 法 仅 仅 对 fromIndex 到 toIndex索引的元素进行排序。
  ➢ String toString(type[] a):该方法将一个数组转换成一个 字符串。 该方法按顺序把多个数组元素连缀在一起, 多个数组 元素使用英文逗号(,)和空格隔开。
下面程序示范了Arrays类的用法。

    public static void main(String[] args) {
        // 定义一个a数组
        int[] a = new int[]{3, 4, 5, 6};
        // 定义一个a2数组
        int[] a2 = new int[]{3, 4, 5, 6};
        // a数组和a2数组的长度相等,每个元素依次相等,将输出true
        System.out.println("a数组和a2数组是否相等:" + Arrays.equals(a, a2));
        // 通过复制a数组,生成一个新的b数组
        int[] b = Arrays.copyOf(a, 6);
        System.out.println("a数组和b数组是否相等:" + Arrays.equals(a, b));
        // 输出b数组的元素,将输出[3, 4, 5, 6, 0, 0]
        System.out.println("b数组的元素为:" + Arrays.toString(b));
        // 将b数组的第3个元素(包括)到第5个元素(不包括)赋为1
        Arrays.fill(b, 2, 4, 1);
        // 输出b数组的元素,将输出[3, 4, 1, 1, 0, 0]
        System.out.println("b数组的元素为:" + Arrays.toString(b));
        // 对b数组进行排序
        Arrays.sort(b);
        // 输出b数组的元素,将输出[0, 0, 1, 1, 3, 4]
        System.out.println("b数组的元素为:" + Arrays.toString(b));
    }

  除 此 之 外 ,在 System 类 里也包含了一个static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)方法, 该方法可以将src数组里的元素值赋给dest数组的 元素,其中srcPos指定从src数组的第几个元素开始赋值,length参数 指定将src数组的多少个元素值赋给dest数组的元素。
  由于计算机硬件的飞速发展,目前几乎所有家用PC都是4核、8 核的CPU,而服务器的CPU则具有更好的性能,因此Java 8与时俱进 地增加了并发支持,并发支持可以充分利用硬件设备来提高程序的 运行性能。Java 8增强了Arrays类的功能, 为Arrays类增加了一些工具方 法,这些工具方法可以充分利用多CPU并行的能力来提高设值、排序的 性能。下面是Java 8为Arrays类增加的工具方法。
  ➢void parallelPrefix(xxx[] array, XxxBinaryOperator op):该方法使用op参数指定的计算公式计算得到的结果作为新 的数组元素。op计算公式包括left、right两个形参,其中left 代表新数组中前一个索引处的元素, right代表array数组中当 前索引处的元素。 新数组的第一个元素无须计算, 直接等于 array数组的第一个元素。
  ➢ void parallelPrefix(xxx[] array, int fromIndex, int toIndex, XxxBinaryOperator op):该方法与上一个方法相 似, 区别是该方法仅重新计算fromIndex到toIndex索引的元 素。
  ➢ void setAll(xxx[] array, IntToXxxFunction generator): 该方法使用指定的生成器(generator)为所有数组元素设置 值,该生成器控制数组元素的值的生成算法。
  ➢ void parallelSetAll(xxx[] array, IntToXxxFunction generator):该方法的功能与上一个方法相同, 只是该方法增 加了并行能力,可以利用多CPU并行来提高性能。
  ➢ void parallelSort(xxx[] a):该方法的功能与Arrays类以前 就有的sort()方法相似, 只是该方法增加了并行能力, 可以利 用多CPU并行来提高性能。
  ➢ void parallelSort(xxx[] a, int fromIndex, int toIndex):该方法与上一个方法相似, 区别是该方法仅对 fromIndex到toIndex索引的元素进行排序。
  ➢ Spliterator.OfXxx spliterator(xxx[] array):将该数组的 所有元素转换成对应的Spliterator对象。
  ➢ Spliterator.OfXxx spliterator(xxx[] array, int startInclusive, int endExclusive):该方法与上一个方法相 似, 区别是该方法仅转换startInclusive到endExclusive索引 的元素。
  ➢ XxxStream stream(xxx[] array): 该 方 法 将 数 组 转 换 为 Stream,Stream是Java 8新增的流式编程的API。
  ➢ XxxStream stream(xxx[] array, int startInclusive, int endExclusive):该方法与上一个方法相似,区别是该方法仅将 fromIndex到toIndex索引的元素转换为Stream。 上面方法列表中, 所有以parallel开头的方法都表示该方法可利用CPU并行的能力来提高性能。
  ps:上面方法中的xxx代表不同的数据类型,比如处理int[]型数组时应将xxx换成int,处理long[]型数组时应 将xxx换成long。
  下面程序示范了Java 8为Arrays类新增的方法。

    public static void main(String[] args) {
        int[] arr1 = new int[]{3, -4, 25, 16, 30, 18};
        // 对数组arr1进行并发排序
        Arrays.parallelSort(arr1);
        System.out.println(Arrays.toString(arr1));
        int[] arr2 = new int[]{3, -4, 25, 16, 30, 18};
        Arrays.parallelPrefix(arr2, new IntBinaryOperator() {
            // left代表新数组中前一个索引处的元素,right代表原数组中当前索引处的元素
            // 新数组的第一个元素总等于原数组第一个元素
            public int applyAsInt(int left, int right) {
                return left * right;
            }
        });
        System.out.println(Arrays.toString(arr2));
        int[] arr3 = new int[5];
        Arrays.parallelSetAll(arr3, new IntUnaryOperator() {
            // operand代表正在计算的元素索引
            public int applyAsInt(int operand) {
                return operand * 5;
            }
        });
        System.out.println(Arrays.toString(arr3));
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值