如何通过 FileWriter 在 BufferedWriter 上设置缓冲区大小

How to set the buffer size on a BufferedWriter over a FileWriter

当我使用一些线程将数据写入单个文件时,我遇到了 BufferedWriter 问题。

我设置了BufferedWriter的buffer size,但是无论我设置多少,当buffer为8192(默认buffer size)时,它会将数据刷新到磁盘,而不是我设置的大小(这里是 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();
            }
        }
    }
}

你看到的不是缓冲区BufferedWriter的大小,而是FileWriter内部使用的缓冲区大小。引用自 Java 文档 (http://docs.oracle.com/javase/7/docs/api/java/io/FileWriter.html)

The constructors of this class assume that the default character encoding and the default byte-buffer size are acceptable. To specify these values yourself, construct an OutputStreamWriter on a FileOutputStream.

因此,如果您想对数据实际写入磁盘的时间进行精细控制,您应该将 BufferedWriter 实例化为

bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File('my_file.txt),true)));

FileWriter 实际上使用了它自己的固定大小的 1024 字节缓冲区。另一方面,BufferedWriter 表明它使用 8192 字节缓冲区大小(默认),用户可以将其配置为任何其他所需的大小。

为了进一步搅浑水,OutputStreamWriter 的 Java 6 实现实际上委托给 StreamEncoder,它使用自己的默认大小为 8192 字节的缓冲区。 StreamEncoder 缓冲区是用户可配置的,尽管无法通过封闭的 OutputStreamWriter 直接访问它。

Is there a problem with my code?

几个。主要是:潜在的IO和并发错误。文件缓冲区大小可能不是一个值得关注的问题(也是您无法有效处理的问题)。

  • 正在尝试打开已打开的文件。您的所有线程都试图写入同一个文件 (1.txt)。这可能是个问题。 FileWriter documentation says:

    Some platforms, in particular, allow a file to be opened for writing by only one FileWriter (or other file-writing object) at a time. In such situations the constructors in this class will fail if the file involved is already open.

  • 台词可能被剪切和混合。如果你有多个线程,它们各自的缓冲区在某个时候刷新到同一个输出中,你甚至可能不需要奇怪的竞争条件或线程在中间停止或写入操作来查看你的输出损坏。

    作为我的解决方案(如果您的线程必须共享相同的输出),您可以使用具有同步访问的共享对象来处理实际的写入。我在示例中实现了 SafeAppender,但可能还有更好的选择。

  • No flushing and closing buffers 将意味着(尾部)你的数据将丢失(如眼泪在雨中)。 finally 块通常可以很好地解决这个问题。

  • 此外,正如其他用户所述,BufferedWriter 缓冲区大小 不会影响 FileOutputStream 中的缓冲区大小(因此FileWriter)。而且看起来 java.iojava.nio API 没有提供任何方法来解决这个问题。如果您查看 Java 库源代码,您可能会注意到 BufferedWriter 缓冲区大小仅表示实际写入委托输出之前存储的字符数量。对于大多数情况,默认大小 (8192) 是最佳的,增加它可能意味着更多麻烦(可能丢失更多数据)而不是好处。

这是我的代码,如果对您有用:

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

}

我是用OutputStream解决问题的,不是writer,这里是代码:

bw = new BufferedOutputStream(
                new FileOutputStream(new File("/Users/liaoliuqing/Downloads/1.txt"),true),165537);