대용량 데이터 파일을 한 줄씩 복사하는 방법?


9

35GB CSV파일이 있습니다. 각 줄을 읽고 조건과 일치하면 줄을 새 CSV에 씁니다.

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

이것은 대략 소요됩니다. 7 분 그 과정을 훨씬 더 빠르게 할 수 있습니까?


1
예, Java 에서이 작업을 시도하지 않고 Linux / Windows / etc에서 직접 수행하십시오. 운영 체제. Java는 해석되며이를 사용하는 데에는 항상 오버 헤드가 있습니다. 이 외에도, 아니오, 속도를 높일 수있는 확실한 방법은 없으며 35GB의 경우 7 분이 합리적입니다.
Tim Biegeleisen

1
어쩌면 제거하면 parallel더 빨라질까요? 그리고 그것은 주위의 선을 뒤섞 지 않습니까?
Thilo

1
버퍼 크기를 설정할 수 BufferedWriter있는 생성자 를 사용 하여 직접 작성하십시오 . 더 큰 (또는 더 작은) 버퍼 크기가 차이를 만들 수 있습니다. BufferedWriter버퍼 크기를 호스트 운영 체제 버퍼 크기 와 일치 시키려고합니다 .
Abra

5
@TimBiegeleisen : "자바는 해석된다"는 기껏해야 오해의 소지가 있으며 거의 ​​항상 잘못된 것입니다. 예, 일부 최적화의 경우 JVM 세계를 떠나야 할 수도 있지만 Java에서 더 빠르게 수행하는 것은 확실히 가능 합니다.
Joachim Sauer

1
응용 프로그램을 프로파일 링하여 수행 할 수있는 핫스팟이 있는지 확인해야합니다. 원시 IO에 대해 많은 것을 할 수는 없습니다 (섹터 크기 등이 포함되어 있기 때문에 기본 8192 바이트 버퍼는 그렇게 나쁘지 않습니다).하지만 (내부적으로) 발생할 수있는 일이있을 수 있습니다 작업.
Kayaman

답변:


4

옵션 인 경우 GZipInputStream / GZipOutputStream을 사용하여 디스크 I / O를 최소화 할 수 있습니다.

Files.newBufferedReader / Writer는 8KB의 기본 버퍼 크기를 사용합니다. 더 큰 버퍼를 시도 할 수 있습니다.

문자열, 유니 코드로 변환하면 속도가 느려지고 메모리가 두 배로 사용됩니다. 사용되는 UTF-8은 StandardCharsets.ISO_8859_1만큼 간단하지 않습니다.

대부분 바이트 로 작업 할 수 있고 특정 CSV 필드에서만 문자열로 변환하는 것이 가장 좋습니다.

메모리 매핑 파일이 가장 적합 할 수 있습니다. 파일 범위에서 병렬 처리를 사용하여 파일을 분리 할 수 ​​있습니다.

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

이것은 약간의 코드가되어 줄을 올바르게 가져 (byte)'\n'오지만 지나치게 복잡하지는 않습니다.


바이트 읽기의 문제점은 실제 세계에서 줄의 시작 부분, 특정 문자의 부분 문자열을 평가하고 줄의 나머지 부분 만 아웃 파일에 작성해야한다는 것입니다. 따라서 줄을 바이트로만 읽을 수 없습니까?
membersound

방금 GZipInputStream + GZipOutputStream램 디스크에서 완전히 메모리를 테스트했습니다 . 성능이 훨씬 나빴습니다 ...
membersound

1
Gzip에서 : 느린 디스크가 아닙니다. 예, 바이트는 옵션입니다. 개행, 쉼표, 탭, 세미콜론은 모두 바이트로 처리 할 수 ​​있으며 문자열보다 훨씬 빠릅니다. UTF-8에서 UTF-16 문자로, 바이트에서 바이트에서 UTF-8로 바이트입니다.
Joop Eggen

1
시간이 지남에 따라 파일의 다른 부분을 매핑하십시오. 한도에 도달하면 MappedByteBuffer마지막으로 성공한 위치에서 새로 만듭니다 ( FileChannel.map시간 이 오래 걸림).
Joachim Sauer

1
2019 년에는 사용할 필요가 없습니다 new RandomAccessFile(…).getChannel(). 그냥 사용하십시오 FileChannel.open(…).
Holger

0

당신은 이것을 시도 할 수 있습니다 :

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

나는 그것이 당신을 1-2 분 절약 할 것이라고 생각합니다. 버퍼 크기를 지정하여 약 4 분 안에 내 컴퓨터에서 테스트를 수행 할 수 있습니다.

더 빠를 수 있습니까? 이 시도:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

이렇게하면 3-4 분이 절약됩니다.

그래도 충분하지 않다면. (당신이 질문을하는 이유는 아마도 작업을 반복적으로 실행해야하기 때문입니다). 1 분 또는 몇 초 안에 완료하려면 그런 다음 데이터를 처리하고 db에 저장 한 다음 여러 서버로 작업을 처리해야합니다.


마지막 예 : cbuf콘텐츠 를 어떻게 평가하고 부분 만 쓸 수 있습니까? 버퍼가 가득 차면 재설정해야합니까? (버퍼가 꽉 찼는 지 어떻게 알 수 있습니까?)
membersound

0

귀하의 모든 제안 덕분에, 내가 생각해 낸 가장 빠른 속도로 작가를 교환하는 것이 BufferedOutputStream약 25 % 개선되었습니다.

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

여전히 내 경우 BufferedReader보다 성능이 우수합니다 BufferedInputStream.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.