Ответы:
Это самая быстрая версия, которую я нашел, примерно в 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
явно медленнее.
Я реализовал другое решение проблемы, я нашел его более эффективным при подсчете строк:
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? Зачем прыгать долго здесь?
wc -l
подсчитывает количество символов новой строки в файле. Это работает, так как каждая строка заканчивается новой строкой, включая последнюю строку в файле. Каждая строка имеет символ новой строки, включая пустые строки, поэтому число символов новой строки == количество строк в файле. Теперь lineNumber
переменная in FileNumberReader
также представляет количество увиденных символов новой строки. Он начинается с нуля до того, как будет найден какой-либо символ новой строки, и увеличивается с каждым увиденным символом новой строки. Так что не добавляйте один к номеру строки, пожалуйста.
wc -l
и сообщается о файлах такого типа. Также см stackoverflow.com/questions/729692/...
wc -l
вернет 1. Я пришел к выводу, что все методы имеют недостатки, и реализовал один из них на основе того, как я хотел бы, чтобы он себя вел, см. Мой другой ответ здесь.
Принятый ответ имеет одну ошибку для многострочных файлов, которые не заканчиваются переводом строки. Файл с одной строкой, заканчивающийся без новой строки, вернул бы 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();
}
}
С участием Java-8Вы можете использовать потоки:
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;
}
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 Гб Методы:
Более того, Java8- подход кажется довольно удобным:
Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]
/**
* 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
И количество строк тоже неверно
BufferedInputStream
когда вы все равно собираетесь читать в свой собственный буфер. Кроме того, даже если ваш метод может иметь небольшое преимущество в производительности, он теряет гибкость, так как он больше не поддерживает \r
терминаторы единственной строки (старые MacOS) и не поддерживает все кодировки.
Прямой способ использования сканера
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
: 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();
}
Как насчет использования класса 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
команду в командной строке.
Единственный способ узнать, сколько строк в файле - это подсчитать их. Конечно, вы можете создать метрику из ваших данных, которая даст вам среднюю длину в одну строку, а затем получить размер файла и разделить его с помощью avg. длина, но это не будет точно.
Лучший оптимизированный код для многострочных файлов, не имеющих символа новой строки ('\ 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;
}
Вы не можете запускать строки с большим числом, любит 100K строк, потому что return от reader.getLineNumber - int. Вам нужен длинный тип данных для обработки максимального количества строк.
int
Может содержать значения до, приблизительно, 2 млрд. Если вы загружаете файл с более чем 2 миллиардами строк, у вас есть проблема переполнения. Тем не менее, если вы загружаете неиндексированный текстовый файл с более чем двумя миллиардами строк, у вас, вероятно, есть другие проблемы.