我想处理一个文本文件(大约400 MB),以便根据每行中给出的数据创建递归父子结构。必须为自上而下的导航准备数据(输入:父级,输出:所有子级和子级)。例如,要读取的行数:(子、id1、id2、父和id3)
132142086;1.21322528589;132528599
132142087;1.31322528589;132528599
132142088;1.01322528589;132528599
323442444;1.0132142088;132528599
454345434;1.0323442444;132528599
132528589:是132142086132142087132142088的父级
132142088:是323442444的父级
323442444:是454345434的父级
给定:操作系统windows xp,32位,2GB可用内存和-Xmx1024m以下是我准备数据的方法:
HashMap<String,ArrayList<String>> hMap=new HashMap<String,ArrayList<String>>();
while ((myReader = bReader.readLine()) != null)
{
String [] tmpObj=myReader.split(delimiter);
String valuesArrayS=tmpObj[0]+";"+tmpObj[1]+";"+tmpObj[2]+";"+tmpObj[3]+";"+tmpObj[4];
ArrayList<String> valuesArray=new ArrayList<String>();
//case of same key
if(hMap.containsKey(tmpObj[3]))
{
valuesArray=(ArrayList<String>)(hMap.get(tmpObj[3])).clone();
}
valuesArray.add(valuesArrayS);
hMap.put(tmpObj[3],valuesArray);
tmpObj=null;
valuesArray=null;
}
return hMap;
之后,我使用递归函数:
HashMap<String,ArrayList<String>> getChildren(input parent)
用于创建所需的数据结构。计划是使用函数getChildren为多个线程提供hMap(只读)。
我用90MB的输入文件测试了这个程序,它似乎工作正常。但是,使用超过380 MB的真实文件运行它会导致:
线程"main"java.lang.OutOfMemoryError中出现异常:java堆空间
我需要一些内存资源管理方面的帮助
从"非常简单的方法"的角度来看:根据你的问题陈述,你不需要保留id1、id2或id3。假设是这样的话,用HashMap<Integer, ArrayList<Integer>>
替换HashMap<String, ArrayList<String>>
怎么样?可以使用Integer.parseInt()
进行字符串到int的转换,Integer应该始终小于相应的string。
其他建议:如果你不在乎重复,用HashSet
替换你的ArrayList
。
根据outofBounds的回答,你不需要每次都克隆一个ArrayList
来添加一个项目。
按照其他人的建议,检查一下是否增加了你的内存。此外,您可以按照Sbodd和其他人的建议,更好地将数据存储在表中。
然而,您可能正在与内存碎片冲突。哈希映射使用数组。大散列映射使用大数组。您没有指定哈希映射的大小,所以每次它决定需要更大时,都会丢弃旧数组并分配一个新数组。过一段时间,您的内存会被丢弃的哈希表数组填满,并且您会得到OutOfMemoryException,尽管从技术上讲您有足够的可用内存。(90%的内存可以使用,但碎片太小,无法使用。)
垃圾收集器(GC)将持续工作,将所有这些空闲位组合成足够大的块来使用。如果你的程序运行得足够慢,你就不会有问题,但你的程序正在全速运行,GC会落后的。如果GC不能以足够快的速度组装足够大的空闲块,它将抛出异常;内存的存在并不能阻止它
如果你知道你的哈希图必须有多大,我会提前设置大小。即使大小不完全正确,它也可以在不增加堆大小的情况下解决内存问题,并且肯定会使程序运行得更快(或者与文件读取允许的速度一样快——使用大文件缓冲区)。
如果你真的不知道你的桌子有多大,可以使用TreeMap。它有点慢,但不会分配巨大的数组,因此对GC更友好。我发现它们更加灵活和有用。您甚至可以查看ConcurrentSkipTreeMap,它比TreeMap慢,但允许您同时添加、读取和删除多个线程。
但你最好的选择是:
hMap = new HashMap<String,ArrayList<String>>( 10000000 );
在While循环中,u可以减少一些空间,类似于
String [] tmpObj=myReader.split(delimiter);
// String = String + String takes more Space than String.format(...)
//String valuesArrayS=tmpObj[0]+";"+tmpObj[1]+";"+tmpObj[2]+";"+tmpObj[3]+";"+tmpObj[4];
// Just Adding if thers is no List for a Key
if(!hMap.containsKey(tmpObj[3]){
hMap.put(tmpObj[3], new ArrayList<String>());
}
// Gettin the list from the Map and adding the new stuff
List<String> values = hMap.get(tmpObj[3]);
values.add(String.format("%s;%s;%s;%s;%s",tmpObj[0], tmpObj[1], tmpObj[2], tmpObj[3], tmpObj[4]));
无需克隆列表
您实际上是在测试1GB内存的极限。
你可以:
- 增加堆空间。32位窗口会将您的容量限制在1.5GB左右,但您仍有更多的活动空间,这可能足以让您脱颖而出
- 构建某种类型的预处理器实用程序,以您知道的大小对文件进行预分区,并一次对其进行操作,也许是在等级上
- 试着重新组织你的课程。在java中,字符串是不可变的拆分字符串并与正在创建的
+
运算符连接新字符串一直存在(10种情况中有9种情况不重要,但在您使用非常有限的资源的情况下,这可能会有所不同)
作为一个不太有用的注释。这里真正的问题是,你没有资源来处理这项任务,而优化只会让你走这么远。这就像在问如何更好地用花园泥铲在山上挖隧道一样。真正的答案可能是你不想听到的,那就是扔掉铲子,投资一些工业设备
第二个更有用的注意事项(如果你和我一样,也很有趣)是,你可以尝试将jVisualVM连接到你的应用程序上,并试图了解你的堆的去向,或者使用jhat和-XX:+HeapDumpOnOutOfMemoryError
jvm标志来查看崩溃时堆发生了什么。