Количество строк в файле в Java

Я использую огромные файлы данных, иногда мне нужно знать только количество строк в этих файлах, обычно я их открываю и читаю по строкам, пока не дойду до конца файла

Мне было интересно, есть ли более умный способ сделать это

Ответ 1

Это самая быстрая версия, которую я нашел, примерно в 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 явно медленнее.

Benchmark Plot

Ответ 2

Я реализовал другое решение проблемы, я счел это более эффективным при подсчете строк:

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
}

Ответ 3

Принятый ответ имеет одну ошибку для нескольких строк файлов, которые не заканчиваются символом новой строки. Однострочный файл, заканчивающийся без новой строки, будет возвращать 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();
    }
}

Ответ 4

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

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

Ответ 5

Ответ с помощью метода 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;
}

Ответ 6

Я знаю, что это старый вопрос, но принятое решение не совсем соответствовало тому, что мне было нужно. Таким образом, я уточнил, что он принимает различные терминаторы строк (а не только фиды строк) и использует заданную кодировку символов (а не 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, как известно, ненадежны).

Ответ 7

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

Протестировано и очень быстро.

Ответ 8

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

И решение @er.vikas на основе LineNumberReader, но добавление одного к счету линии возвращает неинтуитивные результаты в файлах, где последняя строка заканчивается символом новой строки.

Поэтому я сделал algo, который обрабатывает следующее:

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

Ответ 9

Как насчет использования класса 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);
}

Нужно попробовать. Опубликуйте результаты.

Ответ 10

Прямой путь с помощью сканера

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

Ответ 11

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

Размер файла: 1.6 Гб Методы:

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

Более того, Java8-подход кажется весьма удобным: Files.lines(Paths.get(filePath), Charset.defaultCharset()). Count() [Тип возврата: long]

Ответ 12

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

Ответ 13

Это смешное решение работает действительно хорошо!

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

Ответ 14

В системах на базе Unix используйте команду wc в командной строке.

Ответ 15

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

Ответ 16

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

Ответ 17

если вы используете этот

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