2016-05-28 128 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