java - 原始值的映射替代

标签 java java-8 primitive

我对我的应用程序进行了一些分析,结果之一是堆上大约 18% 的内存被类型为 Double 的对象使用。 .原来这些对象是 Map 中的值s,我不能使用原始类型。

我的推理是 double 的原始类型消耗的内存比它的对象少 Double .有没有办法拥有像数据结构这样的 map ,它可以接受任何类型作为键和原始 double作为值(value)观?

主要操作将是:

  • 插入(可能只有一次)
  • 查找(按关键字包含)
  • 检索(按键)
  • 迭代

  • 我拥有的典型 map 是:
  • HashMap<T, HashMap<NodeData<T>, Double>> graph
  • HashMap<Point2D, Boolean> onSea (虽然不是 double 值)
  • ConcurrentHashMap<Point2D, HashMap<Point2D, Double>>

  • 全部用于 Java 8。

    附录

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

    最佳答案

    其他人已经建议了几个原始值映射的第三方实现。为了完整起见,我想提及一些您可能想要考虑的完全摆脱 map 的方法。这些解决方案并不总是可行的,但是当它们可行时,它们通常比任何 map 都更快且内存效率更高。

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

    一个简单的double[] array 可能不像花哨的 map 那么性感,但很少有人能在紧凑性和访问速度方面击败它。

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

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

    如果您实现了某种冲突处理机制,您甚至可以免除 ID 唯一性要求,但此时您已经在实现自己的哈希表了。在某些情况下,这可能确实有意义,但通常在那时使用现有的第三方实现可能更容易。

    备选方案 2:自定义您的对象。

    与其维护从关键对象到原始值的映射,为什么不直接将这些值变成对象本身的属性呢?毕竟,这就是面向对象编程的全部内容——将相关数据分组为有意义的对象。

    例如,而不是维护一个 HashMap<Point2D, Boolean> onSea ,为什么不给你的点一个 boolean 值 onSea属性(property)?当然,您需要为此定义您自己的自定义点类,但没有理由不让它扩展标准 Point2D。如果需要,类,以便您可以将自定义点传递给任何需要 Point2D 的方法。 .

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

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

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

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

    方案三:将多张 map 合二为一。

    如果您有多个具有相同键的映射,并且不能像上面建议的那样将值转换为键对象的新属性,请考虑将它们分组到一个元数据类中,并创建一个从键到该类对象的映射。例如,而不是 Map<Item, Double> accessFrequency和一个 Map<Item, Long> creationTime ,考虑定义一个元数据类,如:
    class ItemMetadata {
        double accessFrequency;
        long creationTime;
    }
    

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

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

    备选方案 4:使用装饰器。

    作为前两种选择的组合,如果您不能直接将额外的元数据属性添加到关键对象中,请考虑使用 decorators 将它们包装起来。可以容纳额外的值。因此,例如,您可以简单地执行以下操作,而不是直接创建您自己的具有额外属性的点类:
    class PointWrapper {
        Point2D point;
        boolean onSea;
        // ...
    }
    

    如果您愿意,您甚至可以通过实现方法转发将这个包装器变成一个成熟的装饰器,但即使只是一个简单的“哑”包装器也可能足以满足多种用途。

    如果您可以安排仅存储和使用包装器,则此方法最有用,这样您就无需查找与未包装对象对应的包装器。当然,如果您确实需要偶尔这样做(例如,因为您只从其他代码接收未包装的对象),那么您可以使用单个 Map<Point2D, PointWrapper> 来做到这一点。 ,但随后您实际上又回到了之前的选择。

    关于java - 原始值的映射替代,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41726286/

    相关文章:

    java - 如何修复 java.lang.UnsupportedClassVersionError : Unsupported major. 次要版本

    java - 如何将非 gradle JUnit 项目(使用 Hibernate 和 EntityManager)转换为 gradle Junit 项目并保持工作

    Java 与主线程并行运行任务而不阻塞它

    java - 将整数连接到字符串 - 从性能和内存的角度来看,使用字符串文字还是原语?

    Java:空循环使用多少时间?

    java - 处理非常大的数字

    java - 我收到错误 java.text.ParseException : Unparseable date

    java - 关键的 RabbitMQ 方法参数

    sorting - 合并和排序多个流 java 8

    java - 我无法理解 Java 8 中的一些 lambda 表达式