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

rth*_*sen 63 java arrays

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

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

但是我想知道是否有一种方法可以利用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;
    }
Run Code Online (Sandbox Code Playgroud)

这是完全相同的过程,这表明可能没有更好的方法来做到这一点. 无论如何,对所有
建议的人都要+1 fill- 你们都是正确的,谢谢你们.

Kur*_*ois 86

尝试Arrays.fill(c, f):阵列javadoc

  • Arrays.fill的源代码表明它只是一个循环(事先加上范围检查).应该进行基准测试,看看JVM是否做了一些聪明的事情...... (15认同)
  • 如果是二维数组怎么办? (2认同)

Ros*_*rew 42

作为另一种选择,对于后人我最近正在研究这个问题并发现这篇文章提供了一个解决方案,通过将一些工作交给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);
  }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案取自R. Dimpsey,R.Arora,K.Kuiper撰写的IBM研究论文"Java服务器性能:构建高效,可扩展的Jvms的案例研究".

简化说明

正如评论建议的那样,这会将目标数组的索引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
  • 通过循环设置:3743488 - 9767744 ( - 10个测试"最高最低")
  • Arrays.fill:12,539,336

JVM和JIT编译

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

  • 这应该是公认的答案,当您寻求速度时,Arrays.fill 对于大型数组来说太慢了。OP 指出“最快的方式”。这个答案确实有帮助 (3认同)
  • @RossDrew,我们从 Apache POI 中删除了代码 - 事实证明,在现代 JVM 中,Arrays.fill 得到了优化(http://psy-lob-saw.blogspot.com/2015/04/on-arraysfill-intrinsics-superword- and.html) - 我使用 jmh 进行基准测试,发现情况确实如此(Zulu 1.8.0_302 和 17.0.0 JVM)。 (3认同)

sou*_*eck 11

使用 Arrays.fill

  char f = '+';
  char [] c = new char [50];
  Arrays.fill(c, f)
Run Code Online (Sandbox Code Playgroud)


小智 7

Java程序员的常见问题B部分第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);
}
Run Code Online (Sandbox Code Playgroud)

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

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


ody*_*dys 6

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

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));
}
Run Code Online (Sandbox Code Playgroud)

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纳秒.

  • 当你的日志声明Arrays.fill(...)每次运行速度更快时,你为什么选择System.arraycopy(...)?一般来说,它快得多! (5认同)

Ous*_* D. 5

从 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);
Run Code Online (Sandbox Code Playgroud)

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

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

这是该setAll方法的第四个重载发挥作用的地方。由于没有消耗字符基元数组的重载,我们唯一的选择是将字符数组的声明更改为 type 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 -> '+'); 
Run Code Online (Sandbox Code Playgroud)

虽然,这是更简单的使用Arrays.fill方法,而不是setAll方法来设置一个特定的值。

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

例如

int[] evenNumbers = new int[10]; 
Arrays.setAll(evenNumbers, i -> i * 2);
Run Code Online (Sandbox Code Playgroud)

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

结论

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