Pergunta

Eu uso arquivos de dados enormes, às vezes eu só precisa saber o número de linhas nesses arquivos, geralmente eu abri-los e lê-los linha por linha até eu chegar ao final do arquivo

Eu queria saber se existe uma maneira mais inteligente de fazer isso

Foi útil?

Solução

Esta é a versão mais rápida que eu encontrei até agora, cerca de 6 vezes mais rápido do que readlines. Em um arquivo de log 150MB isso leva 0,35 segundos, contra 2,40 segundos ao usar readlines (). Apenas por diversão, linux' wc -l comando leva 0,15 segundos.

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 anos mais tarde: Eu tenho praticamente nenhuma experiência java, mas mesmo assim eu tentei referência este código contra a solução LineNumberReader abaixo, uma vez que me incomodou que ninguém fez isso. Parece que, especialmente para arquivos grandes minha solução é mais rápido. Embora pareça tomar algumas corridas até o otimizador faz um trabalho decente. Eu joguei um pouco com o código, e produziram uma nova versão que é consistentemente mais rápido:

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();
    }
}

resuls Índice de referência para um arquivo de texto 1.3GB, eixo Y em segundos. Eu já realizados 100 corridas com a mesma lima, e mediu cada corrida com System.nanoTime(). Você pode ver que countLinesOld tem alguns outliers e countLinesNew tem nenhum e enquanto é apenas um pouco mais rápido, a diferença é estatisticamente significativa. LineNumberReader é claramente mais lento.

 benchmark Plot

Outras dicas

Eu implementaram uma outra solução para o problema, eu encontrei-o mais eficiente em linhas de contagem:

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
}

A resposta aceita tem um off por um erro para arquivos com ramal que não terminam em nova linha. Um arquivo de uma linha que termina sem uma nova linha voltaria 1, mas um arquivo de duas linhas terminando sem uma nova linha voltaria 1 também. Aqui está uma implementação da solução aceita que corrige isso. As verificações endsWithoutNewLine são um desperdício de tudo, mas a leitura final, mas deve ser tempo trivial sábio em comparação com a função global.

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();
    }
}

Com , você pode usar fluxos:

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}

A resposta com a contagem () método acima deu-me alinhar miscounts se um arquivo não tinha uma nova linha no final do arquivo -. Ele não conseguiu contar a última linha no arquivo

Este método funciona melhor para mim:

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;
}

Eu sei que isto é uma questão de idade, mas a solução aceita não combinam muito bem o que eu precisava fazer. Portanto, eu refinado para aceitar várias terminadores de linha (em vez de alimentação de linha apenas) e a utilizar uma codificação de caracteres especificado (em vez de ISO-8859 n ). Tudo em um método (refactor conforme apropriado):

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;
}

Esta solução é comparável em velocidade para a solução aceita, cerca de 4% mais lento em meus testes (embora testes de tempo em Java são notoriamente não confiáveis).

I testados os métodos acima para as linhas de contagem e aqui são os métodos de observações para diferentes como testado no meu sistema

Tamanho: 1.6 Gb Métodos:

  1. Usando o Scanner : 35s aprox
  2. Usando BufferedReader : 5s aprox
  3. Usando Java 8 : 5s aprox
  4. Usando LineNumberReader : 5s aprox

Além disso Java8 abordagem parece bastante útil: Files.lines (Paths.get (filePath), Charset.defaultCharset ()) count () [Tipo de retorno: 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();
    }
}

Testado em JDK8_u31. Mas na verdade o desempenho é lento em comparação com este método:

/**
 * 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;
    }
}

Testado e muito rápido.

Uma maneira simples e direta usando 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);
    }

concluí que wc -l: s método de novas linhas de contagem é bom, mas retorna resultados não-intuitivas em arquivos, onde a última linha não termina com uma nova linha.

E @ er.vikas solução baseada em LineNumberReader mas adicionando um ao número de linhas retornado resultados não-intuitivas em arquivos, onde a última linha não termina com nova linha.

Por isso, fez um algo que lida com o seguinte:

@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 parece que isso:

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 você quer resultados intuitivos, você pode usar este. Se você quiser apenas compatibilidade wc -l, uso simples @ er.vikas solução, mas não adicionar um para o resultado e repetir o salto:

try(LineNumberReader lnr = new LineNumberReader(new FileReader(new File("File1")))) {
    while(lnr.skip(Long.MAX_VALUE) > 0){};
    return lnr.getLineNumber();
}

Que tal usar a classe Process de dentro do código Java? E, em seguida, ler a saída do 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);
}

Necessidade de experimentar embora. Vou postar os resultados.

Se você não tem nenhum estruturas de índice, você não dar a volta a leitura do arquivo completo. Mas você pode otimizá-lo, evitando a lê-lo linha por linha e usar um regex para coincidir com todos os terminadores de linha.

Esta solução engraçado funciona realmente bom na verdade!

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;
    }
}

sistemas baseados em Unix On, use o comando wc na linha de comando.

única maneira de saber quantas linhas existem no arquivo é a contá-los. Pode, claro, criar uma métrica de seus dados dando-lhe um comprimento médio de uma linha e, em seguida, obter o tamanho do arquivo e dividir isso com avg. comprimento, mas que não vai ser preciso.

Melhor código otimizado para arquivos com ramal que não tenham carácter de nova linha ( '\ n') no 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 com 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;
}

não têm cronometrado-lo.

Se você usar este

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;
}

você não pode correr para linhas grandes NUM, gosta 100K linhas, porque o retorno de reader.getLineNumber é int. você precisa de longo tipo de dados para linhas máximas processo ..

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top