Java - 读取文件并将其拆分为多个文件

17

我有一个文件,想在Java中读取并将其拆分为n个(用户输入)输出文件。以下是我读取该文件的方式:

int n = 4;
BufferedReader br = new BufferedReader(new FileReader("file.csv"));
try {
    String line = br.readLine();

    while (line != null) {
        line = br.readLine();
    }
} finally {
    br.close();
}

如何将文件file.csv拆分成n个文件?

注意 - 由于文件中的条目数量达到了10万级别,我无法将文件内容存储到数组中再进行拆分并保存到多个文件。


在 while 循环中,只需将想要的行数收集到一个字符串或 StringBuilder 中,并将它们写入单独的文件。您无法预先知道文件的数量,最好定义一个文件中的最大行数。 - John Smith
你需要循环两次,一次获取行数,一次进行分割。或者你可以猜测行数并以此方式进行分割。 - Boris the Spider
@kw4nta 你到底为什么想要“存储”这些行呢?1)原帖中说存储所有行不是一个选项,2)考虑到你可以直接将这些行写入另一个文件…… - Boris the Spider
我建议您进行第一次遍历,计算行数。在第二次遍历中,将其除以n并创建包含total/n行的n个文件。为此,请使用BufferedReader.readLine() - Arnaud Denoyelle
6
如果在这种情况下有意义,另一个解决方案是使用轮询算法(将第一行写入第一个文件,将第二行写入第二个文件,以此类推)。 - Arnaud Denoyelle
11个回答

25

由于一个文件可能非常大,每个分割文件也可能很大。

示例:

源文件大小:5GB

分割数:5:目标

文件大小:每个1GB(5个文件)

即使我们有如此大的内存,也没有办法一次性读取这个大的分割块。基本上,对于每个分割,我们可以读取一个固定大小的 byte-array,这在性能和内存方面都是可行的。

分割数:10 最大读取字节数:8KB

public static void main(String[] args) throws Exception
    {
        RandomAccessFile raf = new RandomAccessFile("test.csv", "r");
        long numSplits = 10; //from user input, extract it from args
        long sourceSize = raf.length();
        long bytesPerSplit = sourceSize/numSplits ;
        long remainingBytes = sourceSize % numSplits;

        int maxReadBufferSize = 8 * 1024; //8KB
        for(int destIx=1; destIx <= numSplits; destIx++) {
            BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream("split."+destIx));
            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."+(numSplits+1)));
            readWrite(raf, bw, remainingBytes);
            bw.close();
        }
            raf.close();
    }

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

7
可能会在一行中间分割,这对于 CSV 文件很重要。 - Pujan
在我们公司,每个列都有固定的记录大小,我们会将其填充到CSV中,因此我们将文件大小除以一个记录大小,然后进行拆分。同时,在读取时,每行都会被发送到MQ中进行插入,以实现异步。无论如何,您的解决方案很好。 - Kumar Abhishek
您可以扫描缓冲区的末尾,找到最后一行的部分,并添加到下一个文件中。 - Peter Lawrey
1
根据RandomAccessFile文档,.read(不一定要填满整个目标缓冲区。也许这段代码应该使用.readFully(代替? - Jesbus
很好!但是在拆分文件时,保留列/标题也很重要。有什么建议吗? - Ashok kumar Ganesan
显示剩余2条评论

9
import java.io.*;  
import java.util.Scanner;  
public class split {  
public static void main(String args[])  
{  
 try{  
  // Reading file and getting no. of files to be generated  
  String inputfile = "C:/test.txt"; //  Source File Name.  
  double nol = 2000.0; //  No. of lines to be split and saved in each output file.  
  File file = new File(inputfile);  
  Scanner scanner = new Scanner(file);  
  int count = 0;  
  while (scanner.hasNextLine())   
  {  
   scanner.nextLine();  
   count++;  
  }  
  System.out.println("Lines in the file: " + count);     // Displays no. of lines in the input file.  

  double temp = (count/nol);  
  int temp1=(int)temp;  
  int nof=0;  
  if(temp1==temp)  
  {  
   nof=temp1;  
  }  
  else  
  {  
   nof=temp1+1;  
  }  
  System.out.println("No. of files to be generated :"+nof); // Displays no. of files to be generated.  

  //---------------------------------------------------------------------------------------------------------  

  // Actual splitting of file into smaller files  

  FileInputStream fstream = new FileInputStream(inputfile); DataInputStream in = new DataInputStream(fstream);  

  BufferedReader br = new BufferedReader(new InputStreamReader(in)); String strLine;  

  for (int j=1;j<=nof;j++)  
  {  
   FileWriter fstream1 = new FileWriter("C:/New Folder/File"+j+".txt");     // Destination File Location  
   BufferedWriter out = new BufferedWriter(fstream1);   
   for (int i=1;i<=nol;i++)  
   {  
    strLine = br.readLine();   
    if (strLine!= null)  
    {  
     out.write(strLine);   
     if(i!=nol)  
     {  
      out.newLine();  
     }  
    }  
   }  
   out.close();  
  }  

  in.close();  
 }catch (Exception e)  
 {  
  System.err.println("Error: " + e.getMessage());  
 }  

}  

}   

1
这并不是 OP 想要的(设置文件数量),但它实现了我想要的(设置行数)。代码很好!我将其修改为一个函数,接受文件名并动态命名创建的文件。 - Autumn Leonard
将大文件拆分成小文本文件有时,您可能需要将大文件拆分成多个小文件。例如,当您的应用程序需要处理超过内存限制的大型文本文件时,您可以使用Java编写代码来自动将其拆分为多个小文件以便于处理。以下是一个示例Java程序,它将大文件(例如,1 GB)拆分为多个小文件。在此示例中,我们将大文件按行拆分为小文件,并且每个小文件最多包含1000行。您可以根据需要自定义这些参数。请注意,该程序假定大文件采用UTF-8编码,因此如果您的大文件使用其他编码,则需要相应地更改代码。import java.io.*;public class SplitFile {public static void main(String[] args) throws IOException { String inputFileName = "input.txt"; String outputPrefix = "output_"; int maxLinesPerFile = 1000; BufferedReader br = new BufferedReader(new FileReader(inputFileName)); String line; int count = 0; int fileNumber = 0; BufferedWriter bw = new BufferedWriter(new FileWriter(outputPrefix + fileNumber + ".txt")); while ((line = br.readLine()) != null) { if (count == maxLinesPerFile) { bw.close(); count = 0; fileNumber++; bw = new BufferedWriter(new FileWriter(outputPrefix + fileNumber + ".txt")); } bw.write(line + "\n"); count++; } bw.close(); br.close(); }}以上程序仅供参考,并且可能需要针对您的特定需求进行更改。 - bish
为什么代码行数应该翻倍? - Omid.N
有一件非常重要的事情,那就是Scanner不是线程安全的。 - Muhammad_08

2

虽然这是一个老问题,但为了参考,我列出了我用来将大文件分割成任意大小的代码,并且它适用于1.4以上的任何Java版本。

示例分割和合并块如下:

public void join(String FilePath) {
    long leninfile = 0, leng = 0;
    int count = 1, data = 0;
    try {
        File filename = new File(FilePath);
        //RandomAccessFile outfile = new RandomAccessFile(filename,"rw");

        OutputStream outfile = new BufferedOutputStream(new FileOutputStream(filename));
        while (true) {
            filename = new File(FilePath + count + ".sp");
            if (filename.exists()) {
                //RandomAccessFile infile = new RandomAccessFile(filename,"r");
                InputStream infile = new BufferedInputStream(new FileInputStream(filename));
                data = infile.read();
                while (data != -1) {
                    outfile.write(data);
                    data = infile.read();
                }
                leng++;
                infile.close();
                count++;
            } else {
                break;
            }
        }
        outfile.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void split(String FilePath, long splitlen) {
    long leninfile = 0, leng = 0;
    int count = 1, data;
    try {
        File filename = new File(FilePath);
        //RandomAccessFile infile = new RandomAccessFile(filename, "r");
        InputStream infile = new BufferedInputStream(new FileInputStream(filename));
        data = infile.read();
        while (data != -1) {
            filename = new File(FilePath + count + ".sp");
            //RandomAccessFile outfile = new RandomAccessFile(filename, "rw");
            OutputStream outfile = new BufferedOutputStream(new FileOutputStream(filename));
            while (data != -1 && leng < splitlen) {
                outfile.write(data);
                leng++;
                data = infile.read();
            }
            leninfile += leng;
            leng = 0;
            outfile.close();
            count++;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

这里提供完整的Java代码,可以通过文件分割Java程序链接获取。


1
虽然该链接可能回答了问题,但最好在此处包含答案的基本部分,并提供参考链接。如果链接页面更改,仅链接的答案可能会变得无效。-【来自审查】 - CubeJockey
1
感谢,已更新注释。 - user1472187

1

一个干净的编辑解决方案。

这个解决方案涉及将整个文件加载到内存中。

将文件的所有行设置为 List<String> rowsOfFile;

编辑maxSizeFile以选择单个文件分割的最大大小。

public void splitFile(File fileToSplit) throws IOException {
  long maxSizeFile = 10000000 // 10mb
  StringBuilder buffer = new StringBuilder((int) maxSizeFile);
  int sizeOfRows = 0;
  int recurrence = 0;
  String fileName;
  List<String> rowsOfFile;

  rowsOfFile = Files.readAllLines(fileToSplit.toPath(), Charset.defaultCharset());

  for (String row : rowsOfFile) {
      buffer.append(row);
      numOfRow++;
      sizeOfRows += row.getBytes(StandardCharsets.UTF_8).length;
      if (sizeOfRows >= maxSizeFile) {
          fileName = generateFileName(recurrence);
          File newFile = new File(fileName);

          try (PrintWriter writer = new PrintWriter(newFile)) {
              writer.println(buffer.toString());
          }

          recurrence++;
          sizeOfRows = 0;
          buffer = new StringBuilder();
      }
  }
  // last rows
  if (sizeOfRows > 0) {
      fileName = generateFileName(recurrence);
      File newFile = createFile(fileName);

      try (PrintWriter writer = new PrintWriter(newFile)) {
          writer.println(buffer.toString());
      }
  }
  Files.delete(fileToSplit.toPath());
}

生成文件名的方法:
    public String generateFileName(int numFile) {
      String extension = ".txt";
      return "myFile" + numFile + extension;
    }

0

这是一个对我有效的方法,我用它来分割10GB的文件。它还可以让你添加头部和尾部,在拆分基于文档格式的文件(如XML和JSON)时非常有用,因为你需要在新的拆分文件中添加文档包装器。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FileSpliter
{
    public static void main(String[] args) throws IOException
    {
        splitTextFiles("D:\\xref.csx", 750000, "", "", null);
    }

    public static void splitTextFiles(String fileName, int maxRows, String header, String footer, String targetDir) throws IOException
    {
        File bigFile = new File(fileName);
        int i = 1;
        String ext = fileName.substring(fileName.lastIndexOf("."));

        String fileNoExt = bigFile.getName().replace(ext, "");
        File newDir = null;
        if(targetDir != null)
        {
            newDir = new File(targetDir);           
        }
        else
        {
            newDir = new File(bigFile.getParent() + "\\" + fileNoExt + "_split");
        }
        newDir.mkdirs();
        try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName)))
        {
            String line = null;
            int lineNum = 1;
            Path splitFile = Paths.get(newDir.getPath() + "\\" +  fileNoExt + "_" + String.format("%02d", i) + ext);
            BufferedWriter writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);
            while ((line = reader.readLine()) != null)
            {
                if(lineNum == 1)
                {
                    System.out.print("new file created '" + splitFile.toString());
                    if(header != null && header.length() > 0)
                    {
                        writer.append(header);
                        writer.newLine();
                    }
                }
                writer.append(line);

                if (lineNum >= maxRows)
                {
                    if(footer != null && footer.length() > 0)
                    {
                        writer.newLine();
                        writer.append(footer);
                    }
                    writer.close();
                    System.out.println(", " + lineNum + " lines written to file");
                    lineNum = 1;
                    i++;
                    splitFile = Paths.get(newDir.getPath() + "\\" + fileNoExt + "_" + String.format("%02d", i) + ext);
                    writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);
                }
                else
                {
                    writer.newLine();
                    lineNum++;
                }
            }
            if(lineNum <= maxRows) // early exit
            {
                if(footer != null && footer.length() > 0)
                {
                    writer.newLine();
                    lineNum++;
                    writer.append(footer);
                }
            }
            writer.close();
            System.out.println(", " + lineNum + " lines written to file");
        }

        System.out.println("file '" + bigFile.getName() + "' split into " + i + " files");
    }
}

0
以下代码用于将大文件拆分为行数较少的小文件。
    long linesWritten = 0;
    int count = 1;

    try {
        File inputFile = new File(inputFilePath);
        InputStream inputFileStream = new BufferedInputStream(new FileInputStream(inputFile));
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputFileStream));

        String line = reader.readLine();

        String fileName = inputFile.getName();
        String outfileName = outputFolderPath + "\\" + fileName;

        while (line != null) {
            File outFile = new File(outfileName + "_" + count + ".split");
            Writer writer = new OutputStreamWriter(new FileOutputStream(outFile));

            while (line != null && linesWritten < linesPerSplit) {
                writer.write(line);
                line = reader.readLine();
                linesWritten++;
            }

            writer.close();
            linesWritten = 0;//next file
            count++;//nect file count
        }

        reader.close();

    } catch (Exception e) {
        e.printStackTrace();
    }

我上面编写的代码是有效的,而且我已经测试了一个包含40L记录/行的文件。将文件拆分为每个文件1L行的块大约需要10秒钟。 - Narendra Kumar Samal
上面的代码缺少重新添加行分隔符。需要使用 writer.write(System.lineSeparator());,否则它会成为1个巨大的行。 - rveach

0
import java.util.*;
import java.io.*;
public class task13 {
    public static void main(String[] args)throws IOException{
        Scanner s =new Scanner(System.in);
        System.out.print("Enter path:");
        String a=s.next();
        File f=new File(a+".txt");
        Scanner st=new Scanner(f);
        System.out.println(f.canRead()+"\n"+f.canWrite());
        long l=f.length();
        System.out.println("Length is:"+l);
        System.out.print("Enter no.of partitions:");
        int p=s.nextInt();
        long x=l/p;
        st.useDelimiter("\\Z");
        String t=st.next();
        int j=0;
        System.out.println("Each File Length is:"+x);
        for(int i=1;i<=p;i++){
           File ft=new File(a+"-"+i+".txt");
           ft.createNewFile();
           int g=(j*(int)x);
           int h=(j+1)*(int)x;
           if(g<=l&&h<=l){
           FileWriter fw=new FileWriter(a+"-"+i+".txt");
               String v=t.substring(g,h);            
               fw.write(v);
               j++;
               fw.close();
           }}
        }}

1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

0

我有点晚回答,但是这是我的做法:

方法:

首先,我确定每个单独文件应包含多少字节,然后按字节拆分大文件。每次只加载一个文件块的数据到内存中。

例如:如果将一个5GB的文件拆分成10个文件,则每次只会加载500MB的字节到内存中,这些字节保存在下面的splitBySize方法中的buffer变量中。

代码解释:

splitFile方法首先通过调用getSizeInBytes方法获取每个单独的文件块应包含的字节数,然后调用splitBySize方法按大小(即maxChunkSize表示每个文件块将包含的字节数)拆分大文件。

public static List<File> splitFile(File largeFile, int noOfFiles) throws IOException {
    return splitBySize(largeFile, getSizeInBytes(largeFile.length(), noOfFiles));
}

public static List<File> splitBySize(File largeFile, int maxChunkSize) throws IOException {
    List<File> list = new ArrayList<>();
    int numberOfFiles = 0;
    try (InputStream in = Files.newInputStream(largeFile.toPath())) {
        final byte[] buffer = new byte[maxChunkSize];
        int dataRead = in.read(buffer);
        while (dataRead > -1) {
            list.add(stageLocally(buffer, dataRead));
            numberOfFiles++;
            dataRead = in.read(buffer);
        }
    }
    System.out.println("Number of files generated: " + numberOfFiles);
    return list;
}

private static int getSizeInBytes(long totalBytes, int numberOfFiles) {
    if (totalBytes % numberOfFiles != 0) {
        totalBytes = ((totalBytes / numberOfFiles) + 1)*numberOfFiles;
    }
    long x = totalBytes / numberOfFiles;
    if (x > Integer.MAX_VALUE){
        throw new NumberFormatException("Byte chunk too large");

    }
    return (int) x;
}

完整代码:

public class StackOverflow {

private static final String INPUT_FILE_PATH = "/Users/malkesingh/Downloads/5MB.zip";
private static final String TEMP_DIRECTORY = "/Users/malkesingh/temp";

public static void main(String[] args) throws IOException {

    File input = new File(INPUT_FILE_PATH);
    File outPut = fileJoin2(splitFile(input, 5));

    try (InputStream in = Files.newInputStream(input.toPath()); InputStream out = Files.newInputStream(outPut.toPath())) {
        System.out.println(IOUtils.contentEquals(in, out));
    }

}

public static List<File> splitFile(File largeFile, int noOfFiles) throws IOException {
    return splitBySize(largeFile, getSizeInBytes(largeFile.length(), noOfFiles));
}

public static List<File> splitBySize(File largeFile, int maxChunkSize) throws IOException {
    List<File> list = new ArrayList<>();
    int numberOfFiles = 0;
    try (InputStream in = Files.newInputStream(largeFile.toPath())) {
        final byte[] buffer = new byte[maxChunkSize];
        int dataRead = in.read(buffer);
        while (dataRead > -1) {
            list.add(stageLocally(buffer, dataRead));
            numberOfFiles++;
            dataRead = in.read(buffer);
        }
    }
    System.out.println("Number of files generated: " + numberOfFiles);
    return list;
}

private static int getSizeInBytes(long totalBytes, int numberOfFiles) {
    if (totalBytes % numberOfFiles != 0) {
        totalBytes = ((totalBytes / numberOfFiles) + 1)*numberOfFiles;
    }
    long x = totalBytes / numberOfFiles;
    if (x > Integer.MAX_VALUE){
        throw new NumberFormatException("Byte chunk too large");

    }
    return (int) x;
}




private static File stageLocally(byte[] buffer, int length) throws IOException {
    File outPutFile = File.createTempFile("temp-", "split", new File(TEMP_DIRECTORY));
    try(FileOutputStream fos = new FileOutputStream(outPutFile)) {
        fos.write(buffer, 0, length);
    }
    return outPutFile;
}


public static File fileJoin2(List<File> list) throws IOException {
    File outPutFile = File.createTempFile("temp-", "unsplit", new File(TEMP_DIRECTORY));
    FileOutputStream fos = new FileOutputStream(outPutFile);
    for (File file : list) {
        Files.copy(file.toPath(), fos);
    }
    fos.close();
    return outPutFile;
}}

0

有一个计数器来计算条目数量。假设每行一个条目。

步骤1:最初创建新的子文件,设置计数器=0;

步骤2:从源文件读取每个条目到缓冲区时递增计数器

步骤3:当计数器达到要写入每个子文件的条目数限制时,将缓冲区内容刷新到子文件中。关闭子文件

步骤4:跳转到步骤1,直到您有数据可以从源文件中读取


0

没有必要两次循环读取文件。你可以估算每个块的大小,即源文件大小除以所需块数。然后,当块的大小超过估算值时,停止向该块填充数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接