Jumlah baris dalam file di Jawa

213

Saya menggunakan file data yang sangat besar, kadang-kadang saya hanya perlu mengetahui jumlah baris dalam file ini, biasanya saya membukanya dan membacanya baris demi baris sampai saya mencapai akhir file

Saya bertanya-tanya apakah ada cara yang lebih cerdas untuk melakukan itu

Menandai
sumber

Jawaban:

237

Ini adalah versi tercepat yang saya temukan sejauh ini, sekitar 6 kali lebih cepat daripada readLines. Pada file log 150MB ini membutuhkan 0,35 detik, dibandingkan 2,40 detik saat menggunakan readLines (). Hanya untuk bersenang-senang, perintah linux 'wc -l membutuhkan waktu 0,15 detik.

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 tahun kemudian: Praktis saya tidak punya pengalaman java, tapi bagaimanapun saya telah mencoba untuk membandingkan kode ini dengan LineNumberReadersolusi di bawah ini karena itu mengganggu saya bahwa tidak ada yang melakukannya. Tampaknya terutama untuk file besar solusi saya lebih cepat. Meskipun tampaknya perlu beberapa kali hingga pengoptimal melakukan pekerjaan yang layak. Saya telah bermain sedikit dengan kode, dan telah menghasilkan versi baru yang secara konsisten tercepat:

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

Resuls benchmark untuk file teks 1.3GB, sumbu y dalam hitungan detik. Saya telah melakukan 100 berjalan dengan file yang sama, dan diukur masing-masing berjalan dengan System.nanoTime(). Anda dapat melihat bahwa countLinesOldmemiliki beberapa outlier, dan countLinesNewtidak memiliki outlier dan meskipun hanya sedikit lebih cepat, perbedaannya signifikan secara statistik. LineNumberReaderjelas lebih lambat.

Plot Benchmark

martinus
sumber
5
BufferedInputStream seharusnya melakukan buffering untuk Anda, jadi saya tidak melihat bagaimana menggunakan array byte [] menengah akan membuatnya lebih cepat. Anda tidak mungkin melakukan jauh lebih baik daripada menggunakan readLine () berulang kali (karena itu akan dioptimalkan oleh API).
wds
54
Anda akan menutup InputStream itu setelah selesai dengan itu, bukan?
bendin
5
Jika buffering membantu itu karena BufferedInputStream buffer 8K secara default. Tingkatkan byte Anda [] ke ukuran ini atau lebih besar dan Anda dapat menjatuhkan BufferedInputStream. mis. coba 1024 * 1024 byte.
Peter Lawrey
8
Dua hal: (1) Definisi terminator garis dalam sumber Java adalah carriage return, feed line, atau carriage return diikuti oleh feed line. Solusi Anda tidak akan berfungsi untuk CR yang digunakan sebagai terminator garis. Memang, satu-satunya OS yang saya pikir menggunakan CR sebagai terminator garis default adalah Mac OS sebelum Mac OS X. (2) Solusi Anda mengasumsikan pengkodean karakter seperti US-ASCII atau UTF-8. Hitungan baris mungkin tidak akurat untuk penyandian seperti UTF-16.
Nathan Ryan
2
Kode yang luar biasa ... untuk file teks 400MB, hanya butuh satu detik. Terima kasih banyak @martinus
user3181500
199

Saya telah menerapkan solusi lain untuk masalah ini, saya merasa lebih efisien dalam menghitung baris:

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
sumber
LineNumberReader's lineNumberlapangan adalah bilangan bulat ... Akan bukan hanya membungkus untuk file lama dari Integer.MAX_VALUE? Kenapa repot-repot melewatkan waktu lama di sini?
epb
1
Menambahkan satu ke hitungan sebenarnya tidak benar. wc -lmenghitung jumlah karakter baris baru dalam file. Ini berfungsi karena setiap baris diakhiri dengan baris baru, termasuk baris terakhir dalam file. Setiap baris memiliki karakter baris baru, termasuk baris kosong, sehingga jumlah baris baris baru == jumlah baris dalam file. Sekarang, lineNumbervariabel dalam FileNumberReaderjuga mewakili jumlah karakter baris baru yang terlihat. Itu dimulai dari nol, sebelum baris baru ditemukan, dan ditingkatkan dengan setiap baris baris baru dilihat. Jadi jangan menambahkan satu ke nomor baris.
Alexander Torstling
1
@PB_MLT: Meskipun Anda benar bahwa file dengan satu baris tanpa baris baru akan dilaporkan sebagai 0 baris, ini wc -ljuga cara melaporkan jenis file ini. Lihat juga stackoverflow.com/questions/729692/…
Alexander Torstling
@PB_MLT: Anda mendapatkan masalah sebaliknya jika file hanya terdiri dari baris baru. Algo Anda yang disarankan akan mengembalikan 0 dan wc -lakan kembali 1. Saya menyimpulkan bahwa semua metode memiliki kekurangan, dan menerapkan satu berdasarkan pada bagaimana saya ingin berperilaku, lihat jawaban saya yang lain di sini.
Alexander Torstling
3
Saya turun memilih tanggapan ini, karena sepertinya tidak ada di antara Anda yang menjadikannya sebagai patokan
amstegraf
30

Jawaban yang diterima memiliki kesalahan satu demi satu untuk file multi-baris yang tidak berakhir pada baris baru. File satu baris yang berakhir tanpa baris baru akan mengembalikan 1, tetapi file dua baris yang berakhir tanpa baris baru akan mengembalikan 1 juga. Berikut ini adalah implementasi dari solusi yang diterima yang memperbaikinya. Pemeriksaan endsWithoutNewLine sia-sia untuk semuanya kecuali membaca akhir, tetapi harus sepele waktu bijaksana dibandingkan dengan fungsi keseluruhan.

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
sumber
6
Tangkapan yang bagus. Tidak yakin mengapa Anda tidak hanya mengedit jawaban yang diterima dan membuat catatan dalam komentar. Kebanyakan orang tidak akan membaca sampai sejauh ini.
Ryan
@Ryan, rasanya tidak benar untuk mengedit jawaban yang diterima 4 tahun dengan 90+ upvotes.
DMulligan
@ TAinkelstein, saya merasa itulah yang membuat situs ini luar biasa, sehingga Anda dapat mengedit jawaban pilihan teratas.
Sebastian
3
Solusi ini tidak menangani carriage return (\ r) dan carriage return diikuti oleh linefeed (\ r \ n)
Simon Brandhof - SonarSource
@Simon Brandhof, saya bingung mengapa carriage return akan dihitung sebagai jalur lain? "\ N" adalah umpan baris balik Carriage, jadi siapa pun yang menulis "\ r \ n" tidak memahami sesuatu ... Ditambah lagi ia mencari char oleh char, jadi saya cukup yakin jika seseorang menggunakan "\ r \ n "ia masih akan menangkap" \ n "dan menghitung garis. Bagaimanapun saya pikir dia baik-baik saja. Namun, ada banyak skenario di mana ini bukan cara yang cukup untuk mendapatkan jumlah baris.
nckbrz
22

Dengan , Anda dapat menggunakan stream:

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}
msayag
sumber
1
Kode memiliki kesalahan. Sederhana, tetapi sangat lambat ... Cobalah untuk melihat jawaban saya di bawah (di atas).
Ernestas Gruodis
12

Jawaban dengan hitungan metode () di atas memberi saya kesalahan perhitungan baris jika file tidak memiliki baris baru di akhir file - gagal menghitung baris terakhir dalam file.

Metode ini bekerja lebih baik untuk saya:

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
sumber
Dalam hal ini, tidak perlu menggunakan LineNumberReader, cukup gunakan BufferedReader, dalam hal ini Anda akan memiliki fleksibilitas untuk menggunakan tipe data yang panjang cnt.
Syed Aqeel Ashiq
[INFO] Kegagalan PMD: xx: 19 Aturan: EmptyWhileStmt Prioritas: 3 Hindari pernyataan while kosong.
Chhorn Elit
8

Saya tahu ini adalah pertanyaan lama, tetapi solusi yang diterima tidak cocok dengan yang saya butuhkan. Jadi, saya memperbaikinya untuk menerima berbagai terminator garis (bukan hanya umpan baris) dan menggunakan pengkodean karakter yang ditentukan (bukan ISO-8859- n ). Semua dalam satu metode (refactor yang sesuai):

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

Solusi ini sebanding dalam kecepatannya dengan solusi yang diterima, sekitar 4% lebih lambat dalam pengujian saya (meskipun tes waktu di Jawa sangat tidak dapat diandalkan).

Nathan Ryan
sumber
8

Saya menguji metode di atas untuk menghitung garis dan inilah pengamatan saya untuk metode yang berbeda seperti yang diuji pada sistem saya

Ukuran File: 1,6 Gb Metode:

  1. Menggunakan Pemindai : kira-kira 35-an
  2. Menggunakan BufferedReader : kira-kira 5s
  3. Menggunakan Java 8 : 5s approx
  4. Menggunakan LineNumberReader : kira-kira 5s

Selain itu Pendekatan Java8 tampaknya cukup berguna:

Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]
Anshul
sumber
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();
    }
}

Diuji pada JDK8_u31. Tetapi memang kinerjanya lambat dibandingkan dengan metode ini:

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

Diuji dan sangat cepat.

Ernestas Gruodis
sumber
Ini tidak benar. Buat beberapa percobaan dengan kode Anda dan metode ini selalu lebih lambat. Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1Dan jumlah kalimatnya bahkan salah
aw-think
Saya menguji pada mesin 32-bit. Mungkin pada 64-bit akan hasil yang berbeda .. Dan itu bedanya 10 kali atau lebih seperti yang saya ingat. Bisakah Anda memposting teks untuk menghitung baris di suatu tempat? Anda dapat menggunakan Notepad2 untuk melihat jeda baris demi kenyamanan.
Ernestas Gruodis
Itu bisa jadi perbedaannya.
aw-think
Jika Anda peduli dengan kinerja, Anda sebaiknya tidak menggunakan BufferedInputStreamkapan Anda akan membaca ke buffer Anda sendiri. Selain itu, bahkan jika metode Anda mungkin memiliki sedikit keunggulan kinerja, metode ini kehilangan fleksibilitas, karena tidak lagi mendukung satu-satunya \rterminator (MacOS lama) dan tidak mendukung setiap penyandian.
Holger
4

Cara mudah menggunakan 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
sumber
3

Saya menyimpulkan bahwa wc -l: metode penghitungan baris baru baik-baik saja tetapi mengembalikan hasil non-intuitif pada file di mana baris terakhir tidak berakhir dengan baris baru.

Dan solusi @ er.vikas berdasarkan LineNumberReader tetapi menambahkan satu ke jumlah baris menghasilkan hasil yang tidak intuitif pada file di mana baris terakhir diakhiri dengan baris baru.

Karena itu saya membuat algo yang menangani sebagai berikut:

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

Dan terlihat seperti ini:

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

Jika Anda menginginkan hasil yang intuitif, Anda dapat menggunakan ini. Jika Anda hanya ingin wc -lkompatibilitas, gunakan solusi sederhana @ er.vikas, tapi jangan tambahkan satu ke hasilnya dan coba lagi lewati:

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

Bagaimana kalau menggunakan kelas Proses dari dalam kode Java? Dan kemudian membaca output dari perintah.

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

Namun perlu dicoba. Akan memposting hasilnya.

Sunil Shevante
sumber
1

Jika Anda tidak memiliki struktur indeks apa pun, Anda tidak akan bisa menyelesaikan pembacaan file lengkap. Tetapi Anda dapat mengoptimalkannya dengan menghindari membacanya baris demi baris dan menggunakan regex untuk mencocokkan semua terminator baris.

David Schmitt
sumber
Kedengarannya seperti ide yang rapi. Adakah yang mencobanya dan memiliki regexp untuk itu?
willcodejavaforfood
1
Saya ragu itu adalah ide yang bagus: perlu membaca seluruh file sekaligus (martinus menghindari ini) dan regex terlalu banyak (dan lebih lambat) untuk penggunaan seperti itu (pencarian sederhana char tetap).
PhiLho
@will: bagaimana dengan / \ n /? @ PiLo: Pelaksana Regex adalah mesin kinerja yang sangat disetel. Kecuali peringatan read-everything-into-memory, saya tidak berpikir bahwa implementasi manual bisa lebih cepat.
David Schmitt
1

Solusi lucu ini bekerja sangat baik sebenarnya!

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
sumber
0

Pada sistem berbasis Unix, gunakan wcperintah pada baris perintah.

Peter Hilton
sumber
@ IainmH, saran kedua Anda hanya menghitung jumlah entri dalam direktori saat ini. Bukan apa yang dimaksud? (atau diminta oleh OP)
The Archetypal Paul
@IainMH: toh itu yang dilakukan wc (membaca file, menghitung akhir baris).
PhiLho
@ Philho Anda harus menggunakan -l untuk menghitung garis. (Bukan? - sudah lama)
Iain Holder
@ Paul - Anda tentu saja 100% benar. Satu-satunya pertahanan saya adalah bahwa saya mempostingnya sebelum kopi saya. Saya setajam tombol sekarang. : D
Iain Holder
0

Satu-satunya cara untuk mengetahui berapa banyak baris yang ada dalam file adalah dengan menghitungnya. Anda tentu saja dapat membuat metrik dari data Anda, memberi Anda rata-rata panjang satu baris dan kemudian mendapatkan ukuran file dan membaginya dengan rata-rata. panjang tapi itu tidak akan akurat.

Esko
sumber
1
Downvote yang menarik, tidak peduli apa pun alat baris perintah yang Anda gunakan, mereka semua melakukan hal yang sama, hanya secara internal. Tidak ada cara ajaib untuk mengetahui jumlah garis, mereka harus dihitung dengan tangan. Tentu itu bisa diselamatkan sebagai metadata tapi itu keseluruhan cerita lain ...
Esko
0

Kode Dioptimalkan Terbaik untuk file multi-baris yang tidak memiliki karakter baris baru ('\ n') di 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
sumber
0

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

Belum melihatnya.

pengguna176692
sumber
-2

jika Anda menggunakan ini

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

Anda tidak dapat lari ke baris angka besar, menyukai baris 100K, karena kembali dari reader.getLineNumber adalah int. Anda memerlukan tipe data yang panjang untuk memproses baris maksimum ..

Faisal
sumber
14
Sebuah intdapat menyimpan nilai hingga, sekitar 2 miliar. Jika Anda memuat file dengan lebih dari 2 miliar baris, Anda memiliki masalah overflow. Yang mengatakan, jika Anda memuat file teks yang tidak diindeks dengan lebih dari dua miliar baris, Anda mungkin memiliki masalah lain.
Adam Norberg