Я использую огромные файлы данных, иногда мне нужно знать только количество строк в этих файлах, обычно я открываю их и читаю построчно, пока не дойду до конца файла.
Мне было интересно, есть ли способ сделать это поумнее
Я использую огромные файлы данных, иногда мне нужно знать только количество строк в этих файлах, обычно я открываю их и читаю построчно, пока не дойду до конца файла.
Мне было интересно, есть ли способ сделать это поумнее
Это самая быстрая версия, которую я нашел до сих пор, примерно в 6 раз быстрее, чем readLines. В файле журнала размером 150 МБ это занимает 0,35 секунды по сравнению с 2,40 секунды при использовании readLines (). Ради удовольствия, команда wc -l в linux занимает 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
явно медленнее.
\n
в качестве признака конца строки. Счетчик уменьшился на один (на один меньше) для noeol
файлов. На самом деле нужно подсчитывать не количество \n
, а количество появлений последовательностей символов, разделенных символом конца строки.
- person Christian Hujer; 05.03.2015
Я реализовал другое решение проблемы, счел более эффективным подсчет строк:
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
lineNumber
является целым числом ... Не будет ли оно просто переноситься для файлов длиннее Integer.MAX_VALUE? Зачем пропускать здесь долгое время?
- person epb; 03.04.2015
wc -l
подсчитывает количество символов новой строки в файле. Это работает, поскольку каждая строка заканчивается новой строкой, включая последнюю строку в файле. Каждая строка имеет символ новой строки, включая пустые строки, следовательно, количество символов новой строки == количество строк в файле. Теперь переменная lineNumber
в FileNumberReader
также представляет количество увиденных символов новой строки. Он начинается с нуля до того, как будет найден какой-либо символ новой строки, и увеличивается с каждым видимым символом новой строки. Поэтому, пожалуйста, не добавляйте единицу к номеру строки.
- person Alexander Torstling; 16.02.2016
wc -l
также сообщает об этом типе файла. Также см. http://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline
- person Alexander Torstling; 16.02.2016
wc -l
вернет 1. Я пришел к выводу, что все методы имеют недостатки, и реализовал один в зависимости от того, как я хотел бы, чтобы он вел себя, см. Другой мой ответ здесь.
- person Alexander Torstling; 16.02.2016
В принятом ответе есть одна ошибка для многострочных файлов, которые не заканчиваются новой строкой. Однострочный файл, заканчивающийся без новой строки, вернет 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
.
- person Syed Aqeel Ashiq; 30.01.2014
Я протестировал вышеуказанные методы для подсчета линий, и вот мои наблюдения для различных методов, проверенных в моей системе.
Размер файла: 1,6 Гб Методы:
Более того, подход Java8 кажется весьма удобным:
Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]
Я знаю, что это старый вопрос, но принятое решение не совсем соответствовало тому, что мне нужно было сделать. Итак, я усовершенствовал его, чтобы принимать различные символы конца строки (а не только перевод строки) и использовать указанную кодировку символов (вместо 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, как известно, ненадежны).
/**
* 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
И количество строк тоже неправильное
- person aw-think; 27.02.2015
BufferedInputStream
, когда вы собираетесь читать в свой собственный буфер. Кроме того, даже если ваш метод может иметь небольшое преимущество в производительности, он теряет гибкость, поскольку он больше не поддерживает только \r
терминаторы строки (старый MacOS) и не поддерживает все кодировки.
- person Holger; 14.11.2016
Простой способ использования сканера
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;
}
}
Кажется, что есть несколько разных подходов, которые вы можете использовать с LineNumberReader.
Я сделал это:
int lines = 0;
FileReader input = new FileReader(fileLocation);
LineNumberReader count = new LineNumberReader(input);
String line = count.readLine();
if(count.ready())
{
while(line != null) {
lines = count.getLineNumber();
line = count.readLine();
}
lines+=1;
}
count.close();
System.out.println(lines);
Более того, вы можете использовать метод Java BufferedReader lines () для возврата потока элементов, а затем использовать метод Stream count () для подсчета всех элементов. Затем просто добавьте единицу к выходным данным, чтобы получить количество строк в текстовом файле.
В качестве примера:
FileReader input = new FileReader(fileLocation);
LineNumberReader count = new LineNumberReader(input);
int lines = (int)count.lines().count() + 1;
count.close();
System.out.println(lines);
В системах на базе 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;
}
вы не можете работать с большим числом строк, любит 100 тысяч строк, потому что return from reader.getLineNumber - это int. вам нужен длинный тип данных для обработки максимального количества строк ..
int
может содержать значения до примерно 2 миллиардов. Если вы загружаете файл с более чем 2 миллиардами строк, у вас проблема с переполнением. Тем не менее, если вы загружаете неиндексированный текстовый файл с более чем двумя миллиардами строк, у вас, вероятно, есть другие проблемы.
- person Adam Norberg; 03.06.2011