【问题标题】:Java - Read file and split into multiple filesJava - 读取文件并拆分为多个文件
【发布时间】:2020-10-01 22:02:37
【问题描述】:

我有一个文件,我想用 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 文件?

注意 - 由于文件中的条目数约为 100k,因此我无法将文件内容存储到数组中,然后将其拆分并保存到多个文件中。

【问题讨论】:

  • 在 while 循环中,只需根据需要将尽可能多的行收集到 String 或 StringBuilder 中,然后将它们写入单独的文件。您无法事先知道文件的数量,最好定义一个文件中的最大行数。
  • 您需要循环两次,一次获取行数,一次进行拆分。或者您可以猜测行数并以此方式拆分。
  • @kw4nta 你到底为什么要存储这些行。 1) OP 说存储所有行不是一个选项,2) 鉴于您可以将这些行直接写入另一个文件...
  • 我建议您先计算行数。在第二次通过时,将其除以 n 并创建包含 total/n 行的 n 文件。为此目的使用BufferedReader.readLine()
  • 如果在这个用例中有意义,另一种解决方案:使用循环算法(第一行到第一个文件,第二行到第二个文件等)

标签: java


【解决方案1】:

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

示例:

源文件大小:5GB

拆分次数:5:目的地

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

即使我们有这样的内存,也无法一口气读取这个大的拆分块。基本上,对于每个拆分,我们都可以读取一个固定大小byte-array,我们知道这在性能和内存方面应该是可行的。

NumSplits:10 MaxReadBytes: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);
        }
    }

【讨论】:

  • 嗯,它可能会在中途分割一行,这对 csv 文件很重要
  • 有没有办法克服这个问题?这样它就不会分裂中线?
  • 在我的公司中,我们为每列设置了固定的记录大小,并且我们将填充到 CSV,因此我们将文件大小除以一个记录大小,然后进行拆分。在读取每一行的同时,在 MQ 上发送要插入以便它是异步的。无论如何,你的灵魂是好的。
  • 您可以扫描缓冲区的末尾以找到最后一行的部分并将其添加到下一个文件中。
  • 根据RandomAccessFile 文档,.read( 没有义务填充整个目标缓冲区。也许这段代码应该改用.readFully(
【解决方案2】:
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());  
 }  

}  

}   

【讨论】:

  • 这并没有做 OP 想要的(设置文件数),但它做了我想要的(设置行数)。好代码!将其修改为一个接收文件名并动态命名创建文件的函数。
  • 来自javaprogramming.language-tutorial.com/2012/10/…的C&P? (这篇博文来自 2012 年)
  • 为什么行数要加倍?
【解决方案3】:

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

Split 和 Join 块示例如下:

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 代码在 File Split in Java Program 链接中提供。

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
  • 谢谢,更新了评论。
【解决方案4】:

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

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

List&lt;String&gt; 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;
    }

【讨论】:

    【解决方案5】:

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

    step1:初始创建新的子文件,设置counter=0;

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

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

    step4 : 跳转到 step1 直到源文件中有数据可供读取

    【讨论】:

      【解决方案6】:

      无需在文件中循环两次。您可以将每个块的大小估计为源文件大小除以所需的块数。然后你就停止用数据填充每个块,因为它的大小超出了估计。

      【讨论】:

        【解决方案7】:

        这是一个对我有用的,我用它来分割 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");
            }
        }
        

        【讨论】:

          【解决方案8】:

          以下代码用于将大文件拆分为行数较少的小文件。

              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 秒。
          • 上面的代码缺少重新添加行分隔符。 writer.write(System.lineSeparator()); 是必须的,否则是 1 大行。
          【解决方案9】:

          将文件拆分为多个块(在内存操作中),这里我将任何文件拆分为 500kb(500000 字节)的大小:

          public static List<ByteArrayOutputStream> splitFile(File f) {
          List<ByteArrayOutputStream> datalist = new ArrayList<>();
          try {
          
              int sizeOfFiles = 500000;
              byte[] buffer = new byte[sizeOfFiles];
          
              try (FileInputStream fis = new FileInputStream(f); BufferedInputStream bis = new BufferedInputStream(fis)) {
          
                  int bytesAmount = 0;
                  while ((bytesAmount = bis.read(buffer)) > 0) {
                      try (OutputStream out = new ByteArrayOutputStream()) {
                          out.write(buffer, 0, bytesAmount);
                          out.flush();
                          datalist.add((ByteArrayOutputStream) out);
                      }
                  }
              }
          } catch (Exception e) {
              //get the error
          }
          
          return datalist; }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-03-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-08-01
            • 1970-01-01
            • 2014-01-17
            • 1970-01-01
            相关资源
            最近更新 更多