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

时间:2014-04-18 09:59:32

标签: java performance bigdata

我的文件大小约为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对象及其readLine方法来阅读每一行&使用字符级操作来解析每行中的整数数组和元信息,并填充Holder实例。但是,完成整个操作需要将近半个小时。

我同时使用了java Serialization& Externalizable(写metaarr)来序列化和反序列化这个巨大的Holder实例。对于他们两个,序列化的时间差不多是半小时,反序列化也差不多半个小时。

我很感激您对处理此类问题的建议。如果有的话,我一定会喜欢听你的故事。

P.S。主内存不是问题。我的机器里有近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++;
    }
}

4 个答案:

答案 0 :(得分:2)

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

因此,您可以做的最好的事情是减小文件的大小。如果您的数字通常较小,那么使用Google protocol buffers可以获得巨大的提升,generally in one or two bytes将编码小整数{{3}}。

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

答案 1 :(得分:1)

如果您randomly pause,您可能会发现大部分时间都用于解析整数和/或所有new,如new int[]{1, 23, 4, 55}中所示。您应该能够只分配一次内存,并且如果您仔细编码,则以比I / O速度更好的速度将数字加入其中。

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

答案 2 :(得分:1)

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

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

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

由于数据量不足,上述方法不起作用,因为您不能拥有6e9条目数组。但是您可以使用几个大数组,而上述所有数组都适用(从longrow计算col索引并将其拆分为两个int以供访问二维数组)。

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

答案 3 :(得分:1)

有几个备用Java文件i / o库。 This article有点旧,但它提供的概述仍然普遍有效。他用6岁的Mac阅读大约每秒300Mb。因此对于4Gb,您的阅读时间不到15秒。当然我的经验是Mac IO频道非常好。 YMMV,如果你有便宜的PC。

请注意,缓冲区大小为4K左右没有优势。事实上,你更有可能因为大缓冲而引起颠簸,所以不要这样做。

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

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

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