2016-05-28 90 views
-1
合并多个文件

鉴于有一些文件的客户1.txt的,以客户为2.txt和客户3.txt和这些文件具有以下内容:阅读,比较和Java的

客户-1。 TXT

1|1|MARY|SMITH 
2|1|PATRICIA|JOHNSON 
4|2|BARBARA|JONES 

客户-2.txt

1|1|MARY|SMITH 
2|1|PATRICIA|JOHNSON 
3|1|LINDA|WILLIAMS 
4|2|BARBARA|JONES 

客户-3.txt

2|1|PATRICIA|JOHNSON 
3|1|LINDA|WILLIAMS 
5|2|ALEXANDER|ANDERSON 

这些文件有很多重复数据,但每个文件都可能包含一些独特的数据。

而且考虑到实际文件分类,大(几个GB的每个文件),并有许多文件...

那么什么是:
一)内存最低
b )CPU最低
C)在Java中最快
的方法来创建一个文件,这些三个文件将包含分选,然后连接起来像这样每个文件的所有独特的数据:

客户为final.txt

1|1|MARY|SMITH 
2|1|PATRICIA|JOHNSON 
3|1|LINDA|WILLIAMS 
4|2|BARBARA|JONES 
5|2|ALEXANDER|ANDERSON 

我看着下面的解决方案https://github.com/upcrob/spring-batch-sort-merge,但我想知道,如果可能用的FileInputStream和/或非Spring Batch的解决方案,也许这样做。

由于文件的大小和实际数据库的缺失,使用内存或实际数据库加入它们的解决方案对于我的用例不可行。

+1

第一个值是客户ID,是已经按照第一个值排序(数字)的文件,因为您的示例会指出它们是? – Andreas

+0

是的,你是正确的,他们已经排序。感谢您的注意,请将其添加到问题描述中。 –

+0

我不认为你可以在没有阅读文件内容的情况下实现这一点。您需要阅读每个文件,然后将其写入单独的文件。 – Priyamal

回答

1

由于输入文件已经排序,文件的简单的并行迭代,合并它们的内容,是内存最低CPU最低,而要做到这最快方式。

这是一种多路合并连接,即没有“排序”的排序合并连接,消除了重复项,类似于SQL DISTINCT

这是一个版本,可以做无限数量的输入文件(好吧,尽可能多的你可以打开文件)。它使用一个辅助类来分段每个输入文件的下一行,因此每行只需要解析一次前导ID值。

private static void merge(StringWriter out, BufferedReader ... in) throws IOException { 
    CustomerReader[] customerReader = new CustomerReader[in.length]; 
    for (int i = 0; i < in.length; i++) 
     customerReader[i] = new CustomerReader(in[i]); 
    merge(out, customerReader); 
} 

private static void merge(StringWriter out, CustomerReader ... in) throws IOException { 
    List<CustomerReader> min = new ArrayList<>(in.length); 
    for (;;) { 
     min.clear(); 
     for (CustomerReader reader : in) 
      if (reader.hasData()) { 
       int cmp = (min.isEmpty() ? 0 : reader.compareTo(min.get(0))); 
       if (cmp < 0) 
        min.clear(); 
       if (cmp <= 0) 
        min.add(reader); 
      } 
     if (min.isEmpty()) 
      break; // all done 
     // optional: Verify that lines that compared equal by ID are entirely equal 
     out.write(min.get(0).getCustomerLine()); 
     out.write(System.lineSeparator()); 
     for (CustomerReader reader : min) 
      reader.readNext(); 
    } 
} 

private static final class CustomerReader implements Comparable<CustomerReader> { 
    private BufferedReader in; 
    private String   customerLine; 
    private int   customerId; 
    CustomerReader(BufferedReader in) throws IOException { 
     this.in = in; 
     readNext(); 
    } 
    void readNext() throws IOException { 
     if ((this.customerLine = this.in.readLine()) == null) 
      this.customerId = Integer.MAX_VALUE; 
     else 
      this.customerId = Integer.parseInt(this.customerLine.substring(0, this.customerLine.indexOf('|'))); 
    } 
    boolean hasData() { 
     return (this.customerLine != null); 
    } 
    String getCustomerLine() { 
     return this.customerLine; 
    } 
    @Override 
    public int compareTo(CustomerReader that) { 
     // Order by customerId only. Inconsistent with equals() 
     return Integer.compare(this.customerId, that.customerId); 
    } 
} 

TEST

String file1data = "1|1|MARY|SMITH\n" + 
        "2|1|PATRICIA|JOHNSON\n" + 
        "4|2|BARBARA|JONES\n"; 
String file2data = "1|1|MARY|SMITH\n" + 
        "2|1|PATRICIA|JOHNSON\n" + 
        "3|1|LINDA|WILLIAMS\n" + 
        "4|2|BARBARA|JONES\n"; 
String file3data = "2|1|PATRICIA|JOHNSON\n" + 
        "3|1|LINDA|WILLIAMS\n" + 
        "5|2|ALEXANDER|ANDERSON\n"; 
try (
    BufferedReader in1 = new BufferedReader(new StringReader(file1data)); 
    BufferedReader in2 = new BufferedReader(new StringReader(file2data)); 
    BufferedReader in3 = new BufferedReader(new StringReader(file3data)); 
    StringWriter out = new StringWriter(); 
) { 
    merge(out, in1, in2, in3); 
    System.out.print(out); 
} 

OUTPUT

1|1|MARY|SMITH 
2|1|PATRICIA|JOHNSON 
3|1|LINDA|WILLIAMS 
4|2|BARBARA|JONES 
5|2|ALEXANDER|ANDERSON 

该代码由ID值纯粹合流,并且不核实线的该其余部分实际上是相等的。如果需要,请在optional评论中插入代码以检查该问题。

+0

非常详细的答案,正是我在找什么。我所有的文件实际上都有一个排序的唯一标识符(客户文件,但也包括所有其他文件,例如Actor文件)。我的理解是否正确:对于每个“组”文件,我都必须使用一组文件调用合并功能? –

+1

这是正确的。对于需要合并到单个输出文件的每组相似文件,可以调用合并,例如,一个客户文件合并调用,另一个合并调用Actor文件,等等...... – Andreas

+1

供参考:由于您的文件很大,并且一次可能写入一个文件,因此它们可能每个文件都存储在硬盘上的连续区域中。当读取3个文件并且并行写入一个文件时,硬盘臂可能最终移动很多。您可以通过将BufferedReader和BufferedWriter对象的缓冲区大小从默认的8k增加到更高的值来最小化, 64k(甚至可能是1M?)。增加太高只会浪费内存而不提高性能。 – Andreas

0

这可能帮助:

public static void main(String[] args) { 
    String files[] = {"Customer-1.txt", "Customer-2.txt", "Customer-3.txt"}; 
    HashMap<Integer, String> customers = new HashMap<Integer, String>(); 
    try { 
     String line; 
     for(int i = 0; i < files.length; i++) { 
      BufferedReader reader = new BufferedReader(new FileReader("data/" + files[i])); 
      while((line = reader.readLine()) != null) { 
       Integer uuid = Integer.valueOf(line.split("|")[0]); 
       customers.put(uuid, line); 
      } 
      reader.close(); 
     } 

     BufferedWriter writer = new BufferedWriter(new FileWriter("data/Customer-final.txt")); 
     Iterator<String> it = customers.values().iterator(); 
     while(it.hasNext()) writer.write(it.next() + "\n"); 

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

如果您有任何cquestions问我。

+0

这将加载所有(唯一)行到内存,所以它绝对不是“* a)内存最便宜*”。使用'split()'来提取第一个值并不是最有效的方式,所以它不是“* b)cpu最便宜的”。由于您将行存储在'HashSet'中,因此输出*不会被排序*。 – Andreas