如何在 Java 中快速写入大型二进制数据?
How to write large binary data fast in Java?
我正在编写一个 STL 文件,它包含一个 80 字节 header、一个 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 class?
我还想到,STL文件格式应该是低字节在前,而Java可能是高字节在前。所以也许我所有的writeFloats都是徒劳的,我将不得不找到一个手动转换,以便它以little-endian形式出现?
如果必须,我愿意关闭文件,最后在随机访问文件中重新打开,查找字节 81 并写入计数。
所以这个编辑是为了回应应该有效的问题的两个答案。第一个是添加 BufferedWriter。结果出奇的快。我知道这台笔记本电脑是配备 SSD 的高端笔记本电脑,但我没想到会有这种性能,更不用说 Java 了。仅通过缓冲输出,0.5 秒写入 96Mb 文件,1.5 秒写入 196Mb。
为了看看 nio 是否会提供更高的性能,我尝试实现了 @sturcotte06 的解决方案。代码没有尝试写 header,我只是关注每个三角形的 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)
在 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);
}
}
If there is an easy way to buffer
有:
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(...));
DataOutputStream
不是一个特别快速的解决方案。创建一个普通的旧 FileOutputStream
,在其周围放置一个 BufferedOutputStream
,然后编写您自己的代码以将数据写入字节。 Float
和 Double
类 有帮助函数。 doubleToLongBits,例如。
如果这对您来说还不够快,请继续阅读 NIO2。
使用nio的ByteBuffer
class:
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);
}
编辑:
public static boolean tryWriteBinarySTL(Triangle t, ByteBuffer buf) {
final int triangleBytes = 50; // set this.
if (buf.remaining() < triangleBytes) {
return false;
}
buf.putFloat((float)t.normal.x);
// ...
return true;
}
// 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.
for (Triangle triangle : triangles) {
while (!tryWriteBinarySTL(triangle, buf) ) {
// Flush buffer.
buf.flip();
while (buf.hasRemaining()) {
channel.write(buf);
}
buf.flip();
}
}
// Write remaining.
buf.flip();
channel.write(buf);
}
我正在编写一个 STL 文件,它包含一个 80 字节 header、一个 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 class?
我还想到,STL文件格式应该是低字节在前,而Java可能是高字节在前。所以也许我所有的writeFloats都是徒劳的,我将不得不找到一个手动转换,以便它以little-endian形式出现?
如果必须,我愿意关闭文件,最后在随机访问文件中重新打开,查找字节 81 并写入计数。
所以这个编辑是为了回应应该有效的问题的两个答案。第一个是添加 BufferedWriter。结果出奇的快。我知道这台笔记本电脑是配备 SSD 的高端笔记本电脑,但我没想到会有这种性能,更不用说 Java 了。仅通过缓冲输出,0.5 秒写入 96Mb 文件,1.5 秒写入 196Mb。
为了看看 nio 是否会提供更高的性能,我尝试实现了 @sturcotte06 的解决方案。代码没有尝试写 header,我只是关注每个三角形的 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) 在 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);
}
}
If there is an easy way to buffer
有:
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(...));
DataOutputStream
不是一个特别快速的解决方案。创建一个普通的旧 FileOutputStream
,在其周围放置一个 BufferedOutputStream
,然后编写您自己的代码以将数据写入字节。 Float
和 Double
类 有帮助函数。 doubleToLongBits,例如。
如果这对您来说还不够快,请继续阅读 NIO2。
使用nio的ByteBuffer
class:
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);
}
编辑:
public static boolean tryWriteBinarySTL(Triangle t, ByteBuffer buf) {
final int triangleBytes = 50; // set this.
if (buf.remaining() < triangleBytes) {
return false;
}
buf.putFloat((float)t.normal.x);
// ...
return true;
}
// 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.
for (Triangle triangle : triangles) {
while (!tryWriteBinarySTL(triangle, buf) ) {
// Flush buffer.
buf.flip();
while (buf.hasRemaining()) {
channel.write(buf);
}
buf.flip();
}
}
// Write remaining.
buf.flip();
channel.write(buf);
}