4

有没有比创建满足以下标准的流文件阅读器类更好的 [预先存在的可选 Java 1.6] 解决方案?

  • 给定一个任意大尺寸的 ASCII 文件,其中每一行都以\n
  • 对于某些方法的每次调用,readLine()从文件中读取随机行
  • 并且在文件句柄的整个生命周期内,没有调用readLine()应该返回同一行两次

更新:

  • 最终必须读取所有行

上下文:文件的内容是从 Unix shell 命令创建的,以获取给定目录中包含的所有路径的目录列表;有数百万到十亿个文件(在目标文件中产生数百万到十亿行)。如果有某种方法可以在创建期间将路径随机分布到文件中,这也是可接受的解决方案。

4

4 回答 4

5

为了避免在您的情况下可能无法读取整个文件,您可能希望使用 aRandomAccessFile而不是标准 java 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应该在最后保留您随机选择的行。您可能需要检查您是否还没有随机跳到文件末尾以避免在这种情况下出现错误。

编辑:我把它换到文件的开头,以防你到达结尾。这是一个非常简单的检查。

编辑 2:我通过使用HashMap.

于 2013-01-15T13:28:24.867 回答
2

预处理输入文件并记住每个新行的偏移量。使用 aBitSet来跟踪使用的行。如果你想节省一些内存,那么记住每 16 行的偏移量;跳入文件并在 16 行的块内进行顺序查找仍然很容易。

于 2013-01-15T13:20:43.413 回答
2

由于您可以填充线条,因此我会按照这些线条做一些事情,并且您还应该注意,即使那样,对于 aList可以实际容纳的内容也可能存在限制。

每次您想读取该行并将其添加到 a 时使用一个随机数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 回答
1

如果文件的数量确实是任意的,那么在内存使用方面(或 IO 时间,如果在文件而不是列表或集合中跟踪),跟踪处理的文件似乎可能存在相关问题。保留不断增长的选定行列表的解决方案也会遇到与时序相关的问题。

我会考虑以下几点:

  1. 创建n 个“桶”文件。n可以根据考虑到文件数量和系统内存的因素来确定。(如果n很大,您可以生成n的子集以保持打开的文件句柄。)
  2. 每个文件的名称都经过哈希处理,并进入适当的存储桶文件,根据任意标准“分片”目录。
  3. 读入存储桶文件内容(只是文件名)并按原样处理(散列机制提供的随机性),或者选择 rnd(n) 并随时删除,提供更多随机性。
  4. 或者,您可以填充并使用随机访问的想法,在选择时从列表中删除索引/偏移量。
于 2013-01-16T15:00:08.860 回答