Odpowiedzi:
To najszybsza wersja, jaką do tej pory znalazłem, około 6 razy szybsza niż readLines. W przypadku pliku dziennika 150 MB zajmuje to 0,35 sekundy, w porównaniu do 2,40 sekundy w przypadku użycia readLines (). Dla zabawy polecenie wc -l linuksa zajmuje 0,15 sekundy.
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();
}
}
EDYCJA, 9 1/2 lat później: praktycznie nie mam doświadczenia w Javie, ale tak czy inaczej próbowałem porównać ten kod z LineNumberReader
rozwiązaniem poniżej, ponieważ przeszkadzało mi, że nikt tego nie zrobił. Wydaje się, że szczególnie w przypadku dużych plików moje rozwiązanie jest szybsze. Chociaż wydaje się, że optymalizacja zajmuje sporo czasu. Grałem trochę z kodem i stworzyłem nową wersję, która jest konsekwentnie najszybsza:
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();
}
}
Benchmark uruchamia się dla pliku tekstowego 1,3 GB, oś y w sekundach. Wykonałem 100 przebiegów z tym samym plikiem i zmierzyłem każdy z nich System.nanoTime()
. Widać, że countLinesOld
ma kilka wartości odstających i countLinesNew
nie ma go, a chociaż jest tylko nieco szybszy, różnica jest istotna statystycznie. LineNumberReader
jest wyraźnie wolniejszy.
Wdrożyłem inne rozwiązanie problemu, uważam, że jest bardziej wydajne w liczeniu wierszy:
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
pole jest liczbą całkowitą ... nie będzie to tylko owinąć plików dłużej niż Integer.MAX_VALUE? Po co męczyć się tutaj tak długo?
wc -l
zlicza znaki nowego wiersza w pliku. Działa to, ponieważ każda linia kończy się nową linią, w tym ostatnią linią w pliku. Każda linia ma znak nowej linii, w tym puste linie, stąd liczba znaków nowej linii == liczba linii w pliku. Teraz lineNumber
zmienna w FileNumberReader
reprezentuje również liczbę znaków nowego wiersza, które widzimy. Zaczyna się od zera, zanim zostanie znaleziona nowa linia, i jest zwiększana z każdym znakiem nowej linii. Więc nie dodawaj go do numeru linii.
wc -l
też raportuje ten rodzaj pliku. Zobacz także stackoverflow.com/questions/729692/…
wc -l
zwróci 1. Doszedłem do wniosku, że wszystkie metody mają wady i zaimplementowałem jedną w oparciu o to, jak chciałbym się zachowywać, zobacz moją drugą odpowiedź tutaj.
Zaakceptowana odpowiedź ma wyłączony jeden błąd dla plików wieloliniowych, które nie kończą się na nowej linii. Plik jednowierszowy kończący się bez nowej linii zwraca 1, ale plik dwuliniowy kończący się bez nowej linii również zwraca 1. Oto implementacja zaakceptowanego rozwiązania, które to rozwiązuje. Kontrole EndWithoutNewLine są marnotrawstwem dla wszystkiego oprócz odczytu końcowego, ale powinny być trywialne pod względem czasu w porównaniu do ogólnej funkcji.
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();
}
}
Z java-8, możesz używać strumieni:
try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
long numOfLines = lines.count();
...
}
Odpowiedź z powyższą metodą count () dała mi błędne informacje o wierszach, jeśli plik nie miał nowej linii na końcu pliku - nie udało się zliczyć ostatniej linii w pliku.
Ta metoda działa dla mnie lepiej:
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
.
Wiem, że to stare pytanie, ale zaakceptowane rozwiązanie nie do końca pasuje do tego, czego potrzebowałem. Udoskonaliłem go tak, aby akceptował różne terminatory linii (zamiast tylko przejścia do nowego wiersza) i używał określonego kodowania znaków (zamiast ISO-8859- n ). Wszystko w jednej metodzie (odpowiednio refaktor):
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;
}
To rozwiązanie jest porównywalne pod względem prędkości do przyjętego rozwiązania, około 4% wolniej w moich testach (chociaż testy czasowe w Javie są notorycznie niewiarygodne).
Przetestowałem powyższe metody zliczania linii i oto moje obserwacje dotyczące różnych metod testowanych w moim systemie
Rozmiar pliku: 1,6 Gb Metody:
Co więcej, podejście Java8 wydaje się całkiem przydatne:
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();
}
}
Testowane na JDK8_u31. Ale w rzeczywistości wydajność jest niska w porównaniu do tej metody:
/**
* 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;
}
}
Testowany i bardzo szybki.
Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1
Liczba wierszy jest nawet błędna
BufferedInputStream
jeśli i tak chcesz czytać w swoim buforze. Poza tym, nawet jeśli twoja metoda może mieć niewielką przewagę wydajnościową, traci elastyczność, ponieważ nie obsługuje już \r
terminatorów pojedynczych linii (stary MacOS) i nie obsługuje każdego kodowania.
Prosty sposób za pomocą skanera
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);
}
Doszedłem do wniosku, że wc -l
: metoda liczenia znaków nowej linii jest w porządku, ale zwraca nieintuicyjne wyniki w plikach, w których ostatni wiersz nie kończy się znakiem nowego wiersza.
I rozwiązanie @ er.vikas oparte na LineNumberReader, ale dodanie jednego do liczby wierszy zwróciło nieintuicyjne wyniki w plikach, w których ostatni wiersz kończy się znakiem nowej linii.
Dlatego stworzyłem algo, które działa w następujący sposób:
@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"));
}
I wygląda to tak:
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;
}
}
Jeśli chcesz uzyskać intuicyjne wyniki, możesz tego użyć. Jeśli chcesz tylko wc -l
kompatybilności, użyj prostego rozwiązania @ er.vikas, ale nie dodawaj jednego do wyniku i ponów próbę:
try(LineNumberReader lnr = new LineNumberReader(new FileReader(new File("File1")))) {
while(lnr.skip(Long.MAX_VALUE) > 0){};
return lnr.getLineNumber();
}
Co powiesz na użycie klasy Process z kodu Java? A następnie odczytanie wyniku polecenia.
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);
}
Trzeba jednak spróbować. Opublikuje wyniki.
Jeśli nie masz żadnych struktur indeksu, nie obejdziesz się odczytywania pełnego pliku. Ale możesz to zoptymalizować, unikając czytania linii po linii i użyj wyrażenia regularnego, aby dopasować wszystkie terminatory linii.
To zabawne rozwiązanie działa naprawdę dobrze!
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;
}
}
W systemach uniksowych użyj wc
polecenia w wierszu polecenia.
Jedynym sposobem, aby dowiedzieć się, ile wierszy jest w pliku, jest ich policzenie. Możesz oczywiście utworzyć metrykę na podstawie danych, podając średnią długość jednej linii, a następnie uzyskać rozmiar pliku i podzielić go za pomocą śr. długość, ale to nie będzie dokładne.
Najlepszy kod zoptymalizowany dla plików wieloliniowych nieposiadających znaku nowej linii („\ n”) w 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;
}
Skaner z wyrażeniem regularnym:
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;
}
Nie zarejestrowałem tego.
jeśli tego użyjesz
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;
}
nie możesz biegać do dużych liczb, lubi 100 000 wierszy, ponieważ return from reader.getLineNumber is int. potrzebujesz długiego typu danych do przetworzenia maksymalnej liczby wierszy ..
int
Może pomieścić maksymalnie do wartości, w przybliżeniu, 2 mld. Jeśli ładujesz plik zawierający ponad 2 miliardy linii, masz problem z przepełnieniem. To powiedziawszy, jeśli ładujesz nieindeksowany plik tekstowy z ponad dwoma miliardami linii, prawdopodobnie masz inne problemy.