Количество строк в файле в Java - PullRequest
203 голосов
/ 17 января 2009

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

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

Ответы [ 18 ]

227 голосов
/ 17 января 2009

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

195 голосов
/ 17 марта 2011

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

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
}
28 голосов
/ 19 января 2013

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

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

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}
12 голосов
/ 30 октября 2009

Ответ с помощью метода 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;
}
8 голосов
/ 22 сентября 2012

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

4 голосов
/ 21 февраля 2015
/**
 * 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;
    }
}

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

4 голосов
/ 19 ноября 2018

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

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

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

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

3 голосов
/ 16 февраля 2016

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

И решение @ 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();
}
3 голосов
/ 14 сентября 2014

Простой способ использования сканера

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