设置数组所有值的最快方法?

时间:2012-02-03 12:33:00

标签: java arrays

我有char [],我想将每个索引的值设置为相同的char值。
有明显的方法(迭代):

  char f = '+';
  char [] c = new char [50];
  for(int i = 0; i < c.length; i++){
      c[i] = f;
  }

但是我想知道是否有一种方法可以利用System.arraycopy或类似的东西来绕过迭代的需要。有没有办法做到这一点?

编辑: 来自Arrays.java

public static void fill(char[] a, int fromIndex, int toIndex, char val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

这是完全相同的过程,这表明可能没有更好的方法来做到这一点。 向所有提出fill的人提出+1 - 你们都是正确的,谢谢你。

14 个答案:

答案 0 :(得分:82)

试试Arrays.fill(c, f)Arrays javadoc

答案 1 :(得分:38)

作为另一种选择,对于后人我最近正在研究这篇文章并找到了this文章,该文章提供了一种解决方案,通过将一些工作交给System类来实现更短的循环, (如果您使用的JVM足够智能)可以转换为memset操作: -

/*
 * initialize a smaller piece of the array and use the System.arraycopy 
 * call to fill in the rest of the array in an expanding binary fashion
 */
public static void bytefill(byte[] array, byte value) {
  int len = array.length;

  if (len > 0){
    array[0] = value;
  }

  //Value of i will be [1, 2, 4, 8, 16, 32, ..., len]
  for (int i = 1; i < len; i += i) {
    System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i);
  }
}

此解决方案取自IBM研究论文"Java server performance: A case study of building efficient, scalable Jvms" by R. Dimpsey, R. Arora, K. Kuiper

简化说明

正如评论建议的那样,这会将目标数组的索引0设置为您的值,然后使用System类复制一个对象,即索引0处的对象到索引1然后是那两个对象(索引0和1) )进入2和3,然后将这四个对象(0,1,2和3)分为4,5,6和7等等......

效率(写作时)

快速浏览,抓住前后System.nanoTime()并计算我想出的持续时间: -

  • 此方法: 332,617 - 390,262 ('最高 - 最低'来自10次测试)
  • Float[] n = new Float[array.length]; //Fill with null:666,650
  • 通过循环设置: 3,743,488 - 9,767,744 ('最高 - 最低'来自10次测试)
  • Arrays.fill 12,539,336

JVM和JIT编译

应该注意的是,随着JVM和JIT的发展,这种方法很可能会过时,因为只需使用fill(),库和运行时优化就可以达到甚至超过这些数字。 在撰写本文时,这是我找到的最快的选择。有人提到现在情况可能并非如此,但我没有检查过。这就是Java的美丽和诅咒。

答案 2 :(得分:10)

使用Arrays.fill

  char f = '+';
  char [] c = new char [50];
  Arrays.fill(c, f)

答案 3 :(得分:6)

Java Programmer's FAQ Part B Sect 6建议:

public static void bytefill(byte[] array, byte value) {
    int len = array.length;
    if (len > 0)
    array[0] = value;
    for (int i = 1; i < len; i += i)
        System.arraycopy( array, 0, array, i,
            ((len - i) < i) ? (len - i) : i);
}

这实际上使得对System.arraycopy的log2(array.length)调用有希望利用优化的memcpy实现。

但是,现代Java JIT(如Oracle / Android JIT)仍然需要这种技术吗?

答案 4 :(得分:6)

System.arraycopy是我的答案。请让我知道有没有更好的方法。 THX

private static long[] r1 = new long[64];
private static long[][] r2 = new long[64][64];

/**Proved:
 * {@link Arrays#fill(long[], long[])} makes r2 has 64 references to r1 - not the answer;
 * {@link Arrays#fill(long[], long)} sometimes slower than deep 2 looping.<br/>
 */
private static void testFillPerformance() {
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    System.out.println(sdf.format(new Date()));
    Arrays.fill(r1, 0l);

    long stamp0 = System.nanoTime();
    //      Arrays.fill(r2, 0l); -- exception
    long stamp1 = System.nanoTime();
    //      System.out.println(String.format("Arrays.fill takes %s nano-seconds.", stamp1 - stamp0));

    stamp0 = System.nanoTime();
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++)
            r2[i][j] = 0l;
    }
    stamp1 = System.nanoTime();
    System.out.println(String.format("Arrays' 2-looping takes %s nano-seconds.", stamp1 - stamp0));

    stamp0 = System.nanoTime();
    for (int i = 0; i < 64; i++) {
        System.arraycopy(r1, 0, r2[i], 0, 64);
    }
    stamp1 = System.nanoTime();
    System.out.println(String.format("System.arraycopy looping takes %s nano-seconds.", stamp1 - stamp0));

    stamp0 = System.nanoTime();
    Arrays.fill(r2, r1);
    stamp1 = System.nanoTime();
    System.out.println(String.format("One round Arrays.fill takes %s nano-seconds.", stamp1 - stamp0));

    stamp0 = System.nanoTime();
    for (int i = 0; i < 64; i++)
        Arrays.fill(r2[i], 0l);
    stamp1 = System.nanoTime();
    System.out.println(String.format("Two rounds Arrays.fill takes %s nano-seconds.", stamp1 - stamp0));
}

12时33分18秒
阵列的2循环需要133536纳秒 System.arraycopy循环需要22070纳秒 一轮Arrays.fill需要9777纳秒 两轮Arrays.fill需要93028纳秒。

12点33分38秒
阵列的2循环需要133816纳秒 System.arraycopy循环需要22070纳秒 一轮Arrays.fill需要17042纳秒 两轮Arrays.fill需要95263纳秒。

12点33分51秒
阵列的2循环需要199187纳秒 System.arraycopy循环需要44140纳秒 一轮Arrays.fill需要19555纳秒 两轮Arrays.fill需要449219纳秒。

12时34分16秒
阵列的2循环需要199467纳秒 System.arraycopy循环需要42464纳秒 一轮Arrays.fill需要17600纳秒 两轮Arrays.fill需要170971纳秒。

12时34分26秒
阵列的2循环需要198907纳秒 System.arraycopy循环需要24584纳秒 一轮Arrays.fill需要10616纳秒 两轮Arrays.fill需要94426纳秒。

答案 5 :(得分:3)

请参阅Arrays.fill方法:

char f = '+';
char [] c = new char [50];
Arrays.fill(c, f);

答案 6 :(得分:3)

如果您有另一个char数组char[] b并且想要将c替换为b,则可以使用c=b.clone();

答案 7 :(得分:2)

Arrays.fill(myArray, 'c');

Arrays.fill

虽然这很可能是在后台进行循环,因此不比你拥有的更有效(除了代码行节省)。如果您真的关心效率,请尝试以下与上述相比:

int size = 50;
char[] array = new char[size];
for (int i=0; i<size; i++){
  array[i] = 'c';
}

请注意,上面的内容不会为每次迭代调用array.size()。

答案 8 :(得分:1)

Arrays.fill可能符合您的需求

答案 9 :(得分:1)

   /**
     * Assigns the specified char value to each element of the specified array
     * of chars.
     *
     * @param a the array to be filled
     * @param val the value to be stored in all elements of the array
     */
    public static void fill(char[] a, char val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

这就是Arrays.fill的做法。

(我想你可以进入JNI并使用memset。)

答案 10 :(得分:1)

从Java-8开始,setAll方法有四种变体,它们使用提供的生成器函数来设置指定数组的所有元素,以计算每个元素。

在这四个重载中,只有三个接受一个声明为这样的基元数组:

     

如何使用上述方法的例子:

// given an index, set the element at the specified index with the provided value
double [] doubles = new double[50];
Arrays.setAll(doubles, index -> 30D);

// given an index, set the element at the specified index with the provided value
int [] ints = new int[50];
Arrays.setAll(ints, index -> 60);

 // given an index, set the element at the specified index with the provided value
long [] longs = new long[50];
Arrays.setAll(longs, index -> 90L);

提供给setAll方法的函数接收元素索引并返回该索引的值。

你可能想知道字符数组怎么样?

这是setAll方法的第四次重载发挥作用的地方。由于没有重载消耗一系列字符基元,我们唯一的选择是将字符数组的声明更改为类型Character[]

如果将数组类型更改为Character不合适,那么您可以回退到Arrays.fill方法。

setAll使用Character[]方法的示例:

// given an index, set the element at the specified index with the provided value
Character[] character = new Character[50];
Arrays.setAll(characters, index -> '+'); 

尽管使用Arrays.fill方法而不是setAll方法来设置特定的值更简单。

setAll方法的优点是,您可以将数组的所有元素设置为具有相同的值,也可以生成偶数,奇数或任何其他公式的数组:

e.g。

int[] evenNumbers = new int[10]; 
Arrays.setAll(evenNumbers, i -> i * 2);

parallelSetAll方法还有几个并行执行的重载,但重要的是要注意传递给parallelSetAll方法的函数必须是无副作用的

<强>结论

如果您的目标只是为数组的每个元素设置特定的值,那么使用Arrays.fill重载将是最合适的选项。但是,如果您想要更灵活或按需生成元素,那么使用Arrays.setAllArrays.parallelSetAll(适当时)将是您的选择。

答案 11 :(得分:0)

您可以使用arraycopy,但这取决于您是否可以预定义源数组, - 每次都需要填充不同的字符,还是使用相同的char重复填充数组?

显然,填充的长度很重要 - 要么你需要一个比所有可能目的地都大的源,要么你需要一个循环来重复数组化一大块数据,直到目标已满。

    char f = '+';
    char[] c = new char[50];
    for (int i = 0; i < c.length; i++)
    {
        c[i] = f;
    }

    char[] d = new char[50];
    System.arraycopy(c, 0, d, 0, d.length);

答案 12 :(得分:0)

Arrays.fill是通用的最佳选择。 如果您需要填充大型数组,尽管从最新的idk 1.8 u102开始,有一种更快的方式可以利用System.arraycopy。 您可以查看此替代Arrays.fill实施:

根据JMH benchmarks,您可以将大型阵列(1000 +)的性能提升近2倍

在任何情况下,只应在需要时使用这些实现。 JDKs Arrays.fill应该是首选。

答案 13 :(得分:0)

我对罗斯德鲁的回答略有改进。

对于 small 数组,由于与设置System.arraycopy相关的开销,简单循环比System.arraycopy方法更快。因此,最好使用一个简单的循环填充数组的前几个字节,并且只有当填充的数组具有一定的大小时才移动到System.arraycopy。

初始循环的最佳大小当然是JVM特定的和系统特定的。

private static final int SMALL = 16;

public static void arrayFill(byte[] array, byte value) {
  int len = array.length;
  int lenB = len < SMALL ? len : SMALL;

  for (int i = 0; i < lenB; i++) {
    array[i] = value;
  }

  for (int i = SMALL; i < len; i += i) {
    System.arraycopy(array, 0, array, i, len < i + i ? len - i : i);
  }
}