【问题标题】:creating a cache of byte arrays创建字节数组的缓存
【发布时间】:2013-06-08 13:07:52
【问题描述】:

在我的代码中,通常在我不控制配置的服务器上运行,我有用户集合,每个用户都有一个 byte[] 数组。

有时这些byte[] 数组对用户来说是唯一的。但是,通常会有大量用户使用完全相同的 byte[] 数组。

我正在尝试减少服务器的 RAM 消耗。

我尝试将我的byte[] 数组转换为字符串并将它们实习,但是我经常遇到PERM-GEN 内存不足错误。当我想为用户访问 byte[] 数组时,我还看到编码/解码的性能显着下降,并且我看到更坏情况下的内存使用量大大增加 - 可能字符串比数组大得多。

当 Java 数组不可散列并且 SoftReferences 不包装对象的散列点时,我如何进行Set<SoftReference<byte[]>> 查找。 Map<byte[],SoftReference<byte[]>> 显然也在打败自己,因为关键是它自己并且阻止了收集;而Set 在内部以Mapanyway 的形式实现。

那么我该如何实习生 byte[] 数组?

【问题讨论】:

  • 浮现在脑海中的一件事是享元模式。也看看stackoverflow.com/questions/1058149/…
  • 我认为你必须包装你的字节数组,有效地将它们装箱,例如与new ByteArray(byte [] theBytes) 一起使用,并且永远不要在ByteArray 本身之外对theBytes 进行额外的长期引用。然后对ByteArrays 的软引用将正常工作。您也可以查看 WeakHashMap<ByteArray, User> 以了解您的应用程序。
  • 这些字节数组有多大?用户如何获得一个?
  • @fge 它们通过网络到达,并且是“房间”密钥 - 可变长度,有时短,有时 1K 或更多 - 在另一个服务器系统上。有时很多用户在同一个“房间”,有时他们有自己的房间等等。

标签: java garbage-collection


【解决方案1】:

如果您实际上有许多相同的数组,请使用HashSet<ByteBuffer> 作为缓存。您可以使用 array() 方法获取 ByteBuffer 数组,而 ByteBuffer 类具有 hashCodeequals 方法。当然,如果你的数组是不可变的,那就更好了。

EDIT2来自@Will 的评论是准确的,为了能够取回数组,使用WeakHashMap<ByteBuffer,WeakReference<ByteBuffer>> 并执行类似的操作:

public byte[] internalize(byte[] bytes) {
 ByteBuffer wrapped = ByteBuffer.wrap(bytes);
 if(cache.containsKey(wrapped)) {
  wrapped = cache.get(wrapped).get();
 }
 else {
  cache.put(wrapped, new WeakReference<ByteBuffer>(wrapped);
 }
 return wrapped.array();
}

【讨论】:

  • 当系统需要内存并且当时没有用户引用它们时,如何从缓存中收集它们?
  • WeakHashMap&lt;ByteBuffer, Void&gt; 可能会更清楚地传达意图。您可以将空值存储为值。
  • 虽然如果你使用WeakHashMap&lt;ByteBuffer, Boolean&gt;,那么你可以使用Collections#newSetFromMap 包装它以获得Set&lt;ByteBuffer&gt;,这样你的代码就更干净了。
  • @Oak:我对Map&lt;E, Boolean&gt; 的不满是它让一个元素处于以下三种状态之一:不在地图中、映射为真、映射为假。我们只需要两个状态,所以有冗余。这不存在实际问题,但它是不优雅的。 Void 只有一个可能的值(null),所以只有两种可能的状态。
  • @TomAnderson 如何从 O(1) 中的 Map 获取键值本身?我们需要检索我们正在缓存的内容吗?
【解决方案2】:

我尝试将我的 byte[] 数组转换为字符串并将它们实习,但我经常遇到PERM-GEN 内存不足错误。

我同意你需要像String.intern() 这样的东西,但标准实现是native,所以没有太多乐趣。

您可以有一个Map&lt;Integer,Collection&lt;SoftReference&lt;byte[]&gt;&gt;&gt;,使用字节数组的哈希码作为Map 键。然后,您的 intern 方法可以使用与给定字节数组相同的代码来查找现有字节数组的集合。具有良好的哈希码,应该提供一小组数组来检查匹配。


编辑:澄清:

类似这样的:

 class ByteArrayCache
 {
      private final Map<Integer,Collection<SoftReference<byte[]>> map = new ...;

      public final byte[] intern(byte[] byteArray)
      {
           final int hash = Arrays.hashCode(byteArray);
           final Collection<SoftReference<byte[]>> arrays = map.get(hash);
           if (arrays != null) {
              // Search through arrays for a match, and return the match.
              // If no match found, add byteArray to the collection and return it
           } else {
              // create a new map entry, add byteArray to it, and return byte array
           }
      }
 }

【讨论】:

  • 如何在不使用时清理孤立的IntegerSet?在没有大量重复的最坏情况下,这么多装箱的 RAM 开销是多少?
  • 如果数组又大又稀疏,md5 的第一个字节可能是有用的散列。如果它们很小,滚你自己的可能就足够了。
  • @tucuxi Arrays.hashCode(byte[]) 救援 :) 但根本问题是你不能继承 byte[] 来修补它
  • @将使用 HashSet 代替吗?避免装箱散列键;但是您需要包装这些字节数组,以覆盖 hashCode()。
  • 无需重写 hashCode()。这是一张地图,而不是一组。
【解决方案3】:

我将实现基于 Guava 弱值映射的缓存。它保证如果没有更多对字节数组的强引用,该条目将被自动删除。

class Cache {
    private final ConcurrentMap<Key, byte[]> map = new MapMaker().weakValues().makeMap();

    private static class Key {
        byte[] a;
        int hash;

        Key(byte[] a) {
            this.a = a;
            hash = Arrays.hashCode(a);
        }

        @Override
        public int hashCode() {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Key) {
                return Arrays.equals(a, ((Key) obj).a);
            }
            return false;
        }
    }

    public byte[] intern(byte[] a) {
        byte[] a1 = map.putIfAbsent(new Key(a), a);
        if (a1 != null) {
            return a1; 
        }
        return a;
    }
}

【讨论】:

    猜你喜欢
    • 2017-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-14
    • 2011-10-05
    • 2015-11-19
    • 2013-09-23
    相关资源
    最近更新 更多