[002 数据结构与算法]- Java数组分析探秘

数组的定义

    首先引用维基百科对数组的定义如下:

在计算机科学中,阵列资料结构(英语:array data structure),简称数组(英语:Array),是由相同类型的元素(element)的集合所组成的资料结构,分配一块连续的内存来存储。利用元素的索引(index)可以计算出该元素对应的储存地址。

    个人认为这个定义简洁又准确,我对重点作了加粗处理。

  1. 数组存储的是相同类型的数据;

    数组在初始化时需要指定类型,在给每个位置赋值时,值类型必须相同。

    如我们定义一个数组用于存储“人”,那么就可以放入“男人”/“女人”/“老人”/“小孩”等都可以。

    但是如果定义一个数组存储“男人”,那么“女人”/“女孩”/“中年妇女”都是不可以存放的,只可以存放“男人”及其子类型,虽然都是从属于“人”这个大类。

  1. 数组在内存中的分配是连续的;

    数组大小(元素个数)在初始定义时即确定了,同时分配了存储需要使用的内存。所以可以分配一块连续的内存,这提高了数组操作的效率。

    为什么这样效率高?很好理解,一个旅行团入住一家酒店,是在连续的一排房间找人好找,还是让他们随机入住,东一个西一个好找?答案不言而喻。

  1. 通过索引可以直接查找到该位置的数据;

    还是以旅行团为例,每个人都有自己的房间编号,我们通过编号就可以找到所有人。

    Java 数组的补充:

  1. Java 数组的长度固定,且在初始化时就已经确定,不可更改;

一种奇怪的对象

    Java 是一种面向对象的编程语言(虽然现在也添加面向函数的支持,但我依然认为 Java 首先是一种面向对象的语言),但又不是完全的面向对象,它还有些基本数据类型,如 int, byte, char, long 等等,这些基本数据类型都有提供包装类,用于兼容面向对象。这种语言设计上的取舍,方便了编码,同时又补上了非完全面向对象的遗憾。

    数组特殊在哪?
    它是对象,但又找不到对应的类定义。代码如下:

        // 声明基本类型-整数
        int n = 5;
        // 声明包装类型-整数
        Integer in = 5;
        // 声明普通类型对象
        ArrayList list = new ArrayList<String>();
        // 声明数组
        int[] array = new int[2];

    按照 Java 声明对象的语法,数组的类型应该是 int[], 真是奇怪!我们尝试打印出它的类型看看,代码如下:

        System.out.println(int.class.getName());
        System.out.println(Integer.class.getName());
        System.out.println(ArrayList.class.getName());
        System.out.println(int[].class.getName());
        System.out.println(String[].class.getName());

    输出会是这样的:

int
java.lang.Integer
java.util.ArrayList
[I
[Ljava.lang.String;

    所以数组的类型是 [ ,这是个啥?虽然很奇怪,但在 JVM 的解释中,它确实是一种奇怪的对象。

    另外根据对象的定义,我也可以推断它是一种对象。

    对象的定义:

类是对事物的一种抽象,用于泛指某一些具体或抽象的具有同一共性的物体/行为/概念…

而对象,就是类的具体实例。

    很抽象,还是举例:

  1.     我们定义"车"是一个类,这是一种具体事物类,有轮子,能行驶,就是"车"。那么号牌是"鲁ABC456"的汽车,就是该类的一个对象,我那辆破自行车也是该类的一个对象。
  2.     我们定义"发送"是一个类,这是一种行为类,把信息从一方传递给另一方,就是"发送"。那么"通过 Email(abc@456.com) 发送",就是该类的一个对象,"通过手机号(130xxxxxxxx)发送信息到手机号(150xxxxxxxx)"也是该类的一个对象。

    数组呢?它有长度,有toString()equals() 等行为,还有它特有的寻值操作 [],很明显,数组也是一种类型,每个具体的数组就是一个对象。

数组是对象,虽然它很奇怪!!!

数组在数据结构中的重要性

数组性能优秀

    数组内存连续,这种天生概念上就造就了优秀性能,这点不再说了。只再单纯分析下字节码,看看它在 JVM 的地位有多高。对比下面两个方法的字节码:

    public void objArray() {
        Integer[] n = new Integer[10];
        n[0] = 2;
        n[9] = 21;
        int m = n[0] + n[9];
    }

    public void list() {
        var list = new ArrayList<Integer>(10);
        list.set(0, 2);
        list.set(9, 21);
        int lm = list.get(0) + list.get(9);
    }

    代码任务一样,只是一个用数组实现,一个使用 ArrayList 实现(ArrayList 底层是使用数组实现的,在通常情况下,我们认为 ArrayList 性能也没有问题,具体实现以后再表)。每行代码对应的字节码我用空行格开。

    objArray 的字节码:

 0 bipush 10
 2 anewarray #2 <java/lang/Integer>
 5 astore_1

 6 aload_1
 7 iconst_0
 8 iconst_2
 9 invokestatic #3 <java/lang/Integer.valueOf>
12 aastore

13 aload_1
14 bipush 9
16 bipush 21
18 invokestatic #3 <java/lang/Integer.valueOf>
21 aastore

22 aload_1
23 iconst_0
24 aaload
25 invokevirtual #4 <java/lang/Integer.intValue>
28 aload_1
29 bipush 9
31 aaload
32 invokevirtual #4 <java/lang/Integer.intValue>
35 iadd
36 istore_2
37 return

    list 的字节码:

 0 new #5 <java/util/ArrayList>
 3 dup
 4 bipush 10
 6 invokespecial #6 <java/util/ArrayList.<init>>
 9 astore_1

10 aload_1
11 iconst_0
12 iconst_2
13 invokestatic #3 <java/lang/Integer.valueOf>
16 invokevirtual #7 <java/util/ArrayList.set>
19 pop

20 aload_1
21 bipush 9
23 bipush 21
25 invokestatic #3 <java/lang/Integer.valueOf>
28 invokevirtual #7 <java/util/ArrayList.set>
31 pop

32 aload_1
33 iconst_0
34 invokevirtual #8 <java/util/ArrayList.get>
37 checkcast #2 <java/lang/Integer>
40 invokevirtual #4 <java/lang/Integer.intValue>
43 aload_1
44 bipush 9
46 invokevirtual #8 <java/util/ArrayList.get>
49 checkcast #2 <java/lang/Integer>
52 invokevirtual #4 <java/lang/Integer.intValue>
55 iadd
56 istore_2
57 return

    数组实现的虚拟机指令很明显再少,而且它有自己特殊的指令,如 anewarray ,对应普通对象的创建指令 new ,同时也省去了对象内方法的调用。如数组使用 aastore 指令存值,而list要使用 invokevirtual 指令调用 set 方法存值。

数组是其他数据结构的基础

    数组虽然是对象,但在使用时,我们可以将它看作是如同基本类型一样,它是组成 Java 世界的基石之一。如同上面提到的 ArrayList 的底层实现就是数组,另外还有 ArrayDeque (双端队列),Vector(动态数组),Stack(栈)等,很多数据结构都可以使用数组实现,我们以后会经常用到。所以熟悉数组的特性很重要。

关于数组需记住的要点

  1. 数组内存是连续的;
  2. 数组通过下标(索引)可以快速定位元素;
  3. 数组不可变,这也是数组最大的劣势,所以 Java 又实现了 ArrayList 来替代它。

    这三点有因果关系。因为需要数组内存连续,所以数组不可变;因为数组不可变,才实现了通过下标快速查找元素。

    怎么实现的快速定位?一个数组,记录它的起始点,如内存位置 32(只是举例),那么查找第 0 个元素,只需要返回 (32+0)位置的数据即可;查找第 10 个元素,只需要返回 (32+10)位置的数据即可。是不是很快速?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值