【问题标题】:"Persistent streams" for Java FileWriter?Java FileWriter 的“持久流”?
【发布时间】:2013-07-08 18:08:47
【问题描述】:

当我想编写将文本写入文件的 Java 代码时,它通常看起来像这样:

File logFile = new File("/home/someUser/app.log");
FileWriter writer;

try {
    writer = new FileWriter(logFile, true);

    writer.write("Some text.");

    writer.close();
} catch (IOException e) {
    e.printStackTrace();
}

但是,如果我要连续执行 大量 写操作怎么办?如果我每秒记录数十、数百甚至数千次写入操作会怎样?

与数据库(和 JDBC)允许“持久连接”(跨多个调用保持打开的连接)的方式相同,有没有办法让“持久流”不需要跨多个 @ 打开/关闭987654322@ 调用?如果是这样,这将如何工作?我必须注意哪些陷阱/警告?提前致谢!

【问题讨论】:

  • 顺便说一句,在您的示例中,如果write 引发异常,则编写器不会在write 之后立即关闭。

标签: java performance file-io filewriter persistent-connection


【解决方案1】:

如果你看看这个实现

class Logger {
    private final BufferedWriter w;

    public Logger(final File file) throws IOException {
        this.w = new BufferedWriter(new FileWriter(file));
        LoggerRegistry.register(this);
    }

    public void log(String s) throws IOException {
        synchronized (this.w) {
            this.w.write(s);
            this.w.write("\n");
        }
    }

    public void close() throws IOException {
        this.w.close();
    }
}

文件保持打开状态。

如果你有多个线程,你必须在 write 方法中进行同步(但无论如何都要考虑到这一点)。

如果文件保持打开状态,可能存在这些问题:

  • 理论上,您可能会用完文件句柄。这些可能会受到限制(例如,请参阅 Linux 系统上的 ulimit -a):每个记录器使用一个句柄。

  • 如果您使用不带缓冲的FileWriter,则每次调用write 都会有I/O 调用。这可能会很慢。

  • 如果您在 FileWriter 之上使用 BufferedWriter,则必须确保它在程序结束时正确关闭,否则缓冲区中的剩余内容可能不会被写入磁盘。因此,您需要在程序周围设置一个 try/finally 块,该块必须正确关闭所有记录器。

因此您需要注册所有记录器。这是一个简化版本(它不是线程安全的):

class LoggerRegistry {
    private final static List<Logger> loggers = new ArrayList<Logger>();

    public static void register(Logger l) {
        loggers.add(l);
    }

    public static void close() {
        for (Logger l : loggers) {
            try {
                l.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

并像这样在你的主程序中使用它:

public static void main(String[] args) throws IOException {
    try {
        final Logger l = new Logger(new File("/tmp/1"));
        l.log("Hello");

        // ... 

    } finally {
        LoggerRegistry.close();
    }
}

如果您有一个 Web 应用程序,您可以在 ServletContextListener 中调用 close 方法(方法 contextDestroyed)。

最大的性能提升可能是BufferedWriter。如果您为每个写操作打开/关闭它,这个优势就会丢失,因为close 必须调用flush。所以打开文件和缓冲结合起来会很快。

【讨论】:

    【解决方案2】:

    我不确定记录器框架如何管理文件连接,但我建议您的一种方法是在static 实例中缓存 fileWriter,而不关闭它。这样,您的程序将保持对该文件的实时引用,并且不会创建新的连接来再次写入。

    这种方法的一个缺点是,即使没有写入活动,文件也会由FileWriter 持有。这意味着,FileWriter锁定文件,并且在锁定期间,没有其他进程可以写入文件。

    【讨论】:

    • 感谢@sanbhat (+1) - 当你说“这种方法的一个缺点是即使没有写入活动,文件也会被 FileWriter 保存。”,这意味着什么?这是否意味着文件被“锁定”?多个日志记录线程是否无法写入文件?这种策略会施加什么限制? 最重要的是这会带来任何性能提升吗?如果没有,这样做有什么意义?!?再次感谢!
    • 啊,非常好 - 再次感谢@sanbhat。很抱歉在这里挑剔,但有两件事:(1)“没有其他进程可以写入日志文件”,您的意思是:其他线程,其他JVM或其他系统级进程,(还是全部 3)?例如,一个 Perl 脚本是否可以在被 Java FileWriter“锁定”时写入同一个日志文件?并且 (2) 您能否指出任何支持您的“文件锁定”语句的 Oracle/Java 文档?并不是说我不相信你(我相信!),但我需要带着概念证明去找我的老板,并且需要引用参考文献。再次感谢!
    • 当文件被FileWriter 锁定时,没有其他进程(另一个JVM 或perl)甚至同一个JVM 中的其他线程都不能写入该文件。请参阅docs.oracle.com/javase/6/docs/api/java/io/RandomAccessFile.htmldocs.oracle.com/javase/6/docs/api/java/nio/channels/… 了解更多信息
    • 最后跟进@sanbhat:这样的“持续流”是否值得?!?你能预见性能的提升吗?是否存在使用此功能或多或少有益的情况?
    • 进行基准测试是一件好事。 1 通过有一些持久连接,2 通过在每次写入后关闭流,这个测试可以得出持久流提供了多少性能。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-05
    • 2016-11-04
    • 2011-03-19
    • 2010-12-09
    • 2011-02-03
    • 1970-01-01
    相关资源
    最近更新 更多