it-swarm.it

Il modo migliore per scrivere String su file usando Java nio

Devo scrivere (aggiungere) una stringa enorme in un file flat usando Java nio. La codifica è ISO-8859-1.

Attualmente stiamo scrivendo come mostrato di seguito. Esiste un modo migliore di fare lo stesso?

public void writeToFile(Long limit) throws IOException{
     String fileName = "/xyz/test.txt";
     File file = new File(fileName);        
     FileOutputStream fileOutputStream = new FileOutputStream(file, true);  
     FileChannel fileChannel = fileOutputStream.getChannel();
     ByteBuffer byteBuffer = null;
     String messageToWrite = null;
     for(int i=1; i<limit; i++){
         //messageToWrite = get String Data From database
         byteBuffer = ByteBuffer.wrap(messageToWrite.getBytes(Charset.forName("ISO-8859-1")));
         fileChannel.write(byteBuffer);         
     }
     fileChannel.close();
}

EDIT: ho provato entrambe le opzioni. Di seguito sono riportati i risultati.

@Test
public void testWritingStringToFile() {
    DiagnosticLogControlManagerImpl diagnosticLogControlManagerImpl = new DiagnosticLogControlManagerImpl();
    try {
        File file = diagnosticLogControlManagerImpl.createFile();
        long startTime = System.currentTimeMillis();
        writeToFileNIOWay(file);
        //writeToFileIOWay(file);
        long endTime = System.currentTimeMillis();
        System.out.println("Total Time is  " + (endTime - startTime));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

/**
 *
 * @param limit
 *            Long
 * @throws IOException
 *             IOException
 */
public void writeToFileNIOWay(File file) throws IOException {
    FileOutputStream fileOutputStream = new FileOutputStream(file, true);
    FileChannel fileChannel = fileOutputStream.getChannel();
    ByteBuffer byteBuffer = null;
    String messageToWrite = null;
    for (int i = 1; i < 1000000; i++) {
        messageToWrite = "This is a test üüüüüüööööö";
        byteBuffer = ByteBuffer.wrap(messageToWrite.getBytes(Charset
            .forName("ISO-8859-1")));
        fileChannel.write(byteBuffer);
    }
}

/**
 *
 * @param limit
 *            Long
 * @throws IOException
 *             IOException
 */
public void writeToFileIOWay(File file) throws IOException {
    FileOutputStream fileOutputStream = new FileOutputStream(file, true);
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
        fileOutputStream, 128 * 100);
    String messageToWrite = null;
    for (int i = 1; i < 1000000; i++) {
        messageToWrite = "This is a test üüüüüüööööö";
        bufferedOutputStream.write(messageToWrite.getBytes(Charset
            .forName("ISO-8859-1")));
    }
    bufferedOutputStream.flush();
    fileOutputStream.close();
}

private File createFile() throws IOException {
    File file = new File(FILE_PATH + "test_sixth_one.txt");
    file.createNewFile();
    return file;
}

sando ByteBuffer e Channel: ho impiegato 4402 ms

sando Buffered Writer: Ho impiegato 563 ms

37
nobody

Non credo che sarai in grado di ottenere una risposta rigorosa senza eseguire il benchmark del tuo software. NIO può velocizzare notevolmente l'applicazione nelle giuste condizioni, ma può anche rallentare le cose. Ecco alcuni punti:

  • Hai davvero bisogno di stringhe? Se si memorizzano e si ricevono byte dal proprio database, è possibile evitare tutti i costi di allocazione e codifica delle stringhe.
  • Hai davvero bisogno di rewind e flip? Sembra che tu stia creando un nuovo buffer per ogni stringa e lo stia semplicemente scrivendo sul canale. (Se vai nel modo NIO, analizza le strategie che riutilizzano i buffer invece di avvolgere/scartare, penso che faranno meglio).
  • Tieni presente che wrap e allocateDirect possono produrre buffer abbastanza diversi. Benchmark sia per cogliere i compromessi. Con l'allocazione diretta, assicurarsi di riutilizzare lo stesso buffer per ottenere le migliori prestazioni.
  • E la cosa più importante è: assicurati di confrontare NIO con BufferedOutputStream e/o BufferedWritter approcci (usa un intermedio byte[] o char[] buffer con dimensioni ragionevoli). Ho visto molti , molti , molti persone che hanno scoperto che NIO non è un proiettile d'argento.

Se desideri un bordo sanguinante ... Torna a IO Trails per alcuni NIO2: D.

Ed ecco un punto di riferimento interessante sulla copia di file usando strategie diverse . So che si tratta di un problema diverso, ma penso che la maggior parte dei fatti e delle conclusioni dell'autore si applichino anche al tuo problema.

Saluti,

AGGIORNAMENTO 1:

Dato che @EJP mi ha suggerito che i buffer diretti non sarebbero efficienti per questo problema, ho fatto il benchmark da solo e ho finito con una soluzione Nice NIO usando file mappati in nemory. Nel mio Macbook con OS X Lion questo batte BufferedOutputStream con un margine solido. ma tieni presente che questo potrebbe essere OS/Hardware/VM specifico:

public void writeToFileNIOWay2(File file) throws IOException {
    final int numberOfIterations = 1000000;
    final String messageToWrite = "This is a test üüüüüüööööö";
    final byte[] messageBytes = messageToWrite.
            getBytes(Charset.forName("ISO-8859-1"));
    final long appendSize = numberOfIterations * messageBytes.length;
    final RandomAccessFile raf = new RandomAccessFile(file, "rw");
    raf.seek(raf.length());
    final FileChannel fc = raf.getChannel();
    final MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_WRITE, fc.
            position(), appendSize);
    fc.close();
    for (int i = 1; i < numberOfIterations; i++) {
        mbf.put(messageBytes);
    }
} 

Ammetto di aver barato un po 'calcolando in anticipo la dimensione totale da aggiungere (circa 26 MB). Questo potrebbe non essere possibile per diversi scenari del mondo reale. Tuttavia, è sempre possibile utilizzare una "dimensione aggiunta abbastanza grande per le operazioni e successivamente troncare il file.

AGGIORNAMENTO 2 (2019):

A chiunque cerchi una soluzione moderna (come in, Java 11+) al problema, seguirei i consigli di @ DodgyCodeException e userò Java.nio.file.Files.writeString :

String fileName = "/xyz/test.txt";
String messageToWrite = "My long string";
Files.writeString(Paths.get(fileName), messageToWrite, StandardCharsets.ISO_8859_1);
15
Anthony Accioly

Esiste una soluzione na riga, usando Java nio:

Java.nio.file.Files.write(Paths.get(file.toURI()), 
                          "My string to save".getBytes("utf-8"),
                          StandardOpenOption.CREATE,
                          StandardOpenOption.TRUNCATE_EXISTING);

Non ho confrontato questa soluzione con gli altri, ma l'utilizzo dell'implementazione integrata per il file open-write-close dovrebbe essere veloce e il codice è piuttosto piccolo.

54
Roberto

Un BufferedWriter attorno a un FileWriter sarà quasi sicuramente più veloce di qualsiasi schema NIO che puoi inventare. Il tuo codice certamente non è ottimale, con un nuovo ByteBuffer per scrittura e quindi eseguendo operazioni inutili su di esso quando sta per uscire dall'ambito di applicazione, ma in ogni caso la tua domanda si basa su un malinteso. NIO non "scarica il footprint di memoria sul sistema operativo", a meno che non si stia utilizzando FileChannel.transferTo/From (), cosa impossibile in questo caso.

NB: non utilizzare PrintWriter come suggerito nei commenti, poiché ciò ingoia eccezioni. PW è davvero solo per console e file di registro in cui non ti interessa.

8
user207421

Ecco un modo semplice e breve. Crea un file e scrive i dati relativi al tuo progetto di codice:

private void writeToFile(String filename, String data) {
    Path p = Paths.get(".", filename);
    try (OutputStream os = new BufferedOutputStream(
        Files.newOutputStream(p, StandardOpenOption.CREATE, StandardOpenOption.APPEND))) {
        os.write(data.getBytes(), 0, data.length());
    } catch (IOException e) {
        e.printStackTrace();
    }
}
0
darkhorse21