如何用Java快速编写大型二进制数据?

时间:2017-11-04 15:26:03

标签: java nio binary-data

我正在写一个STL文件,它由一个80字节的头,一个4字节的整数组成,然后是50个字节的记录,每个字节由浮点数和一个短整数组成。

使用RandomAccessFile我可以轻松地编写数据,但速度非常慢。 它使用与DataOutputStream相同的接口。如果有一种简单的方法来缓冲数据输出流,我可以使用它,但令人讨厌的部分是需要写出所有记录,并在最后计算三角形输出的数量并将该整数写入字节81- 84。

直截了当但缓慢的方式,只关注大部分工作,即写下每个方面:

public static void writeBinarySTL(Triangle t, RandomAccessFile d) throws IOException {
    d.writeFloat((float)t.normal.x);
    d.writeFloat((float)t.normal.y);
    d.writeFloat((float)t.normal.z);
    d.writeFloat((float)t.p1.x);
    d.writeFloat((float)t.p1.y);
    d.writeFloat((float)t.p1.z);
    d.writeFloat((float)t.p2.x);
    d.writeFloat((float)t.p2.y);
    d.writeFloat((float)t.p2.z);
    d.writeFloat((float)t.p3.x);
    d.writeFloat((float)t.p3.y);
    d.writeFloat((float)t.p3.z);
    d.writeShort(0);
}

有没有优雅的方法将这种二进制数据写入块状快速I / O类?

我也发现STL文件格式应该是低字节优先,而Java可能是高字节优先。所以也许我的所有writeFloats都是徒劳的,我必须找到一个手动转换,以便以小端形式出现?

如果必须,我愿意关闭文件,最后在randomaccessfile中重新打开,寻找字节81并写入计数。

因此,此编辑是对应该起作用的问题的两个答案的回应。第一个是添加BufferedWriter。结果非常快。我知道这台笔记本电脑是高端的SSD,但我并不期待这种性能,更不用说来自Java了。只需缓冲输出,在0.5秒内写入96Mb文件,在1.5秒内写入196Mb。

为了了解nio是否会提供更高的性能,我试图通过@ sturcotte06实现解决方案。代码不会尝试编写标题,我只关注每个三角形的50字节记录。

public static void writeBinarySTL2(Shape3d s, String filename) {
    java.nio.file.Path filePath = Paths.get(filename);

    // Open a channel in write mode on your file.
    try (WritableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.CREATE)) {
        // Allocate a new buffer.
        ByteBuffer buf = ByteBuffer.allocate(50 * 1024);
        ArrayList<Triangle> triangles = s.triangles;
        // Write your triangle data to the buffer.
        for (int i = 0; i < triangles.size(); i += 1024) {
            for (int j = i; j < i + 1024; ++j)
                writeBinarySTL(triangles.get(j), buf);
            buf.flip(); // stop modifying buffer so it can be written to disk
            channel.write(buf);  // Write your buffer's data.
        }
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

我尝试了WRITE(文档中说需要现有文件)和CREATE文档声称要写入现有文件或创建新文件。

这两个选项都无法创建文件Sphere902_bin2.stl

java.nio.file.NoSuchFileException: Sphere902_bin2.stl
sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
        at 
sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
        at 
sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
        at 

sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230)             在java.nio.file.Files.newByteChannel(Files.java:361)             at java.nio.file.Files.newByteChannel(Files.java:407)             在edu.stevens.scad.STL.writeBinarySTL2(STL.java:105)

我不相信写入字节的代码是相关的,但这就是我提出的:

public static void writeBinarySTL(Triangle t, ByteBuffer buf) {
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.normal.x)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.normal.y)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.normal.y)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p1.x)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p1.y)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p1.z)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p2.x)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p2.y)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p2.z)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p3.x)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p3.y)));
    buf.putInt(Integer.reverseBytes(Float.floatToIntBits((float)t.p3.z)));
    buf.putShort((short)0);
}

这是一个MWE,显示当写入超出缓冲区大小时代码不起作用:

package language;
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.nio.channels.*;

public class FastWritenio {
    public static void writeUsingPrintWriter() throws IOException {
        PrintWriter pw = new PrintWriter(new FileWriter("test.txt"));
        pw.print("testing");
        pw.close();
    }
    public static void writeUsingnio(int numTrials, int bufferSize, int putsPer) throws IOException {
        String filename = "d:/test.dat";
        java.nio.file.Path filePath = Paths.get(filename);
        WritableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);
        ByteBuffer buf = ByteBuffer.allocate(bufferSize);
        for (int t = 0; t < numTrials; ++t) {
            for (int i = 0; i < putsPer; i ++) {
                buf.putInt(i);
            }
            buf.flip(); // stop modifying buffer so it can be written to disk
            channel.write(buf);  // Write your buffer's data.
            // Without this line, it crashes:  buf.flip();
            // but with it this code is very slow.
        }
        channel.close();
    }
    public static void main(String[] args) throws IOException {
        writeUsingPrintWriter();
        long t0 = System.nanoTime();
        writeUsingnio(1024*256, 8*1024, 2048);
        System.out.println((System.nanoTime()-t0)*1e-9);

    }
}

3 个答案:

答案 0 :(得分:6)

使用nio&#39; <a href="#start" class="btn btn-lg btn-success">the anchor button</a> 课程:

ByteBuffer

ByteBuffer tutorial

ByteBuffer javadoc

WriteableByteChannel javadoc

Files javadoc

编辑:

public static void writeBinarySTL(Triangle t, ByteBuffer buf) {
    buf.putFloat((float)t.normal.x);
    // ...
}

// Create a new path to your file on the default file system.
Path filePath = Paths.get("file.txt");

// Open a channel in write mode on your file.
try (WritableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.WRITE)) {
    // Allocate a new buffer.
    ByteBuffer buf = ByteBuffer.allocate(8192);

    // Write your triangle data to the buffer.
    writeBinarySTL(t, buf);

    // Flip from write mode to read mode.
    buf.flip();

    // Write your buffer's data.
    channel.write(buf);
}

答案 1 :(得分:5)

  

如果有一种简单的缓冲方式

有:

DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(...));

答案 2 :(得分:1)

DataOutputStream不是特别快速的解决方案。创建一个普通的旧FileOutputStream,在其周围放置一个BufferedOutputStream,然后编写自己的代码将数据写为字节。 FloatDouble类具有辅助函数。例如doubleToLongBits

如果这对你来说不够快,那么请阅读NIO2。