0

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

これを行う最速の方法についていくつかの提案を見つけたいと思っていました。私は主に UNIX 上の Java で作業していますが、より高速な言語固有のソリューション (または awk などを使用したもの) が発生した場合は、それも受け入れます。

現在、これをメモリ内で行っていますが、時間の経過とともにファイルがメモリ容量を超えています。明らかに「より大きなマシンを購入する」ことが解決策ですが、現時点ではそうではありません。

4

2 に答える 2

1

以下のようなものがあなたのために働くかもしれません。このコードは、最初にソースファイルをとして開きBufferedReader、次に最初の行を読み取り、それをに対して分割し\tます。

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

次に、ソースファイルが再度読み取られ、空になるまで1行ずつ分割され、すべてのファイルホルダーが追加されます。

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

これがサンプルコードです(LONG、ここでも入手可能)。それは確かに改善することができます(リソースは正しい場所で実際に閉じられていないなど)が、それは機能します。ここでは、275 MBのファイルを約25秒で置き換え(クアッドコアQ6600、4 GB RAM、x86_64 Linux 3.1.2-rc5)、Sunの(64ビット)JDKの64MBの「薄っぺらな」デフォルト値で実行されます。

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);
        }
    }
}
于 2011-12-16T17:17:02.713 に答える
0

@fge: 1) 多くの文字列をインスタンス化する代わりに、CharBuffer を使用することをお勧めします。

2) パターン マッチングを次のように使用することをお勧めします。

initially..

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

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

matcher.reset(charBuffer).find()

なぜなら、中を見ると

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

多くのインスタンス化や文字列の使用を引き起こすコードを書くことは常に控えてください。これにより大量のメモリが使用され、パフォーマンスが低下します。

于 2011-12-16T19:01:13.680 に答える