4

次の基準を満たすストリーミング ファイル リーダー クラスを作成するよりも優れた [既存のオプションの Java 1.6] ソリューションはありますか?

  • 各行が\n
  • メソッドの呼び出しごとにreadLine()、ファイルからランダムな行を読み取ります
  • そして、ファイルハンドルの存続期間中、 への呼び出しreadLine()は同じ行を 2 回返すべきではありません

アップデート:

  • 最終的にはすべての行を読み取る必要があります

コンテキスト: ファイルの内容は、指定されたディレクトリ内に含まれるすべてのパスのディレクトリ リストを取得するために、Unix シェル コマンドから作成されます。数百万から 10 億のファイルがあります (これにより、ターゲット ファイルに数百万から 10 億行が生成されます)。作成時にパスをファイルにランダムに分散する方法があれば、それも許容できる解決策です。

4

4 に答える 4

5

あなたのケースでは不可能なファイル全体の読み込みを避けるためにRandomAccessFile、標準の java の代わりに aを使用することをお勧めしますFileInputStream。では、メソッドをRandomAccessFile使用してseek(long position)、ファイル内の任意の場所にスキップして、そこから読み始めることができます。コードは次のようになります。

RandomAccessFile raf = new RandomAccessFile("path-to-file","rw");
HashMap<Integer,String> sampledLines = new HashMap<Integer,String>();
for(int i = 0; i < numberOfRandomSamples; i++)
{
    //seek to a random point in the file
    raf.seek((long)(Math.random()*raf.length()));

    //skip from the random location to the beginning of the next line
    int nextByte = raf.read();
    while(((char)nextByte) != '\n')
    {
        if(nextByte == -1) raf.seek(0);//wrap around to the beginning of the file if you reach the end
        nextByte = raf.read();
    }

    //read the line into a buffer
    StringBuffer lineBuffer = new StringBuffer();
    nextByte = raf.read();
    while(nextByte != -1 && (((char)nextByte) != '\n'))
        lineBuffer.append((char)nextByte);

    //ensure uniqueness
    String line = lineBuffer.toString();
    if(sampledLines.get(line.hashCode()) != null)
        i--;
    else
       sampledLines.put(line.hashCode(),line);
}

ここでsampledLinesは、ランダムに選択した行を最後に保持する必要があります。その場合のエラーを回避するために、ファイルの最後までランダムにスキップしていないことも確認する必要がある場合があります。

編集:最後に到達した場合に備えて、ファイルの先頭にラップしました。かなり簡単なチェックでした。

EDIT 2:を使用して、行の一意性を検証するようにしましたHashMap

于 2013-01-15T13:28:24.867 に答える
2

線を埋めることができるので、私はそれらの線に沿って何かをします、そしてそれでも、List実際に保持できるものに関して制限があるかもしれないことに注意する必要があります。

行を読み取りたいときに毎回乱数を使用してそれをに追加するSetこともできますが、これにより、ファイルが完全に読み取られるようになります。

public class VeryLargeFileReading
    implements Iterator<String>, Closeable
{
    private static Random RND = new Random();
    // List of all indices
    final List<Long> indices = new ArrayList<Long>();
    final RandomAccessFile fd;

    public VeryLargeFileReading(String fileName, long lineSize)
    {
        fd = new RandomAccessFile(fileName);
        long nrLines = fd.length() / lineSize;
        for (long i = 0; i < nrLines; i++)
            indices.add(i * lineSize);
        Collections.shuffle(indices);
    }

    // Iterator methods
    @Override
    public boolean hasNext()
    {
        return !indices.isEmpty();
    }

    @Override
    public void remove()
    {
        // Nope
        throw new IllegalStateException();
    }

    @Override
    public String next()
    {
        final long offset = indices.remove(0);
        fd.seek(offset);
        return fd.readLine().trim();
    }

    @Override
    public void close() throws IOException
    {
        fd.close();
    }
}
于 2013-01-15T13:25:32.873 に答える
2

入力ファイルを前処理し、新しい各行のオフセットを覚えておいてください。a を使用しBitSetて、使用された行を追跡します。メモリを節約したい場合は、16 行ごとのオフセットを覚えておいてください。ファイルにジャンプして、16 行のブロック内でシーケンシャル ルックアップを実行するのは簡単です。

于 2013-01-15T13:20:43.413 に答える
1

ファイルの数が本当に恣意的である場合、処理されたファイルをメモリ使用量 (またはリストまたはセットではなくファイルで追跡する場合は IO 時間) に関して追跡することに関連する問題があるようです。選択された行の増加するリストを保持するソリューションも、タイミング関連の問題に遭遇します。

私は次の行に沿って何かを考えます:

  1. n 個の「バケット」ファイルを作成します。nは、ファイル数とシステム メモリを考慮したものに基づいて決定できます。( nが大きい場合は、 nのサブセットを生成して、開いているファイル ハンドルを抑えることができます。)
  2. 各ファイルの名前はハッシュされ、適切なバケット ファイルに入れられ、任意の基準に基づいてディレクトリが「シャーディング」されます。
  3. バケット ファイルの内容 (ファイル名のみ) を読み込んでそのまま処理するか (ハッシュ メカニズムによって提供されるランダム性)、または rnd(n) を選択して削除することで、もう少しランダム性を高めます。
  4. または、ランダムアクセスのアイデアをパディングして使用し、インデックス/オフセットが選択されたときにリストから削除することもできます。
于 2013-01-16T15:00:08.860 に答える