Java - 如何高效地存储大量的字符串数组

5
我正在尝试用Java高效地加载大型CSV格式文件(通常为200-600MB),需要使用更少的内存和尽可能快的访问速度。目前,程序正在利用一个String数组列表。以前,Lua程序使用一个表格处理每个CSV行,并使用一个表格来保存每个“行”表格。
以下是内存差异和加载时间的示例:
- CSV文件 - 232 MB - Lua - 549 MB内存 - 加载157秒 - Java - 1,378 MB内存 - 加载12秒
如果我记得正确,Lua表中的重复项存在于实际值的引用中。我怀疑在Java示例中,List正在保存每个重复值的副本,这可能与较大的内存使用有关。
以下是CSV文件中数据的一些背景信息:
- 每个字段都由字符串组成。 - 每行中的特定字段可能包括一组字符串中的一个(例如,字段3可以是“红色”,“绿色”或“蓝色”)。 - 内容中有许多重复的字符串。
以下是加载的数据可能需要完成的一些示例:
- 搜索所有字符串以尝试与给定字符串匹配并返回匹配的字符串。 - 在GUI表中显示匹配项(可通过字段排序)。 - 更改或替换字符串。
我的问题是 - 是否有一个集合可以需要更少的内存来保存数据,但仍然提供易于快速搜索/排序数据的功能?

1
如果您知道第三列只包含一些可能的值,您可以将它们内部化以减少内存使用。另请参阅:https://dev59.com/9nI-5IYBdhLWcg3wbXxN#1855195 - assylias
谢谢assylias,我会用它来进行一些测试。你知道对于短字符串(例如"To"或"Go")它是否高效吗?大多数字段包含长度为45个字符以上的字符串,但有些非常短(4个字符或更少)。 - user1816198
2
请看https://dev59.com/s2nWa4cB1Zd3GeqPzlvF。 - Peter Lawrey
@PeterLawrey 不错 - 它与 intern() 相比表现如何? - assylias
1
@assylias 它更快速更可扩展,但仅以最佳努力为基础运作,如果您的大小小于唯一对象数量,您将获得所有重复项。 - Peter Lawrey
我刚尝试使用intern()加载相同的数据。这已经在小幅度增加加载时间的情况下显著地提高了内存使用率。 596mb的内存 - 26秒的加载时间。这里有很多好的建议...我将会用其中一些来运行测试。 - user1816198
5个回答

1
一个简单的解决方案是,您可以使用一些HashMap,将所有唯一字符串的引用放入其中。然后在ArrayList中,只需引用HashMap中现有的唯一字符串即可。
类似于以下内容:
private HashMap<String, String> hashMap = new HashMap<String, String>();

public String getUniqueString(String ns) {
   String oldValue = hashMap.get(ns);
   if (oldValue != null) { //I suppose there will be no null strings inside csv
    return oldValue;
   }        
   hashMap.put(ns, ns);
   return ns;
}

简单使用:

List<String> s = Arrays.asList("Pera", "Zdera", "Pera", "Kobac", "Pera", "Zdera", "rus");
List<String> finS = new ArrayList<String>();
for (String er : s) {
   String ns = a.getUniqueString(er);
   finS.add(ns);
}

1
听起来你正在尝试优化Java已经优化过的东西(在内存中保存重复的字符串以节省内存),没有必要这样实现,可以看看我的回答。 - Peter Butkovic

0

1
我最终尝试了文章中提供的两个示例。结果表明,intern()可以节省最多的内存。我将继续进行实验(特别是在完成更多项目后),但这肯定使我的内存使用量与Lua保持一致,尽管加载时间要快得多。 - user1816198
这就是为什么你不应该只提供链接作为答案 - 链接现在已经失效了。 - Andreas Hartmann

0
为了优化您的内存问题,我建议使用享元模式,特别是对于具有大量重复项的字段。
作为集合,您可以使用TreeSetTreeMap
如果您为LineItem类提供良好的实现(实现equalshashcodeComparable),则可以大大优化内存使用。

0

DAWG

有向无环词图是存储单词的最有效方式(无论如何都是最节省内存的)。

但在这里可能过度了,正如其他人所说,不要创建重复项,只需对同一实例进行多次引用即可。


谢谢,我会进一步研究这个选项。目前我不认为任何东西过度 - 这种方式越有效率每次会话就能加载更多数据,对最终用户来说更好。 - user1816198

0

仅作为附注。

关于你怀疑的重复字符串数据,你不需要担心,因为Java本身会处理这个问题,所有字符串都是final的,并且所有引用都指向内存中的同一个对象。

所以不确定Lua如何处理这个问题,但在Java中应该也非常高效。


但如果这是真的,那么equals根本不必要,==就可以完成比较的工作。 - Igor
嗯,我不确定Java虚拟机内部为保留字符串引用而持有多少内存,但我非常确定,在足够大的程序中,==将无法正常工作。 - Igor
你在开玩笑吧?参见:https://dev59.com/NnRA5IYBdhLWcg3w9izq(Michal Bernhard的回答),为什么JVM只会以这种优化的方式引用一些字符串(而不是全部)? - Peter Butkovic
是的,但在这个问题示例中,我相信@user1816198不会有一堆静态的“Some String”字符串,而是会有一堆动态字符串(我想他会使用StringBuilders或其他东西来解析csv)。 例如尝试这个简单的程序String a =“Helo,what s up,baby,Hello,baby”; String [] b = a.split(“,”); for(String c:b){ System.out.println(c); }if(b [0] == b [3]){ System.out.println(“Equals Hello”); }if(b [2] == b [4]){ System.out.println(“Equals baby”); }我的机器上最后两个if语句都是false。 - Igor
正确的,一个字符串是一个对象引用,对此没有异议。 - Peter Butkovic
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接