Número de linhas em um arquivo em Java

213

Eu uso arquivos de dados enormes, às vezes eu só preciso saber o número de linhas nesses arquivos, geralmente eu os abro e os leio linha por linha até chegar ao final do arquivo

Eu queria saber se existe uma maneira mais inteligente de fazer isso

Marca
fonte

Respostas:

237

Esta é a versão mais rápida que encontrei até agora, cerca de 6 vezes mais rápida que a readLines. Em um arquivo de log de 150 MB, leva 0,35 segundos, contra 2,40 segundos ao usar readLines (). Apenas por diversão, o comando linux 'wc -l leva 0,15 segundos.

public static int countLinesOld(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];
        int count = 0;
        int readChars = 0;
        boolean empty = true;
        while ((readChars = is.read(c)) != -1) {
            empty = false;
            for (int i = 0; i < readChars; ++i) {
                if (c[i] == '\n') {
                    ++count;
                }
            }
        }
        return (count == 0 && !empty) ? 1 : count;
    } finally {
        is.close();
    }
}

EDIT, 9 anos e meio depois: praticamente não tenho experiência em java, mas, de qualquer forma, tentei comparar esse código com a LineNumberReadersolução abaixo, pois me incomodava o fato de ninguém fazer isso. Parece que, especialmente para arquivos grandes, minha solução é mais rápida. Embora pareça levar algumas execuções até que o otimizador faça um trabalho decente. Eu brinquei um pouco com o código e produzi uma nova versão que é consistentemente mais rápida:

public static int countLinesNew(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];

        int readChars = is.read(c);
        if (readChars == -1) {
            // bail out if nothing to read
            return 0;
        }

        // make it easy for the optimizer to tune this loop
        int count = 0;
        while (readChars == 1024) {
            for (int i=0; i<1024;) {
                if (c[i++] == '\n') {
                    ++count;
                }
            }
            readChars = is.read(c);
        }

        // count remaining characters
        while (readChars != -1) {
            System.out.println(readChars);
            for (int i=0; i<readChars; ++i) {
                if (c[i] == '\n') {
                    ++count;
                }
            }
            readChars = is.read(c);
        }

        return count == 0 ? 1 : count;
    } finally {
        is.close();
    }
}

O resultado de referência resulta em um arquivo de texto de 1,3 GB, eixo y em segundos. Eu executei 100 execuções com o mesmo arquivo e medi cada execução com System.nanoTime(). Você pode ver que countLinesOldpossui alguns valores discrepantes e countLinesNewnenhum, e embora seja um pouco mais rápido, a diferença é estatisticamente significativa. LineNumberReaderé claramente mais lento.

Gráfico de Referência

martinus
fonte
5
BufferedInputStream deve fazer o buffer para você, portanto não vejo como o uso de uma matriz de bytes intermediários [] o tornará mais rápido. É improvável que você faça muito melhor do que usar readLine () repetidamente de qualquer maneira (já que isso será otimizado pela API).
Wd
54
Você vai fechar esse InputStream quando terminar, não é?
24409 bendin
5
Se o buffer ajudasse, o BufferedInputStream armazenaria em buffer 8K por padrão. Aumente o seu byte [] para esse tamanho ou maior e você pode soltar o BufferedInputStream. por exemplo, tente 1024 * 1024 bytes.
Peter Lawrey
8
Duas coisas: (1) A definição de um terminador de linha na origem Java é um retorno de carro, um avanço de linha ou um retorno de carro seguido por um avanço de linha. Sua solução não funcionará para o CR usado como terminador de linha. É verdade que o único sistema operacional do qual posso pensar que usa o CR como terminador de linha padrão é o Mac OS anterior ao Mac OS X. (2) Sua solução assume uma codificação de caracteres como US-ASCII ou UTF-8. A contagem de linhas pode ser imprecisa para codificações como UTF-16.
Nathan Ryan
2
Código incrível ... para um arquivo de texto de 400mb, demorou apenas um segundo. Por
favor
199

Eu implementei outra solução para o problema, achei mais eficiente na contagem de linhas:

try
(
   FileReader       input = new FileReader("input.txt");
   LineNumberReader count = new LineNumberReader(input);
)
{
   while (count.skip(Long.MAX_VALUE) > 0)
   {
      // Loop just in case the file is > Long.MAX_VALUE or skip() decides to not read the entire file
   }

   result = count.getLineNumber() + 1;                                    // +1 because line index starts at 0
}
er.vikas
fonte
LineNumberReaderO lineNumbercampo de é um número inteiro ... Não será apenas agrupado para arquivos maiores que Integer.MAX_VALUE? Por que se preocupar em pular um longo tempo aqui?
EPB
1
Adicionar um à contagem está realmente incorreto. wc -lconta o número de caracteres de nova linha no arquivo. Isso funciona, pois todas as linhas são finalizadas com uma nova linha, incluindo a linha final em um arquivo. Toda linha tem um caractere de nova linha, incluindo as linhas vazias, portanto, o número de linhas nova = = número de linhas em um arquivo. Agora, a lineNumbervariável in FileNumberReadertambém representa o número de caracteres de nova linha vistos. Começa em zero, antes que qualquer nova linha seja encontrada e aumenta a cada caractere de nova linha visto. Portanto, não adicione um ao número da linha, por favor.
Alexander Torstling
1
@ PB_MLT: Embora você esteja certo de que um arquivo com uma única linha sem nova linha seria reportado como 0 linhas, é assim que wc -ltambém informa esse tipo de arquivo. Veja também stackoverflow.com/questions/729692/…
Alexander Torstling
@PB_MLT: Você obtém o problema oposto se o arquivo consistir apenas em uma nova linha. Seu algo sugerido retornaria 0 e wc -lretornaria 1. Concluí que todos os métodos têm falhas e implementei um com base em como gostaria que ele se comportasse. Veja minha outra resposta aqui.
Alexander Torstling
3
Votei
negativamente
30

A resposta aceita tem um erro de um por um para arquivos com várias linhas que não terminam em nova linha. Um arquivo de uma linha que termina sem uma nova linha retornará 1, mas um arquivo de duas linhas que termina sem uma nova linha retornará 1 também. Aqui está uma implementação da solução aceita que corrige isso. As verificações endsWithoutNewLine são um desperdício para tudo, exceto a leitura final, mas devem ser triviais em termos de tempo em comparação com a função geral.

public int count(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];
        int count = 0;
        int readChars = 0;
        boolean endsWithoutNewLine = false;
        while ((readChars = is.read(c)) != -1) {
            for (int i = 0; i < readChars; ++i) {
                if (c[i] == '\n')
                    ++count;
            }
            endsWithoutNewLine = (c[readChars - 1] != '\n');
        }
        if(endsWithoutNewLine) {
            ++count;
        } 
        return count;
    } finally {
        is.close();
    }
}
DMulligan
fonte
6
Boa pegada. No entanto, não sei por que você não editou a resposta aceita e fez uma anotação em um comentário. A maioria das pessoas não lê até aqui.
21713 Ryan
@ Ryan, não parecia certo editar uma resposta aceita com 4 anos de idade com mais de 90 votos positivos.
DMulligan
@AFinkelstein, acho que é isso que torna este site tão bom que você pode editar a resposta mais votada.
Sebastian
3
Esta solução não pode tratar retorno do carro (\ r) e o retorno seguido de um avanço de linha (\ r \ n)
Simon Brandhof - SonarSource
@ Simon Brandhof, estou confuso sobre por que um retorno de carro seria contado como outra linha? Um "\ n" é um feed de linha de retorno de carro, então quem escreve "\ r \ n" não está entendendo nada ... Além disso, ele está procurando char por char, por isso tenho certeza de que alguém usaria "\ r \ n "ainda pegaria o" \ n "e contaria a linha. De qualquer maneira, acho que ele fez o argumento muito bem. No entanto, existem muitos cenários em que essa não é uma maneira suficiente de obter uma contagem de linhas.
Nckbrz 08/04
22

Com , você pode usar fluxos:

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}
msayag
fonte
1
O código tem erros. Simples, mas muito lento ... Tente olhar para a minha resposta abaixo (acima).
Ernestas Gruodis
12

A resposta com o método count () acima me deu erros de linha se um arquivo não tivesse uma nova linha no final do arquivo - ele falhou ao contar a última linha do arquivo.

Este método funciona melhor para mim:

public int countLines(String filename) throws IOException {
    LineNumberReader reader  = new LineNumberReader(new FileReader(filename));
int cnt = 0;
String lineRead = "";
while ((lineRead = reader.readLine()) != null) {}

cnt = reader.getLineNumber(); 
reader.close();
return cnt;
}
Dave Bergert
fonte
Nesse caso, não há necessidade de usar o LineNumberReader, basta usar o BufferedReader; nesse caso, você terá flexibilidade para usar o tipo de dados longo cnt.
Syed Aqeel Ashiq
[INFO] Falha no PMD: xx: 19 Regra: EmptyWhileStmt Prioridade: 3 Evite instruções while em vazio.
Chhorn Elit 01/01
8

Sei que essa é uma pergunta antiga, mas a solução aceita não corresponde exatamente ao que eu precisava. Portanto, refinei-o para aceitar vários terminadores de linha (em vez de apenas feed de linha) e usar uma codificação de caracteres especificada (em vez da ISO-8859- n ). Tudo em um método (refatorar conforme apropriado):

public static long getLinesCount(String fileName, String encodingName) throws IOException {
    long linesCount = 0;
    File file = new File(fileName);
    FileInputStream fileIn = new FileInputStream(file);
    try {
        Charset encoding = Charset.forName(encodingName);
        Reader fileReader = new InputStreamReader(fileIn, encoding);
        int bufferSize = 4096;
        Reader reader = new BufferedReader(fileReader, bufferSize);
        char[] buffer = new char[bufferSize];
        int prevChar = -1;
        int readCount = reader.read(buffer);
        while (readCount != -1) {
            for (int i = 0; i < readCount; i++) {
                int nextChar = buffer[i];
                switch (nextChar) {
                    case '\r': {
                        // The current line is terminated by a carriage return or by a carriage return immediately followed by a line feed.
                        linesCount++;
                        break;
                    }
                    case '\n': {
                        if (prevChar == '\r') {
                            // The current line is terminated by a carriage return immediately followed by a line feed.
                            // The line has already been counted.
                        } else {
                            // The current line is terminated by a line feed.
                            linesCount++;
                        }
                        break;
                    }
                }
                prevChar = nextChar;
            }
            readCount = reader.read(buffer);
        }
        if (prevCh != -1) {
            switch (prevCh) {
                case '\r':
                case '\n': {
                    // The last line is terminated by a line terminator.
                    // The last line has already been counted.
                    break;
                }
                default: {
                    // The last line is terminated by end-of-file.
                    linesCount++;
                }
            }
        }
    } finally {
        fileIn.close();
    }
    return linesCount;
}

Essa solução é comparável em velocidade à solução aceita, cerca de 4% mais lenta em meus testes (embora os testes de temporização em Java sejam notoriamente não confiáveis).

Nathan Ryan
fonte
8

Testei os métodos acima para contar linhas e aqui estão minhas observações para diferentes métodos testados no meu sistema

Tamanho do arquivo: 1.6 Gb Métodos:

  1. Usando o Scanner : aproximadamente 35s
  2. Usando o BufferedReader : 5s aprox.
  3. Usando o Java 8 : 5s aprox.
  4. Usando LineNumberReader : 5s aprox.

Além disso, a abordagem Java8 parece bastante útil:

Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]
Anshul
fonte
5
/**
 * Count file rows.
 *
 * @param file file
 * @return file row count
 * @throws IOException
 */
public static long getLineCount(File file) throws IOException {

    try (Stream<String> lines = Files.lines(file.toPath())) {
        return lines.count();
    }
}

Testado em JDK8_u31. Mas, na verdade, o desempenho é lento em comparação com este método:

/**
 * Count file rows.
 *
 * @param file file
 * @return file row count
 * @throws IOException
 */
public static long getLineCount(File file) throws IOException {

    try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file), 1024)) {

        byte[] c = new byte[1024];
        boolean empty = true,
                lastEmpty = false;
        long count = 0;
        int read;
        while ((read = is.read(c)) != -1) {
            for (int i = 0; i < read; i++) {
                if (c[i] == '\n') {
                    count++;
                    lastEmpty = true;
                } else if (lastEmpty) {
                    lastEmpty = false;
                }
            }
            empty = false;
        }

        if (!empty) {
            if (count == 0) {
                count = 1;
            } else if (!lastEmpty) {
                count++;
            }
        }

        return count;
    }
}

Testado e muito rápido.

Ernestas Gruodis
fonte
Isto não está correto. Fiz alguns experimentos com seu código e o método é sempre mais lento. Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1E o número de linhas também está errado também
aw-think
Eu testei em uma máquina de 32 bits. Talvez em 64 bits haja resultados diferentes. E foi a diferença 10 vezes ou mais, pelo que me lembro. Você poderia postar o texto para contar a linha em algum lugar? Você pode usar o Notepad2 para ver as quebras de linha por conveniência.
Ernestas Gruodis 27/02
Essa poderia ser a diferença.
aw-think
Se você se preocupa com o desempenho, não deve usar a BufferedInputStreamquando quiser ler o seu próprio buffer. Além disso, mesmo que seu método possa ter uma pequena vantagem de desempenho, ele perde flexibilidade, pois não suporta mais \rterminadores de linha única (MacOS antigo) e não suporta todas as codificações.
Holger
4

Uma maneira direta de usar o Scanner

static void lineCounter (String path) throws IOException {

        int lineCount = 0, commentsCount = 0;

        Scanner input = new Scanner(new File(path));
        while (input.hasNextLine()) {
            String data = input.nextLine();

            if (data.startsWith("//")) commentsCount++;

            lineCount++;
        }

        System.out.println("Line Count: " + lineCount + "\t Comments Count: " + commentsCount);
    }
Terry Bu
fonte
3

Concluí que wc -l: o método de contar novas linhas é bom, mas retorna resultados não intuitivos em arquivos nos quais a última linha não termina com uma nova linha.

E a solução @ er.vikas baseada em LineNumberReader, mas a adição de uma na contagem de linhas retornou resultados não intuitivos em arquivos nos quais a última linha termina com nova linha.

Portanto, fiz um algo que lida com o seguinte:

@Test
public void empty() throws IOException {
    assertEquals(0, count(""));
}

@Test
public void singleNewline() throws IOException {
    assertEquals(1, count("\n"));
}

@Test
public void dataWithoutNewline() throws IOException {
    assertEquals(1, count("one"));
}

@Test
public void oneCompleteLine() throws IOException {
    assertEquals(1, count("one\n"));
}

@Test
public void twoCompleteLines() throws IOException {
    assertEquals(2, count("one\ntwo\n"));
}

@Test
public void twoLinesWithoutNewlineAtEnd() throws IOException {
    assertEquals(2, count("one\ntwo"));
}

@Test
public void aFewLines() throws IOException {
    assertEquals(5, count("one\ntwo\nthree\nfour\nfive\n"));
}

E fica assim:

static long countLines(InputStream is) throws IOException {
    try(LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is))) {
        char[] buf = new char[8192];
        int n, previousN = -1;
        //Read will return at least one byte, no need to buffer more
        while((n = lnr.read(buf)) != -1) {
            previousN = n;
        }
        int ln = lnr.getLineNumber();
        if (previousN == -1) {
            //No data read at all, i.e file was empty
            return 0;
        } else {
            char lastChar = buf[previousN - 1];
            if (lastChar == '\n' || lastChar == '\r') {
                //Ending with newline, deduct one
                return ln;
            }
        }
        //normal case, return line number + 1
        return ln + 1;
    }
}

Se você deseja resultados intuitivos, pode usar isso. Se você deseja apenas wc -lcompatibilidade, use a solução @ er.vikas, mas não adicione uma ao resultado e tente pular novamente:

try(LineNumberReader lnr = new LineNumberReader(new FileReader(new File("File1")))) {
    while(lnr.skip(Long.MAX_VALUE) > 0){};
    return lnr.getLineNumber();
}
Alexander Torstling
fonte
2

Que tal usar a classe Process a partir do código Java? E então lendo a saída do comando.

Process p = Runtime.getRuntime().exec("wc -l " + yourfilename);
p.waitFor();

BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
int lineCount = 0;
while ((line = b.readLine()) != null) {
    System.out.println(line);
    lineCount = Integer.parseInt(line);
}

Precisa tentar embora. Irá publicar os resultados.

Sunil Shevante
fonte
1

Se você não possui nenhuma estrutura de índice, não contorna a leitura do arquivo completo. Mas você pode otimizá-lo, evitando lê-lo linha por linha e usar um regex para corresponder a todos os terminadores de linha.

David Schmitt
fonte
Parece uma boa idéia. Alguém tentou e tem um regexp para isso?
willcodejavaforfood
1
Duvido que seja uma boa idéia: ele precisará ler o arquivo inteiro de uma vez (o martinus evita isso) e as expressões regulares são um exagero (e mais lento) para esse uso (pesquisa simples de caracteres fixos).
PhiLho
@ will: e quanto a \ \ n /? @ PhiLo: Os Executores Regex são máquinas de desempenho altamente ajustadas. Exceto a advertência de ler tudo na memória, não acho que uma implementação manual possa ser mais rápida.
David Schmitt
1

Esta solução divertida funciona realmente muito bem!

public static int countLines(File input) throws IOException {
    try (InputStream is = new FileInputStream(input)) {
        int count = 1;
        for (int aChar = 0; aChar != -1;aChar = is.read())
            count += aChar == '\n' ? 1 : 0;
        return count;
    }
}
Ilya Gazman
fonte
0

Em sistemas baseados em Unix, use o wccomando na linha de comandos.

Peter Hilton
fonte
@IainmH, sua segunda sugestão conta apenas o número de entradas no diretório atual. Não é o que foi planejado? (ou solicitado pelo OP)
The Archetypal Paul
@IainMH: é o que o wc faz de qualquer maneira (lendo o arquivo, contando o final da linha).
PhiLho
@ PhiLho Você teria que usar a opção -l para contar as linhas. (Você não faz? - já faz um tempo) #
1025 Iain Holder
@ Paul - é claro que você está 100% certo. Minha única defesa é que eu postei isso antes do meu café. Eu sou tão afiada quanto um botão agora. : D
Iain Holder
0

A única maneira de saber quantas linhas existem no arquivo é contá-las. Obviamente, você pode criar uma métrica a partir dos seus dados, fornecendo um comprimento médio de uma linha e, em seguida, obter o tamanho do arquivo e dividi-lo com o valor médio. comprimento, mas isso não será preciso.

Esko
fonte
1
Voto negativo interessante, não importa qual ferramenta de linha de comando você esteja usando, todos fazem a mesma coisa, apenas internamente. Não existe uma maneira mágica de descobrir o número de linhas, elas devem ser contadas à mão. Claro que pode ser salvo como metadados, mas isso é um todo outra história ...
Esko
0

Melhor código otimizado para arquivos de várias linhas sem caracteres de nova linha ('\ n') no EOF.

/**
 * 
 * @param filename
 * @return
 * @throws IOException
 */
public static int countLines(String filename) throws IOException {
    int count = 0;
    boolean empty = true;
    FileInputStream fis = null;
    InputStream is = null;
    try {
        fis = new FileInputStream(filename);
        is = new BufferedInputStream(fis);
        byte[] c = new byte[1024];
        int readChars = 0;
        boolean isLine = false;
        while ((readChars = is.read(c)) != -1) {
            empty = false;
            for (int i = 0; i < readChars; ++i) {
                if ( c[i] == '\n' ) {
                    isLine = false;
                    ++count;
                }else if(!isLine && c[i] != '\n' && c[i] != '\r'){   //Case to handle line count where no New Line character present at EOF
                    isLine = true;
                }
            }
        }
        if(isLine){
            ++count;
        }
    }catch(IOException e){
        e.printStackTrace();
    }finally {
        if(is != null){
            is.close();    
        }
        if(fis != null){
            fis.close();    
        }
    }
    LOG.info("count: "+count);
    return (count == 0 && !empty) ? 1 : count;
}
Pramod Yadav
fonte
0

Scanner com regex:

public int getLineCount() {
    Scanner fileScanner = null;
    int lineCount = 0;
    Pattern lineEndPattern = Pattern.compile("(?m)$");  
    try {
        fileScanner = new Scanner(new File(filename)).useDelimiter(lineEndPattern);
        while (fileScanner.hasNext()) {
            fileScanner.next();
            ++lineCount;
        }   
    }catch(FileNotFoundException e) {
        e.printStackTrace();
        return lineCount;
    }
    fileScanner.close();
    return lineCount;
}

Ainda não cronometrei.

user176692
fonte
-2

se você usar isso

public int countLines(String filename) throws IOException {
    LineNumberReader reader  = new LineNumberReader(new FileReader(filename));
    int cnt = 0;
    String lineRead = "";
    while ((lineRead = reader.readLine()) != null) {}

    cnt = reader.getLineNumber(); 
    reader.close();
    return cnt;
}

você não pode executar linhas grandes, gosta de 100 mil linhas, porque o retorno de reader.getLineNumber é int. você precisa de um tipo longo de dados para processar o máximo de linhas.

Faisal
fonte
14
Um intpode armazenar valores de até aproximadamente 2 bilhões. Se você estiver carregando um arquivo com mais de 2 bilhões de linhas, há um problema de estouro. Dito isto, se você estiver carregando um arquivo de texto não indexado com mais de dois bilhões de linhas, provavelmente terá outros problemas.
Adam Norberg