【问题标题】:Splitting and Merging large files (size in GB) in Java在 Java 中拆分和合并大文件(大小以 GB 为单位)
【发布时间】:2015-09-19 16:05:00
【问题描述】:

假设,

  • 我将 2590400 KB(约 2.5 GB)文件分成 30 个部分。

  • 它将生成 30 个文件,大小为 86347 KB
    这似乎是正确的,2590400/30 = 86346.66666667

  • 现在,如果我再次合并所有部分 (30),它会生成 3453873 KB 文件,它应该是 2590410 KB

谁能帮助我为什么会有这种差异?我正在使用下面的代码来合并和拆分文件。

SplitFile.java

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;

/**
 * @author vishal.zanzrukia
 * 
 */
public class SplitFile {

    public static final String INPUT_FILE = "D:\\me\\projects\\input\\file\\path.txt";
    public static final int NUMBER_OF_OUTPUT_FILES = 30;
    public static final String FILE_SUFFIX = ".txt";

    /**
     * split file
     * 
     * @throws Exception
     */
    static void splitFile() throws Exception{

        File inputFile = new File(INPUT_FILE + "_Splits");
        inputFile.mkdir();

        RandomAccessFile raf = new RandomAccessFile(INPUT_FILE, "r");

        long sourceSize = raf.length();
        long bytesPerSplit = sourceSize / NUMBER_OF_OUTPUT_FILES;
        long remainingBytes = sourceSize % NUMBER_OF_OUTPUT_FILES;

        int maxReadBufferSize = 8 * 1024; // 8KB
        for (int destIx = 1; destIx <= NUMBER_OF_OUTPUT_FILES; destIx++) {
            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(INPUT_FILE + "_Splits\\split." + destIx + FILE_SUFFIX));
            if (bytesPerSplit > maxReadBufferSize) {
                long numReads = bytesPerSplit / maxReadBufferSize;
                long numRemainingRead = bytesPerSplit % maxReadBufferSize;
                for (int i = 0; i < numReads; i++) {
                    readWrite(raf, bw, maxReadBufferSize);
                }
                if (numRemainingRead > 0) {
                    readWrite(raf, bw, numRemainingRead);
                }
            } else {
                readWrite(raf, bw, bytesPerSplit);
            }
            bw.close();
        }
        if (remainingBytes > 0) {
            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream("split." + NUMBER_OF_OUTPUT_FILES + 1));
            readWrite(raf, bw, remainingBytes);
            bw.close();
        }
        raf.close();
    }

    /**
     * join file
     * 
     * @throws Exception
     */
    static void joinFiles() throws Exception{
        int maxReadBufferSize = 8 * 1024; 

        BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(INPUT_FILE + "_Splits\\fullJoin" + FILE_SUFFIX));
        File inputFileDir = new File(INPUT_FILE + "_Splits");
        RandomAccessFile raf = null;
        if(inputFileDir.isDirectory()){
            for(File file : inputFileDir.listFiles()){
                raf = new RandomAccessFile(file, "r");
                long numReads = raf.length() / maxReadBufferSize;
                long numRemainingRead = raf.length()  % maxReadBufferSize;
                for (int i = 0; i < numReads; i++) {
                    readWrite(raf, bw, maxReadBufferSize);
                }
                if (numRemainingRead > 0) {
                    readWrite(raf, bw, numRemainingRead);
                }
                raf.close();
            }
        }
        bw.close();
    }

    public static void mergeFiles() {

        File[] files = new File[NUMBER_OF_OUTPUT_FILES];
        for(int i=1;i<=NUMBER_OF_OUTPUT_FILES;i++){
            files[i-1] = new File(INPUT_FILE + "_Splits\\split."+i+FILE_SUFFIX);
        }

        String mergedFilePath = INPUT_FILE + "_Splits\\fullJoin" + FILE_SUFFIX;


        File mergedFile = new File(mergedFilePath);

        mergeFiles(files, mergedFile);
    }

    public static void mergeFiles(File[] files, File mergedFile) {

        FileWriter fstream = null;
        BufferedWriter out = null;
        try {
            fstream = new FileWriter(mergedFile, true);
             out = new BufferedWriter(fstream);
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        for (File f : files) {
            System.out.println("merging: " + f.getName());
            FileInputStream fis;
            try {
                fis = new FileInputStream(f);
                BufferedReader in = new BufferedReader(new InputStreamReader(fis));

                String aLine;
                while ((aLine = in.readLine()) != null) {
                    out.write(aLine);
                    out.newLine();
                }

                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws Exception {
//      splitFile();
        mergeFiles();
    }

    static void readWrite(RandomAccessFile raf, BufferedOutputStream bw, long numBytes) throws IOException {
        byte[] buf = new byte[(int) numBytes];
        int val = raf.read(buf);
        if (val != -1) {
            bw.write(buf);
        }
    }
}

【问题讨论】:

  • 不幸的是,我的任何编辑都无法打开这么大的文件:(
  • 试试 Glogg (glogg.bonnefon.org/download.html) 或 010Editor(sweetscape.com/010editor)... 010记事本打开一切... 但需要付费,但试用版
  • 您应该尝试更小的测试用例 - 尝试具有相似内容的更小的文件,或者只查看大文件的前 10K。然后你可以自己检查输出,看看问题出在哪里。
  • 为什么你突然使用阅读器逐行阅读,而不是像分割文件那样按字节阅读。您的 joinFiles 方法看起来比您的 mergeFiles 方法好得多,但它可能会以错误的顺序合并文件。

标签: java file io merge split


【解决方案1】:

问题出在最后一行代码:

static void readWrite(RandomAccessFile raf, BufferedOutputStream bw, long numBytes) throws IOException {
    byte[] buf = new byte[(int) numBytes];
    int val = raf.read(buf);
    if (val != -1) {
        bw.write(buf);
    }
}

写的时候写回numBytes的数据,但是read函数有usefully returned

读入缓冲区的总字节数,如果由于已到达此文件的末尾而没有更多数据,则为 -1。

因此,您的解决方法是use a different write

bw.write(buf, 0 val);

【讨论】:

  • 如果他从套接字、管道或控制台读取,那将是一个问题。但他正在阅读RandomAccessFile 并没有超出结尾,所以这不是问题(尽管您提出的代码肯定更好)
  • 实际上,我查看了他对readWrite 的电话,我认为您是正确的!当val变为-1时,我个人会摆脱numReads的计算并让readWrite返回false。
【解决方案2】:

使用您的joinFiles 方法:如果您想保持原样,请不要尝试使用Reader 逐行读取文件,因为行尾可能因平台而异。

改为使用InputStreamRandomAccessFile 将它们作为二进制文件读取,并使用OutputStream 写入。

您的 joinFiles 方法中唯一的问题是它使用了 File.listFiles(),这不能保证文件返回的顺序。

我将您的 mergeFiles() 代码与 joinFiles() 结合使用来完成这项工作(请记住从您的 main 方法调用 joinFiles() 而不是 mergeFiles()

static void joinFiles(File[] files) throws Exception {
    int maxReadBufferSize = 8 * 1024;

    BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(INPUT_FILE + "_Splits\\fullJoin"
            + FILE_SUFFIX));

    RandomAccessFile raf = null;
    for (File file : files) {
        raf = new RandomAccessFile(file, "r");
        long numReads = raf.length() / maxReadBufferSize;
        long numRemainingRead = raf.length() % maxReadBufferSize;
        for (int i = 0; i < numReads; i++) {
            readWrite(raf, bw, maxReadBufferSize);
        }
        if (numRemainingRead > 0) {
            readWrite(raf, bw, numRemainingRead);
        }
        raf.close();

    }
    bw.close();
}

public static void joinFiles() throws Exception {

    File[] files = new File[NUMBER_OF_OUTPUT_FILES];
    for (int i = 1; i <= NUMBER_OF_OUTPUT_FILES; i++) {
        files[i - 1] = new File(INPUT_FILE + "_Splits\\split." + i + FILE_SUFFIX);
    }

    joinFiles(files);
}

【讨论】:

  • 嗨 Erwin,非常感谢,我在 mergeFile 之前尝试过 joinFiles,但在使用该文件时遇到了问题,但我无法了解文件的问题。但现在我得到了你提到的问题是文件顺序
猜你喜欢
  • 2014-10-03
  • 2020-09-30
  • 1970-01-01
  • 2016-09-06
  • 2017-02-21
  • 1970-01-01
  • 2012-07-27
  • 2018-08-18
相关资源
最近更新 更多