簡體   English   中英

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

[英]How to parse a huge file line by line, serialize & deserialize a huge object efficiently?

我有一個大小約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

arrmeta中的每個索引都對應於文件中的行號。 從上面的行,我首先提取整數數組,然后提取元信息。 在這種情況下,

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

現在,我使用BufferedReader對象,它的readLine方法讀取每一行並使用字符級操作來解析每行的整數數組和元信息,並填充Holder實例。 但是,完成整個操作需要將近半個小時。

我使用了java SerializationExternalizable (編寫metaarr )來序列化和反序列化這個巨大的Holder實例。 對於他們兩個,序列化的時間幾乎是半小時,反序列化也差不多半小時。

我很感激你對處理這類問題的建議,如果有的話,我很樂意聽你的故事。

PS主內存不是問題。 我的機器里有近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所花費的時間是主要的限制因素,因為序列化(二進制格式)和您自己的自定義格式大約需要同一時間。

因此,您可以做的最好的事情是減小文件的大小。 如果您的數字通常很小,那么使用Google協議緩沖區可以獲得巨大的提升,這些緩沖區 通常會以一個或兩個字節編碼小整數。

或者,如果您知道所有數字都在0-255范圍內,則可以使用byte []而不是int []並將大小(因此加載時間)縮小到現在的四分之一。 (假設您返回序列化或只是寫入ByteChannel)

如果您隨機暫停它,您可能會看到大部分時間用於解析整數和/或所有new ,如new int[]{1, 23, 4, 55} 如果您仔細編碼,您應該能夠只分配一次內存並以優於I / O速度將數字加入其中。

但還有另一種方法 - 為什么ASCII文件? 如果它是二進制的,你可以把它捏起來。

它根本不能花那么長時間。 你正在使用一些6e9 int ,這意味着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如果你有便宜的PC。

請注意,緩沖區大小為4K左右沒有優勢。 事實上,你更有可能因為大緩沖而導致顛簸,所以不要這樣做。

這意味着將字符解析為您需要的數據是瓶頸。

我在其他應用程序中發現,讀取一個字節塊並編寫類似C的代碼來提取我需要的東西比split和正則表達式等內置Java機制更快。

如果仍然不夠快,則必須回退到本機C擴展。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM