Domanda

Uso enormi file di dati, a volte ho solo bisogno di conoscere il numero di righe in questi file, di solito li apro e li leggo riga per riga fino a raggiungere la fine del file

Mi chiedevo se esiste un modo più intelligente per farlo

È stato utile?

Soluzione

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 1/2 anni dopo: non ho praticamente nessuna esperienza Java, ma comunque ho provato a confrontare questo codice con la soluzione LineNumberReader di seguito dato che mi dava fastidio che nessuno lo facesse. Sembra che soprattutto per file di grandi dimensioni la mia soluzione sia più veloce. Anche se sembra che occorrano alcune prove finché 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 ho misurato ciascuna 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.

 Grafico benchmark

Altri suggerimenti

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
}

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 un file di due righe che termina senza una nuova riga restituisce anche 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 , puoi utilizzare 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;
}

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 (anche se i test di cronometraggio in Java sono notoriamente inaffidabili).

Ho testato i metodi di cui sopra per contare le righe e qui ci sono le mie osservazioni per metodi diversi testati sul mio sistema

Dimensione file: 1,6 GB Metodi:

  1. Uso dello scanner : 35 secondi circa
  2. Uso di BufferedReader : circa 5 secondi
  3. Uso di Java 8 : circa 5 secondi
  4. Utilizzo di LineNumberReader : circa 5 secondi

Inoltre Java8 Approach sembra abbastanza utile: Files.lines (Paths.get (filePath), Charset.defaultCharset ()). count () [Tipo restituito: lungo]

/**
 * 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.

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 si presenta 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 desideri solo la compatibilità wc -l , usa 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à aggirare la lettura del file completo. Ma puoi ottimizzarlo evitando di leggerlo riga per riga e utilizzare una regex per abbinare tutti i terminatori di riga.

Questa divertente soluzione in realtà funziona davvero 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;
    }
}

Su sistemi basati su Unix, utilizzare il comando wc sulla 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.

Migliore 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)<*>quot;);  
    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 ..

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top