[英]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
arr
& meta
中的每個索引都對應於文件中的行號。 從上面的行,我首先提取整數數組,然后提取元信息。 在這種情況下,
--pseudo_code--
arr[line_num] = new int[]{1, 23, 4, 55}
meta[line_num]=99
現在,我使用BufferedReader
對象,它的readLine
方法讀取每一行並使用字符級操作來解析每行的整數數組和元信息,並填充Holder
實例。 但是,完成整個操作需要將近半個小時。
我使用了java Serialization
和Externalizable
(編寫meta
和arr
)來序列化和反序列化這個巨大的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)
等方法訪問它,這些方法將row
和col
轉換為單個索引。 根據數組如何被訪問(通常是行方式或通常是列方式),該索引將被計算為N * row + col
或N * col + row
以最大化局部性。 我還將meta
存儲在同一個數組中。
將一個巨大的int[]
寫入內存應該非常快,肯定沒有半個小時。
由於數據量,上述方法不起作用,因為您不能擁有6e9條目數組。 但是您可以使用幾個大數組,並且以上所有都適用(從row
和col
計算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.