【问题标题】:Time complexity of System.arraycopy(...)?System.arraycopy(...) 的时间复杂度?
【发布时间】:2011-08-23 18:11:28
【问题描述】:

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 是本机方法。

这个方法的时间复杂度是多少?

【问题讨论】:

标签: java algorithm time-complexity


【解决方案1】:

它必须遍历数组中的所有元素才能做到这一点。数组是一种独特的数据结构,您必须在初始化时指定其大小。顺序是源数组的大小,或者用大 O 术语来说是它的 O(length)。

事实上,这发生在 ArrayList 内部。 ArrayList 包装一个数组。虽然 ArrayList 看起来像一个动态增长的集合,但在内部它必须扩展时会执行 arrycopy。

【讨论】:

  • O(n) 已经足够接近了,但实际上我认为它只是 O(length),即要复制的长度,而不是源数组的长度。
  • @alex c - 你能展示一个从 int 到 string 的转换的例子吗?我不认为 arraycopy 进行任何转换,而不是根据文档:Otherwise, if any of the following is true, an ArrayStoreException is thrown ... °The src argument refers to an array with a primitive component type and the dest argument refers to an array with a reference component type °...
  • 响应不正确! system.arrayCopy 是本机方法,可以使用单个 memcpy / memmove 实现
  • 它不必遍历所有数据类型的所有元素。对于许多数据类型,它能够进行块复制,这可以快得多。只要数组类型相同,就不需要进行任何类型检查。使用与源和目标相同的数组会稍微减慢速度(取决于平台,可能会很多!),但总的来说 System.arraycopy 通常比迭代更好,很少比迭代更差。
  • @jdramaix:单个 memcpy/memmove 所需的时间仍然与完成的复制量成正比,而且并不总是单个 memcpy/memmove(由于类型检查和转换)。
【解决方案2】:

我做了一些调查,后来决定写一个测试代码,这就是我所拥有的。

我的测试代码如下:

import org.junit.Test;

public class ArrayCopyTest {

  @Test
  public void testCopy() {
    for (int count = 0; count < 3; count++) {
      int size = 0x00ffffff;
      long start, end;
      Integer[] integers = new Integer[size];
      Integer[] loopCopy = new Integer[size];
      Integer[] systemCopy = new Integer[size];

      for (int i = 0; i < size; i++) {
        integers[i] = i;
      }

      start = System.currentTimeMillis();
      for (int i = 0; i < size; i++) {
        loopCopy[i] = integers[i];
      }
      end = System.currentTimeMillis();
      System.out.println("for loop: " + (end - start));

      start = System.currentTimeMillis();
      System.arraycopy(integers, 0, systemCopy, 0, size);
      end = System.currentTimeMillis();
      System.out.println("System.arrayCopy: " + (end - start));
    }
  }

}

它产生如下所示的结果

for loop: 47
System.arrayCopy: 24

for loop: 31
System.arrayCopy: 22

for loop: 36
System.arrayCopy: 22

所以,Bragboy 是正确的。

【讨论】:

  • +1 - 该测试在什么操作系统上运行?因为正如我最近的回答中所详述的那样,arraycopy() 的实现很可能取决于平台。
  • 当时最新的ubuntu操作系统。我使用的是带有 8GB 内存的联想 thinkpad i5 处理器。我不记得操作系统和机器的确切细节。
  • 你是否测试过它的升温 java 和大值?
  • @GKislin 不,只是简单的运行。
  • @Kowser 但这些数字毫无意义。我喜欢其他一些措施:faisalferoz.wordpress.com/2007/12/24/loop-vs-systemarraycopy,但也不确定它的正确性。
【解决方案3】:

这是来自 OpenJDK 8 (openjdk-8-src-b132-03_mar_2014) 的一些相关源代码。我在Java native method source code 的帮助下找到了它(注意:那里的说明令人困惑;我只是在来源中搜索了相关标识符)。我认为Captain Ford's comment 是正确的;即,有(很多)情况不需要迭代每个元素。请注意,不迭代每个元素并不必然意味着 O(1),它只是意味着“更快”。我认为,无论如何,数组副本必须基本上是 O(x),即使 x 不是数组中的项目数;即,无论你怎么做,数组中的元素越多,复制就越昂贵,如果你有一个非常大的数组,它将花费线性长时间。警告:我不确定确实这是您正在运行的 Java 的实际源代码;只是这是我在 OpenJDK 8 源代码中可以找到的 only 实现。我认为这是一个跨平台的实现,但我可能错了——我肯定还没有弄清楚如何构建这段代码。另请参阅:Differences between Oracle JDK and Open JDK。以下来自:/openjdk/hotspot/src/share/vm/oops/objArrayKlass.cpp

// Either oop or narrowOop depending on UseCompressedOops.
template <class T> void ObjArrayKlass::do_copy(arrayOop s, T* src,
                               arrayOop d, T* dst, int length, TRAPS) {

  BarrierSet* bs = Universe::heap()->barrier_set();
  // For performance reasons, we assume we are that the write barrier we
  // are using has optimized modes for arrays of references.  At least one
  // of the asserts below will fail if this is not the case.
  assert(bs->has_write_ref_array_opt(), "Barrier set must have ref array opt");
  assert(bs->has_write_ref_array_pre_opt(), "For pre-barrier as well.");

  if (s == d) {
    // since source and destination are equal we do not need conversion checks.
    assert(length > 0, "sanity check");
    bs->write_ref_array_pre(dst, length);
    Copy::conjoint_oops_atomic(src, dst, length);
  } else {
    // We have to make sure all elements conform to the destination array
    Klass* bound = ObjArrayKlass::cast(d->klass())->element_klass();
    Klass* stype = ObjArrayKlass::cast(s->klass())->element_klass();
    if (stype == bound || stype->is_subtype_of(bound)) {
      // elements are guaranteed to be subtypes, so no check necessary
      bs->write_ref_array_pre(dst, length);
      Copy::conjoint_oops_atomic(src, dst, length);
    } else {
      // slow case: need individual subtype checks
      // note: don't use obj_at_put below because it includes a redundant store check
      T* from = src;
      T* end = from + length;
      for (T* p = dst; from < end; from++, p++) {
        // XXX this is going to be slow.
        T element = *from;
        // even slower now
        bool element_is_null = oopDesc::is_null(element);
        oop new_val = element_is_null ? oop(NULL)
                                      : oopDesc::decode_heap_oop_not_null(element);
        if (element_is_null ||
            (new_val->klass())->is_subtype_of(bound)) {
          bs->write_ref_field_pre(p, new_val);
          *p = *from;
        } else {
          // We must do a barrier to cover the partial copy.
          const size_t pd = pointer_delta(p, dst, (size_t)heapOopSize);
          // pointer delta is scaled to number of elements (length field in
          // objArrayOop) which we assume is 32 bit.
          assert(pd == (size_t)(int)pd, "length field overflow");
          bs->write_ref_array((HeapWord*)dst, pd);
          THROW(vmSymbols::java_lang_ArrayStoreException());
          return;
        }
      }
    }
  }
  bs->write_ref_array((HeapWord*)dst, length);
}

【讨论】:

    【解决方案4】:

    我不明白 Kowser 的回答如何回答他自己的问题。我想检查算法的时间复杂度您必须比较不同大小输入的运行时间,如下所示:

    import org.junit.Test;
    
    public class ArrayCopyTest {
    
      @Test
      public void testCopy() {
        int size = 5000000;
        for (int count = 0; count < 5; count++) {
          size = size * 2;
          long start, end;
          Integer[] integers = new Integer[size];
          Integer[] systemCopy = new Integer[size];
    
          start = System.currentTimeMillis();
          System.arraycopy(integers, 0, systemCopy, 0, size);
          end = System.currentTimeMillis();
          System.out.println(end - start);
        }
      }
    
    }
    

    输出:

    10
    22
    42
    87
    147
    

    【讨论】:

      【解决方案5】:

      只是为了总结另一个问题的相关 cmets(标记为这个问题的重复)。

      当然,它只是将所有条目添加到新数组中 其他?这将是 O(n),其中 n 是要添加的值的数量。

      bragboy 的回答当然同意,但后来我认为获得确定答案的唯一方法是找到源代码以获得规范答案,但这是不可能的。这是System.arraycopy();的声明

      public static native void arraycopy(Object src, int src_position,  
                                          Object dst, int dst_position,  
                                          int length);
      

      它是native,用操作系统的语言编写,这意味着arraycopy()的实现是平台相关的。

      所以,总而言之,它可能 O(n),但也可能不是。

      【讨论】:

      • "...找到源代码...,但这是不可能的..."。您当然可以获得本机方法的源代码。见Java native method source code
      • @HawkeyeParker,您可以获取特定平台上本机方法的源代码。当然可以,但这不会告诉您该本地方法在所有其他平台上会是什么样子。完全有可能存在用于新系统的糟糕实现。
      • 我同意。要确定,您当然需要实际运行的任何东西的实际源代码。
      猜你喜欢
      • 2016-02-13
      • 2012-08-14
      • 2013-09-12
      • 2018-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多