Кількість рядків у файлі на Java


213

Я використовую величезні файли даних, іноді мені потрібно знати лише кількість рядків у цих файлах, зазвичай я відкриваю їх і читаю їх по рядках, поки не дойду до кінця файлу

Мені було цікаво, чи є розумніший спосіб зробити це

Відповіді:


237

Це найшвидша версія, яку я знайшов поки що, приблизно в 6 разів швидше, ніж readLines. Для файлу журналу розміром 150 Мб це займає 0,35 секунди проти 2,40 секунди при використанні readLines (). Для задоволення команда linux 'wc -l займає 0,15 секунди.

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 років пізніше: Я практично не маю досвіду Java, але все одно я намагався порівняти цей код із LineNumberReaderрішенням нижче, оскільки це мені непокоїло, що ніхто цього не робив. Здається, що особливо для великих файлів моє рішення швидше. Хоча, здається, потрібно кілька пробіжок, поки оптимізатор не зробить гідну роботу. Я трохи пограв з кодом і створив нову версію, яка постійно швидша:

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

Результати порівняння для текстового файлу 1,3 Гб, вісь y в секундах. Я виконав 100 запусків з тим самим файлом і виміряв кожен пробіг System.nanoTime(). Ви можете бачити, що він countLinesOldмає кілька людей, що не мають виходу, а countLinesNewйого немає, і хоча це лише трохи швидше, різниця є статистично значущою. LineNumberReaderявно повільніше.

Оцінка еталону


5
BufferedInputStream повинен робити буферизацію для вас, тому я не бачу, як використання проміжного байтового масиву [] зробить це швидше. Ви навряд чи зможете зробити набагато краще, ніж використовувати readLine () неодноразово (оскільки це буде оптимізовано API).
вд

54
Ви закінчите цей InputStream, коли закінчите з ним, чи не так?
бендін

5
Якщо буферизація допомогла, це було б тому, що BufferedInputStream буфери 8K за умовчанням. Збільшити байт [] до цього розміру або більше, і ви можете скинути BufferedInputStream. наприклад, спробуйте 1024 * 1024 байт.
Пітер Лорі

8
Дві речі: (1) Визначення термінатора рядка в джерелі Java - це повернення каретки, канал лінії або повернення каретки з подальшим поданням рядка. Ваше рішення не працюватиме для CR, що використовується як лінійний термінатор. Звичайно, я вважаю, що єдиною ОС, яка використовує CR як термінатор рядка за замовчуванням, є Mac OS до Mac OS X. (2) Ваше рішення передбачає кодування символів, такі як US-ASCII або UTF-8. Кількість рядків може бути неточною для кодувань, таких як UTF-16.
Натан Раян

2
Дивовижний код ... для текстового файлу 400 Мб пройшло всього лише секунду. Дякую багато @martinus
користувач3181500

199

Я реалізував ще одне рішення проблеми, і вважав його більш ефективним при підрахунку рядків:

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
}

LineNumberReader«S lineNumberполе являє собою ціле ... Чи не буде просто обернути файли більше ніж Integer.MAX_VALUE? Навіщо турбуватися пропускати тут довго?
epb

1
Додавання одного до підрахунку насправді невірно. wc -lпідраховує кількість символів нового рядка у файлі. Це працює, оскільки кожен рядок закінчується новим рядком, включаючи остаточний рядок у файлі. Кожен рядок має новий рядок, включаючи порожні рядки, отже, кількість символів нового рядка == кількість рядків у файлі. Тепер lineNumberзмінна в FileNumberReaderтакож представляє кількість переглянутих символів нового рядка. Він починається з нуля до того, як буде знайдено будь-який новий рядок, і збільшується з кожним побаченим символом нового рядка. Тому не додайте його до номера рядка.
Олександр Торстлінг

1
@PB_MLT: Хоча ви маєте рацію, що про файл з однією рядком без нової лінії повідомляється як 0 рядків, саме так wc -lтакож повідомляється про такий файл. Також см stackoverflow.com/questions/729692 / ...
Олександр Torstling

@PB_MLT: Ви отримуєте протилежну проблему, якщо файл складається виключно з нового рядка. Ваш запропонований algo поверне 0 та wc -lповернеться 1. Я зробив висновок, що всі методи мають недоліки, і застосував один на основі того, як я хотів би, щоб він поводився, дивіться іншу відповідь тут.
Олександр Торстлінг

3
Я вниз проголосував за цю відповідь, тому що, здається, ніхто з вас не став її орієнтованою
amstegraf

30

Прийнята відповідь вимикається однією помилкою для багаторядкових файлів, які не закінчуються в новому рядку. Файл одного рядка, що закінчується без нового рядка, повертає 1, а два файли, що закінчуються без нового рядка, також повертають 1. Ось реалізація прийнятого рішення, яке це виправляє. Перевірки з кінцямиWithoutNewLine марно витрачають на все, окрім остаточного прочитаного, але мають бути тривіальними у часі порівняно із загальною функцією.

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
Хороший улов. Не впевнений, чому ви не просто відредагували прийняту відповідь та зробили помітку в коментарі. Більшість людей поки не читатимуть.
Райан

@Ryan, це просто не було правильним для редагування 4-річної прийнятої відповіді з 90+ оновленнями.
DMulligan

@AFinkelstein, я вважаю, що саме це робить цей сайт настільки чудовим, що ви можете редагувати найкращу відповідь.
Себастьян

3
Це рішення не обробляє повернення перевезення (\ r) та повернення перевезення з наступним
подачею

@Simon Brandhof, я розгублений, чому повернення вагона вважатиметься ще одним рядком? "\ N" - це канал зворотної лінії перевезення, тому той, хто пише "\ r \ n", щось не розуміє ... Плюс, він шукає char за допомогою char, тому я майже впевнений, чи хтось повинен використовувати "\ r \ n "вона все одно буде ловити" \ n "і рахувати рядок. Так чи інакше, я думаю, що він зробив крапку. Однак їх багато сценаріїв, коли це недостатній спосіб отримати кількість ліній.
nckbrz

22

З , ви можете використовувати потоки:

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

1
У коді є помилки. Просте, але дуже повільне ... Спробуйте подивитися на мою відповідь нижче (вище).
Ернестас Груодіс

12

Відповідь із методом count (), наведеним вище, дав мені рахунки у рядку, якщо в кінці файлу не було нового рядка - не вдалося підрахувати останній рядок у файлі.

Цей метод для мене працює краще:

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

У цьому випадку немає необхідності використовувати LineNumberReader, просто використовуйте BufferedReader, у цьому випадку у вас буде можливість використовувати довгий тип даних cnt.
Syed Aqeel Ashiq

[ІНФОРМАЦІЯ] Невдача PMD: xx: 19 Правило: Пріоритет EmptyWhileStmt: 3 Уникайте порожніх під час операцій.
Хорн Еліт

8

Я знаю, що це старе питання, але прийняте рішення не зовсім відповідало тому, що мені потрібно було зробити. Отже, я вдосконалив її для прийому різних термінаторів ліній (а не просто подачі рядків) та використання заданого кодування символів (а не ISO-8859- n ). Все в одному методі (рефактор відповідно)

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

Це рішення порівняно за швидкістю з прийнятим рішенням, на 4% повільніше в моїх тестах (хоча тести на терміни на Java явно сумнівно ненадійні).


8

Я перевірив вищевказані методи для підрахунку рядків, і ось мої спостереження за різними методами, як перевірені в моїй системі

Розмір файлу: 1,6 Gb Методи:

  1. Використання сканера : приблизно 35 с
  2. Використання BufferedReader : 5s прибл
  3. Використання Java 8 : 5s прибл
  4. Використання LineNumberReader : 5 с

Більше того, підхід Java8 виглядає досить зручним:

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. Але насправді продуктивність повільна порівняно з цим методом:

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

Випробували і дуже швидко.


Це не правильно. Зробив кілька експериментів зі своїм кодом, і метод завжди повільніше. Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1І кількість рядків теж неправильна
подумайте,

Я тестував на 32-розрядній машині. Можливо, на 64-розрядні були б різні результати .. І це було різницею в 10 і більше разів, як я пам’ятаю. Чи можете ви розмістити текст, щоб десь підрахувати рядок? Ви можете використовувати Notepad2, щоб побачити розриви рядків для зручності.
Ернестас Груодіс

Це може бути різниця.
aw-think

Якщо ви піклуєтесь про продуктивність, BufferedInputStreamу будь-якому випадку ви не повинні використовувати a, коли ви збираєтесь читати у власному буфері. Крім того, навіть якщо ваш метод може мати невелику перевагу в продуктивності, він втрачає гнучкість, оскільки він більше не підтримує єдині \rлінійні термінатори (старий MacOS) і не підтримує кожне кодування.
Холгер

4

Прямий шлях за допомогою сканера

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

Я зробив висновок, що wc -l: s підрахунок нових рядків чудово, але повертає неінтуїтивні результати у файлах, де останній рядок не закінчується новим рядком.

І рішення @ er.vikas на основі LineNumberReader, але додавши його до числа рядків, повертає неінтуїтивні результати у файлах, де останній рядок закінчується новим рядком.

Тому я створив альго, яке обробляє так:

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

І це виглядає приблизно так:

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

Якщо ви хочете отримати інтуїтивні результати, ви можете скористатися цим. Якщо ви просто хочете wc -lсумісності, просто скористайтеся рішенням @ er.vikas, але не додайте його до результату та повторіть спробу:

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

2

Як щодо використання класу Process з коду Java? А потім зчитування результатів команди.

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

Потрібно все ж спробувати. Опублікуємо результати.


1

Якщо у вас немає індексних структур, ви не зможете обійти читання повного файлу. Але ви можете оптимізувати його, уникаючи читати його рядок за рядком і використовувати регулярний вираз, щоб відповідати всім терміналам рядків.


Звучить акуратною ідеєю. Хтось спробував це і має для нього регулярний вираз?
willcodejavaforfood

1
Я сумніваюсь, що це така гарна ідея: потрібно буде прочитати весь файл одразу (мартінус цього уникає), і регулярні вирази будуть надмірними (і повільнішими) для такого використання (простий пошук фіксованих символів).
PhiLho

@will: як щодо / \ n /? @PhiLo: Виконавці Regex - це високоналагоджені машини для роботи. За винятком застереження «читання все в пам’яті», я не думаю, що ручна реалізація може бути швидшою.
Девід Шмітт

1

Це смішне рішення насправді добре працює!

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 використовуйте wcкоманду в командному рядку.


@IainmH, ваша друга пропозиція просто підраховує кількість записів у поточному каталозі. Не те, що було призначено? (або просили в ОП)
Архетипаль Павло

@IainMH: це все-таки робить wc (читання файлу, підрахунок рядкового закінчення).
PhiLho

@PhiLho Вам потрібно буде використовувати перемикач -l для підрахунку рядків. (Чи не так?
минув

@Paul - ти, звичайно, на 100% прав. Єдиний мій захист полягає в тому, що я розмістив це перед кавою. Я зараз такий гострий, як ґудзик. : D
Iain Holder

0

Єдиний спосіб дізнатися, скільки рядків у файлі - це порахувати їх. Звичайно, ви можете створити показник зі своїх даних, даючи середню довжину одного рядка, а потім отримати розмір файлу та розділити його на середню. довжина, але це не буде точним.


1
Цікавий результат, незалежно від того, яким інструментом командного рядка ви користуєтесь, вони все роблять те саме, лише внутрішньо. Немає магічного способу визначити кількість рядків, їх треба рахувати від руки. Звичайно, їх можна зберегти як метадані, але це зовсім інша історія ...
Есько,

0

Найкращий оптимізований код для багаторядкових файлів, що не мають символу newline ('\ n') в 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;
}

0

Сканер з регулярним виразом:

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

Його не завели.


-2

якщо ви використовуєте це

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

ви не можете переходити до великих числових рядків, любить 100K рядків, тому що return from reader.getLineNumber - це int. вам потрібен довгий тип даних для обробки максимальних рядків ..


14
А intможе містити значення приблизно до 2 мільярдів. Якщо ви завантажуєте файл з більш ніж 2 мільярдами рядків, у вас є проблема переповнення. Однак, якщо ви завантажуєте не доданий текстовий файл з більш ніж двома мільярдами рядків, у вас, ймовірно, є інші проблеми.
Адам Норберг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.