Java 파일의 줄 수


213

나는 거대한 데이터 파일을 사용하며 때로는이 파일의 줄 수만 알아야합니다. 일반적으로 파일을 열고 파일 끝에 도달 할 때까지 한 줄씩 읽습니다.

더 똑똑한 방법이 있는지 궁금합니다.

답변:


237

이것은 지금까지 찾은 가장 빠른 버전으로 readLine보다 약 6 배 빠릅니다. 150MB 로그 파일에서는 readLines ()를 사용할 때 2.40 초와 비교하여 0.35 초가 걸립니다. linux의 wc -l 명령은 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 년 후 : 나는 실제로 자바 경험이 없지만 어쨌든 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.3GB 텍스트 파일, y 축 (초)입니다. 나는 같은 파일로 100 회 실행을 수행하고을 사용하여 각 실행을 측정했습니다 System.nanoTime(). 당신은 countLinesOld몇 가지 특이 치가 countLinesNew있고 아무것도 없다는 것을 알 수 있으며 조금 더 빠르지 만 그 차이는 통계적으로 중요합니다. LineNumberReader분명히 느리다.

벤치 마크 플롯


5
BufferedInputStream이 버퍼링을 수행해야하므로 중간 byte [] 배열을 사용하여 더 빠르게 만드는 방법을 모르겠습니다. 어쨌든 readLine ()을 반복해서 사용하는 것보다 API를 최적화하는 것보다 훨씬 나을 것 같지 않습니다.
wds

54
입력이 끝나면 InputStream을 닫을 것입니다.
bendin

5
버퍼링이 도움이 되었다면 BufferedInputStream은 기본적으로 8K를 버퍼링하기 때문입니다. 바이트 []를이 크기 이상으로 늘리면 BufferedInputStream을 삭제할 수 있습니다. 예를 들어 1024 * 1024 바이트를 시도하십시오.
Peter Lawrey

8
두 가지 : (1) Java 소스에서 줄 종결 자의 정의는 캐리지 리턴, 줄 바꿈 또는 줄 바꿈이 뒤 따르는 캐리지 리턴입니다. 라인 터미네이터로 사용 된 CR에는 솔루션이 작동하지 않습니다. 물론 CR을 기본 줄 종결 자로 사용하는 유일한 OS는 Mac OS X 이전의 Mac OS입니다. (2) 귀하의 솔루션은 US-ASCII 또는 UTF-8과 같은 문자 인코딩을 가정합니다. UTF-16과 같은 인코딩의 경우 줄 수가 정확하지 않을 수 있습니다.
Nathan Ryan

2
멋진 코드 ... 400MB 텍스트 파일의 경우 1 초 밖에 걸리지 않았습니다. 고마워요 @martinus
user3181500

199

문제에 대한 다른 솔루션을 구현했는데 행을 계산하는 것이 더 효율적이라는 것을 알았습니다.

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
}

LineNumberReaderlineNumber필드는 정수입니다. Integer.MAX_VALUE보다 긴 파일 만 래핑하지 않습니까? 왜 여기서 길게 건너 뛰는가?
epb

1
카운트에 하나를 추가하는 것은 실제로 올바르지 않습니다. wc -l파일의 개행 문자 수를 계산합니다. 이것은 모든 줄이 파일의 마지막 줄을 포함하여 줄 바꿈으로 끝나기 때문에 작동합니다. 모든 줄에는 빈 줄을 포함하여 줄 바꿈 문자가 있으므로 줄 바꿈 문자 수 == 파일의 줄 수입니다. 이제 lineNumberin 변수 FileNumberReader는 줄 바꿈 문자 수를 나타냅니다. 개행이 발견되기 전에 0에서 시작하며 모든 개행 문자가 표시 될 때마다 증가합니다. 따라서 줄 번호에 하나를 추가하지 마십시오.
Alexander Torstling

1
@PB_MLT : 줄 바꿈이없는 한 줄의 파일은 0 줄로보고되는 것이 맞지만, wc -l이런 종류의 파일을보고 하는 방법 입니다. stackoverflow.com/questions/729692/…
Alexander Torstling

@PB_MLT : 파일이 개행으로 만 구성되면 반대의 문제가 발생합니다. 귀하의 제안 된 알고리즘은 0 wc -l을 반환하고 1을 반환합니다. 나는 모든 방법에 결함이 있다고 결론 내렸다.
Alexander Torstling

3
당신의 아무도 그것을 벤치마킹하지 않은 것 때문에 나는 아래로,이 응답을 투표 한
amstegraf

30

수락 된 답변에는 줄 바꿈으로 끝나지 않는 여러 줄 파일에 대해 하나의 오류가 있습니다. 줄 바꿈없이 끝나는 한 줄 파일은 1을 반환하지만 줄 바꿈없이 끝나는 두 줄 파일도 1을 반환합니다. 다음은이를 해결하는 수용 솔루션의 구현입니다. endsWithoutNewLine 검사는 최종 읽기 이외의 모든 것에 대해 낭비이지만 전체 기능에 비해 시간이 현명하지 않아야합니다.

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

6
잘 잡았습니다. 수락 된 답변을 수정하지 않고 왜 댓글에 메모를했는지 모르겠습니다. 대부분의 사람들은이 글을 읽지 않을 것입니다.
Ryan

@Ryan, 90 세 이상의 공감대를 가진 4 년 된 답변을 편집하는 것이 옳지 않다.
DMulligan

@AFinkelstein, 이것이이 사이트를 너무나 훌륭하게 만들어서 가장 많이 투표 된 답변을 편집 할 있다고 생각합니다 .
Sebastian

3
이 솔루션은 캐리지 리턴 (\ r) 및 캐리지 리턴과 줄 바꿈 (\ r \ n)을 처리하지 않습니다.
Simon Brandhof-

@Simon Brandhof, 왜 캐리지 리턴이 다른 라인으로 계산되는지 혼란 스럽습니까? "\ n"은 캐리지 리턴 줄 바꿈이므로 "\ r \ n"을 쓰는 사람은 무언가를 이해하지 못합니다 ... 또한 그는 char로 char을 검색하므로 누군가 "\ r \ n "여전히"\ n "을 잡고 줄을 계산합니다. 어느 쪽이든 나는 그가 요점을 잘 지적했다고 생각합니다. 그러나 라인 수를 얻는 데 충분하지 않은 많은 시나리오가 있습니다.
nckbrz

22

스트림을 사용할 수 있습니다.

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

1
코드에 오류가 있습니다. 간단하지만 매우 느립니다 ... 아래의 답변을 아래에서보십시오.
Ernestas Gruodis

12

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

이 경우 LineNumberReader를 사용할 필요가 없습니다. 간단히 BufferedReader를 사용하면에 대한 long 데이터 유형을 사용할 수있는 유연성이 있습니다 cnt.
Syed Aqeel Ashiq

[INFO] PMD 실패 : xx : 19 규칙 : EmptyWhileStmt 우선 순위 : 3 while 문을 비우지 마십시오.
Chhorn Elit

8

나는 이것이 오래된 질문이라는 것을 알고 있지만 수용 된 해결책은 내가 해야하는 것과 일치하지 않았다. 따라서 줄 바꿈이 아닌 다양한 줄 종결자를 수락하고 지정된 문자 인코딩 (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의 타이밍 테스트는 신뢰할 수 없음).


8

위의 라인 계산 방법을 테스트했으며 다음은 시스템에서 테스트 한 다른 방법에 대한 관찰 결과입니다.

파일 크기 : 1.6 Gb 방법 :

  1. 스캐너 사용 : 약 35 초
  2. BufferedReader 사용 : 약 5 초
  3. Java 8 사용 : 약 5 초
  4. LineNumberReader 사용 : 약 5 초

또한 Java8 접근법은 매우 편리합니다.

Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]

5
/**
 * 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그리고 라인의 수는 잘못된 너무 짝수
AW-생각

32 비트 컴퓨터에서 테스트했습니다. 아마도 64 비트에서는 다른 결과가 나올 것입니다. 그리고 내가 기억 한 것처럼 10 배 이상의 차이가있었습니다. 당신은 어딘가에 카운트 라인을 텍스트를 게시 할 수 있습니까? 메모장 2를 사용하여 편의를 위해 줄 바꿈을 볼 수 있습니다.
Ernestas Gruodis

그것은 차이가 될 수 있습니다.
aw-think

성능에 관심이 있다면 BufferedInputStream어쨌든 자신의 버퍼를 읽을 때를 사용해서는 안됩니다 . 또한, 분석법에 약간의 성능 이점이 있더라도 \r더 이상 단독 라인 터미네이터 (이전 MacOS)를 지원하지 않으며 모든 인코딩을 지원하지 않기 때문에 유연성이 떨어 집니다.
Holger

4

스캐너를 사용하는 간단한 방법

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

3

wc -l바꿈을 계산 하는 : s 방법은 훌륭하지만 마지막 줄이 줄 바꿈으로 끝나지 않는 파일에 대해서는 직관적이지 않은 결과를 반환합니다.

LineNumberReader를 기반으로하는 @ er.vikas 솔루션이지만 줄 수에 1을 추가하면 마지막 줄이 줄 바꿈으로 끝나는 파일에 직관적이지 않은 결과가 반환됩니다.

따라서 다음과 같이 처리하는 알고리즘을 만들었습니다.

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

2

Java 코드 내에서 Process 클래스를 사용하는 것은 어떻습니까? 그런 다음 명령의 출력을 읽습니다.

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

그래도 시도해야합니다. 결과를 게시합니다.


1

인덱스 구조가 없으면 전체 파일을 읽을 수 없습니다. 그러나 한 줄씩 읽지 않고 정규식을 사용하여 모든 줄 종결자를 일치시키지 않고 최적화 할 수 있습니다.


깔끔한 아이디어처럼 들립니다. 누구나 그것을 시도하고 정규 표현식을 가지고 있습니까?
willcodejavaforfood

1
나는 그것이 좋은 아이디어라고 의심한다 : 그것은 전체 파일을 한 번에 읽을 필요가 있고 (martinus는 이것을 피한다) 정규 표현식은 그러한 사용법 (고정 된 문자의 간단한 검색)을 위해 과도하고 (느려진다).
PhiLho

@will : / \ n /은 어떻습니까? @PhiLo : Regex Executor는 고도로 조정 된 성능 기계입니다. 모든 것을 메모리로 읽어야한다는 경고를 제외하고 수동 구현이 더 빠를 수 있다고 생각하지 않습니다.
David Schmitt

1

이 재미있는 솔루션은 실제로 실제로 잘 작동합니다!

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

0

Unix 기반 시스템의 wc경우 명령 행 에서 명령을 사용하십시오 .


@IainmH, 두 번째 제안은 현재 디렉토리의 항목 수를 계산합니다. 의도 된 것이 아닌가? (또는 OP에 의해 요청)
Archetypal Paul

@IainMH : 그것은 wc가 어쨌든하는 일입니다 (파일을 읽고 줄 끝을 세는 것).
필리

@PhiLho 줄을 세려면 -l 스위치를 사용해야합니다. (안 그래?
오랜만이다

@ 폴-물론 100 % 맞습니다. 나의 유일한 방어는 내가 커피 전에 그것을 게시했다는 것입니다. 지금은 버튼처럼 날카 롭습니다. :디
Iain Holder

0

파일에 몇 줄이 있는지 아는 유일한 방법은 그 개수를 세는 것입니다. 물론 데이터에서 메트릭을 만들어 한 줄의 평균 길이를 얻은 다음 파일 크기를 가져와 평균으로 나눌 수 있습니다. 길이는 정확하지 않습니다.


1
어떤 명령 줄 도구를 사용하든 관계없이 흥미로운 다운 보트는 모두 내부적으로 만 마찬가지입니다. 줄 수를 알아낼 수있는 마법의 방법은 없습니다. 수작업으로 계산해야합니다. 물론 메타 데이터로 저장할 수는 있지만 완전히 다른 이야기입니다 ...
Esko

0

EOF에서 줄 바꿈 ( '\ n') 문자가없는 여러 줄 파일에 가장 최적화 된 코드입니다.

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

0

정규식 스캐너 :

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

그것을 시계하지 않았습니다.


-2

이것을 사용하면

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

reader.getLineNumber의 리턴이 int이기 때문에 100K 행을 좋아하는 큰 수의 행으로 실행할 수 없습니다. 최대 행을 처리하려면 긴 유형의 데이터가 필요합니다.


14
int약 2 억까지의 값을 저장할 수 있습니다. 20 억 줄 이상의 파일을로드하는 경우 오버플로 문제가 있습니다. 즉, 인덱싱되지 않은 텍스트 파일을 20 억 줄 이상으로로드하는 경우 다른 문제가있을 수 있습니다.
Adam Norberg
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.