【问题标题】:Performance of Collection class in JavaJava中Collection类的性能
【发布时间】:2011-04-27 18:28:26
【问题描述】:

全部,

我浏览了很多网站,这些网站发布了关于各种 Collection 类在各种操作(例如添加元素、搜索和删除)中的性能。但我也注意到,它们都提供了不同的测试环境,即操作系统、内存、运行的线程等。

我的问题是,是否有任何网站/材料可以在最佳测试环境的基础上提供相同的性能信息?即配置不应成为任何特定数据结构性能不佳的问题或催化剂。

[已更新]:例如,HashSet 和 LinkedHashSet 插入元素的复杂度均为 O(1)。然而,Bruce Eckel 的测试声称 LinkedHashSet 的插入将比 HashSet 花费更多的时间 [http://www.artima.com/weblogs/viewpost.jsp?thread=122295]。那么我还是应该使用 Big-Oh 符号吗?

【问题讨论】:

  • 你到底在追求什么?比如说,当您使用原语时,免费和优秀的 Trove 集合围绕着默认的 Java 集合运行是有原因的。例如,将 Trove 的 TLongLongHashMap 的性能与默认的 Java HashMap{Long,Long} 进行比较甚至都不好笑:Trove 击败了 Java。 Big-O 并不是唯一重要的事情......
  • @Webinator:更新了我的查询。

标签: java performance collections


【解决方案1】:

以下是我的建议:

  1. 首先,不要优化 :) 我不是在告诉你设计垃圾软件,而只是关注设计和代码质量而不是过早的优化。假设你已经这样做了,现在你真的需要担心除了纯粹的概念原因之外哪个集合是最好的,让我们继续第 2 点
  2. Really, don't optimize yet(大致盗自M. A. Jackson
  3. 很好。所以你的问题是,即使你有最佳情况、最坏情况和平均情况的理论时间复杂度公式,你已经注意到人们说的不同,实际设置与理论有很大不同。所以运行你自己的基准测试!您只能阅读这么多,而当您这样做时,您的代码不会自己编写。一旦你完成了理论,编写你自己的基准测试 - 为你的现实生活应用程序,而不是一些无关的迷你应用程序用于测试目的 - 看看你的软件实际发生了什么以及为什么。然后选择最佳算法。这是经验性的,可能会被视为浪费时间,但它是唯一真正完美运行的方法(直到你到达下一个点)。
  4. 既然您已经这样做了,那么您就拥有了有史以来最快的应用程序。直到 JVM 的下一次更新。或者您的特定性能瓶颈所依赖的操作系统的某些底层组件。你猜怎么了?也许您的客户有不同的客户。乐趣来了:您需要确保您的基准测试对其他人或在大多数情况下有效(或者为不同的情况编写代码很有趣)。您需要从用户那里收集数据。很多。然后你需要一遍又一遍地这样做,看看会发生什么,以及它是否仍然成立。然后一遍又一遍地相应地重写你的代码(现在终止的Engineering Windows 7 blog 实际上是一个很好的例子,说明用户数据收集如何帮助做出明智的决策以改善用户体验。

或者你可以......你知道......不要优化。平台和编译器会发生变化,但一个好的设计应该——平均而言——表现得足够好。

您还可以做的其他事情:

  • 查看 JVM 的源代码。它很有教育意义,你会发现一大堆隐藏的东西(我并不是说你必须使用它们......)
  • 在你的待办事项清单上看到你需要处理的其他事情了吗?是的,靠近顶部的那个,但你总是跳过它,因为它太难或不够有趣。那个就在那里。好吧,不要管优化问题:它是潘多拉魔盒和莫比乌斯乐队的邪恶孩子。你永远无法摆脱它,你会为你试图用自己的方式解决问题而深感遗憾。

话虽如此,我不知道你为什么需要性能提升,所以也许你有一个非常正当的理由。 p>

我并不是说选择正确的系列并不重要。只要你知道为特定问题选择哪一个,并且你已经研究过替代方案,那么你就完成了你的工作而不必感到内疚。集合通常具有语义含义,只要您尊重它就可以了。

【讨论】:

    【解决方案2】:

    在我看来,关于数据结构,您需要了解的只是对它的操作的 Big-O,而不是来自不同架构的主观测量。不同的收藏有不同的用途。

    Maps 是字典
    Sets 断言唯一性
    Lists 提供分组并保留迭代顺序
    Trees 提供廉价排序和快速搜索动态变化的内容,需要不断排序

    编辑以包含 bwawok 关于树结构用例的声明

    更新
    来自javadoc on LinkedHashSet

    Set 接口的哈希表和链表实现,具有可预测的迭代顺序。

    ...

    由于维护链表的额外费用,性能可能略低于 HashSet,但有一个例外:迭代 LinkedHashSet 所需的时间与集合的大小成正比,而不管其容量如何。 HashSet 的迭代可能会更昂贵,需要的时间与其容量成正比。

    现在我们已经从选择合适的数据结构接口的非常一般的情况转移到使用哪种实现的更具体的情况。但是,我们仍然最终得出结论,即基于每个实现提供的独特、微妙的不变量,特定的实现非常适合特定的应用程序。

    【讨论】:

    • 总体来说非常真实,我的想法也是如此。我的小评论是树(树图和我假设的集合)并不是那么便宜的订购。如果您只想制作一个包含 1000000 个项目的列表,然后查看它们是否已排序,那么使用最后排序的 ArrayList 会更好。树图/集的实际用例非常少见,必须是您添加很多的东西,并且需要在任何给定点对其进行排序。
    • @bwawok,你说得对。我已经更新了我的答案,希望能更好地反映你的观点。
    • 同意来自不同架构的测量不太可能广泛有用,但我会将“您只需要知道大 O”修改为“您只需要了解大 O 并对常数因子有一些了解”。常数因素可能非常重要,在很多情况下,简单的 O(n) 算法在 n 的常见值上优于复杂的 O(1) 算法。
    • @Porculus 平均运行时间很重要。但您通常遇到性能问题的唯一时间是 N 很大的时候。所以如果你专注于大 O 运行时,你会做得很好。小 N 可能损失的时间并不重要,因为无论如何小 N 应该跑得很快。
    • @Porculus,@bwawok 在大多数情况下是正确的。当然,一个例外是 Java 的数组排序实现确实从合并排序转换为对 N 的小值(称为 Z)的插入排序,这很重要,因为合并排序将 N 拆分为大小为 Z 的 X 个子问题,允许使用与合并排序相比,插入排序的平均运行时间将获得 X 次....如果这有意义的话。所以平均时间很重要,但前提是你知道 N 很小并且函数要执行很多次。
    【解决方案3】:

    您需要了解它们的哪些信息,为什么?基准测试显示给定 JDK 和硬件设置的原因是它们可以(理论上)被复制。您应该从基准测试中获得的是对事物如何工作的想法。对于绝对数字,您需要运行它而不是您自己的代码做自己的事情。

    最重要的是要知道各种集合的Big O 运行时。知道从未排序的 ArrayList 中取出元素是 O(n),但从 HashMap 中取出元素是 O(1) 是 HUGE

    如果您已经为给定的工作使用了正确的集合,那么您已经完成了 90% 的工作。您需要担心从 HashMap 中获取项目的速度有多快的时候应该是非常罕见的。

    一旦您离开单线程领域并进入多线程领域,您将需要开始担心诸如 ConcurrentHashMap 与 Collections.synchronized 哈希图之类的事情。直到你是多线程的,你才可以不用担心这种东西,而专注于哪个集合用于哪个用途。

    HashSet 与 LinkedHashSet 的更新

    我从未找到需要链接哈希集的用例(因为如果我关心订单,我倾向于使用列表,如果我关心 O(1) 获取,我倾向于使用哈希集。实际上,大多数代码都会使用 ArrayList、HashMap 或 HashSet。如果您需要其他任何东西,那么您就处于“边缘”情况。

    【讨论】:

    • LinkedHashSet 用于当您希望能够在添加元素的顺序中迭代哈希集时。
    • @Jason S:好的,我会更新以澄清。我从来没有在我的代码中遇到过对它的需求......如果我关心订单,我倾向于使用 ArrayList。所以我想你需要关心订单并且 O(1) 需要一个 LinkedHashSet。
    • @bwawok:出于检查错误的原因,我开始使用 LinkedHashSets(这看起来很糟糕,但这就是我所坚持的)。当我的程序按预期工作时,迭代顺序无关紧要......但是当我有一个错误(更新图表)时,顺序产生了很大的不同,直到我将一堆 HashMaps 转换为 LinkedHashMaps 之前我无法重现错误.
    • @bwawok:我刚刚意识到为什么我需要一个 LinkedHashSet——我没有实现 hashCode(我想按身份进行哈希),因此哈希是基于内存位置的,每次运行都会改变.
    【解决方案4】:

    不同的集合类具有不同的 big-O 性能,但能告诉您的只是它们在变大时如何扩展。如果您的集合足够大,那么具有 O(1) 的集合将优于具有 O(N) 或 O(logN) 的集合,但是除了通过实验之外,没有办法判断 N 的值是盈亏平衡点。

    一般来说,我只使用最简单的东西,然后如果它成为“瓶颈”,如对该数据结构的操作所花费的时间百分比所示,那么我将切换到具有更好大 O 评级的东西.很多时候,要么集合中的项目数量永远不会接近收支平衡点,要么有另一种简单的方法来解决性能问题。

    【讨论】:

      【解决方案5】:

      HashSetLinkedHashSet 都具有 O(1) 性能。与HashMapLinkedHashMap 相同(实际上前者是在后者的基础上实现的)。这只会告诉您这些算法如何扩展,而不是它们的实际执行情况。在这种情况下,LinkHashSet 所做的工作与HashSet 相同,但还必须始终更新前一个和下一个指针以维持顺序。这意味着HashSet常数(这在谈论实际算法性能时也是一个重要值)低于LinkHashSet

      因此,由于这两个具有相同的 Big-O,因此它们的扩展本质上是相同的——也就是说,随着 n 的变化,两者具有相同的性能变化,并且具有 O(1) 的性能,平均而言,不会改变。

      所以现在您的选择是基于功能和您的要求(这确实应该是您首先考虑的)。如果您只需要快速 addget 操作,则应始终选择HashSet。如果您还需要一致的顺序 - 例如上次访问或插入顺序 - 那么您必须还使用 Linked... 版本的类。

      我在生产应用程序中使用了“链接”类,LinkedHashMap。我在一种情况下将其用于表之类的符号,因此希望快速访问符号和相关信息。但我还想按照用户定义这些符号的顺序(插入顺序)在至少一个上下文中输出信息。这使得输出对用户更加友好,因为他们可以按照定义的顺序查找内容。

      【讨论】:

        【解决方案6】:

        如果我必须对数百万行进行排序,我会尝试寻找不同的方式。也许我可以改进我的 SQL,改进我的算法,或者将元素写入磁盘并使用操作系统的排序命令。

        我从来没有遇到过导致我的性能问题的集合。

        【讨论】:

        【解决方案7】:

        我创建了自己的 HashSet 和 LinkedHashSet 实验。对于 add() 并包含运行时间是 O(1) ,没有考虑到很多冲突。在linkedhashset 的add() 方法中,我将对象放入用户创建的O(1) 哈希表中,然后将对象放入单独的链表中以说明顺序。所以从linkedhashset中移除一个元素的运行时间,你必须在hashtable中找到该元素,然后在有顺序的linkedlist中搜索。所以运行时间分别是 O(1) + O(n),对于 remove() 来说是 o(n)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-05-19
          • 1970-01-01
          • 1970-01-01
          • 2014-08-10
          • 1970-01-01
          • 2015-09-03
          • 2017-10-31
          • 2014-06-28
          相关资源
          最近更新 更多