我的程序要读入一个数据文件,文件的每一行是这样的:
[1001,0.0,2.846335314101648,0.2385266331350405,0.6889106550987336,0.6265634872937542,0.7916442990777731...]
每一行是由逗号分隔的11063个字段,我把读到的每一个字段转换成了float类型。每一行存到一个 ArrayList<Float>里面,一共是11063行,也就是说我存下了一个11063*11063的矩阵,矩阵的每个元素是float类型,所以总的大小应该是11063*11063*4/(1024*1024)=466M
程序读入到1万行的时候就卡住了,查看任务管理器显示内存占用量是3.4G,并且还发生了一个小插曲,当读到4000行的时候卡了20秒,按我的理解可能是在这里数组重新申请了内存,可是有有点疑惑,因为我在程序的最开始是初始化了K_L数组的:
ArrayList<ArrayList<Float>> K_L = new ArrayList<ArrayList<Float>>(12000);
按理来说不会发生类似于rehash这种动作的啊。这只是一个小插曲,我的问题是,为什么计算得到的内存是466M,而实际上用了3.4G都没有读完,并且最后停在了10000行处,就算是有额外的开销,也不至于这么大啊。我的程序非常简单,在数据结构和算法上应该是不能更精简了。我想问问同学们关于我的内存问题。还有就是大家在将这种较大的数据读入到内存中时,有什么好的方案吗,比如redis?我不太了解,只是听人提起过。大家分享下吧,thanks。代码如下。
String each_line = null;
while((each_line = K_L_file_handler.readLine())!=null){
String tokens [] = each_line.split(",");
ArrayList<Float> K_L_item = new ArrayList<Float>();
for(int i = 0 ; i < tokens.length ; i++){
K_L_item.add( Float.parseFloat(tokens[i]));
}
K_L.add(K_L_item);
}
System.out.println("finish initialing the Kullback–Leibler divergence");
1
kaneg 2015-01-12 17:20:53 +08:00 1
以下是我的一点看法:
1. 你这里用的是Float对象,并不是float基本类型,基本类型是4个byte,而一个Float对象则要大得多。每个对象都与一个引用,64位jvm,每个应用要8个字节,加上float本身的存储,就已经是3倍了。索尼你不要用Float,直接用float 2. 其次你使用了ArrayList, 你只初始化了一个维度的长度,另一个维度的没有,那它就需要不断动态调整,这样也有额外的内存消耗。这里你既然已经知道了长度,就应该用数组,不要用ArrayList |
2
buptlee OP @kaneg 可是ArrayList<float> K_L_item = new ArrayList<float>();
ArrayList<ArrayList<float>> K_L = new ArrayList<ArrayList<float>>(12000); 都不被允许啊。 还有就是数组和ArrayList有本质的区别吗,或者说,什么原因使得ArrayList比数组低效了呢? |
3
buptlee OP @kaneg 我其实也是想直接用float的,但是在声明的时候,
ArrayList<float> K_L_item = new ArrayList<float>();会出错,必须要包装类型才可以。 |
4
kaneg 2015-01-12 17:32:07 +08:00 1
基本类型是不能用泛型的,所以ArrayList<float>不支持。
数组是Java原生提供的数据结构,ArrayList是一个普通的类,不考虑性能的话,ArrayList是很好用,但考虑性能最好老老实实用数组,除非你不在乎内存消耗 float[][] data = new float[12000][12000]; |
5
songco 2015-01-12 17:33:13 +08:00 1
我记得java有8 byte的object header, 另外你用Float的话, 还有reference的开销. 还有内存对齐的padding, 我记得java对象是按8 byte对齐的. 这些加起来估计比你算的4 byte多很多.
具体的情况还是弄个heap dump看看吧. 这种原始数据如果需要全部加载, 建议用primitive type的多维数组, 或者用c之类的写, 当然如果能根据业务优化一下不要全部加载进来就更好了. |
6
buptlee OP @kaneg 我重新写一下程序。用惯了ArrayList,对原生态数组表示有点生疏了,thanks,it's so kind of you.
|
7
buptlee OP @songco 嗯,总是被java的内存折磨,看来primitive type数组才是真爱,不该贪图方便一股脑ArrayList。谢谢你。
|
8
mfaner 2015-01-12 17:45:52 +08:00 1
ArrayList<Float> K_L_item = new ArrayList<Float>();
这里也要指定capacity,觉得应该会影响很大 |
9
ericson 2015-01-12 18:17:42 +08:00
也有支持primitve type的高性能集合库:
* [fastutil](http://fastutil.di.unimi.it/) * [OpenHFT](https://github.com/OpenHFT/Koloboke) * [hppc](http://labs.carrotsearch.com/hppc.html) * [trove](http://trove.starlight-systems.com/) |
11
msg7086 2015-01-12 20:53:56 +08:00 via iPhone 1
典型的内存密集型操作,托管语言的短板之一啊。
用C系应该会好得多。 |
12
mfaner 2015-01-12 22:20:19 +08:00 1
再来补充下,split方法是正则匹配,而且内部也是一个无参构造的ArrayList。
|
13
coolcfan 2015-01-12 23:05:17 +08:00 1
token那块老老实实indexof或者找个高效的库处理吧。。。
|
14
icespace 2015-01-13 09:32:01 +08:00 1
有两个思路值得考虑
1.使用内存型数据库管理数据 2.使用内存映射文件 |
15
thinkmore 2015-01-13 09:34:09 +08:00 1
数据量过大了,就算你使用基本类型数组提高也不大,当然是肯定有提升的,毕竟ArrayList内部就是使用的基本类型数组罢了,建议你可以一次性读1W行(不一定是1W,找一个比较合理的数据)
|
21
wudikua 2015-01-13 14:08:15 +08:00
你想想为什么array list可以不用管数组的下标限制直接可以无限的add而不抛出out of bound就知道了
|
22
icespace 2015-01-14 10:18:01 +08:00
@buptlee MappedByteBuffer , 即利用file chanel(NIO)将文件部分或全部映射到内存中,用来处理大文件。这样,你就可以利用操作系统的功能来间接操作文件,而且避免了文件操作错误。需要注意的是,如果处理4G以上的大文件,你需要使用64位JVM来运行。卤煮请移步http://www.linuxidc.com/Linux/2013-11/92895.htm 。所以这个基本上就是你要的。
|
23
icespace 2015-01-14 10:23:06 +08:00
@buptlee 另外的一个思路是使用嵌入型数据库,将文件结构化的导入嵌入型数据库后,数据的存储与缓存就交由它来处理,典型的如BerkeleyDB。这个办法比内存映射文件更加有效是因为你的数据是结构化的,这样你就可以充分利用数据的排序、查找、过滤,其处理速度将会远远超过你自己写代码,而且极大减少debug的时间。你不在需要自己组织庞大的数组结构,也不在需要自己去hash。我想唯一的缺点可能就是程序包会稍微大一点。卤煮,我给你的这两条建议,你还满意么?
|
24
buptlee OP 满意到哭啊,多谢大神。
目前在钻研第一个思路。 第二个思路感觉有点复杂,先不尝试了。 thanks so much |