Как быстро записать большие двоичные данные на Java? [дубликат]
На этот вопрос уже есть ответ здесь:
- Самый быстрый способ записи в файл? 6 ответов
Я пишу 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);
}
Существует ли какой-либо элегантный способ записи такого рода двоичных данных в блочный быстрый ввод-вывод класс?
Мне также приходит в голову, что формат файла STL должен быть сначала low byte, а Java, вероятно, сначала high byte. Так что, возможно, все мои писанины напрасны, и мне придется найти ручное преобразование, чтобы оно вышло в Литтл-эндианской форме?
Если придется, я готов закрыть файл, открыть его снова в конце в файле randomaccessfile, найти байт 81 и записать счетчик.
Таким образом, это редактирование является ответом на два ответа на вопрос, который должен работа. Первый-это добавление BufferedWriter. Результат получается поразительно быстрым. Я знаю, что этот ноутбук высокого класса с SSD, но я не ожидал такой производительности, не говоря уже о Java. Просто путем буферизации вывода, 96мб файл записывается за 0,5 секунды, а 196мб за 1,5 секунды.
Чтобы посмотреть, будет ли 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();
}
}
Я попробовал как написать (что, как сказано в документации, требует наличия существующего файла), так и создать, что документация утверждает, что либо запишет в существующий файл, либо создаст новый.
Обе опции не позволяют создать файл 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
Солнце.НИО.ФС.WindowsFileSystemProvider.newByteChannel (WindowsFileSystemProvider.java: 230) на Яве.НИО.файл.Файлы.newByteChannel (файлы.java: 361) около Ява.НИО.файл.Файлы.newByteChannel (файлы.java: 407) в Эдуарде.Стевенс.ставрида.Стл.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 ответа:
Используйте класс nio
ByteBuffer
: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); }
Если есть простой способ буферизации
Есть:
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(...));
DataOutputStream
это не особенно быстрое решение. Создайте простой старыйFileOutputStream
, Поместите вокруг негоBufferedOutputStream
, а затем напишите свой собственный код для записи данных в виде байтов. КлассыFloat
иDouble
имеют вспомогательные функции для этого. doubleToLongBits, например.Если это не достаточно быстро для вас, то читайте на NIO2.