"Постоянные потоки" для Java FileWriter?

Когда я хочу написать код 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) допускают "постоянные подключения" (подключения, которые остаются открытыми при нескольких вызовах), существует ли способ иметь "постоянные потоки", которые не нужно открывать / закрывать через несколько write(String) вызовы? Если так, как это будет работать? О каких подводных камнях / предостережениях я должен знать? Заранее спасибо!

2 ответа

Решение

Если вы посмотрите на эту реализацию

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

файл остается открытым.

Если у вас есть несколько потоков, вы должны синхронизировать в методе записи (но это нужно учитывать в любом случае).

Возможно, есть следующие проблемы, если файл остается открытым:

  • Теоретически у вас могут не хватать файловые дескрипторы. Они могут быть ограничены (см. ulimit -a в системах Linux, например): каждый регистратор использует один дескриптор.

  • Если вы используете FileWriter без буферизации у вас есть вызовы ввода / вывода для каждого вызова write, Это может быть довольно медленно.

  • Если вы используете BufferedWriter на вершине FileWriterВы должны убедиться, что он закрывается должным образом в конце вашей программы, иначе оставшееся содержимое в буфере может не быть записано на диск. Поэтому вам нужен блок 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();
    }
}

Если у вас есть веб-приложение, вы можете позвонить close метод в ServletContextListener (метод contextDestroyed).

Наибольший прирост производительности, вероятно, BufferedWriter, Это преимущество теряется, если вы открываете / закрываете его для каждой операции записи, поскольку close должен позвонить flush, Таким образом, сочетание открытого файла с буферизацией будет довольно быстрым.

Я не уверен, как фреймворки логгеров управляют файловыми соединениями, но я могу предложить вам один способ - кэшировать fileWriter в static экземпляр, не закрывая его. Таким образом, ваша программа будет хранить прямую ссылку на файл и не будет создавать новое соединение для повторной записи.

Одним из недостатков этого подхода является то, что файл будет храниться FileWriter даже если нет никаких действий записи. Это означает, что FileWriter заблокирует файл, и пока он заблокирован, никакой другой процесс не сможет записать файл.

Другие вопросы по тегам