Как установить размер буфера в BufferedWriter вместо FileWriter

Я встретил проблему с BufferedWriter, когда я пишу данные в один файл с несколькими потоками.

Я установил размер буфера BufferedWriter, но независимо от того, какое число я установил, он сбрасывает данные на диск, когда буфер равен 8192 (размер буфера по умолчанию), а не размер, который я установил (здесь 16384). Есть ли проблема с моим кодом?

Вот как я создаю BufferedWriter:

new BufferedWriter(new FileWriter(fileName, true), 16384);

Это полный код:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class Test1 {
    public static void main(String[] args) throws IOException {
        for(int i =0;i<10;i++){
            MyThread r = new MyThread();
            Thread t = new Thread(r);
            t.start();
        }
    }
}

class MyThread implements Runnable {
    public void run() {
        String s = "{addffffffkkkljlkj2015dd}\n";
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter(
                    "/Users/liaoliuqing/Downloads/1.txt", true),16384);
        } catch (IOException e) {
            e.printStackTrace();
        }
        for(int i =0 ; i<1000; i++){
            try {
                bw.write(String.format("%03d", i)+s);
                //bw.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

person jinhong_lu    schedule 08.09.2015    source источник
comment
Я запускаю программу в Debian и Mac, результат тот же. Как я ни меняю размер буфера, это не влияет.   -  person jinhong_lu    schedule 08.09.2015
comment
А какой эффект вы хотите?   -  person Lærne    schedule 08.09.2015
comment
если размер буфера установлен на 16384, он должен записывать на диск, когда данные составляют 16384 байта, а не 8192. Теперь независимо от того, что я установил, он записывает на диск каждые 8192 байта.   -  person jinhong_lu    schedule 08.09.2015
comment
FileWriter буферизуется внутри: stackoverflow.com/questions/6108043/ В документе говорится, что для указания [кодировки символов и размера байтового буфера] самостоятельно создайте OutputStreamWriter на a FileOutputStream, но я не вижу конструктора ни для одного из этих классов, который позволяет указать размер буфера :(   -  person Chris Martin    schedule 08.09.2015


Ответы (4)


Есть ли проблема с моим кодом?

Немного. В основном: потенциальные ошибки ввода-вывода и параллелизма. Размер буфера файла может быть меньшей проблемой (и с этим вы не можете эффективно справиться).

  • #P3# <блочная цитата> #P4#
  • Строки могут быть обрезаны и смешаны. Если у вас есть несколько потоков с соответствующими буферами, которые в какой-то момент сбрасываются в один и тот же вывод, вам могут даже не понадобиться странные условия гонки или потоки, остановленные прямо посередине, или операция записи, чтобы увидеть поврежденный вывод.

    В качестве решения (если ваши потоки должны совместно использовать один и тот же вывод) вы можете использовать общий объект с синхронизированным доступом, чтобы позаботиться о фактической записи. Я реализовал SafeAppender в своем примере, но, вероятно, есть лучшие альтернативы.

  • Никакие очистка и закрытие буферов не будут означать, что ваши данные будут потеряны (как слезы под дождем). Блок finally обычно хорош, чтобы позаботиться об этом.

  • Кроме того, как утверждают другие пользователи, BufferedWriter размер буфера не влияет на размер буфера в FileOutputStream (и, следовательно, FileWriter). И похоже, что java.io и java.nio API не предлагают никакого способа возиться с этим. Если вы посмотрите на исходные коды библиотеки Java, вы можете заметить, что размер буфера BufferedWriter просто означает количество символов, которые вы сохраняете до фактической записи в вывод делегата. Размер по умолчанию (8192) оптимален для большинства случаев, и его увеличение может привести к большим проблемам (возможно, потере большего количества данных), чем к преимуществам.

Это мой код, если он вам служит:

// http://stackoverflow.com/questions/32451526/how-to-set-the-buffer-size-on-a-bufferedwriter-over-a-filewriter
public class TestWriter {

public static class SafeAppender {
    private BufferedWriter bw;
    private int users = 0;
    public SafeAppender(File f) throws IOException {
        bw = new BufferedWriter(new FileWriter(f));
    }

    public synchronized void append(String s) throws IOException {
        bw.write(s);
    }
    public synchronized void incrUsers() { 
        users ++; 
    }
    public synchronized void decrUsers() {
        if (--users <= 0) {
            try {
                bw.flush();
                System.err.println("INFO-appender-flush()");
            } catch (Throwable whatever) { /* log-if-you-care*/}
        }
    }
    // Might be called by GC, or not
    @Override protected void finalize() throws Throwable {
        try {
            bw.close();
            System.err.println("INFO-appender-close()");
        } catch (Throwable whatever) { /* log-if-you-care */}
        super.finalize();
    }
}

private static class MyRunnable implements Runnable {
    final static String S = "{addffffffkkkljlkj2015dd}";
    SafeAppender appender;
    String threadId;
    public MyRunnable (SafeAppender a, String tid) {
        appender = a; threadId = tid;
    }

    public void run() {
        appender.incrUsers();
        try {
            for(int i =0 ; i<1000; i++){
                // NOTE: Not a good idea to printStackTrace if each line fails. Let thread fail
                String line = String.format("%s-%03d-%s\n", threadId, i, S);
                appender.append(line);
            }
        } catch (IOException e) {
            System.err.printf("ERROR-%s-%s\n", threadId, e.toString());
        } finally {
            appender.decrUsers();
        }
    }
}

public static void main(String[] args) {
    try {
        File f = File.createTempFile("TestWriter", ".txt");
        System.err.printf("INFO-main-Writing into %s\n", f.getCanonicalPath());
        SafeAppender appender = new SafeAppender (f);
        for(int i =0;i<10;i++){
            MyRunnable r = new MyRunnable(appender, ""+i);
            Thread t = new Thread(r);
            t.start();
        }
    } catch (Throwable e) {
        e.printStackTrace(System.err);
    }
}

}
person Javier    schedule 08.09.2015

FileWriter фактически использует свой собственный буфер фиксированного размера 1024 байта. С другой стороны, BufferedWriter показывает, что он использует размер буфера 8192 байта (по умолчанию), который может быть настроен пользователем на любой другой желаемый размер.

И чтобы еще больше замутить воду, реализация Java 6 OutputStreamWriter фактически делегирует StreamEncoder, который использует свой собственный буфер с размером по умолчанию 8192 байта. И буфер StreamEncoder настраивается пользователем, хотя нет возможности получить к нему прямой доступ через охватывающий OutputStreamWriter.

person Raman Shrivastava    schedule 08.09.2015
comment
Этот ответ, по-видимому, был скопирован из stackoverflow.com/questions/6976893/, который содержит некоторые дополнительные полезные ссылки. - person Chris Martin; 08.09.2015
comment
В самом деле. Но копировать было бы неправильным словом. Я мог бы перефразировать это, чтобы предложить это в своем ответе, но смысл был в том, чтобы передать информацию, и нет смысла переписывать ее, если она уже есть. я пропустил эти ссылки, чтобы ответ был минимальным и актуальным, и оставил двери открытыми для ОП, чтобы он сам провел небольшое исследование. :) - person Raman Shrivastava; 08.09.2015

Я решаю проблему, используя OutputStream, а не писатель, вот код:

bw = new BufferedOutputStream(
                new FileOutputStream(new File("/Users/liaoliuqing/Downloads/1.txt"),true),165537);
person jinhong_lu    schedule 08.09.2015
comment
Хорошо для вас, но это странно, так как Writer делегирует OutputStream и все поведение (флаг добавления, размер буфера, точные операции при записи ASCII) идентично. Любые другие изменения? - person Javier; 08.09.2015

Вы видите не размер буфера BufferedWriter, а размер буфера, используемого внутри FileWriter. Цитата из документации по Java (http://docs.oracle.com/javase/7/docs/api/java/io/FileWriter.html)

Конструкторы этого класса предполагают, что кодировка символов по умолчанию и размер байтового буфера по умолчанию являются приемлемыми. Чтобы указать эти значения самостоятельно, создайте OutputStreamWriter в FileOutputStream.

Поэтому, если вы хотите иметь точный контроль над тем, когда данные фактически записываются на диск, вы должны создать экземпляр BufferedWriter как

bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File('my_file.txt),true)));
person Marco Sandrini    schedule 08.09.2015
comment
Как это поможет вам контролировать размер буфера? - person Chris Martin; 08.09.2015
comment
Я установил это: bw = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(new File(/Users/liaoliuqing/Downloads/1.txt),true)),65536); bw.write(String.format(%03d, i)+s); бв.флеш(); моя строка составляет 27340 байт, поэтому размер буфера достаточно велик, чтобы содержать строку. но все же он записывает на диск каждые 8192 байта. - person jinhong_lu; 08.09.2015
comment
Мой ответ был основан на документации: поскольку в нем указано «указать эти значения» (во множественном числе), я предположил, что в этом случае FileOutputStream не будет иметь собственного буфера.... - person Marco Sandrini; 08.09.2015