Risposte:
Questa è la versione più veloce che ho trovato finora, circa 6 volte più veloce di readLines. Su un file di registro da 150 MB, sono necessari 0,35 secondi, rispetto a 2,40 secondi quando si utilizza readLines (). Per divertimento, il comando wc -l di linux richiede 0,15 secondi.
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 anni e mezzo dopo: non ho praticamente nessuna esperienza Java, ma comunque ho cercato di confrontare questo codice con la LineNumberReader
soluzione di seguito poiché mi ha dato fastidio che nessuno l'abbia fatto. Sembra che soprattutto per file di grandi dimensioni la mia soluzione sia più veloce. Anche se sembra richiedere alcune correzioni fino a quando l'ottimizzatore non fa un lavoro decente. Ho giocato un po 'con il codice e ho prodotto una nuova versione che è costantemente più veloce:
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();
}
}
Risultati benchmark per un file di testo da 1,3 GB, asse y in secondi. Ho eseguito 100 corse con lo stesso file e misurato ogni corsa con System.nanoTime()
. Puoi vedere che countLinesOld
ha alcuni valori anomali e countLinesNew
non ne ha e, sebbene sia solo un po 'più veloce, la differenza è statisticamente significativa. LineNumberReader
è chiaramente più lento.
Ho implementato un'altra soluzione al problema, l'ho trovato più efficiente nel conteggio delle righe:
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
Il lineNumber
campo è un numero intero ... Non è sufficiente per i file più lunghi di Integer.MAX_VALUE? Perché preoccuparsi di saltare a lungo qui?
wc -l
conta il numero di caratteri newline nel file. Funziona poiché ogni riga è terminata con una nuova riga, inclusa la riga finale in un file. Ogni riga ha un carattere di nuova riga, comprese le righe vuote, quindi il numero di caratteri di nuova riga == numero di righe in un file. Ora, la lineNumber
variabile in FileNumberReader
rappresenta anche il numero di caratteri newline visti. Inizia da zero, prima che sia stata trovata una nuova riga, e viene aumentata con ogni carattere newline visto. Quindi non aggiungerne uno al numero di riga, per favore.
wc -l
riporta anche questo tipo di file. Vedere anche stackoverflow.com/questions/729692/...
wc -l
restituirebbe 1. Ho concluso che tutti i metodi hanno dei difetti e ne ho implementato uno in base al modo in cui vorrei che si comportasse, vedi qui la mia altra risposta.
La risposta accettata ha un errore di un errore per i file multilinea che non terminano con la nuova riga. Un file di una riga che termina senza una nuova riga restituisce 1, ma anche un file di due righe che termina senza una nuova riga restituisce 1. Ecco un'implementazione della soluzione accettata che risolve questo problema. Il fine senza controlli NewLine sono dispendiosi per tutto tranne che per la lettura finale, ma dovrebbe essere banale in termini di tempo rispetto alla funzione generale.
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();
}
}
Con java-8, puoi usare gli stream:
try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
long numOfLines = lines.count();
...
}
La risposta con il metodo count () sopra mi ha dato errori di riga se un file non aveva una nuova riga alla fine del file - non è riuscito a contare l'ultima riga nel file.
Questo metodo funziona meglio per me:
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
.
So che questa è una vecchia domanda, ma la soluzione accettata non corrispondeva esattamente a ciò di cui avevo bisogno. Quindi, l'ho perfezionato per accettare vari terminatori di riga (anziché solo avanzamento di riga) e utilizzare una codifica di caratteri specificata (anziché ISO-8859- n ). Metodo tutto in uno (refactor a seconda dei casi):
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;
}
Questa soluzione è paragonabile in termini di velocità alla soluzione accettata, circa il 4% più lenta nei miei test (sebbene i test di cronometraggio in Java siano notoriamente inaffidabili).
Ho testato i metodi di cui sopra per contare le righe e qui ci sono le mie osservazioni per i metodi diversi testati sul mio sistema
Dimensione file: 1.6 Gb Metodi:
Inoltre Java8 Approach sembra abbastanza utile:
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();
}
}
Testato su JDK8_u31. Ma in effetti le prestazioni sono lente rispetto a questo metodo:
/**
* 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;
}
}
Testato e molto veloce.
Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1
E anche il numero di righe è sbagliato
BufferedInputStream
quando hai intenzione di leggere nel tuo buffer comunque. Inoltre, anche se il tuo metodo potrebbe avere un leggero vantaggio in termini di prestazioni, perde flessibilità, poiché non supporta più i \r
terminatori a linea singola (vecchio MacOS) e non supporta tutte le codifiche.
Un modo semplice con Scanner
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);
}
Ho concluso che wc -l
: il metodo di conteggio delle nuove righe va bene ma restituisce risultati non intuitivi su file in cui l'ultima riga non termina con una nuova riga.
E la soluzione @ er.vikas basata su LineNumberReader ma l'aggiunta di uno al conteggio delle righe ha restituito risultati non intuitivi sui file in cui l'ultima riga termina con newline.
Ho quindi creato un algo che gestisce come segue:
@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"));
}
E sembra così:
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;
}
}
Se vuoi risultati intuitivi, puoi usarlo. Se vuoi solo la wc -l
compatibilità, usa semplicemente la soluzione @ er.vikas, ma non aggiungerne una al risultato e riprovare a saltare:
try(LineNumberReader lnr = new LineNumberReader(new FileReader(new File("File1")))) {
while(lnr.skip(Long.MAX_VALUE) > 0){};
return lnr.getLineNumber();
}
Che ne dici di usare la classe Process dal codice Java? E poi leggendo l'output del comando.
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);
}
Devo provarlo però. Pubblicherà i risultati.
Se non si dispone di alcuna struttura di indice, non si potrà evitare la lettura del file completo. Ma puoi ottimizzarlo evitando di leggerlo riga per riga e utilizzare un regex per abbinare tutti i terminatori di riga.
Questa soluzione divertente funziona davvero molto bene!
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;
}
}
Sui sistemi basati su Unix, utilizzare il wc
comando dalla riga di comando.
L'unico modo per sapere quante righe ci sono nel file è contarle. Puoi ovviamente creare una metrica dai tuoi dati che ti dia una lunghezza media di una riga e quindi ottenere la dimensione del file e dividerla con avg. lunghezza ma non sarà preciso.
Miglior codice ottimizzato per file a più righe senza carattere di nuova riga ('\ n') in 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;
}
Scanner con regex:
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;
}
Non l'ho cronometrato.
se lo usi
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;
}
non puoi correre su grandi numeri, ti piacciono le 100.000 righe, perché il ritorno da reader.getLineNumber è int. hai bisogno di un lungo tipo di dati per elaborare le righe massime.
int
può contenere valori fino a circa 2 miliardi di euro. Se stai caricando un file con più di 2 miliardi di righe, hai un problema di overflow. Detto questo, se stai caricando un file di testo non indicizzato con più di due miliardi di righe, probabilmente hai altri problemi.