Как добавить текст в существующий файл на Java без перезаписи его содержимого?
Основное решение: FileWriter с флагом append
Ваш интуитивный подход с FileWriter("log.txt", true) абсолютно верен. Второй параметр true включает режим добавления (append mode). Когда вы передаёте true, FileWriter не пересоздаёт файл, а открывает его для записи в конец. Если файл не существует, он будет создан автоматически.
Вот правильная реализация с обработкой ресурсов (try-with-resources):
import java.io.FileWriter;
import java.io.IOException;
public class Logger {
public static void log(String message) {
try (FileWriter writer = new FileWriter("log.txt", true)) {
writer.write(message + "\n");
} catch (IOException e) {
System.err.println("Ошибка записи в лог: " + e.getMessage());
}
}
public static void main(String[] args) {
log("Первая запись");
log("Вторая запись");
log("Третья запись");
}
}
Конструкция try-with-resources (Java 7+) гарантирует автоматическое закрытие FileWriter, даже если возникнет исключение. Это избавляет от необходимости вызывать close() вручную.
Более производительный подход: BufferedWriter
Для частых операций записи или больших объёмов данных используйте буферизацию. BufferedWriter уменьшает количество операций ввода-вывода, накапливая данные в буфере и сбрасывая их на диск пачками.
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedLogger {
public static void log(String message) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("log.txt", true))) {
writer.write(message);
writer.newLine();
} catch (IOException e) {
System.err.println("Ошибка: " + e.getMessage());
}
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
log("Событие #" + i);
}
}
}
Метод newLine() использует системный разделитель строк, что делает код кроссплатформенным.
Работа с большими файлами и многопоточность
Для файлов размером 100+ МБ и многопоточного доступа рекомендую использовать FileChannel с блокировками или специализированные библиотеки логирования (Log4j, SLF4J). Вот пример с FileChannel:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
public class ChannelLogger {
public static synchronized void log(String message) {
try (RandomAccessFile file = new RandomAccessFile("log.txt", "rw");
FileChannel channel = file.getChannel()) {
// Перемещаемся в конец файла
channel.position(channel.size());
// Подготавливаем данные
String line = message + System.lineSeparator();
ByteBuffer buffer = ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8));
// Записываем
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ключевые моменты для больших файлов:
- Буферизация обязательна — никогда не пишите по одному байту.
- Синхронизация — используйте
synchronizedдля потокобезопасности. - Кодировка — всегда явно указывайте кодировку (UTF-8).
- Не держите файл открытым — открывайте, пишите, закрывайте.
Что выбрать для продакшена?
Для реальных проектов используйте готовые библиотеки логирования:
- Log4j 2 — самый популярный, поддерживает асинхронную запись, ротацию файлов.
- SLF4J + Logback — современный стандарт, эффективен и гибок.
- java.util.logging — встроенное решение, но уступает по функциональности.
Пример конфигурации Logback для добавления в существующий файл:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log.txt</file>
<append>true</append>
<!-- Настройки ротации -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log.%d{yyyy-MM-dd}.txt</fileNamePattern>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</configuration>
Итог: для простых задач используйте FileWriter("file.txt", true) с BufferedWriter. Для серьёзных проектов — библиотеки логирования.
Альтернативный подход: Files.write() из Java NIO
Начиная с Java 7, есть элегантный однострочник через класс Files из пакета java.nio.file. Он подходит для небольших объёмов данных и когда нужно быстро дописать несколько строк.
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
public class NioLogger {
public static void main(String[] args) {
try {
// Добавляем одну строку
Files.write(
Paths.get("log.txt"),
"Новая запись\n".getBytes(),
StandardOpenOption.APPEND,
StandardOpenOption.CREATE
);
// Добавляем список строк
Files.write(
Paths.get("log.txt"),
java.util.Arrays.asList("Строка 1", "Строка 2"),
java.nio.charset.StandardCharsets.UTF_8,
StandardOpenOption.APPEND
);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Плюсы этого метода:
- Минимум кода — одна строчка.
- Автоматическое управление ресурсами (файл закрывается после записи).
- Поддержка разных опций: APPEND, CREATE, TRUNCATE_EXISTING.
Минусы:
- Нет буферизации — при частых вызовах производительность ниже, чем у BufferedWriter.
- Не подходит для многопоточной записи без внешней синхронизации.
Этот способ идеален для скриптов, прототипов и случаев, когда нужно быстро дописать данные без лишних накладных расходов на настройку.