Como posso ler um arquivo de texto grande linha por linha usando Java?

848

Preciso ler um arquivo de texto grande de cerca de 5-6 GB, linha por linha, usando Java.

Como posso fazer isso rapidamente?

manoj singh
fonte
69
@kamaci et. al. Esta pergunta não deve ser marcada como duplicada. "Ler rapidamente a última linha" não é uma alternativa, e é discutível se "A maneira mais rápida de ler arquivos de texto linha por linha" é. A maneira mais rápida de fazer algo não é necessariamente a maneira comum. Além disso, as respostas abaixo incluem código, a alternativa mais relevante que você lista não. Esta pergunta é útil. Atualmente, é o principal resultado de pesquisa do Google por "arquivo de leitura java linha por linha". Finalmente, é difícil chegar ao estouro da pilha e descobrir que 1 em cada 2 perguntas é sinalizada para descarte.
Patrick Cullen
5
Aqui está uma comparação de velocidade para seis possíveis implementações.
Serg M Dez
4
Mesmo que eu tenha lido comentários argumentando que a política estreita da SO é péssima, a SO persiste nela. É uma perspectiva de desenvolvedor tão limitada que deseja evitar redundância a todo custo! Deixa estar assim! O creme subirá para o topo e o sh * t afundará até o fundo, por si só. Mesmo que uma pergunta possa ter sido feita antes (que pergunta não é ??), isso não significa que uma nova pergunta possa não ser capaz de expressá-la melhor, obter melhores respostas, ter uma classificação mais alta nos mecanismos de pesquisa etc. questão agora é 'protegido' ....
Stijn de Witt
3
É incrível como as perguntas são marcadas como duplicadas, basta ler o título.
Lucas

Respostas:

1064

Um padrão comum é usar

try (BufferedReader br = new BufferedReader(new FileReader(file))) {
    String line;
    while ((line = br.readLine()) != null) {
       // process the line.
    }
}

Você pode ler os dados mais rapidamente se considerar que não há codificação de caracteres. por exemplo, ASCII-7, mas não fará muita diferença. É altamente provável que o que você faz com os dados demore muito mais.

EDIT: Um padrão menos comum de usar que evita o escopo de linevazamento.

try(BufferedReader br = new BufferedReader(new FileReader(file))) {
    for(String line; (line = br.readLine()) != null; ) {
        // process the line.
    }
    // line is not visible here.
}

ATUALIZAÇÃO: No Java 8 você pode fazer

try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
        stream.forEach(System.out::println);
}

NOTA: Você deve colocar o Stream em um bloco try-with-resource para garantir que o método #close seja chamado, caso contrário, o identificador de arquivo subjacente nunca será fechado até que o GC o faça muito mais tarde.

Peter Lawrey
fonte
6
Como esse padrão se parece com o tratamento adequado de exceções? Observo que br.close () lança IOException, o que parece surpreendente - o que poderia acontecer ao fechar um arquivo aberto para leitura? O construtor FileReader pode lançar uma exceção FileNotFound.
21813 MikeB #
3
Se eu tenho um arquivo de 200MB e ele pode ler a 90MB / s, espero que leve ~ 3s? As minhas parecem levar minutos, com essa maneira "lenta" de ler. Estou em um SSD, então a velocidade de leitura não deve ser um problema?
Jiew Meng
4
@JiewMeng SO, eu suspeitaria que outra coisa que você está fazendo está demorando. Você pode tentar apenas ler as linhas do arquivo e nada mais.
Peter Lawrey
44
Por que não for(String line = br.readLine(); line != null; line = br.readLine()), no Java 8 você pode fazer o try( Stream<String> lines = Files.lines(...) ){ for( String line : (Iterable<String>) lines::iterator ) { ... } }que é difícil não odiar.
Aleksandr Dubinsky
26
@AleksandrDubinsky O problema que tenho com os fechamentos no Java 8 é que, com muita facilidade, torna o código mais complicado de ler (além de ser mais lento). Posso ver muitos desenvolvedores usando demais porque é "legal".
Peter Lawrey
155

Veja este blog:

O tamanho do buffer pode ser especificado ou o tamanho padrão pode ser usado. O padrão é grande o suficiente para a maioria dos propósitos.

// Open the file
FileInputStream fstream = new FileInputStream("textfile.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fstream));

String strLine;

//Read File Line By Line
while ((strLine = br.readLine()) != null)   {
  // Print the content on the console
  System.out.println (strLine);
}

//Close the input stream
fstream.close();
Naveed
fonte
6
Meu arquivo tem 1,5 Gig e não é possível ler o arquivo usando sua resposta!
Aboozar Rajabi (
3
@AboozarRajabi Claro que é possível. Este código pode ler qualquer arquivo de texto.
Marquês de Lorne #
10
Voto por link de baixa qualidade. Há um completamente inútil DataInputStream, e o fluxo errado é fechado. Nada de errado com o Tutorial Java, e não há necessidade de citar lixo arbitrário da Internet de terceiros como este.
Marquês de Lorne #
1
Eu abandonei os comentários, você tem 4 linhas de comentários 100% redundantes para 6 linhas de código.
Buffalo
98

Quando o Java 8 for lançado (março de 2014), você poderá usar fluxos:

try (Stream<String> lines = Files.lines(Paths.get(filename), Charset.defaultCharset())) {
  lines.forEachOrdered(line -> process(line));
}

Imprimindo todas as linhas no arquivo:

try (Stream<String> lines = Files.lines(file, Charset.defaultCharset())) {
  lines.forEachOrdered(System.out::println);
}
msayag
fonte
1
Use StandardCharsets.UTF_8, use Stream<String>por concisão e evite usá forEach()-lo, especialmente a forEachOrdered()menos que haja um motivo.
Aleksandr Dubinsky
2
Por que evitar forEach ()? É ruim?
steventrouble
Se eu forEach em vez de forEachOrdered, as linhas podem ser impressas fora de ordem, não são?
msayag
2
@steventrouble Dê uma olhada em: stackoverflow.com/questions/16635398/… Não é ruim se você passar uma referência curta de função como forEach(this::process), mas fica feio se você escrever blocos de código como lambdas dentro forEach().
Aleksandr Dubinsky
2
@msayag, Você está certo, precisa forEachOrderedexecutar em ordem. Esteja ciente de que não será possível paralelizar o fluxo nesse caso, embora eu tenha descoberto que a paralelização não é ativada, a menos que o arquivo tenha milhares de linhas.
Aleksandr Dubinsky
38

Aqui está uma amostra com tratamento completo de erros e especificação de conjunto de caracteres de suporte para pré-Java 7. Com o Java 7, você pode usar a sintaxe try-with-resources, o que torna o código mais limpo.

Se você deseja apenas o conjunto de caracteres padrão, pode pular o InputStream e usar o FileReader.

InputStream ins = null; // raw byte-stream
Reader r = null; // cooked reader
BufferedReader br = null; // buffered for readLine()
try {
    String s;
    ins = new FileInputStream("textfile.txt");
    r = new InputStreamReader(ins, "UTF-8"); // leave charset out for default
    br = new BufferedReader(r);
    while ((s = br.readLine()) != null) {
        System.out.println(s);
    }
}
catch (Exception e)
{
    System.err.println(e.getMessage()); // handle exception
}
finally {
    if (br != null) { try { br.close(); } catch(Throwable t) { /* ensure close happens */ } }
    if (r != null) { try { r.close(); } catch(Throwable t) { /* ensure close happens */ } }
    if (ins != null) { try { ins.close(); } catch(Throwable t) { /* ensure close happens */ } }
}

Aqui está a versão Groovy, com manipulação completa de erros:

File f = new File("textfile.txt");
f.withReader("UTF-8") { br ->
    br.eachLine { line ->
        println line;
    }
}
Estrela Escura
fonte
1
O que um ByteArrayInputStreamliteral alimentado por uma string tem a ver com a leitura de um arquivo de texto grande?
Marquês de Lorne #
absolutamente inútil fecha. Não há motivo para fechar todos os fluxos. Se você fechar qualquer desses fluxos automaticamente feche todas as outras correntes ...
Enerccio
21

No Java 8, você pode fazer:

try (Stream<String> lines = Files.lines (file, StandardCharsets.UTF_8))
{
    for (String line : (Iterable<String>) lines::iterator)
    {
        ;
    }
}

Algumas notas: O fluxo retornado por Files.lines(diferente da maioria dos fluxos) precisa ser fechado. Pelas razões mencionadas aqui , evito usar forEach(). O código estranho (Iterable<String>) lines::iteratorlança um fluxo em um iterável.

Aleksandr Dubinsky
fonte
Ao não implementar Iterableesse código, é definitivamente feio, embora útil. Ele precisa de um elenco (ou seja (Iterable<String>)) para funcionar.
101313 Stephan
Como posso pular a primeira linha com esse método?
qed
2
@qedfor(String line : (Iterable<String>) lines.skip(1)::iterator)
Aleksandr Dubinsky
1
Se você não está com a intenção de realmente usar Streamrecursos, usando Files.newBufferedReader, em vez de Files.linese repetidamente chamado readLine()até nullem vez de usar construções como (Iterable<String>) lines::iteratorparece ser muito mais simples ...
Holger
Por que você usa :: in lines :: iterator? O único uso conhecido por :: é empacotar o nome do método na função lambda. Em para o parâmetro de loop depois: deve ser variável, enquanto você obter algum método lambda usando ::
Trismegistos
19

O que você pode fazer é digitalizar o texto inteiro usando o Scanner e percorrer o texto linha por linha. Obviamente, você deve importar o seguinte:

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public static void readText throws FileNotFoundException {
    Scanner scan = new Scanner(new File("samplefilename.txt"));
    while(scan.hasNextLine()){
        String line = scan.nextLine();
        //Here you can manipulate the string the way you want
    }
}

O scanner basicamente digitaliza todo o texto. O loop while é usado para percorrer o texto inteiro.

A .hasNextLine()função é um booleano que retorna true se ainda houver mais linhas no texto. A .nextLine()função fornece uma linha inteira como uma String, que você pode usar da maneira que desejar. Tente System.out.println(line)imprimir o texto.

Nota: .txt é o texto do tipo de arquivo.

iskandarchacra
fonte
A declaração do método não deve parecer em vez disso: 'public static void readText lança FileNotFoundException () {' Like: 'public public void readText () lança FileNotFoundException {'
Ketcomp
Isso é consideravelmente mais lento que BufferedReader.readLine(), e ele pediu o método com melhor desempenho.
Marquês de Lorne #
18

O FileReader não permitirá que você especifique a codificação, use-a InputStreamReaderse precisar especificá-la:

try {
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "Cp1252"));         

    String line;
    while ((line = br.readLine()) != null) {
        // process the line.
    }
    br.close();

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

Se você importou esse arquivo do Windows, ele pode ter codificação ANSI (Cp1252), portanto, você deve especificar a codificação.

amor ao vivo
fonte
17

Documentei e testei 10 maneiras diferentes de ler um arquivo em Java e, em seguida, executei-os um contra o outro, fazendo-os ler em arquivos de teste de 1 KB a 1 GB. Aqui estão os três métodos de leitura de arquivos mais rápidos para ler um arquivo de teste de 1 GB.

Observe que, ao executar os testes de desempenho, não produzi nada para o console, pois isso realmente atrasaria o teste. Eu só queria testar a velocidade de leitura bruta.

1) java.nio.file.Files.readAllBytes ()

Testado em Java 7, 8, 9. Esse foi o método mais rápido. A leitura de um arquivo de 1 GB consistia em pouco menos de 1 segundo.

import java.io..File;
import java.io.IOException;
import java.nio.file.Files;

public class ReadFile_Files_ReadAllBytes {
  public static void main(String [] pArgs) throws IOException {
    String fileName = "c:\\temp\\sample-1GB.txt";
    File file = new File(fileName);

    byte [] fileBytes = Files.readAllBytes(file.toPath());
    char singleChar;
    for(byte b : fileBytes) {
      singleChar = (char) b;
      System.out.print(singleChar);
    }
  }
}

2) java.nio.file.Files.lines ()

Isso foi testado com sucesso no Java 8 e 9, mas não funcionará no Java 7 devido à falta de suporte para expressões lambda. Demorou cerca de 3,5 segundos para ler um arquivo de 1 GB, o que o colocou em segundo lugar na leitura de arquivos maiores.

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.stream.Stream;

public class ReadFile_Files_Lines {
  public static void main(String[] pArgs) throws IOException {
    String fileName = "c:\\temp\\sample-1GB.txt";
    File file = new File(fileName);

    try (Stream linesStream = Files.lines(file.toPath())) {
      linesStream.forEach(line -> {
        System.out.println(line);
      });
    }
  }
}

3) BufferedReader

Testado para funcionar em Java 7, 8, 9. Demorou cerca de 4,5 segundos para ler em um arquivo de teste de 1 GB.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ReadFile_BufferedReader_ReadLine {
  public static void main(String [] args) throws IOException {
    String fileName = "c:\\temp\\sample-1GB.txt";
    FileReader fileReader = new FileReader(fileName);

    try (BufferedReader bufferedReader = new BufferedReader(fileReader)) {
      String line;
      while((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
      }
    }
  }

Você pode encontrar as classificações completas para todos os 10 métodos de leitura de arquivos aqui .

gomisha
fonte
1
Seu guia é incrível :)
Faisal Julaidan
Você está na maior parte do tempo System.out.print/println()aqui; você também está assumindo que o arquivo caberá na memória nos dois primeiros casos.
Marquês de Lorne
Justo. Talvez eu pudesse ter tornado essas suposições mais explícitas na minha resposta.
gomisha 12/09/19
16

No Java 7:

String folderPath = "C:/folderOfMyFile";
Path path = Paths.get(folderPath, "myFileName.csv"); //or any text file eg.: txt, bat, etc
Charset charset = Charset.forName("UTF-8");

try (BufferedReader reader = Files.newBufferedReader(path , charset)) {
  while ((line = reader.readLine()) != null ) {
    //separate all csv fields into string array
    String[] lineVariables = line.split(","); 
  }
} catch (IOException e) {
    System.err.println(e);
}
Diego Duarte
fonte
9
estar ciente! usar line.split dessa maneira NÃO será analisado corretamente se um campo contiver vírgula e estiver entre aspas. Essa divisão ignorará isso e apenas separará o campo em pedaços usando a vírgula interna. Marcelo, HTH.
Marcelo Finki 13/10
CSV: arquivo de valores separados por vírgula; portanto, você não deve usar vírgula em um campo csv, a menos que queira adicionar outro campo. Assim, o uso de divisão para vírgula token no java ao analisar um arquivo CSV é perfeitamente bem e direita
Diego Duarte
7
Diego, isso não está correto. O único padrão CSV (RFC 4180) diz especificamente "Os campos que contêm quebras de linha (CRLF), aspas duplas e vírgulas devem ser colocados entre aspas duplas".
27515 serg.nechaev
2
Use StandardCharsets.UTF_8para evitar a exceção verificada noCharset.forName("UTF-8")
Aleksandr Dubinsky
2
Obrigado "Diego Duarte" pelo seu comentário; devo dizer que concordo com o que "serg.nechaev" responde. Vejo vírgulas incorporadas nos arquivos CSV 'o tempo todo'. As pessoas esperam que isso seja aceito. com todo o respeito. também um grande obrigado a "serg.nechaev". IMHO você está certo. Cheerse Everyone.
Marcelo Finki 13/03/2015
13

No Java 8, também há uma alternativa ao uso Files.lines(). Se a sua fonte de entrada não for um arquivo, mas algo mais abstrato como um Readerou um InputStream, você pode transmitir as linhas através do método BufferedReaders lines().

Por exemplo:

try (BufferedReader reader = new BufferedReader(...)) {
  reader.lines().forEach(line -> processLine(line));
}

chamará processLine()cada linha de entrada lida pelo BufferedReader.

Rüdiger Herrmann
fonte
10

Para ler um arquivo com Java 8

package com.java.java8;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

/**
 * The Class ReadLargeFile.
 *
 * @author Ankit Sood Apr 20, 2017
 */
public class ReadLargeFile {

    /**
     * The main method.
     *
     * @param args
     *            the arguments
     */
    public static void main(String[] args) {
        try {
            Stream<String> stream = Files.lines(Paths.get("C:\\Users\\System\\Desktop\\demoData.txt"));
            stream.forEach(System.out::println);
        }
        catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
Ankit Sood
fonte
9

Você pode usar a classe Scanner

Scanner sc=new Scanner(file);
sc.nextLine();
Abhilash
fonte
2
@ Tim 'Bomba horrivelmente' não é um termo que reconheço no CS. O que exatamente você quer dizer?
Marquês de Lorne
Pule para baixo, execute muito devagar, provavelmente trava. Provavelmente eu deveria evitar expressões idiomáticas neste site;) #
Tim Tim
4
@ Tim Por que faria isso?
xehpuk 22/02
2
O uso Scanneré bom, mas esta resposta não inclui o código completo para usá-lo corretamente.
Aleksandr Dubinsky
5
@ Tim Este código não irá 'bombardear horrivelmente' nem 'atolar' nem 'executar muito lentamente' nem 'provavelmente travar'. De fato, como está escrito, ele lerá apenas uma linha, quase que instantaneamente. Você pode ler megabytes por segundo dessa maneira, embora BufferedReader.readLine()seja certamente várias vezes mais rápido. Se você pensa o contrário, forneça seus motivos.
Marquês de Lorne
7

Você precisa usar o readLine()método class BufferedReader. Crie um novo objeto dessa classe e opere esse método nele e salve-o em uma string.

Javadoc BufferReader

Mestre C
fonte
Parece que o link para o BufferReaderAPI está quebrado
Sandeep
6

A maneira clara de conseguir isso,

Por exemplo:

Se você tem dataFile.txtno seu diretório atual

import java.io.*;
import java.util.Scanner;
import java.io.FileNotFoundException;

public class readByLine
{
    public readByLine() throws FileNotFoundException
    {
        Scanner linReader = new Scanner(new File("dataFile.txt"));

        while (linReader.hasNext())
        {
            String line = linReader.nextLine();
            System.out.println(line);
        }
        linReader.close();

    }

    public static void main(String args[])  throws FileNotFoundException
    {
        new readByLine();
    }
}

A saída como abaixo, insira a descrição da imagem aqui

Rajamohan S
fonte
Por que é mais claro? E não poste fotos de texto aqui. Poste o texto.
Marquês de Lorne
Você postou uma foto. É uma imagem de texto. Você poderia ter recortado e colado o texto diretamente nesta página. Ninguém disse nada sobre a publicação de programas. Postar fotos de texto é uma perda de tempo, da qual não me importo, e sua largura de banda, o que eu faço.
Marquês de Lorne #
6

Java 9:

try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
    stream.forEach(System.out::println);
}
Abdennour TOUMI
fonte
2
Eu acho que você precisaSystem.getProperty("os.name").equals("Linux")
SpringLearner
5
Não compare cordas com ==!
JonasCz - Restabelece Monica
6
Este é o exemplo canônico do Java 8, como já publicado por outros. Por que você afirma que este é "Java-9"?
21917 Holger
Memória @Holger arquivos mapeados que ele esqueceu de mencionar pode ser?
28817 Eugene
para processá-lo linha por linha, você pode tentar (Fluxo <>> stream = Files.lines (Paths.get (inputFile))) {stream.forEach ((line) -> {System.out.println (line);} ); }
thanos.a 31/10/1919
3
BufferedReader br;
FileInputStream fin;
try {
    fin = new FileInputStream(fileName);
    br = new BufferedReader(new InputStreamReader(fin));

    /*Path pathToFile = Paths.get(fileName);
    br = Files.newBufferedReader(pathToFile,StandardCharsets.US_ASCII);*/

    String line = br.readLine();
    while (line != null) {
        String[] attributes = line.split(",");
        Movie movie = createMovie(attributes);
        movies.add(movie);
        line = br.readLine();
    }
    fin.close();
    br.close();
} catch (FileNotFoundException e) {
    System.out.println("Your Message");
} catch (IOException e) {
    System.out.println("Your Message");
}

Funciona para mim. Espero que também ajude você.

Dipendra Ghatal
fonte
3

Você pode usar fluxos para fazer isso com mais precisão:

Files.lines(Paths.get("input.txt")).forEach(s -> stringBuffer.append(s);
salgado
fonte
2
Eu concordo que está realmente bem. Não obstante, as pessoas não gostam disso por causa da escolha estranha do StringBuffer (o StringBuilder geralmente é o preferido, mesmo que seja apenas um mau nome para a variável). Também porque já foi mencionado acima.
Andrii Rubtsov
2

Eu costumo fazer a rotina de leitura direta:

void readResource(InputStream source) throws IOException {
    BufferedReader stream = null;
    try {
        stream = new BufferedReader(new InputStreamReader(source));
        while (true) {
            String line = stream.readLine();
            if(line == null) {
                break;
            }
            //process line
            System.out.println(line)
        }
    } finally {
        closeQuiet(stream);
    }
}

static void closeQuiet(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (IOException ignore) {
        }
    }
}
Binkan Salaryman
fonte
0

Você pode usar este código:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ReadTextFile {

    public static void main(String[] args) throws IOException {

        try {

            File f = new File("src/com/data.txt");

            BufferedReader b = new BufferedReader(new FileReader(f));

            String readLine = "";

            System.out.println("Reading file using Buffered Reader");

            while ((readLine = b.readLine()) != null) {
                System.out.println(readLine);
            }

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

    }

}
Usman Yaqoob
fonte
Uma explicação estaria em ordem.
Peter Mortensen
0

Ao usar o pacote org.apache.commons.io , ele deu mais desempenho, especialmente no código legado que usa Java 6 e abaixo.

O Java 7 tem uma API melhor, com menos manipulação de exceções e métodos mais úteis:

LineIterator lineIterator = null;
try {
    lineIterator = FileUtils.lineIterator(new File("/home/username/m.log"), "windows-1256"); // The second parameter is optionnal
    while (lineIterator.hasNext()) {
        String currentLine = lineIterator.next();
        // Some operation
    }
}
finally {
    LineIterator.closeQuietly(lineIterator);
}

Maven

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
mohsen.nour
fonte
0

Você também pode usar o Apache Commons IO :

File file = new File("/home/user/file.txt");
try {
    List<String> lines = FileUtils.readLines(file);
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
Para Kra
fonte
3
FileUtils.readLines(file)é um método obsoleto. Além disso, o método chama IOUtils.readLines, que usa um BufferedReader e ArrayList. Este não é um método linha por linha, e certamente não seria prático para a leitura de vários GB.
precisa saber é o seguinte