Determinar o número de linhas em um arquivo de texto

209

Existe uma maneira fácil de determinar programaticamente o número de linhas em um arquivo de texto?

TK.
fonte

Respostas:

396

Edição seriamente tardia: se você estiver usando o .NET 4.0 ou posterior

A Fileclasse possui um novo ReadLinesmétodo que enumera preguiçosamente as linhas, em vez de lê-las avidamente em uma matriz semelhante ReadAllLines. Portanto, agora você pode ter eficiência e concisão com:

var lineCount = File.ReadLines(@"C:\file.txt").Count();

Resposta original

Se você não está muito preocupado com a eficiência, basta escrever:

var lineCount = File.ReadAllLines(@"C:\file.txt").Length;

Para um método mais eficiente, você pode fazer:

var lineCount = 0;
using (var reader = File.OpenText(@"C:\file.txt"))
{
    while (reader.ReadLine() != null)
    {
        lineCount++;
    }
}

Edit: Em resposta a perguntas sobre eficiência

A razão pela qual eu disse que o segundo era mais eficiente foi em relação ao uso de memória, não necessariamente à velocidade. O primeiro carrega todo o conteúdo do arquivo em uma matriz, o que significa que ele deve alocar pelo menos tanta memória quanto o tamanho do arquivo. O segundo apenas faz um loop de uma linha de cada vez, para nunca precisar alocar mais do que uma linha de memória por vez. Isso não é importante para arquivos pequenos, mas para arquivos maiores pode ser um problema (se você tentar encontrar o número de linhas em um arquivo de 4 GB em um sistema de 32 bits, por exemplo, onde simplesmente não há o suficiente espaço de endereço no modo de usuário para alocar uma matriz desse tamanho).

Em termos de velocidade, eu não esperaria que houvesse muito nele. É possível que o ReadAllLines tenha algumas otimizações internas, mas, por outro lado, pode ser necessário alocar um grande pedaço de memória. Eu acho que o ReadAllLines pode ser mais rápido para arquivos pequenos, mas significativamente mais lento para arquivos grandes; embora a única maneira de saber seja medi-lo com um cronômetro ou um criador de perfil de código.

Greg Beech
fonte
2
Nota pequena: como String é um tipo de referência, a matriz teria o tamanho do número de linhas x o tamanho de um ponteiro, mas você está certo de que ainda precisa armazenar o texto, cada linha como um único objeto String.
Mike Dimmick
15
FYI: Para fazer ReadLines().Count()isso, você precisará adicionar um using System.Linqàs suas inclusões. Parecia bastante intuitivo exigir essa adição, e é por isso que eu a menciono. Se você estiver usando o Visual Studio, é provável que essa adição seja feita automaticamente.
Nucleon
2
Eu testei as duas abordagens: "File.ReadLines.Count ()" v / s "reader.ReadLine ()" e "reader.ReadLine ()" é um pouco mais rápido, mas é mais rápido por uma margem muito pequena. "ReadAllLines" é mais flexível, o que leva o dobro do tempo e consome muita memória). Isso ocorre porque "File.ReadLines.Count ()" e "reader.ReadLine ()" são um enumerador que lê o arquivo linha por linha e não carrega o arquivo inteiro na memória, lê-o novamente na RAM.
Yogee
9
Sim, ninguém nunca trabalha com arquivos de 4 GB ou mais. Certamente nunca lidamos com arquivos de log tão grandes. Oh espere.
Greg Beech
2
Se você quiser ver o interior de File.ReadLines (), clique aqui: System.IO.File.cs Quando você drill down através das sobrecargas que você leva aqui: ReadLinesIterator.cs
Steve Kinyon
12

O mais fácil:

int lines = File.ReadAllLines("myfile").Length;
leppie
fonte
8

Isso usaria menos memória, mas provavelmente levaria mais tempo

int count = 0;
string line;
TextReader reader = new StreamReader("file.txt");
while ((line = reader.ReadLine()) != null)
{
  count++;
}
reader.Close();
benPearce
fonte
5

Se por fácil você quer dizer linhas de código fáceis de decifrar, mas por acaso ineficientes?

string[] lines = System.IO.File.RealAllLines($filename);
int cnt = lines.Count();

Essa é provavelmente a maneira mais rápida de saber quantas linhas.

Você também pode fazer (dependendo se você está armazenando o buffer)

#for large files
while (...reads into buffer){
string[] lines = Regex.Split(buffer,System.Enviorment.NewLine);
}

Existem outras maneiras, mas uma das opções acima é provavelmente a que você irá usar.

user8456
fonte
3
Eu argumento que este método é muito ineficiente; porque você está lendo o arquivo inteiro na memória e em uma matriz de seqüências de caracteres. Você não precisa copiar o buffer ao usar o ReadLine. Veja a resposta de @GregBeech. Desculpe chover no seu desfile.
Mike Christian
2

Você pode ler rapidamente e incrementar um contador, basta usar um loop para incrementar, sem fazer nada com o texto.

Mitchel Sellers
fonte
3
Isso deve ser um comentário, não uma resposta.
IamBatman #
2

A leitura de um arquivo por si só leva algum tempo, a coleta de lixo é outro problema, à medida que você lê o arquivo inteiro apenas para contar os caracteres da nova linha,

Em algum momento, alguém terá que ler os caracteres no arquivo, independentemente se esse for o framework ou se for o seu código. Isso significa que você precisa abrir o arquivo e lê-lo na memória, se o arquivo for grande, isso poderá ser um problema, pois a memória precisa ser coletada como lixo.

Nima Ara fez uma boa análise que você pode levar em consideração

Aqui está a solução proposta, que lê 4 caracteres por vez, conta o caractere de alimentação de linha e reutiliza o mesmo endereço de memória novamente para a próxima comparação de caracteres.

private const char CR = '\r';  
private const char LF = '\n';  
private const char NULL = (char)0;

public static long CountLinesMaybe(Stream stream)  
{
    Ensure.NotNull(stream, nameof(stream));

    var lineCount = 0L;

    var byteBuffer = new byte[1024 * 1024];
    const int BytesAtTheTime = 4;
    var detectedEOL = NULL;
    var currentChar = NULL;

    int bytesRead;
    while ((bytesRead = stream.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
    {
        var i = 0;
        for (; i <= bytesRead - BytesAtTheTime; i += BytesAtTheTime)
        {
            currentChar = (char)byteBuffer[i];

            if (detectedEOL != NULL)
            {
                if (currentChar == detectedEOL) { lineCount++; }

                currentChar = (char)byteBuffer[i + 1];
                if (currentChar == detectedEOL) { lineCount++; }

                currentChar = (char)byteBuffer[i + 2];
                if (currentChar == detectedEOL) { lineCount++; }

                currentChar = (char)byteBuffer[i + 3];
                if (currentChar == detectedEOL) { lineCount++; }
            }
            else
            {
                if (currentChar == LF || currentChar == CR)
                {
                    detectedEOL = currentChar;
                    lineCount++;
                }
                i -= BytesAtTheTime - 1;
            }
        }

        for (; i < bytesRead; i++)
        {
            currentChar = (char)byteBuffer[i];

            if (detectedEOL != NULL)
            {
                if (currentChar == detectedEOL) { lineCount++; }
            }
            else
            {
                if (currentChar == LF || currentChar == CR)
                {
                    detectedEOL = currentChar;
                    lineCount++;
                }
            }
        }
    }

    if (currentChar != LF && currentChar != CR && currentChar != NULL)
    {
        lineCount++;
    }
    return lineCount;
}

Acima, você pode ver que uma linha é lida com um caractere de cada vez, além da estrutura subjacente, pois você precisa ler todos os caracteres para ver o feed da linha.

Se você criar um perfil como o bay Nima concluído, verá que essa é uma maneira bastante rápida e eficiente de fazer isso.

Walter Vehoeven
fonte
1

conte os retornos de carro / avanços de linha. Eu acredito em unicode eles ainda são 0x000D e 0x000A, respectivamente. Dessa forma, você pode ser tão eficiente ou ineficiente quanto quiser e decidir se precisa lidar com os dois personagens ou não.

geocoin
fonte
1

Uma opção viável, e que eu pessoalmente usei, seria adicionar seu próprio cabeçalho à primeira linha do arquivo. Eu fiz isso para um formato de modelo personalizado para o meu jogo. Basicamente, eu tenho uma ferramenta que otimiza meus arquivos .obj, livrando-se da porcaria de que não preciso, os converte em um layout melhor e depois grava o número total de linhas, faces, normais, vértices e UVs de textura em a primeira linha. Esses dados são usados ​​por vários buffers de matriz quando o modelo é carregado.

Isso também é útil porque você só precisa percorrer o arquivo uma vez para carregá-lo, em vez de contar uma vez as linhas e novamente ler os dados nos buffers criados.

Krythic
fonte
-1
try {
    string path = args[0];
    FileStream fh = new FileStream(path, FileMode.Open, FileAccess.Read);
    int i;
    string s = "";
    while ((i = fh.ReadByte()) != -1)
        s = s + (char)i;

    //its for reading number of paragraphs
    int count = 0;
    for (int j = 0; j < s.Length - 1; j++) {
            if (s.Substring(j, 1) == "\n")
                count++;
    }

    Console.WriteLine("The total searches were :" + count);

    fh.Close();

} catch(Exception ex) {
    Console.WriteLine(ex.Message);
}         
Muhammad Usman -kai hiwatari
fonte
4
-1: será LENTO, consumirá muita memória e dificultará o GC!
ya23
-2

Você pode iniciar o executável " wc .exe" (fornecido com o UnixUtils e não precisa de instalação) executado como um processo externo. Ele suporta diferentes métodos de contagem de linhas (como unix vs mac vs windows).

Sklivvz
fonte
Não há como isso ser rápido o suficiente para ser útil. A sobrecarga de apenas chamar o executável seria duas vezes maior (exagero óbvio é óbvio) que um único loop de incremento.
Krythic 20/05