Dumping um Java Stringbuilder para arquivar
-
16-09-2019 - |
Pergunta
Qual é a maneira mais eficiente/elegante de despejar um StringBuilder em um arquivo de texto?
Você pode fazer:
outputStream.write(stringBuilder.toString().getBytes());
Mas isso é eficiente para um arquivo muito longo?
Existe uma maneira melhor?
Solução
Como apontado por outros, use um escritor e use um escritor de buffer, mas depois não ligue writer.write(stringBuilder.toString());
em vez disso writer.append(stringBuilder);
.
EDIT: Mas vejo que você aceitou uma resposta diferente porque era uma linha. Mas essa solução tem dois problemas:
não aceita um
java.nio.Charset
. MAU. Você sempre deve especificar um charset explicitamente.ainda está fazendo você sofrer um
stringBuilder.toString()
. Se a simplicidade é realmente o que você procura, tente o seguinte do Goiaba projeto:
Outras dicas
Você deve usar um bufferwriter para otimizar as gravações (sempre escreva dados de caracteres usando um escritor em vez de um outputStream). Se você não estivesse escrevendo dados de caracteres, usaria um bufferoutputStream.
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();
}
Ou, usando o Try-With-Resources (Java 7 ou mais)
File file = new File("path/to/file.txt");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(stringBuilder.toString());
}
Como você está escrevendo em um arquivo, uma abordagem melhor seria escrever para o BufferWriter com mais frequência, em vez de criar um enorme StringBuilder na memória e escrever tudo no final (dependendo do seu caso de uso, você pode até poder ser capaz para eliminar completamente o StringBuilder). Escrever incrementalmente durante o processamento salvará a memória e fará melhor uso da sua largura de banda de E/S limitada, a menos que outro thread esteja tentando ler muitos dados do disco ao mesmo tempo em que você está escrevendo.
Bem, se a string for enorme, toString().getBytes()
criará bytes duplicados (2 ou 3 vezes). O tamanho da string.
Para evitar isso, você pode extrair pedaços da corda e escrevê -la em peças separadas.
Aqui está como pode parecer:
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); // Crie nenhum novo buffer
final CharArrayReader aCARead = new CharArrayReader(aChars); // Crie nenhum novo buffer
// isso pode ser lento, mas não criará mais buffer (para bytes)
int aByte;
while((aByte = aCARead.read()) != -1)
outputStream.write(aByte);
}
Espero que isto ajude.
Você poderia usar o Apache Commons io biblioteca, o que te dá FileUtils:
FileUtils.writeStringToFile(file, stringBuilder.toString(), Charset.forName("UTF-8"))
Para dados de caracteres, melhor uso Reader/Writer
. No seu caso, use um BufferedWriter
. Se possível, use BufferedWriter
Desde o início em vez de StringBuilder
Para salvar a memória.
Observe que sua maneira de chamar o não-Arg getBytes()
O método usaria a codificação de caracteres padrão da plataforma para decodificar os caracteres. Isso pode falhar se a codificação padrão da plataforma for, por exemplo ISO-8859-1
Enquanto os dados da sua string contêm caracteres fora do ISO-8859-1
Charset. Melhor usar o getBytes(charset)
onde você pode especificar o charset, como UTF-8
.
Se a string em si for longa, você definitivamente deve evitar o ToString (), o que faz outra cópia da string. A maneira mais eficiente de escrever para transmitir deve ser algo assim,
OutputStreamWriter writer = new OutputStreamWriter(
new BufferedOutputStream(outputStream), "utf-8");
for (int i = 0; i < sb.length(); i++) {
writer.write(sb.charAt(i));
}
Como Java 8 você só precisa fazer isso:
Files.write(Paths.get("/path/to/file/file_name.extension"), stringBuilder.toString().getBytes());
Você não precisa de bibliotecas de terceiros para fazer isso.
Baseado em https://stackoverflow.com/a/1677317/980442
Eu crio essa função que usa OutputStreamWriter
e a write()
, isso também é otimizado pela memória, melhor do que apenas usar 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);
}
}
}
Referências para a maioria das respostas aqui + Implementação aprimorada: https://www.genuitec.com/dump--stringbuilder-to-file/
A implementação final é ao longo das linhas de
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();
}