如何逐行解析一个巨大的文件,有效地序列化和反序列化一个巨大的对象?



我有一个大小约为 4-5 Gigs(近十亿行(的文件。从文件的每一行,我必须解析整数数组和其他整数信息,并更新我的自定义数据结构。我的类保存这些信息看起来像

class Holder {
    private int[][] arr = new int[1000000000][5]; // assuming that max array size is 5
    private int[] meta = new int[1000000000];
}

文件中的示例行如下所示

(1_23_4_55)    99

arr & meta 中的每个索引都对应于文件中的行号。从上面的行中,我首先提取整数数组,然后提取元信息。在这种情况下,

--pseudo_code--
arr[line_num] = new int[]{1, 23, 4, 55}
meta[line_num]=99

现在,我正在使用BufferedReader对象,它是读取每一行并使用字符级别操作来解析每行的整数数组和元信息并填充Holder实例readLine方法。但是,完成整个操作需要将近半个小时。

我使用java SerializationExternalizable(编写metaarr(来序列化和反序列化这个HUGE Holder实例。而对于它们,序列化的时间几乎是半小时,反序列化的时间也几乎是半小时。

我将不胜感激您对处理此类问题的建议,如果有的话,我绝对希望听到您的故事部分。

附言主内存不是问题。我的机器中有近 50 GB 的 RAM。我还将 BufferedReader 大小增加到 40 MB(当然,考虑到磁盘访问大约需要 100 MB/秒,我可以将其增加到 100 MB(。甚至内核和CPU也不是问题。

编辑一

下面提供了我用来执行此任务的代码(在匿名化很少的信息之后(;

public class BigFileParser {
private int parsePositiveInt(final String s) {
    int num = 0;
    int sign = -1;
    final int len = s.length();
    final char ch = s.charAt(0);
    if (ch == '-')
        sign = 1;
    else
        num = '0' - ch;
    int i = 1;
    while (i < len)
        num = num * 10 + '0' - s.charAt(i++);
    return sign * num;
}
private void loadBigFile() {
    long startTime = System.nanoTime();
    Holder holder = new Holder();
    String line;
    try {
        Reader fReader = new FileReader("/path/to/BIG/file");
        // 40 MB buffer size
        BufferedReader bufferedReader = new BufferedReader(fReader, 40960);
        String tempTerm;
        int i, meta, ascii, len;
        boolean consumeNextInteger;
        // GNU Trove primitive int array list
        TIntArrayList arr;
        char c;
        while ((line = bufferedReader.readLine()) != null) {
            consumeNextInteger = true;
            tempTerm = "";
            arr = new TIntArrayList(5);
            for (i = 0, len = line.length(); i < len; i++) {
                c = line.charAt(i);
                ascii = c - 0;
                // 95 is the ascii value of _ char
                if (consumeNextInteger && ascii == 95) {
                    arr.add(parsePositiveInt(tempTerm));
                    tempTerm = "";
                } else if (ascii >= 48 && ascii <= 57) { // '0' - '9'
                    tempTerm += c;
                } else if (ascii == 9) { // 't'
                    arr.add(parsePositiveInt(tempTerm));
                    consumeNextInteger = false;
                    tempTerm = "";
                }
            }
            meta = parsePositiveInt(tempTerm);
            holder.update(arr, meta);
        }
        bufferedReader.close();
        long endTime = System.nanoTime();
        System.out.println("@time -> " + (endTime - startTime) * 1.0
                / 1000000000 + " seconds");
    } catch (IOException exp) {
        exp.printStackTrace();
    }
}
}
public class Holder {
    private static final int SIZE = 500000000;
    private TIntArrayList[] arrs;
    private TIntArrayList metas;
    private int idx;
    public Holder() {
        arrs = new TIntArrayList[SIZE];
        metas = new TIntArrayList(SIZE);
        idx = 0;
    }
    public void update(TIntArrayList arr, int meta) {
        arrs[idx] = arr;
        metas.add(meta);
        idx++;
    }
}
听起来文件

I/O 所花费的时间是主要的限制因素,因为序列化(二进制格式(和您自己的自定义格式需要大约相同的时间。

因此,您可以做的最好的事情就是减小文件的大小。如果你的数字通常很小,那么你可以从使用谷歌协议缓冲区中获得巨大的提升,它将编码小整数通常在一个或两个字节中。

或者,如果你知道你所有的数字都在0-255范围内,你可以使用byte[]而不是int[],并将大小(以及加载时间(减少到现在的四分之一。(假设您返回到序列化或只是写入字节通道(

根本用不了那么长时间。您正在使用大约 6e9 int s,这意味着 24 GB。将 24 GB 写入磁盘需要一些时间,但不会超过半小时。

我会将所有数据放在一个一维数组中,并通过int getArr(int row, int col)等方法访问它,这些方法将rowcol转换为单个索引。根据数组的访问方式(通常按行或通常按列(,此索引将计算为N * row + colN * col + row以最大化局部性。我还会将meta存储在同一个数组中。

将一个巨大的int[]写入内存应该非常快,肯定没有半小时。

由于数据量的原因,上述方法不起作用,因为您不能拥有 6e9 条目数组。但是你可以改用几个大数组,上述所有内容都适用(从rowcol计算一个long索引,并将其分成两个int以访问2D数组(。

确保您没有交换。交换是我能想到的速度慢的最可能原因。

有几种替代的 Java 文件 I/O 库。 这篇文章有点旧,但它提供了一个仍然普遍有效的概述。 他使用6岁的Mac每秒读取约300Mb。 因此,对于4Gb,您的读取时间不到15秒。 当然,我的经验是Mac IO通道非常好。YMMV 如果你有一台便宜的电脑。

请注意,与 4K 左右的缓冲区大小相比没有任何优势。 事实上,你更有可能在大缓冲区下引起捶打,所以不要这样做。

这意味着将字符解析到您需要的数据中是瓶颈。

我发现在其他应用程序中,读取字节块并编写类似 C 的代码来提取我需要的内容比内置的 Java 机制(如 split 和正则表达式(更快。

如果这仍然不够快,则必须回退到本机 C 扩展。

如果你随机暂停它,你可能会看到大部分时间都用于解析整数和/或所有new -ing,如new int[]{1, 23, 4, 55} .如果您仔细编码,您应该能够分配一次内存并以比 I/O 速度更好的速度将数字插入其中。

但是还有另一种方法 - 为什么文件是 ASCII?如果它是二进制的,你可以把它啜一口。

相关内容

  • 没有找到相关文章

最新更新