Сохранение Java StringBuilder в файл
-
16-09-2019 - |
Вопрос
Каков наиболее эффективный/элегантный способ сохранить StringBuilder в текстовый файл?
Ты можешь сделать:
outputStream.write(stringBuilder.toString().getBytes());
Но эффективно ли это для очень длинного файла?
Есть ли способ лучше?
Решение
Как указывали другие, используйте Writer и используйте BufferedWriter, но тогда не вызывайте writer.write(stringBuilder.toString());
вместо этого просто writer.append(stringBuilder);
.
РЕДАКТИРОВАТЬ:Но я вижу, что вы приняли другой ответ, потому что он был однострочным.Но у этого решения есть две проблемы:
он не принимает
java.nio.Charset
.ПЛОХОЙ.Вы всегда должны явно указывать кодировку.это все еще заставляет тебя страдать
stringBuilder.toString()
.Если простота действительно то, что вам нужно, попробуйте следующее из Гуава проект:
Другие советы
Вам следует использовать BufferedWriter для оптимизации записи (всегда записывайте символьные данные с помощью Writer вместо OutputStream).Если бы вы не записывали символьные данные, вы бы использовали BufferedOutputStream.
File file = new File("path/to/file.txt");
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(file));
writer.write(stringBuilder.toString());
} finally {
if (writer != null) writer.close();
}
или с помощью try-with-resources (Java 7 и более поздних версий)
File file = new File("path/to/file.txt");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(stringBuilder.toString());
}
Поскольку в конечном итоге вы пишете в файл, лучшим подходом было бы чаще писать в BufferedWriter вместо создания огромного StringBuilder в памяти и записи всего в конце (в зависимости от вашего варианта использования вы можете даже чтобы полностью исключить StringBuilder).Поэтапная запись во время обработки позволит сэкономить память и лучше использовать ограниченную пропускную способность ввода-вывода, если только другой поток не пытается прочитать много данных с диска в то же время, когда вы записываете.
Ну, если строка огромна, toString().getBytes()
создаст повторяющиеся байты (2 или 3 раза).Размер строки.
Чтобы избежать этого, вы можете извлечь фрагмент строки и записать его отдельными частями.
Вот как это может выглядеть:
final StringBuilder aSB = ...;
final int aLength = aSB.length();
final int aChunk = 1024;
final char[] aChars = new char[aChunk];
for(int aPosStart = 0; aPosStart < aLength; aPosStart += aChunk) {
final int aPosEnd = Math.min(aPosStart + aChunk, aLength);
aSB.getChars(aPosStart, aPosEnd, aChars, 0); // Не создавать новый буфер
final CharArrayReader aCARead = new CharArrayReader(aChars); // Не создавать новый буфер
// Это может быть медленно, но не создаст больше буфера (для байтов)
int aByte;
while((aByte = aCARead.read()) != -1)
outputStream.write(aByte);
}
Надеюсь это поможет.
Вы можете использовать Apache Commons IO библиотека, которая дает вам ФайлУтилитс:
FileUtils.writeStringToFile(file, stringBuilder.toString(), Charset.forName("UTF-8"))
Для символьных данных лучше использовать Reader/Writer
.В вашем случае используйте BufferedWriter
.Если возможно, используйте BufferedWriter
с самого начала вместо StringBuilder
для экономии памяти.
Обратите внимание, что ваш способ вызова не-arg getBytes()
метод будет использовать кодировку символов платформы по умолчанию для декодирования символов.Это может привести к сбою, если, например, кодировка платформы по умолчанию ISO-8859-1
в то время как ваши строковые данные содержат символы вне ISO-8859-1
кодировка.Лучше используйте getBytes(charset)
где вы можете указать кодировку самостоятельно, например UTF-8
.
Если сама строка длинная, вам определенно следует избегать метода toString(), который создает еще одну копию строки.Самый эффективный способ записи в поток должен быть примерно таким:
OutputStreamWriter writer = new OutputStreamWriter(
new BufferedOutputStream(outputStream), "utf-8");
for (int i = 0; i < sb.length(); i++) {
writer.write(sb.charAt(i));
}
Начиная с Java 8 вам нужно сделать только это:
Files.write(Paths.get("/path/to/file/file_name.extension"), stringBuilder.toString().getBytes());
Для этого вам не нужны сторонние библиотеки.
На основе https://stackoverflow.com/a/1677317/980442
Я создаю эту функцию, которая использует OutputStreamWriter
и write()
, это тоже оптимизировано для памяти, лучше, чем просто использовать StringBuilder.toString()
.
public static void stringBuilderToOutputStream(
StringBuilder sb, OutputStream out, String charsetName, int buffer)
throws IOException {
char[] chars = new char[buffer];
try (OutputStreamWriter writer = new OutputStreamWriter(out, charsetName)) {
for (int aPosStart = 0; aPosStart < sb.length(); aPosStart += buffer) {
buffer = Math.min(buffer, sb.length() - aPosStart);
sb.getChars(aPosStart, aPosStart + buffer, chars, 0);
writer.write(chars, 0, buffer);
}
}
}
Тесты для большинства ответов здесь + улучшенная реализация: https://www.genuitec.com/dump-a-stringbuilder-to-file/
Окончательная реализация соответствует принципам
try {
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(file, append), charset), BUFFER_SIZE);
try {
final int length = sb.length();
final char[] chars = new char[BUFFER_SIZE];
int idxEnd;
for ( int idxStart=0; idxStart<length; idxStart=idxEnd ) {
idxEnd = Math.min(idxStart + BUFFER_SIZE, length);
sb.getChars(idxStart, idxEnd, chars, 0);
bw.write(chars, 0, idxEnd - idxStart);
}
bw.flush();
} finally {
bw.close();
}
} catch ( IOException ex ) {
ex.printStackTrace();
}