Java RandomAccessFile writeInt(int i)与写入(字节[]b)-性能

Java RandomAccessFile writeInt(int i)与写入(字节[]b)-性能,java,file-writing,randomaccessfile,Java,File Writing,Randomaccessfile,今天我遇到了一件关于RandomAccessFile的有趣事情 我注意到使用RandomAccessFile的writeInt(inti)方法比使用RandomAccessFile的write(byte[]b)方法要慢得多,在这里我首先将int值转换为byte[4]数组 我正在用这个代码进行转换 private static byte[] intToByte(int i) { byte[] result = new byte[4]; result[0] = (byte) (i &g

今天我遇到了一件关于
RandomAccessFile
的有趣事情

我注意到使用
RandomAccessFile
writeInt(inti)
方法比使用
RandomAccessFile
write(byte[]b)
方法要慢得多,在这里我首先将int值转换为byte[4]数组

我正在用这个代码进行转换

private static byte[] intToByte(int i)
{
   byte[] result = new byte[4];

   result[0] = (byte) (i >> 24);
   result[1] = (byte) (i >> 16);
   result[2] = (byte) (i >> 8);
   result[3] = (byte) (i);

  return result;
}
差异非常显著,有利于
写入(字节[]b)

使用JDK 8在我的笔记本电脑上写入100万个
int
s:

  • 通过
    writeInt(inti)
    方法花费了~9秒
  • 通过
    写入(字节[]b)
    花费了~2,3秒
我在另一个环境中得到了类似的结果,我使用的是JDK 7和一台完全不同的机器

writeInt(inti)方法委托给本机
write0(intb)
方法和
write(byte[]b)
委托给本机
writeBytes

当我进行评测时,我注意到大部分执行时间都花在
writeInt
方法上


有人知道我为什么看到如此大的不同吗?似乎
writeInt
的效率要低得多。

不打算详细介绍我所做的更改,但您的测试有点缺陷。我大胆地对它们进行了一些更新,并进行了一些测试:

@BenchmarkMode(value = { Mode.AverageTime })
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
public class RandomAccessWriteFileTest {

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder().include(RandomAccessWriteFileTest.class.getSimpleName())
                .jvmArgs("-ea")
                .shouldFailOnError(true)
                .build();
        new Runner(opt).run();
    }

    @Benchmark()
    @Fork(1)
    public long benchamrkWriteDirectInt(BenchmarkPlainIntSetup setupTest) {
        try {
            setupTest.raf.writeInt(6969);
            return setupTest.raf.length();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Benchmark()
    @Fork(1)
    public long benchamrkWriteConvertedInt(BenchmarkConvertedIntSetup setupTest) {
        try {
            setupTest.raf.write(intToBytes(6969));
            return setupTest.raf.length();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static byte[] intToBytes(int i) {
        byte[] result = new byte[4];

        result[0] = (byte) (i >> 24);
        result[1] = (byte) (i >> 16);
        result[2] = (byte) (i >> 8);
        result[3] = (byte) i;

        return result;
    }

    @State(Scope.Thread)
    static public class BenchmarkConvertedIntSetup {

        public RandomAccessFile raf;

        public File f;

        @Setup(Level.Iteration)
        public void setUp() {
            try {
                f = new File("jmhDirectIntBenchamrk.ser" + ThreadLocalRandom.current().nextInt());
                raf = new RandomAccessFile(f, "rw");
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        @TearDown(Level.Iteration)
        public void tearDown() {
            f.delete();
        }
    }

    @State(Scope.Thread)
    static public class BenchmarkPlainIntSetup {

        public RandomAccessFile raf;

        public File f;

        @Setup(Level.Iteration)
        public void setUp() {
            try {
                f = new File("jmhDirectIntBenchamrk.ser" + ThreadLocalRandom.current().nextInt());
                raf = new RandomAccessFile(f, "rw");
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        @TearDown(Level.Iteration)
        public void tearDown() {
            f.delete();
        }
    }
}
绝对结果存在差异(每次操作的结果为ms)

不知道为什么(可能是因为我会在一段时间后挖掘集合以了解,但我可以确认您的发现。好问题!)


这是使用最新的java-8和java-9 btw运行的。RandomAccessFile实际上有两种本机方法来写入字节:

//writes an array
private native void writeBytes(byte b[], int off, int len) throws IOException;

方法writeInt(int)使用本机write(int)方法分别写入每个字节,而write(byte[])使用本机writeBytes(byte[],int,int)方法


方法执行4次方法调用来写入传递的整数值的每个字节,另一个方法只使用一次调用来写入数组。在java中,方法调用实际上是昂贵的操作:对于每次调用,JVM都会为操作数堆栈和局部变量数组分配额外的内存。

反过来调用这些方法如何?您可能已经用第一个JVM加热了JVM,而用热VM调用了第二个JVM。不管怎样,JMH的存在是有原因的,在这里受到了如此多的赞扬;除非你真的找到了有趣的东西;)谢谢你的评论。我理解你所说的,我会做JMH基准测试,然后返回结果。虽然我在一个“冷”JVM上做这两个测试,在不同的运行中分别调用这些测试-结果相同。明白,但同时您需要明确区分一些数字和一些有意义的数字。通过适当的JMH测试(这也不是在公园里散步),您的数字可能有意义……好的,我有一些数字。这里可以找到测试源代码,似乎写入(字节[]b)甚至在JMH中也更快。结果可以在这里找到:但是有一件事-因为raf.writeInt和raf.write(byte[]b)方法没有返回任何值,所以我无法在这里使用JMH黑洞。如果您不在两次写入之间进行搜索,只需按顺序写入,那么根本不应该使用
RandomAccessFile
。围绕
FileOutputStream
DataOutputStream
缓冲输出流将快几个数量级。RAF用于,呃,随机访问文件。它没有得到任何优化。谢谢!你能用你的更改创建一个请求吗?@Kristoff np,但我现在太忙了:(很抱歉,很酷-我不再需要PR了,只是从你的答案中提取了代码。在你更改RandomAccessWriteItem.BenchamRkrWriteConvertedInt 0004 ms/op RandomAccessWriteItem.BenchamRkrWriteDirectInt 0011 ms后,我得到了类似的结果/op@Kristoff下一步是实际查看反汇编代码,或者更好地尝试为了理解本机实现……我希望今天能有时间尝试一下(因为孩子,这很有趣!)每次调用将某些内容写入磁盘时都会有开销。因此,在写入磁盘时,最好将写入操作分批放入较少的调用中。在convertedInt方法中,这就是您要做的。如果一次写入较大的块,可能会得到更好的结果。
//writes an array
private native void writeBytes(byte b[], int off, int len) throws IOException;
//writes one byte
public native void write(int b) throws IOException;