03-Java数组

数组的概述

  数组是编程语言中最常见的一种数据结构,可用于存储多个数据,每个数组元素存放一个数据,通常可以通过数组元素的索引来访问数组元素,包括为数组元素赋值和取出数组元素的值。

  Java 的数组要求所有的数组元素具有相同的数据类型,一旦数组的初始化完成,数组在内存中所占的空间就被固定下来,因此数组的长度将不可改变。关于数组有以下几点:

  • 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型
  • 创建数组对象会在内存中开辟一整块的连续的空间,而数组名中引用的是这块连续空间的首地址
  • 数组的长度一旦确定,就不能修改
  • 可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快

定义数组

  Java 语言支持两种语法格式来定义数组(通常推荐使用第一种格式):

type[] arrayName;
type arrayName[];

  数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量,这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度,而且由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才可以使用

数组的初始化

  Java 语言中数组必须先初始化,然后才可以使用。所谓初始化,就是为数组的数组元素分配内存空间,并为每个数组元素赋初始值。数组的初始化有如下两种方式:

  • 静态初始化:初始化时候由程序员显式指定每个数组元素的初始值,由系统决定数组长度
  • 动态初始化:初始化时程序员只指定数组长度,由系统为数组元素分配初始值

(一)静态初始化

  静态初始化的语法格式如下:

arrayName = new type[]{element, element2, .....}

  执行静态初始化时,显式指定的数组元素值的类型必须与 new 关键字后的 type 类型相同或者是其子类的实例

public class Test {
    public static void main(String[] args) {
        int[] intArr;
        intArr = new int[] {5, 6, 8, 20};
        
        Object[] objArr;
        objArr = new String[]{"Java", "C#"};

        Object[] objArr2;
        objArr = new Object[]{"Java", "C#"};
    }
}

  除此之外,静态初始化还有如下简化的格式:

type[] arrayName = {element, element2, .....}
public class Test {
    public static void main(String[] args) {
        int[] intArr = {5, 6, 8, 20};
    }
}

(二)动态初始化

  动态初始化只能指定数组的长度,由系统为每个数组元素指定初始值:

arrayName = new type[length];

  动态初始化需要指定一个 int 类型的 length 参数:

public class Test {
    public static void main(String[] args) {
        int[] intArr = new int[4];
    }
}

  执行动态初始化时,程序员只需要指定数组的长度,系统会负责将这些数组元素分配初始值:

01

不要同时使用静态初始化和动态初始化。

使用数组

  数组最常用的就是访问数组元素,包括数组元素赋值和取出数组元素的值。访问数组元素都是通过在数组引用变量后紧跟一个方括号,方括号里是数组元素的索引值,Java 的数组索引是从 0 开始的:

public class Test {
    public static void main(String[] args) {
        int[] intArray = {1, 2, 3, 4};
        System.out.println(intArray[0]);    // 1

        intArray[0] = 10;
        System.out.println(intArray[0]);    // 10
    }
}

  如果访问数组元素时指定的索引超出范围,编译程序的时候不会出现任何错误,但在运行的时候就会出现异常(ArrayIndexOutOfBoundsException)。所有的数组都提供了一个 length 属性,通过这个属性可以访问到数组的长度:

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

  从 Java 5 之后,Java 提供了一种更简单的循环(foreach 循环),这种循环遍历数组和集合更加的简洁:

for (type variableName : array | collection)
{
    
}

  其中 type 是数组元素或集合元素的类型,variableName 是一个形参名,foreach 循环将自动将数组元素、集合元素依次赋给该变量:

public class Test {
    public static void main(String[] args) {
        int[] intArray = {1, 2, 3, 4};
        for (int i : intArray) {
            System.out.println(i);
        }
    }
}

  当使用 foreach 循环来迭代输出数组元素或集合元素时,通常不要对循环变量进行赋值

深入数组

内存中的数组

  数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当该引用指向有效内存后,才可以通过该数组变量来访问数组元素。实际的数组对象对存储在堆(heap)内存中,如果引用该数组对象的数组引用变量时一个局部变量,那么它被存储在栈(stack)内存中:

02

  如果堆内存中数组不再有任何引用变量指向自己,则这个数组称为 垃圾,该数组所占的内存将会被系统的垃圾回收机制回收。因此,为了让垃圾回收机制回收一个数组所占的内存空间,可以将该数组变量赋为 null,也就切断了数组引用变量和实际数组之间的引用关系。

基本类型数组的初始化

  对于基本类型数组,数组元素的值直接存储在对应的数组元素中,因此初始化数组时,先为该数组分配内存空间,然后直接将数组元素的值存入对应数组元素中:

public class Test {
    public static void main(String[] args) {
        int[] iArr;
        iArr = new int[5];
        for (int i = 0; i < iArr.length; i++) {
            iArr[i] = i + 10;
        }
    }
}

  执行 int[] iArr; 代码后仅在栈内存定义了一个空引用,这个引用并未指向任何有效的内存:

03

  执行 iArr = new int[5]; 之后,系统将会负责为该数组分配内存空间,并分配默认的初始值:

04

  执行循环赋值后的内存:

05

引用类型数组的初始化

public class Test {
    public static void main(String[] args) {
        Person[] students;
        students = new Person[2];

        Person zhang = new Person();
        zhang.age = 15;
        zhang.height = 158;

        Person lee = new Person();
        lee.age = 16;
        lee.height = 161;

        students[0] = zhang;
        students[1] = lee;

        lee.info();;
        students[1].info();
    }
}

class Person
{
    public int age;
    public double height;
    public void info () {
        System.out.println("age: " + age + ",height: " + height);
    }
}

  执行 Person[] students; 仅在栈内存定义一个引用变量,并未指向任何有效的内存区:

06

  然后再执行初始化,动态初始化的时候由系统为每个元素分配默认的初始值(null):

07

  然后创建两个 Person 对象:

08

  此时两个数组元素依然是 null,直到程序将两个 Person 对象赋值给数组元素:

09

没有多维数组

  在 Java 中,数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组类型,这种形式看上去很像多维数组。

  下面以二维数组演示多维数组的初始化:

public class Test {
    public static void main(String[] args) {
        // 动态初始化语法一
        int[][] a = new int[4][];
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
        a[0] = new int[2];
        a[0][1] = 6;
        for (int i = 0; i < a[0].length; i++) {
            System.out.println(a[0][i]);
        }

        // 动态初始化语法二
        int[][] b = new int[4][2];
        for (int i = 0; i < b.length; i++) {
            for (int j = 0; j < b[i].length; j++) {
                System.out.print(b[i][j] + "   ");
            }
            System.out.println();
        }

        // 静态初始化
        int[][] c = {{1,2}, {3, 4}, {5, 6}, {7, 8}};
        for (int i = 0; i < c.length; i++) {
            for (int j = 0; j < c[i].length; j++) {
                System.out.print(c[i][j] + "   ");
            }
            System.out.println();
        }
    }
}

Arrays 工具

  Java 提供了 Arrays 类里包含的一些 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 类处于 java.util 包下,为了在程序中使用 Arrays 类,必须在程序中导入 java.util.Arrays 类。

  除此之外,在 System 类里也包含了一个 static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 方法,该方法可以将 src 数组里的元素值赋给 dest 数组的元素,其中 srcPos 指定从 src 数组的第几个元素开始赋值,length 参数指定将 src 数组的多少个元素值赋给 dest 数组的元素。

  Java8 增强了 Arrays 类的功能,增加了一些工具方法,这些工具方法可以充分利用多 CPU 并行的能力来提高设值、排序的性能:

方法说明
void parallelPrefix(xxx[] array, XxxBinaryOperator op)该方法使用 op 参数指定的计算公式计算得到的结果作为新的元素。op 计算公式包括 left、right 两个形参,其中 left 代表数组中前一个索引处的元素,right 代表数组中当前所引出的元素,当计算第一个新数组元素时,left 的值默认为 1
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 是 Java8 新增的流式编程的 API
XxxStream stream(xxx[] array, int startInclusive, int endExclusive)该方法与上一个方法相似,区别是该方法仅将 startInclusive 到 endExclusive 索引的元素转换为 Stream

  上面的方法中,所有以 parallel 开头的方法都表示该方法可利用 CPU 并行的能力来提高性能。上面方法中的 xxx 代表不同的数据类型。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值