用于原始值的替代映射

我在我的应用程序上做了一些分析,其中一个结果表明,堆中大约18%的内存被Double类型的对象使用。 原来这些对象是Map中的值,我不能使用原始类型。

我的理由是,基本类型的double消耗更少的内存比它的对象Double 。 有没有办法有一个像地图数据结构,将接受任何类型的键以及一个原生double的价值观?

主要业务是:

  • 插入(可能只有一次)
  • 查找(包含按键)
  • 复原(按键)
  • 迭代
  • 我有的典型地图是:

  • HashMap<T, HashMap<NodeData<T>, Double>> graph
  • HashMap<Point2D, Boolean> onSea (尽管不是double值)
  • ConcurrentHashMap<Point2D, HashMap<Point2D, Double>>
  • 所有与Java 8一起使用。

    附录

    我主要对有这些类型的地图的解决方案的框架不感兴趣,但是在解决这些问题时必须考虑哪些问题。 如果你愿意,任何这样的框架背后的概念/想法/方法是什么。 或者解决方案也可能在另一个层面上,其中地图被一些特定模式的对象替换,如@Ilmari Karonen在他的答案中指出的。


    其他人已经提出了几个原始值映射的第三方实现。 为了完整起见,我想提出一些完全摆脱地图的方法,您可能需要考虑。 这些解决方案并不总是可能的,但是当它们是时,它们通常会比任何地图都更快,更有记忆效率。

    方案1:使用普通的旧数组。

    一个简单的double[]数组可能不如花式地图般性感,但很少能在紧凑性和访问速度上胜出。

    当然,数组有一些限制:它们的大小是固定的(尽管你总是可以创建一个新数组并将旧内容复制到它中),而且它们的键只能是小的正整数,为了提高效率,应该是合理的密集(即使用的密钥总数应该是最高密钥值的合理大部分)。 但是,如果这种情况发生在您的密钥的情况下,或者您可以安排这种情况,那么原始值的数组可以非常有效。

    特别是,如果您可以为每个关键对象分配一个唯一的小整数ID,那么您可以将该ID用作数组中的索引。 同样,如果您已经将对象存储在数组中(例如,作为一些更复杂的数据结构的一部分)并通过索引查找它们,则可以简单地使用相同的索引来查找另一个数组中的任何其他元数据值。

    如果您实现了某种碰撞处理机制,那么您甚至可以免除身份标识唯一性要求,但此时您已经很好地实现了自己的哈希表。 在某些情况下,实际上可能有意义,但通常在此时使用现有的第三方实现可能更容易。

    选择2:自定义您的对象。

    为什么不将这些值保存到对象本身的属性中,而不是将关键对象的映射保留为原始值? 毕竟,这是面向对象编程的全部内容 - 将相关数据分组为有意义的对象。

    例如,不是维护HashMap<Point2D, Boolean> onSea ,为什么不只是给你的点布尔onSea属性? 当然,你需要为此定义你自己的自定义点类,但是没有理由为什么你不能扩展标准的Point2D类,以便你可以将自定义点传递给任何期望的方法一个Point2D

    同样,这种方法可能并不总是直接工作,例如,如果您需要使用您无法修改的类(但请参见下文),或者要存储的值与多个对象相关联(如在ConcurrentHashMap<Point2D, HashMap<Point2D, Double>> )。

    但是,对于后一种情况,您可能仍然可以通过适当地重新设计数据表示来解决问题。 例如,您可以将一个加权图形表示为Map<Node, Map<Node, Double>> ,而不是像下面这样定义一个Edge类:

    class Edge {
        Node a, b;
        double weight;
    }
    

    然后向每个包含连接到该节点的边的节点添加Edge[] (或Vector<Edge> )属性。

    方案3:将多个地图组合成一个。

    如果您有多个具有相同键的映射,并且不能像上面所建议的那样将这些值转换为关键对象的新属性,请考虑将它们分组为单个元数据类,并从键创建一个映射到该类的对象中。 例如,不要使用Map<Item, Double> accessFrequencyMap<Item, Long> creationTime ,而应考虑定义单个元数据类,如下所示:

    class ItemMetadata {
        double accessFrequency;
        long creationTime;
    }
    

    并有一个Map<Item, ItemMetadata>来存储所有的元数据值。 这比拥有多个地图更具有内存效率,并且还可以通过避免冗余地图查找来节省时间。

    在某些情况下,为了方便,您可能还希望在每个元数据对象中包含对其相应主对象的引用,以便您可以通过对元数据对象的单个引用来访问这两个对象。 自然会陷入......

    替代方案4:使用装饰器。

    作为前两种替代方法的组合,如果不能直接向关键对象中添加额外的元数据属性,请考虑使用可容纳额外值的装饰器来包装它们。 因此,例如,您可以简单地执行如下操作,而不是直接创建具有额外属性的自己的点类:

    class PointWrapper {
        Point2D point;
        boolean onSea;
        // ...
    }
    

    如果你喜欢,你甚至可以通过实现方法转发将这个包装器变成一个成熟的装饰器,但即使只是一个简单的“哑”包装器可能已经足够用于许多目的。

    如果你可以安排存储和使用包装器,这种方法是非常有用的,所以你永远不需要查找与解包对象相对应的包装器。 当然,如果你偶尔需要这样做(例如因为你只接收来自其他代码的解包对象),那么你可以用一个Map<Point2D, PointWrapper>来做到这Map<Point2D, PointWrapper> ,但然后你有效地回到以前的选择。


    Eclipse Collections具有对象和原始映射,并且具有两个可变和不可变版本。

    MutableObjectDoubleMap<String> doubleMap = ObjectDoubleMaps.mutable.empty();
    doubleMap.put("1", 1.0d);
    doubleMap.put("2", 2.0d);
    
    MutableObjectBooleanMap<String> booleanMap = ObjectBooleanMaps.mutable.empty();
    booleanMap.put("ok", true);
    
    ImmutableObjectDoubleMap<String> immutableMap = doubleMap.toImmutable();
    Assert.assertEquals(doubleMap, immutableMap);
    

    通过调用toImmutableMutableMap可以用作Eclipse集合中ImmutableMap的工厂,就像我在上面的例子中所做的那样。 可变和不可变映射都共享一个公共父接口,在上面的MutableObjectDoubleMapImmutableObjectDoubleMap的情况下,该MutableObjectDoubleMap被命名为ObjectDoubleMap

    Eclipse集合对于库中的所有可变容器也具有同步的和不可修改的版本。 以下代码将为您提供一个包裹在原始地图上的同步视图。

    MutableObjectDoubleMap<String> doubleMap = 
            ObjectDoubleMaps.mutable.<String>empty().asSynchronized();
    doubleMap.put("1", 1.0d);
    doubleMap.put("2", 2.0d);
    
    MutableObjectBooleanMap<String> booleanMap = 
            ObjectBooleanMaps.mutable.<String>empty().asSynchronized();
    booleanMap.put("ok", true);
    

    大型地图的性能比较是在几年前发布的。

    Large HashMap概述:JDK,FastUtil,Goldman Sachs,HPPC,Koloboke,Trove - 2015年1月版

    GS Collections已经被迁移到Eclipse Foundation,现在是Eclipse Collections。

    注意:我是Eclipse集合的提交者。


    你在寻找的是来自fastutil (具有小内存占用和快速访问和插入的集合框架)的Object2DoubleOpenHashMap ,它提供了类型double getDouble(Object k)double put(K k, double v)

    例如:

    // Create a Object2DoubleOpenHashMap instance
    Object2DoubleMap<String> map = new Object2DoubleOpenHashMap<>();
    // Put a new entry
    map.put("foo", 12.50d);
    // Access to the entry
    double value = map.getDouble("foo");
    

    Object2DoubleOpenHashMap类是一个非线程安全的Map的实际实现,但仍然可以使用实用程序方法Object2DoubleMaps.synchronize(Object2DoubleMap<K> m)使其对线程安全,这要归功于装饰器。

    创建代码将是:

    // Create a thread safe Object2DoubleMap
    Object2DoubleMap<String> map =  Object2DoubleMaps.synchronize(
        new Object2DoubleOpenHashMap<>()
    );
    
    链接地址: http://www.djcxy.com/p/92191.html

    上一篇: Map alternative for primitive values

    下一篇: How to ignore the case sensitive when we look for a key in the Map?