2011-12-16 6 views
0

大きなタブ区切りのデータファイルがあります。これらのファイルは、列よりも数桁多い行を持ちます。問題は、これらのファイルをピボットしたいのですが、この場合、「大」はメモリ内でこれを行うには大きすぎると定義されています。大きなデータファイルをピボットする

私は、これを行う最速の方法についていくつかの提案を見つけることを望んでいました。私は主にUNIX上のJavaで作業していますが、より速い言語固有の解決策(またはawkなどを使用しているもの)が発生したとしても、私はそれにもオープンしています。

現在のところ、私たちはこれをメモリ上で行っていますが、時間の経過と共に変化するにつれて、ファイルは私たちのメモリ容量を超えています。明らかに "大きなマシンを買う"というのは解決策ですが、現時点ではカードにはありません。

+0

結果ファイルを同じ名前にしたいと思いますか? – fge

+0

いいえ、実際には理想的なケースですが、あらかじめファイルのコピーを作成することはそれほど重要ではありません。 – geoffjentry

+0

ピボットではどういう意味ですか? – Nayuki

答えて

1

次のようなものが役立ちます。このコードは、まずソースファイルをBufferedReaderとして開き、最初の行を読み取り、それを\tに対して分割します。

結果の配列の長さは、出力先ファイルの行数です。 FileHolderオブジェクトの新しい配列が作成されます。FileHolderは基本的にファイル記述子を保持し、ByteBufferはキャッシュとして使用します(各単語をすべて書き込まないように)。すべての所有者が作成されると、最初の行が書き込まれます。

ソースファイルをもう一度読み込み、行単位で分割して空にし、すべてのファイルホルダを追加します。

これが完了すると、最終的に出力先ファイルが作成され、すべてのFileHolderインスタンスが配列順に、つまり行順に書き込まれます。

ここにサンプルコードを示します(LONG、hereも可)。確かに改善することができます(リソースは実際には正しい場所などで閉じられていませんが)。これは275 MBのファイルを約25秒で転記します(クワッドコアQ6600、4 GB RAM、x86_64 Linux 3.1.2-rc5)。64 MBのSun(64ビット)JDKのデフォルト値 "flimsy"で動作します。

package net.sf.jpam; 

import java.io.BufferedReader; 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileReader; 
import java.io.IOException; 
import java.io.RandomAccessFile; 
import java.io.Reader; 
import java.nio.ByteBuffer; 
import java.nio.channels.FileChannel; 
import java.util.regex.Pattern; 

public final class Test 
{ 
    private static final Pattern TAB = Pattern.compile("\t"); 

    private static class FileHolder 
    { 
     private static final byte TABCHAR[] = "\t".getBytes(); 
     // Size of the buffer size 
     private static final int BUFSZ = 32768; 

     // Format string for a file 
     private static final String FORMAT = "/home/fge/t2.txt.%d"; 

     // The ByteBuffer 
     private final ByteBuffer buf = ByteBuffer.allocate(BUFSZ); 

     // The File object 
     private final File fd; 

     // RandomAccessFile 
     private final RandomAccessFile file; 

     FileHolder(final int index) 
      throws FileNotFoundException 
     { 
      final String name = String.format(FORMAT, index); 
      fd = new File(name); 
      file = new RandomAccessFile(fd, "rw"); 
     } 

     public void write(final String s) 
      throws IOException 
     { 
      final byte[] b = s.getBytes(); 
      if (buf.remaining() < b.length + TABCHAR.length) 
       flush(); 
      buf.put(b).put(TABCHAR); 
     } 

     private void flush() 
      throws IOException 
     { 
      file.write(buf.array(), 0, buf.position()); 
      buf.position(0); 
     } 

     public void copyTo(final RandomAccessFile dst) 
      throws IOException 
     { 
      flush(); 
      final FileChannel source = file.getChannel(); 
      final FileChannel destination = dst.getChannel(); 
      source.force(false); 
      final long len = source.size() - TABCHAR.length; 

      source.transferTo(0, len, destination); 
      dst.writeBytes("\n"); 

     } 

     public void tearDown() 
      throws IOException 
     { 
      file.close(); 
      if (!fd.delete()) 
       System.err.println("Failed to remove file " + fd); 
     } 

     @Override 
     public String toString() 
     { 
      return fd.toString(); 
     } 
    } 

    public static void main(final String... args) 
     throws IOException 
    { 
     long before, after; 

     before = System.currentTimeMillis(); 
     final Reader r = new FileReader("/home/fge/t.txt"); 
     final BufferedReader reader = new BufferedReader(r); 

     /* 
     * Read first line, count the number of elements. All elements are 
     * separated by a single tab. 
     */ 
     String line = reader.readLine(); 
     String[] elements = TAB.split(line); 

     final int nrLines = elements.length; 
     final FileHolder[] files = new FileHolder[nrLines]; 

     /* 
     * Initialize file descriptors 
     */ 
     for (int i = 0; i < nrLines; i++) 
      files[i] = new FileHolder(i); 


     /* 
     * Write first lines, then all others 
     */ 
     writeOneLine(elements, files); 

     while ((line = reader.readLine()) != null) { 
      elements = TAB.split(line); 
      writeOneLine(elements, files); 
     } 

     reader.close(); 
     r.close(); 
     after = System.currentTimeMillis(); 

     System.out.println("Read time: " + (after - before)); 

     before = System.currentTimeMillis(); 
     final RandomAccessFile out = new RandomAccessFile("/home/fge/t2.txt", 
      "rw"); 

     for (final FileHolder file: files) { 
      file.copyTo(out); 
      file.tearDown(); 
     } 

     out.getChannel().force(false); 
     out.close(); 

     after = System.currentTimeMillis(); 

     System.out.println("Write time: " + (after - before)); 
     System.exit(0); 
    } 

    private static void writeOneLine(final String[] elements, 
     final FileHolder[] fdArray) 
     throws IOException 
    { 
     final int len = elements.length; 
     String element; 
     FileHolder file; 

     for (int index = 0; index < len; index++) { 
      element = elements[index]; 
      file = fdArray[index]; 
      file.write(element); 
     } 
    } 
} 
+0

それは動作しますが、メモリフットプリントの問題を解決していないようです。例えば、4.5GBのファイルでは、-Xmx4000でこれを実行することができませんでした。私たちが持っているファイルは、利用可能なRAMよりも大きいことが多いので、ファイルサイズより少ないRAMを使用するソリューションが必要です。 – geoffjentry

+0

好奇心で、このような4.5 GBのファイルでこのコマンドを実行できますか? 'head -1 thefile | wc -c'? – fge

+0

16968 ...問題はバッファサイズを超えている可能性がありますか? (私は現時点でそれを微調整する立場にはない) – geoffjentry

0

@fge: 1)多くの文字列をインスタンス化する代わりに、CharBufferを使用する方がよいでしょう。

2)このようにパターンマッチングを使用した方が良いです:あなたは

Pattern.matcher(CharSequence input) { 
Matcher m = new Matcher(this, input); 
} 

の中を見たときには、必ずインスタンス化や文字列の使用の多くの原因となるコードを書くことは控え、ので

initially.. 

private Matcher matcher; 
Pattern regexPattern = Pattern.compile(pattern); 
matcher = regexPattern.matcher(""); 

and then for matching pattern.. you do this.. 

matcher.reset(charBuffer).find() 

を。これにより、多くのメモリが使用され、パフォーマンスが低下します。

+0

これは、ここに関わるものほど大きなデータでは動作しません。とにかく私のコードには現在修正中の欠陥があります.64MBのヒープサイズの256MBファイルを書き込むことができます。さらに、最終的なタブを削除する正規表現は必要ありません – fge

関連する問題