我有大量的名称 - 值对(大约100k),我需要存储在某种缓存中(比如哈希映射),其中值是一个平均大小约为30k字节的字符串。 / p>
现在我知道一个事实,即大量的值具有完全相同的字符串数据。为了避免必须多次分配相同的字符串数据,我想以某种方式重用先前分配的字符串,从而消耗更少的内存。此外,这需要相当快。即,逐个扫描所有先前分配的值不是一种选择。
有关如何解决此问题的任何建议?
答案 0 :(得分:10)
不使用String.intern(多年来一直存在与此相关的各种内存问题)。相反,创建自己的缓存,类似于String.intern。基本上,你想要一个Map,每个键映射到自己。然后,在缓存任何字符串之前,你“实习”它:
private Map<String,WeakReference<String>> myInternMap = new WeakHashMap<String,,WeakReference<String>>();
public String intern(String value) {
synchronized(myInternMap) {
WeakReference<String> curRef = myInternMap.get(value);
String curValue = ((curRef != null) ? curRef.get() : null);
if(curValue != null) {
return curValue;
}
myInternMap.put(value, new WeakReference<String>(value));
return value;
}
}
请注意,您对键和值使用弱引用,这样就不会保留对不再使用的字符串的引用。
答案 1 :(得分:9)
String.intern()会帮助你(最有可能)。它会将相同字符串的多个实例解析为一个副本。
编辑:我建议这“最有可能”帮助。在什么情况下不会?实习字符串将具有存储那些内部字符串表示永久的效果。如果问题域是一次性过程,这可能不是问题。如果这是一个长时间运行的过程(例如Web应用程序),那么您可能会遇到问题。我会毫不犹豫地说从不使用实习(我会犹豫说从不做任何事情)。然而,有些情况并不理想。
答案 2 :(得分:4)
String.intern
是明显的选择。但是如果你不想在内存中的所有String中实习,你可以使用Set来首先查看该值是否存在。这是未经测试的代码。从主
class Map2<K, V> implements Map<K, V>
{
Map<K, V> _map = Maps.newHashMap();
Set<V, V> _rev = Maps.newHashMap();
V put(K k, V v) {
if (_rev.containsKey(v)) {
V prev = _rev.get(v);
return _map.put(k, prev);
} else {
_rev.put(v, v);
return _map.put(k,v);
}
}
答案 3 :(得分:1)
这在某种程度上取决于您如何创建String
。
一种可能的方法是使用TreeSet
使用可以比较现有Comparator
和新String
来源的String
。使用SortedSet.tailSet
和Iterator
查找现有String
。或者NavigableSet.ceiling/floor
或具有类似设置的TreeMap
。
我写了一个weblog entry关于另一种缓存不可变对象(特别是字符串)的技术,但这更适合于较小的对象。
String.intern
存在性能问题。
答案 4 :(得分:1)
与其他人一致不使用String.intern():一旦你在那里放了一个字符串,它就永远不会消失。看看Xerces的早期修订,为什么这是一个坏主意。
更好的解决方案是使用WeakHashMap,将值包装在WeakReference中:
private Map<String,WeakReference<String>> _map
= new WeakHashMap<String,WeakReference<String>>();
public synchronized String intern(String str)
{
WeakReference<String> ref = _map.get(str);
String s2 = (ref != null) ? ref.get() : null;
if (s2 != null)
return s2;
str = new String(str);
_map.put(str, new WeakReference(str));
return str;
}
此代码来自Java参考对象的article that I wrote。你会在那里找到解释。
编辑:需要在这里创建一个新的字符串(我将更新文章),因为原始字符串可能是更大的字符数组的子字符串。我认为这是围绕JDK 1.3修复的,但显然不是(至少不是1.5)。
答案 5 :(得分:0)
你可以压缩字符串。一个30K的字符串应该获得良好的压缩比。我编写了一个hack来压缩大型String作为练习,但你可以使用压缩数据的byte []来存储String。
30K字符串将使用大约60KB(每个字符2个字节),因此即使使用getBytes()也可能是一种改进。
答案 6 :(得分:0)
你真的需要字符串,还是只需要任何旧的 CharSequence ?如果没有,那么考虑实施"compact" CharSequence,例如我在链接中建议的那个。