我的文件大小约为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实例。对于他们两个,序列化的时间差不多是半小时,反序列化也差不多半个小时。
我很感激您对处理此类问题的建议。如果有的话,我一定会喜欢听你的故事。
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++;
}
}
答案 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)
等方法访问它,将row
和col
转换为单个索引。根据数组的访问方式(通常是行方式或通常按列方式),此索引将计算为N * row + col
或N * col + row
以最大化位置。我还将meta
存储在同一个数组中。
将一个巨大的int[]
写入内存应该非常快,肯定没有半个小时。
由于数据量不足,上述方法不起作用,因为您不能拥有6e9条目数组。但是您可以使用几个大数组,而上述所有数组都适用(从long
和row
计算col
索引并将其拆分为两个int
以供访问二维数组)。
确保您没有交换。交换是我能想到的速度慢的最可能的原因。
答案 3 :(得分:1)
有几个备用Java文件i / o库。 This article有点旧,但它提供的概述仍然普遍有效。他用6岁的Mac阅读大约每秒300Mb。因此对于4Gb,您的阅读时间不到15秒。当然我的经验是Mac IO频道非常好。 YMMV,如果你有便宜的PC。
请注意,缓冲区大小为4K左右没有优势。事实上,你更有可能因为大缓冲而引起颠簸,所以不要这样做。
这意味着将字符解析为您需要的数据是瓶颈。
我在其他应用程序中发现,读取一个字节块并编写类似C的代码来提取我需要的内容比内置的Java机制(如split
和正则表达式)更快。
如果仍然不够快,则必须回退到本机C扩展名。