Как быстро записать большие двоичные данные на Java? [дубликат]


На этот вопрос уже есть ответ здесь:

Я пишу 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 4

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);
}

Учебник ByteBuffer

ByteBuffer javadoc

WriteableByteChannel javadoc

Файлы javadoc

Правка:

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.