Количество строк в файле в 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();
    }
}

РЕДАКТИРОВАТЬ, 9 с половиной лет спустя: у меня практически нет опыта работы с 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 должен делать буферизацию для вас, поэтому я не вижу, как использование промежуточного массива byte [] сделает это быстрее. Вряд ли вы в любом случае добьетесь большего успеха, чем повторное использование readLine () (так как это будет оптимизировано API).
wds
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
user3181500
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
}
er.vikas
источник
LineNumberReader«S lineNumberполе представляет собой целое ... Не будет ли просто обернуть файлы больше чем Integer.MAX_VALUE? Зачем прыгать долго здесь?
EPB
1
Добавление одного на счет фактически неверно. wc -lподсчитывает количество символов новой строки в файле. Это работает, так как каждая строка заканчивается новой строкой, включая последнюю строку в файле. Каждая строка имеет символ новой строки, включая пустые строки, поэтому число символов новой строки == количество строк в файле. Теперь lineNumberпеременная in FileNumberReaderтакже представляет количество увиденных символов новой строки. Он начинается с нуля до того, как будет найден какой-либо символ новой строки, и увеличивается с каждым увиденным символом новой строки. Так что не добавляйте один к номеру строки, пожалуйста.
Александр Торстлинг
1
@PB_MLT: Хотя вы правы в том, что файл с одной строкой без новой строки будет отображаться как 0 строк, именно так wc -lи сообщается о файлах такого типа. Также см stackoverflow.com/questions/729692/...
Александр Torstling
@PB_MLT: вы столкнулись с противоположной проблемой, если файл состоит исключительно из новой строки. Ваш предложенный алгоритм вернет 0 и wc -lвернет 1. Я пришел к выводу, что все методы имеют недостатки, и реализовал один из них на основе того, как я хотел бы, чтобы он себя вел, см. Мой другой ответ здесь.
Александр Торстлинг
3
Я проголосовал за этот ответ, потому что, кажется, никто из вас не
оценил
30

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

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
источник
6
Хороший улов. Не уверен, почему вы не просто отредактировали принятый ответ и сделали заметку в комментарии. Большинство людей не будут читать это далеко.
Райан
@ Райан, было просто неуместно редактировать принятый 4-летний ответ с более чем 90 голосами против.
DMulligan
@AFinkelstein, я чувствую, что это делает этот сайт таким замечательным, что вы можете редактировать самый популярный ответ.
Себастьян
3
Это решение не обрабатывает возврат каретки (\ r) и возврат каретки с последующей переводом строки (\ r \ n)
Саймон Брандхоф - SonarSource
@ Симон Брандхоф, я не понимаю, почему возврат каретки считается другой строкой? «\ N» - это перевод строки возврата каретки, поэтому тот, кто пишет «\ r \ n», что-то не понимает ... Плюс он ищет символ за символом, так что я уверен, что кто-то будет использовать «\ r» \ n "все равно поймает" \ n "и посчитает строку. В любом случае, я думаю, он сделал это замечательно. Тем не менее, существует множество сценариев, в которых этого недостаточно для определения количества строк.
nckbrz
22

С участием Вы можете использовать потоки:

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}
msayag
источник
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.
Сайед Акил Ашик
[INFO] Отказ PMD: xx: 19 Правило: EmptyWhileStmt Приоритет: 3 Избегайте пустых операторов while.
Чхорн Элит
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 Гб Методы:

  1. Использование сканера : около 35 с
  2. Использование BufferedReader : 5 с
  3. Использование Java 8 : 5 с
  4. Использование LineNumberReader : 5 с

Более того, Java8- подход кажется довольно удобным:

Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]
Anshul
источник
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, чтобы увидеть разрывы строк для удобства.
Эрнестас Груодис
Это может быть разница.
Если вы заботитесь о производительности, вам не следует использовать, BufferedInputStreamкогда вы все равно собираетесь читать в свой собственный буфер. Кроме того, даже если ваш метод может иметь небольшое преимущество в производительности, он теряет гибкость, так как он больше не поддерживает \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
Я сомневаюсь, что это такая хорошая идея: ему нужно будет прочитать весь файл сразу (martinus избегает этого), и регулярные выражения излишни (и медленнее) для такого использования (простой поиск фиксированных символов).
PhiLho
@will: как насчет / \ n /? @PhiLo: Regex Executors - высокопроизводительные машины. За исключением предупреждения о чтении всего в память, я не думаю, что ручная реализация может быть быстрее.
Дэвид Шмитт
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 для подсчета строк. (Не так ли? - это было какое-то время)
Iain Holder
@ Пол - вы, конечно, на 100% правы. Моя единственная защита в том, что я разместил это перед своим кофе. Теперь я острый, как кнопка. : D
Iain Holder
0

Единственный способ узнать, сколько строк в файле - это подсчитать их. Конечно, вы можете создать метрику из ваших данных, которая даст вам среднюю длину в одну строку, а затем получить размер файла и разделить его с помощью avg. длина, но это не будет точно.

Эско
источник
1
Интересно понизить голос, независимо от того, какой инструмент командной строки вы используете, они все равно делают то же самое, только внутри. Нет волшебного способа выяснить количество линий, их нужно считать вручную. Конечно, его можно сохранить как метаданные, но это совсем другая история ...
Эско
0

Лучший оптимизированный код для многострочных файлов, не имеющих символа новой строки ('\ 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;
}

Не разобрались в этом.

user176692
источник
-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 от reader.getLineNumber - int. Вам нужен длинный тип данных для обработки максимального количества строк.

Фейсал
источник
14
intМожет содержать значения до, приблизительно, 2 млрд. Если вы загружаете файл с более чем 2 миллиардами строк, у вас есть проблема переполнения. Тем не менее, если вы загружаете неиндексированный текстовый файл с более чем двумя миллиардами строк, у вас, вероятно, есть другие проблемы.
Адам Норберг