优化Java中一系列字符串的内存使用

时间:2009-04-07 13:16:44

标签: java string memory-management

我有大量的名称 - 值对(大约100k),我需要存储在某种缓存中(比如哈希映射),其中值是一个平均大小约为30k字节的字符串。 / p>

现在我知道一个事实,即大量的值具有完全相同的字符串数据。为了避免必须多次分配相同的字符串数据,我想以某种方式重用先前分配的字符串,从而消耗更少的内存。此外,这需要相当快。即,逐个扫描所有先前分配的值不是一种选择。

有关如何解决此问题的任何建议?

7 个答案:

答案 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)

Brian说,

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.tailSetIterator查找现有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,例如我在链接中建议的那个。