java는 불완전한 상태에서 레코드를 끊지 않고 여러 줄 레코드를 분할하면서 큰 파일을 작은 파일로 분할합니다.

Nov 30 2020

파일에 여러 줄로 분할 된 레코드가 있습니다. 레코드의 끝을 식별하는 유일한 방법은 새 레코드가 ABC로 시작하는 경우입니다. 아래는 샘플입니다. 파일 크기는 5-10GB가 될 수 있으며 파일을 분할하는 데만 효율적인 Java 논리를 찾고 있지만 (모든 줄을 읽을 필요가 없음) 논리 분할은 새 레코드로 새 파일을 시작하도록 확인해야합니다. 이 경우 "ABC"입니다.

몇 가지 세부 사항을 추가하여 파일 분할을 찾고 있으며 마지막 레코드를 분할하는 동안 파일에서 올바르게 종료되어야합니다.

누군가 제안 해 주시겠습니까?

HDR
ABCline1goesonforrecord1   //first record 
line2goesonForRecord1      
line3goesonForRecord1          
line4goesonForRecord1
ABCline2goesOnForRecord2  //second record
line2goesonForRecord2
line3goesonForRecord2
line4goesonForRecord2
line5goesonForRecord2
ABCline2goesOnForRecord3     //third record
line2goesonForRecord3
line3goesonForRecord3
line4goesonForRecord3
TRL

답변

1 OctavianR. Nov 30 2020 at 20:36

따라서 이것이 필요한 코드입니다. 10Gb 파일에서 테스트했는데 파일을 분할하는 데 64 초가 걸립니다.

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.TimeUnit;

public class FileSplitter {

    private final Path filePath;
    private BufferedWriter writer;
    private int fileCounter = 1;

    public static void main(String[] args) throws Exception {
        long startTime = System.nanoTime();
        new FileSplitter(Path.of("/tmp/bigfile.txt")).split();
        System.out.println("Time to split " + TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime));
    }

    private static void generateBigFile() throws Exception {
        var writer = Files.newBufferedWriter(Path.of("/tmp/bigfile.txt"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
        for (int i = 0; i < 100_000; i++) {
            writer.write(String.format("ABCline1goesonforrecord%d\n", i + 1));
            for (int j = 0; j < 10_000; j++) {
                writer.write(String.format("line%dgoesonForRecord%d\n", j + 2, i + 1));
            }
        }

        writer.flush();
        writer.close();
    }

    public FileSplitter(Path filePath) {
        this.filePath = filePath;
    }

    void split() throws IOException {
        try (var stream = Files.lines(filePath, StandardCharsets.UTF_8)) {
            stream.forEach(line -> {
                if (line.startsWith("ABC")) {
                    closeWriter();
                    openWriter();
                }
                writeLine(line);
            });
        }
        closeWriter();
    }

    private void writeLine(String line) {
        if (writer != null) {
            try {
                writer.write(line);
                writer.write("\n");
            } catch (IOException e) {
                throw new UncheckedIOException("Failed to write line to file part", e);
            }
        }
    }

    private void openWriter() {
        if (this.writer == null) {
            var filePartName = filePath.getFileName().toString().replace(".", "_part" + fileCounter + ".");
            try {
                writer = Files.newBufferedWriter(Path.of("/tmp/split", filePartName), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
            } catch (IOException e) {
                throw new UncheckedIOException("Failed to write line to file", e);
            }
            fileCounter++;
        }
    }

    private void closeWriter() {
        if (writer != null) {
            try {
                writer.flush();
                writer.close();
                writer = null;
            } catch (IOException e) {
                throw new UncheckedIOException("Failed to close writer", e);
            }
        }
    }
}

Btw, 스캐너 솔루션도 작동합니다.

모든 줄을 읽지 않는 것에 관해서는 왜 이것을 원하지 않는지 모르겠습니다. 모든 줄을 읽지 않기로 선택하면 (가능함) 먼저 솔루션을 과도하게 복잡하게 만들고 두 번째로 분할에 통합해야하는 논리로 인해 성능이 저하 될 것이라고 확신합니다.

JohnSmith Nov 30 2020 at 19:28

나는 이것을 테스트하지 않았지만 이와 같은 것이 작동해야합니다. 메모리의 전체 파일을 한 번에 한 줄씩 읽지 않으므로 나쁘지 않아야합니다.

public void spiltRecords(String filename) {
        /*
            HDR
            ABCline1goesonforrecord1   //first record
            line2goesonForRecord1
            line3goesonForRecord1
            line4goesonForRecord1
            ABCline2goesOnForRecord2  //second record
            line2goesonForRecord2
            line3goesonForRecord2
            line4goesonForRecord2
            line5goesonForRecord2
            ABCline2goesOnForRecord3     //third record
            line2goesonForRecord3
            line3goesonForRecord3
            line4goesonForRecord3
            TRL
         */
        try {
            Scanner scanFile = new Scanner(new File(filename));
            // now you do not want to edit the existing file in case things go wrong. one way is to get list of index
            // where a new record starts.
            LinkedList<Long> startOfRecordIndexes = new LinkedList<>();
            long index = 0;
            while (scanFile.hasNext()) {
                if (scanFile.nextLine().startsWith("ABC")) {
                    startOfRecordIndexes.add(index);
                }
                index++;
            }

            // Once you have the starting index for all records you can iterate through the list and create new records
            scanFile = scanFile.reset();
            index = 0;

            BufferedWriter writer = null;
            
            while (scanFile.hasNext()) {
                if (!startOfRecordIndexes.isEmpty() && index == startOfRecordIndexes.peek()) {
                    if(writer != null) {
                        writer.write("TRL");
                        writer.close();
                    }
                    writer = new BufferedWriter(new OutputStreamWriter(
                        new FileOutputStream("Give unique filename"), StandardCharsets.UTF_8));
                    writer.write("HDR");
                    writer.write(scanFile.nextLine());

                    startOfRecordIndexes.remove();
                } else {
                    writer.write(scanFile.nextLine());
                }
            }
            // Close the last record
            if(writer != null) {
                writer.write("TRL");
                writer.close();
            }
        } catch (IOException e) {
            // deal with exception
        }
    }