【问题标题】:Java: what's the big-O time of declaring an array of size n?Java:声明一个大小为 n 的数组的大 O 时间是多少?
【发布时间】:2011-08-04 04:39:03
【问题描述】:

在Java中声明一个大小为n的数组的运行时间是多少?我想这将取决于内存是在垃圾收集(在这种情况下它可能是 O(1) )还是在初始化(在这种情况下它必须是 O(n) )时清零。

【问题讨论】:

  • 我认为那将是 JVM 依赖
  • 我猜半开玩笑,迂腐的答案是它是 O(1),因为即使它是 O(n)n 对于 Java 数组也受 2^31 的限制,因此将是渐近的低于某个大常数。
  • @Mark,在这种情况下,每次计算都是 O(1) 空间,因为地球上的原子数量是有限的。 :P
  • @amit,好吧,换一种说法,由于计算机的内存有限,任何终止程序都会在O(1)中运行。
  • @amit:该死,这个答案本可以让我在大学里省去很多麻烦!是的,显然这是一个荒谬的评论。

标签: java arrays complexity-theory performance


【解决方案1】:

它是O(n)。考虑这个简单的程序:

public class ArrayTest {

  public static void main(String[] args) {
     int[] var = new int[5];
  }

}

生成的字节码是:

Compiled from "ArrayTest.java"
public class ArrayTest extends java.lang.Object{
public ArrayTest();
  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
   1:   newarray int
   3:   astore_1
   4:   return

}

要查看的指令是newarray 指令(只需搜索newarray)。来自 VM 规范:

从垃圾收集堆中分配一个新数组,其组件类型为 atype 且长度为 count。对这个新数组对象的引用 arrayref 被推入操作数堆栈。 新数组的每个元素都被初始化为数组类型的默认初始值(第 2.5.1 节)。

由于正在初始化每个元素,因此需要O(n) 时间。

编辑

查看提供的链接 amit,可以在恒定时间内使用默认值实现数组初始化。所以我猜它最终取决于JVM。您可以做一些粗略的基准测试,看看是否是这种情况。

【讨论】:

  • amit 发布了一个指向@Jonathan's answer 的链接,了解如何在 JVM 代码中进行延迟初始化。我看不出这样做会如何破坏规范,只要从数组接口(读取和写入)的 POV 将数组初始化为正确的值。
  • @Mark Peters,很有趣。我想这取决于该特定 JVM 是否使用延迟初始化方法来实现它。很酷的算法!
  • 是的。我唯一担心的是是否可以这样做,并且还允许本地方法通过 JNI 与数组交互。
  • 是的。如果 JNI 提供对数组的直接访问,那么这可能是个问题,因为这些位置将包含垃圾数据。
【解决方案2】:

一个关于 JRE1.6 的小型非专业基准测试:

public static void main(String[] args) {
    long start = System.nanoTime();
    int[] x = new int[50];
    long smallArray = System.nanoTime();
    int[] m = new int[1000000];
    long bigArray = System.nanoTime();
    System.out.println("big:" +  new Long( bigArray - smallArray));
    System.out.println("small:" +  new Long( smallArray - start));


}

给出以下结果:

big:6133612
small:6159

所以我假设 O(n)。 当然,仅仅确定是不够的,但这是一个提示。

【讨论】:

  • -1 用于微基准测试,+10 用于对@Jonathan 帖子的评论。
  • @glowcoder:对于-1:我同意,微基准测试是魔鬼。这就是为什么我表示没有什么是决定性的,这是非常不专业的,只是想提示继续讨论。感谢您的 +10。
  • 最大的数组大了2000倍,而它只需要1000倍的时间。那么,你确定它是 O(n) 吗?我真的对big-o一无所知,但对我来说它看起来像:O(n/2)
  • @Martijn O(n/2) 仍然是 O(n) 即仍然是线性的 :)
  • @Martjin:O(n/2) = O(n)。此外,您可以假设无论大小如何,两种初始化都存在一些开销......正如我所说,这个“基准”没有证明什么,它只是表明数组的大小对初始化时间有影响,并且O(n) 只是一个假设。
【解决方案3】:

我很确定它是 O(n),因为在分配数组时会初始化内存。它不应该高于 O(n),而且我看不出有办法让它低于 O(n),所以这似乎是唯一的选择。

为了进一步说明,Java 在分配时初始化数组。如果不经过内存区域,就无法将其归零,并且区域的大小决定了指令的数量。因此,下限为 O(n)。此外,使用比线性慢的归零算法是没有意义的,因为存在线性解,所以上限必须为 O(n)。因此,O(n) 是唯一有意义的答案。

不过,只是为了好玩,想象一下一个奇怪的硬件,其中操作系统可以控制各个内存区域的电源,并且可以通过关闭然后再打开电源来将某个区域归零。这似乎是O(1)。但是一个区域在效用消失之前只能这么大(不想失去一切),所以要求归零一个区域仍然是 O(n) 和一个很大的除数。

【讨论】:

  • 这是一种更好的方法(就大 O 而言)eli.thegreenplace.net/2008/08/23/… - 我不知道 jvm 实际是如何工作的
  • 如果可以证明它是先写入/然后读取的,JVM 可能会决定不将内存归零。阵列.克隆。 System.arraycopy、Arrays.copyOf 等。
  • @amit 这很酷。我怀疑 JVM 是否会这样做,因为开销很大,但仍然很酷。 @bestsss 好点。我假设一般情况下声明type[len] array;
  • 您可以为此使用 DMA 控制器。它仍然是 O(n),但 CPU 可以在此期间做其他事情。
  • @amit,JVM 试图移除对数组的边界检查,所提出的方法对于一般用途来说效率更低(除了微基准)。
【解决方案4】:

让我们测试一下吧。

class ArrayAlloc {
  static long alloc(int n) {
    long start = System.nanoTime();
    long[] var = new long[n];
    long total = System.nanoTime() - start;
    var[n/2] = 8;
    return total;
  }
  public static void main(String[] args) {
    for(int i=1; i<100000000; i+=1000000) {
      System.out.println(i + "," + alloc(i));
    }
  }
}

我的 linux 笔记本电脑(i7-4600M @ 2.90GHz)上的结果:

所以它显然看起来像 O(n),但看起来它也切换到了一种更有效的方法,大约有 500 万个元素。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-14
    • 2020-12-12
    • 2013-08-24
    相关资源
    最近更新 更多