Вопрос

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

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

Это было полезно?

Решение

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

Benchmark Plot

Другие советы

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

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
}

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

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

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

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

Приведенный выше ответ с помощью метода 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;
}

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

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

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

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

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

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

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

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

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

Я пришел к выводу , что 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();
}

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

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

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

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

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

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

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

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

Сканер с регулярным выражением:

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

Я еще не засек это.

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

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

вы не можете перейти к большим числовым строкам, которым нравятся 100 тысяч строк, потому что возврат из reader.getLineNumber равен int.вам нужен длинный тип данных для обработки максимального количества строк..

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top