Java 数组的length方法对时间复杂度的影响问题

最近无意中看到一个问题,提到循环数组时,是否在循环体中使用length会影响时间复杂度。由于看了没保存,现在找不到在哪了,大概意思就如下面的代码。只记得这个问题是基于C语言提出的,所以我很好奇java语言中,是否存在这种问题。


int[] num = {9,3,1,2,7};
//这种写法,每次循环都要获取num.length,而获取num.length就要去遍历num,这样就会造成O(N^2)的时间复杂度
for(int i = 0; i < num.length; i++){
    ...
}
//所以要使用下面的写法
int len = num.length;
for(int i = 0; i < len; i++){
    ...
}

正好最近有时间,就深入的去思考了一下这个问题,最开始我没有去深入思考,也是持肯定态度的,但经过我逐步的去测试,慢慢的否定了这个看法。

首先数组长度是不可变的,在内存中开辟的空间是固定的,如果我是java语言的设计者,要获取数组长度的length属性,肯定是在数组创建时就将内存空间赋给它即可,何须遍历一说。

而且如String等类的length()方法都是直接返回的数组长度,所以关键只在于这个length。既然length是java数组的关键字,那么就可以通过javap反汇编来观察。

写一个测试类Test.java

package test;
public class Test {
    public static void main(String args[]) {
        int[] nums = {9,3,1,2,7};
        int l = nums.length;
        System.out.println(l);
    }
}

编译后,通过javap -c得到了它的反汇编代码,由于汇编代码我也不是很懂,具体指令我也是逐步去查看JVM指令集来参考对照看的,我给部分代码加上了有些中文注释,关键是27位置的arraylength,用于返回数组长度。可以看得出来其实JVM是通过一个arrlength执行来得到当前数组的数组长度。

有需要查看JVM指令集的可以看这篇博客:https://blog.csdn.net/tanggao1314/article/details/53260891

Compiled from "Test.java"
public class test.Test {
  public test.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5                          //将int型5推送至栈顶,这里的5明显就是数组的长度
       1: newarray       int                //创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶  
       3: dup                               //复制栈顶数值并将复制值压入栈顶
       4: iconst_0                          //下面这些操作就是通过iastore将数组和下标分别入栈
       5: bipush        9
       7: iastore
       8: dup
       9: iconst_1
      10: iconst_3
      11: iastore
      12: dup
      13: iconst_2
      14: iconst_1
      15: iastore
      16: dup
      17: iconst_3
      18: iconst_2
      19: iastore
      20: dup
      21: iconst_4
      22: bipush        7
      24: iastore
      25: astore_1                          //将栈顶引用型数值存入第二个本地变量
      26: aload_1                           //将第二个int型本地变量推送至栈顶
      27: arraylength                       //获得数组的长度值并压入栈顶
      28: istore_2                          //将栈顶int型数值存入第三个本地变量
      29: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      32: iload_2
      33: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      36: return
}

所以现在我要知道的是JVM是如何通过arraylength获取到数组长度的?于是通过搜索引擎,最终又找到了这篇博客:https://blog.csdn.net/scjava/article/details/108219216

这篇博客中已经讲的很清楚了,JVM也是通过计算数组首位置+偏移量来得到数组的长度,这属于常数级的时间复杂度。看到这里是不是顿时感觉很熟悉!!!没错,在学习基础课程数据结构时,基本上都会讲过数组长度的计算,通常都是一个偏移量来确定数组在内存中的位置,这也是为什么数组的读取属于常数级别。

所以我得到的结论是:在循环体中使用num.length并不会增加时间复杂度。

jdk8的hotspot JVM源码下载地址:http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509,点击左侧zip即可下载压缩包。

猜你喜欢

转载自blog.csdn.net/c_o_d_e_/article/details/112273713