Java'daki bir dosyadaki satır sayısı


213

Çok büyük veri dosyaları kullanıyorum, bazen sadece bu dosyalardaki satır sayısını bilmem gerekiyor, genellikle onları açıyorum ve dosyanın sonuna kadar satır satır okuyorum

Bunu yapmanın daha akıllı bir yolu olup olmadığını merak ediyordum

Yanıtlar:


237

Bu şimdiye kadar bulduğum en hızlı sürüm, readLines'den yaklaşık 6 kat daha hızlı. 150MB günlük dosyasında, readLines () kullanılırken 0,35 saniye, 2,40 saniye sürer. Sadece eğlence için, linux 'wc -l komutu 0.15 saniye sürer.

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 1/2 yıl sonra: Ben hemen hemen hiç java deneyimim yok, ama LineNumberReaderyine de kimsenin bunu yapma beni rahatsız beri aşağıdaki kodu bu çözümle karşılaştırmak için çalıştım . Özellikle büyük dosyalar için benim çözümüm daha hızlı görünüyor. Her ne kadar optimizer iyi bir iş yapana kadar birkaç çalışma yapmak gibi görünüyor. Kod ile biraz oynadım ve sürekli olarak en hızlı olan yeni bir sürüm ürettim:

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

Karşılaştırma, saniye cinsinden y ekseni olan 1,3 GB metin dosyası için yeniden sonuçlanır. Aynı dosya ile 100 çalışır gerçekleştirdim ve her çalıştırmayı ölçtüm System.nanoTime(). Bunun countLinesOldbirkaç aykırı değeri countLinesNewvardır ve hiçbiri yoktur ve sadece biraz daha hızlı olsa da, fark istatistiksel olarak önemlidir. LineNumberReaderaçıkça daha yavaştır.

Karşılaştırma Grafiği


5
BufferedInputStream arabellekleme sizin için yapıyor olmalı, bu yüzden bir ara bayt [] dizisi kullanarak daha hızlı nasıl yapacağını görmüyorum. Yine de readLine () yöntemini tekrar tekrar kullanmaktan çok daha iyi bir işlem yapmanız mümkün değildir (çünkü bu, API tarafından optimize edilecektir).
wds

54
İşiniz bittiğinde bu InputStream'i kapatacaksınız, değil mi?
bendin

5
Arabelleğe alma yardımcı olduysa, BufferedInputStream varsayılan olarak 8K arabelleğe alır. Baytınızı [] bu boyuta veya daha büyük bir boyuta artırın ve BufferedInputStream öğesini bırakabilirsiniz. örneğin 1024 * 1024 bayt deneyin.
Peter Lawrey

8
İki şey: (1) Java kaynağındaki satır sonlandırıcı tanımı, satır başı, satır beslemesi veya satır başı ve ardından satır beslemesidir. Çözümünüz, hat sonlandırıcı olarak kullanılan CR için çalışmaz. Kabul edildi, varsayılan hat sonlandırıcısı olarak CR'yi kullanabileceğini düşündüğüm tek işletim sistemi Mac OS X'ten önce Mac OS. (2) Çözümünüz US-ASCII veya UTF-8 gibi bir karakter kodlaması olduğunu varsayar. Satır sayısı UTF-16 gibi kodlamalar için yanlış olabilir.
Nathan Ryan

2
Harika kod ... 400mb metin dosyası için sadece bir saniye sürdü. Teşekkürler @martinus
user3181500

199

Soruna başka bir çözüm uyguladım, satırları saymada daha verimli buldum:

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
}

LineNumberReaderadlı kullanıcının lineNumberalanı bir tamsayıdır ... Yalnızca Integer.MAX_VALUE değerinden daha uzun dosyalar için kaydırılmaz mı? Neden burada uzun bir süre zahmete giriyorsun?
epb

1
Sayıma bir tane eklemek aslında yanlış. wc -ldosyadaki yeni satır karakterlerini sayar. Bu, her satır bir dosyadaki son satır da dahil olmak üzere bir satırsonu ile sonlandırıldığından işe yarar. Her satırda boş satırlar da dahil olmak üzere yeni satır karakteri vardır, bu nedenle satır başı karakter sayısı == dosyadaki satır sayısıdır. Şimdi, lineNumberdeğişken FileNumberReaderaynı zamanda görülen yeni satır karakterlerini de temsil etmektedir. Herhangi bir yeni satır bulunmadan önce sıfırdan başlar ve görülen her yeni satır karakteri ile artar. Bu yüzden lütfen satır numarasına bir tane eklemeyin.
Alexander Torstling

1
@PB_MLT: Yeni satır içermeyen tek satırlı bir dosyanın 0 satır olarak bildirilmesinde haklı olmanıza rağmen, wc -lbu tür dosyaları da bu şekilde bildirir. Ayrıca bkz. Stackoverflow.com/questions/729692/…
Alexander Torstling

@PB_MLT: Dosya yalnızca bir satırsonu içeriyorsa, tam tersi bir sorunla karşılaşırsınız. Önerilen algo 0 döndürür ve wc -l1 döndürür. Tüm yöntemlerin kusurları olduğu sonucuna vardım ve nasıl davranmasını istediğime dayanarak bir tane uyguladım, burada diğer cevabımı görün.
Alexander Torstling

3
Bu yanıta oy verdim, çünkü hiçbiriniz bunu kıyaslamıyor gibi görünüyor
amstegraf

30

Kabul edilen yanıt, satırsonu ile bitmeyen çok satırlı dosyalar için bire bir kapalı hataya sahiptir. Yeni satır olmadan biten bir satır dosyası 1 döndürür, ancak yeni satır olmadan biten iki satır dosyası da 1 döndürür. İşte bunu düzelten kabul edilen çözümün bir uygulaması. EndWithoutNewLine kontrolleri son okuma dışında her şey için israftır, ancak genel fonksiyona kıyasla akıllıca bir zaman olmalıdır.

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

6
İyi yakalama. Neden sadece kabul edilen cevabı düzenlemediğinizden ve yorumda not aldığınızdan emin değilsiniz. Çoğu insan şimdiye kadar okumaz.
Ryan

@Ryan, sadece 4 yaşında kabul edilmiş bir yanıtı 90+ upvotes ile düzenlemek doğru gelmedi.
DMulligan

@AFinkelstein, bu siteyi bu kadar harika kılan şey , en çok oy alan cevabı düzenleyebileceğinizi hissediyorum .
Sebastian

3
Bu çözüm satır başı (\ r) ve satır başı ve ardından satır besleme (\ r \ n)
işlemez

@Simon Brandhof, bir satırbaşı neden başka bir satır olarak sayılır diye kafam karıştı? Bir "\ n" bir satırbaşı satır besleme, bu yüzden kim "\ r \ n" yazıyor bir şey anlamıyor ... Artı o char char arıyor, bu yüzden birisi kullanmak için eminim " \ n "hala" \ n "yi yakalar ve satırı sayar. Her iki şekilde de bu noktayı iyi yaptığını düşünüyorum. Ancak, bunların bir satır sayısı elde etmek için yeterli bir yol olmadığı birçok senaryo vardır.
nckbrz

22

İle , akışları kullanabilirsiniz:

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}

1
Kodda hatalar var. Basit, ama çok yavaş ... Aşağıdaki cevaba bakmaya çalışın (yukarıda).
Ernestas Gruodis

12

Yukarıdaki yöntem count () yönteminin yanıtı, dosyanın sonunda bir satırsonu yoksa bana satır yanlış sayımları verdi - dosyadaki son satırı sayamadı.

Bu yöntem benim için daha iyi çalışıyor:

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

Bu durumda, LineNumberReader'ı kullanmaya gerek yoktur, sadece BufferedReader'ı kullanın, bu durumda uzun veri türü kullanma esnekliğine sahip olursunuz cnt.
Syed Aqeel Ashiq

[INFO] PMD Hatası: xx: 19 Kural: EmptyWhileStmt Önceliği: 3 while ifadelerini boş bırakmaktan kaçının.
Chhorn Elit

8

Bunun eski bir soru olduğunu biliyorum, ancak kabul edilen çözüm, yapmam gerekenle tam olarak eşleşmedi. Bu nedenle, çeşitli satır sonlandırıcılarını (yalnızca satır besleme yerine) kabul etmeyi ve belirli bir karakter kodlaması (ISO-8859- n yerine) kullanmayı iyileştirdim . Hepsi tek bir yöntemde (uygun şekilde refactor):

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

Bu çözüm, kabul edilen çözümle karşılaştırılabilir hızdadır, testlerimde yaklaşık% 4 daha yavaştır (ancak Java'daki zamanlama testleri çok güvenilir değildir).


8

Çizgileri saymak için yukarıdaki yöntemleri test ettim ve burada sistemimde test edilen Farklı yöntemler için gözlemlerim var

Dosya Boyutu: 1.6 Gb Yöntemleri:

  1. Tarayıcı Kullanımı : 35s yaklaşık
  2. BufferedReader'ı kullanma : yaklaşık 5s
  3. Java 8 : 5s kullanma
  4. LineNumberReader'ı kullanma : yaklaşık 5s

Üstelik Java8 Yaklaşımı oldukça kullanışlı görünüyor:

Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]

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

JDK8_u31 üzerinde test edildi. Ama aslında bu yönteme kıyasla performans yavaş:

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

Test edildi ve çok hızlı.


Bu doğru değil. Kodunuzla bazı denemeler yapın ve yöntem her zaman daha yavaştır. Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1Ve satır sayısı da yanlış
aw-think

32 bit makinede test ettim. Belki 64-bit üzerinde farklı sonuçlar olurdu .. Ve hatırladığım kadarıyla 10 kat veya daha fazla fark vardı. Satırı bir yere saymak için metni gönderebilir misiniz? Kolaylık olması için satır sonlarını görmek üzere Notepad2'yi kullanabilirsiniz.
Ernestas Gruodis

Aradaki fark bu olabilir.
aw-think

Performansı önemsiyorsanız, BufferedInputStreamyine de kendi tamponunuza okuyacağınız zamanı kullanmamalısınız. Ayrıca, yönteminiz hafif bir performans avantajına sahip olsa bile, artık tek \rsatır sonlandırıcıları (eski MacOS) desteklemediğinden ve her kodlamayı desteklemediğinden esnekliği kaybeder .
Holger

4

Tarayıcıyı kullanarak basit bir yol

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

3

Ben şu sonuca vardım wc -l: s satırları sayma yöntemi gayet iyi ama son satırın satırsonu ile bitmeyen dosyalar üzerinde sezgisel olmayan sonuçlar döndürür.

Ve @ er.vikas çözümü LineNumberReader tabanlı ancak satır sayısına bir tane eklemek son satırın satırsonu ile bittiği dosyalarda sezgisel olmayan sonuçlar verdi.

Bu nedenle aşağıdaki gibi işleyen bir algo yaptım:

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

Ve şöyle görünüyor:

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

Sezgisel sonuçlar istiyorsanız, bunu kullanabilirsiniz. Sadece wc -luyumluluk istiyorsanız , @ er.vikas çözümünü kullanın, ancak sonuca bir tane eklemeyin ve atlamayı tekrar deneyin:

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

2

Process sınıfını Java kodu içinden kullanmaya ne dersiniz? Ve sonra komutun çıktısını okumak.

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

Yine de denemek gerekiyor. Sonuçları gönderir.


1

Herhangi bir dizin yapınız yoksa, dosyanın tamamını okuyamazsınız. Ancak satır satır okumaktan kaçınarak optimize edebilir ve tüm satır sonlandırıcılarla eşleştirmek için bir normal ifade kullanabilirsiniz.


Düzgün bir fikir gibi geliyor. Herkes denedi ve bunun için bir regexp var mı?
willcodejavaforfood

1
Böyle iyi bir fikir olduğundan şüphe: tüm dosyayı bir kerede okumak gerekir (martinus bundan kaçınır) ve regexes bu tür kullanım için (sabit karakter (ler) basit arama) overkill (ve yavaş).
PhiLho

@will: / \ n /? @PhiLo: Regex Uygulayıcıları yüksek performansa sahip performans makineleridir. Hafızaya her şeyi oku uyarısı dışında, manuel bir uygulamanın daha hızlı olabileceğini düşünmüyorum.
David Schmitt

1

Bu komik çözüm aslında gerçekten iyi çalışıyor!

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

0

Unix tabanlı sistemlerde, wckomut satırındaki komutu kullanın.


@IainmH, ikinci öneriniz sadece geçerli dizindeki giriş sayısını sayar. Amaçlanan değil mi? (veya OP tarafından istendi)
Arketipik Paul

@IainMH: wc zaten bunu yapar (dosyayı okumak, satır sonunu saymak).
PhiLho

@PhiLho Satırları saymak için -l anahtarını kullanmanız gerekir. (Yapmaz mısın? - uzun zaman oldu)
Iain Holder

@ Paul - tabii ki% 100 haklısın. Tek savunmam bunu kahvemden önce göndermem. Artık bir düğme kadar keskinim. : D
Iain Holder

0

Dosyada kaç satır olduğunu bilmenin tek yolu onları saymaktır. Elbette, verilerinizden bir metrenin ortalama uzunluğunu veren bir metrik oluşturabilir ve ardından dosya boyutunu alabilir ve bunu avg ile bölebilirsiniz. ancak bu doğru olmayacaktır.


1
Hangi komut satırı aracını kullanırsanız kullanın, hepsi aynı şekilde, yalnızca dahili olarak YAPILIR. Satır sayısını anlamanın sihirli bir yolu yoktur, elle sayılmaları gerekir. Elbette meta veri olarak kaydedilebilir, ancak bu tamamen başka bir hikaye ...
Esko

0

En İyi EOF'da yeni satır ('\ n') karakteri olmayan çok satırlı dosyalar için optimize edilmiş kod.

/**
 * 
 * @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;
}

0

Normal ifade içeren tarayıcı:

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

Saat etmedim.


-2

bunu kullanırsan

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

büyük num satırlarına çalışamazsınız, 100K satırları sever, çünkü reader.getLineNumber öğesinden döndürme int'dir. maksimum satırı işlemek için uzun veri türüne ihtiyacınız vardır.


14
Bir intYaklaşık 2 milyar e kadar değerlerini tutabilir. 2 milyardan fazla satır içeren bir dosya yüklüyorsanız taşma sorununuz var. Bununla birlikte, iki milyardan fazla satır içeren dizine eklenmemiş bir metin dosyası yüklüyorsanız, muhtemelen başka sorunlarınız var demektir.
Adam Norberg
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.