Określ liczbę wierszy w pliku tekstowym


209

Czy istnieje prosty sposób programowo określić liczbę wierszy w pliku tekstowym?

Odpowiedzi:


396

Poważnie spóźniona edycja: jeśli używasz .NET 4.0 lub nowszej wersji

FileKlasa ma nową ReadLinesmetodę, która leniwie wylicza linie zamiast łapczywie czytać je wszystkie do tablicy podobnego ReadAllLines. Teraz możesz zyskać zarówno skuteczność, jak i zwięzłość dzięki:

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

Oryginalna odpowiedź

Jeśli nie przejmujesz się wydajnością, możesz po prostu napisać:

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

Aby uzyskać bardziej wydajną metodę, możesz:

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

Edycja: w odpowiedzi na pytania dotyczące wydajności

Powodem, dla którego powiedziałem, że drugi był bardziej wydajny, było zużycie pamięci, niekoniecznie szybkość. Pierwszy ładuje całą zawartość pliku do tablicy, co oznacza, że ​​musi on przydzielić co najmniej tyle pamięci, ile rozmiar pliku. Drugi zapętla tylko jedną linię na raz, więc nigdy nie musi przydzielać więcej niż jednej pamięci na raz. Nie jest to tak ważne w przypadku małych plików, ale w przypadku większych plików może to stanowić problem (jeśli spróbujesz znaleźć liczbę wierszy w pliku 4 GB w systemie 32-bitowym, na przykład tam, gdzie po prostu nie wystarczy przestrzeń adresowa trybu użytkownika do przydzielenia tak dużej tablicy).

Pod względem prędkości nie spodziewałbym się, że będzie w tym wiele. Możliwe, że ReadAllLines ma pewne wewnętrzne optymalizacje, ale z drugiej strony może być konieczne przydzielenie ogromnej ilości pamięci. Sądzę, że ReadAllLines może być szybszy dla małych plików, ale znacznie wolniejszy dla dużych plików; chociaż jedynym sposobem na określenie tego byłoby zmierzenie go za pomocą stopera lub profilera kodu.


2
Mała uwaga: ponieważ String jest typem referencyjnym, tablica miałaby rozmiar liczby linii x rozmiar wskaźnika, ale masz rację, że nadal musi przechowywać tekst, każda linia jako pojedynczy obiekt String.
Mike Dimmick

16
FYI: Aby to zrobić ReadLines().Count(), musisz dodać a using System.Linqdo swoich załączników. Wymaganie tego dodatku wydawało się dość nieintuicyjne, dlatego o tym wspominam. Jeśli korzystasz z programu Visual Studio, prawdopodobnie dodawanie jest wykonywane automatycznie.
Nucleon

2
Przetestowałem oba podejścia: „File.ReadLines.Count ()„ v / s ”reader.ReadLine ()” i „reader.ReadLine ()” jest nieco szybszy, ale jest bardzo niewielki. „ReadAllLines” jest luźniejsze, co zajmuje dwukrotnie więcej czasu i zjada dużo pamięci). Wynika to z faktu, że „File.ReadLines.Count ()” i „reader.ReadLine ()” to moduł wyliczający, który odczytuje plik wiersz po wierszu i nie ładuje całego pliku do pamięci, odczytuje go ponownie w pamięci RAM.
Yogee

9
Tak, nikt nigdy nie pracuje z plikami 4GB +. Z pewnością nigdy nie mamy do czynienia z tak dużymi plikami dziennika. Zaczekaj.
Greg Beech

2
Jeśli chcesz zobaczyć wnętrze File.ReadLines (), przejdź tutaj: System.IO.File.cs Po przejściu przez przeciążenia zabiera cię tutaj: ReadLinesIterator.cs
Steve Kinyon

12

Najłatwiejszym:

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

8

Zużyłoby to mniej pamięci, ale prawdopodobnie potrwa dłużej

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

5

Jeśli przez „easy” rozumiesz linie kodu, które można łatwo odszyfrować, ale przypadkowo są nieefektywne?

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

To prawdopodobnie najszybszy sposób, aby dowiedzieć się, ile linii.

Możesz także zrobić (w zależności od tego, czy buforujesz)

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

Istnieje wiele innych sposobów, ale prawdopodobnie jednym z powyższych jest to, co wybierzesz.


3
Twierdzę, że ta metoda jest bardzo nieefektywna; ponieważ czytasz cały plik do pamięci i do tablicy ciągów, nie mniej. Podczas korzystania z ReadLine nie musisz kopiować bufora. Zobacz odpowiedź z @GregBeech. Przepraszam, że padam na twoją paradę.
Mike Christian

2

Możesz szybko go odczytać i zwiększyć licznik, po prostu użyj pętli, aby zwiększyć, nie robiąc nic z tekstem.


3
To powinien być komentarz, a nie odpowiedź.
IamBatman

2

Wczytanie pliku samo w sobie zajmuje trochę czasu, zbieranie śmieci jest kolejnym problemem, ponieważ czytasz cały plik, aby policzyć znaki nowego wiersza,

W pewnym momencie ktoś będzie musiał odczytać znaki w pliku, niezależnie od tego, czy jest to framework, czy też jest to twój kod. Oznacza to, że musisz otworzyć plik i wczytać go do pamięci, jeśli plik jest duży, może to stanowić problem, ponieważ pamięć musi zostać wyrzucona.

Nima Ara dokonała miłej analizy, którą możesz wziąć pod uwagę

Oto proponowane rozwiązanie, ponieważ odczytuje 4 znaki na raz, liczy znak nowego wiersza i ponownie używa tego samego adresu pamięci ponownie do następnego porównania znaków.

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;
}

Powyżej widać, że linia jest odczytywana po jednym znaku na raz przez podstawową strukturę, ponieważ musisz odczytać wszystkie znaki, aby zobaczyć linię.

Jeśli profilujesz to jako zrobione bay Nima, zobaczysz, że jest to dość szybki i wydajny sposób na zrobienie tego.


1

policz powroty karetki / linie. Wierzę, że w Unicode wciąż są odpowiednio 0x000D i 0x000A. w ten sposób możesz być tak wydajny lub tak mało wydajny, jak chcesz i zdecydować, czy masz do czynienia z obiema postaciami, czy nie


1

Opcją realną i taką, z której osobiście korzystałem, byłoby dodanie własnego nagłówka do pierwszego wiersza pliku. Zrobiłem to dla niestandardowego formatu modelu dla mojej gry. Zasadniczo mam narzędzie, które optymalizuje moje pliki .obj, pozbywając się bzdur, których nie potrzebuję, konwertuje je na lepszy układ, a następnie zapisuje całkowitą liczbę linii, twarzy, normalnych, wierzchołków i UV tekstury na pierwsza linia. Dane te są następnie wykorzystywane przez różne bufory tablicowe podczas ładowania modelu.

Jest to również przydatne, ponieważ wystarczy raz zapętlić plik, aby go załadować, zamiast raz, aby policzyć linie, i ponownie, aby odczytać dane do utworzonych buforów.


-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);
}         

5
-1: będzie WOLNY, zużywa dużo pamięci i daje GC ciężki czas!
23

-2

Możesz uruchomić plik wykonywalny „ wc .exe” (dostarczany z UnixUtils i nie wymaga instalacji) uruchamiany jako proces zewnętrzny. Obsługuje różne metody zliczania linii (takie jak Unix vs Mac vs Windows).


1
Nie ma mowy, że byłoby to wystarczająco szybkie, aby było przydatne. Narzut związany z samym wywołaniem pliku wykonywalnego byłby dwa razy większy (oczywista przesada jest oczywista) niż pojedyncza pętla zwiększająca.
Krythic
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.