【问题标题】:Fastest way to compare strings (literal and numerical)比较字符串(文字和数字)的最快方法
【发布时间】:2009-08-27 09:16:04
【问题描述】:

我遇到了与字符串比较相关的性能问题(在 Java 中)。

我正在处理一个需要对大量列表进行排序的项目(Eclipse 中的 TableViewer)。无论如何,我已经将瓶颈定位到调用 compareTo() 来比较字符串。

有没有办法优化字符串比较的性能?我已经搜索和谷歌搜索无济于事......

由于该项目严格限于 Win32 环境,我想也许可以利用它...

任何建议将不胜感激。

编辑:我忘了说我需要对字符串进行数值比较和文字比较。

EDIT2: 目标本质上是加快用户界面的速度,因为每次单击表格标题执行排序时都等待几秒钟是不可接受的。我正在研究可能以某种方式缓存值以加快比较速度。由于字符串几乎是静态的,我认为这是可能的。

EDIT3: 我知道你们中的很多人都被 try()-catch() 的事情所困扰。实际上,这并不是什么大问题,因为即使我删除了该代码并仅执行 catch 块(单个 compareTo()),它仍然以与原始代码几乎相同的速度执行。但是,如果我也注释掉 compareTo() ;只剩下比较功能的开销(获取标签等),它快如闪电。所以我仍然需要一种更好的方法来比较字符串。通过缓存或做一些其他的魔法。

不幸的是,无法更改排序算法 - 但是我怀疑它是否会这么慢,因为它成功地对纯整数进行了非常快的排序。

澄清:

比较函数是作为 TableViewer 框架的一部分实现的,用于执行排序操作,这意味着我没有实现特定的排序算法,而是由 SWT/JFace 实现。我只是实现了比较功能。

更有趣的是,排序双精度的代码比字符串比较。仅使用数字对列进行排序比使用实际文字字符串排序更快……这使我得出结论,在 compareTo() 方法中发生了一些可疑的事情……

这里是函数的核心:

// e1Label and e2Label is Strings to be compared
//

// Be smart about the comparison and use non-lexical comparison if
// possible (i.e. if both strings are actually numbers...)
//
// Warning: This is only "semi-smart" as the sorting might get "a bit"
// messed up if some of the values in a column can be parsed as
// doubles while others can not...
//
try {
    // Try using numeric (double) comparison of label values
    //
    double e1_double = Double.parseDouble(e1Label);
    double e2_double = Double.parseDouble(e2Label);
    rc = Double.compare(e1_double, e2_double);
} catch (NumberFormatException e) {
    // Use lexical comparison if double comparison is not possible
    //
    rc = e1Label.compareToIgnoreCase(e2Label);
}

【问题讨论】:

  • 在这里给出一些上下文,列表中有多少项?
  • 哦,对不起。实际项目实际上并没有那么多。它可能在 3000-8000 项之间。

标签: java performance eclipse optimization


【解决方案1】:

如果您了解您的 String 内容,您可以预先计算并存储其他信息以加快比较速度。例如,假设您的 Strings 仅包含大写字母 A-Z。您可以根据前 3 个字母为 String 分配排名;例如

  • AAA := 1
  • AAB := 2
  • ...
  • ABA := 27

然后,您可以通过首先比较每个 String 的排名(基于快速 int 的比较)然后仅在排名相等时执行完整的 String 比较来加快您的 compareTo

【讨论】:

  • +1 这实际上是个好主意。至少在有限的一段时间内,字符串几乎是恒定的。
【解决方案2】:

即使瓶颈似乎是 compareTo() 函数,它也可能在分析器中脱颖而出,因为它是循环中调用次数最多的函数。

了解您的排序例程的具体功能可能会有所帮助。更改排序算法可能会更好,因为那里可以获得更快的速度。

【讨论】:

  • 虽然我在算法部分完全同意你的看法,但我无法控制。我已经稍微更新了问题以更好地反映场景。
【解决方案3】:

几乎可以肯定,减慢比较速度的是例外情况。抛出和捕获异常是一项昂贵的操作,并且每个非数字单元格值都会出现异常。

考虑首先使用正则表达式来检查值是否为数字,如果不是,则不要尝试解析它。

private static final Pattern numberPattern = Pattern.compile("[-+0-9.e]+");

// ...

// e1Label and e2Label is Strings to be compared
//

// Be smart about the comparison and use non-lexical comparison if
// possible (i.e. if both strings are actually numbers...)
//
// Warning: This is only "semi-smart" as the sorting might get "a bit"
// messed up if some of the values in a column can be parsed as
// doubles while others can not...
//
if (numberPattern.matches(e1Label) && numberPattern.matches(e2Label)) {
    try {
        // Try using numeric (double) comparison of label values
        //
        double e1_double = Double.parseDouble(e1Label);
        double e2_double = Double.parseDouble(e2Label);
        rc = Double.compare(e1_double, e2_double);
    } catch (NumberFormatException e) {
        // Use lexical comparison if double comparison is not possible
        //
        rc = e1Label.compareToIgnoreCase(e2Label);
    }
} else {
    rc = e1Label.compareToIgnoreCase(e2Label);
}

【讨论】:

  • 我也是这样,但我已经尝试并删除了异常,并且只进行了字符串比较(即只有最后一个 catch 块),但性能基本上是相同的。
  • 我尝试了你的建议,但我很遗憾地说它并没有真正影响速度。不过,这是一次不错的尝试。
  • 正则表达式的速度也是出了名的慢。
【解决方案4】:

不要将值存储为字符串对象。创建您自己的包装器,它只为每个字符串调用一次 Double.parseDouble。缓存响应(值或异常)。它也可能缓存不区分大小写的字符串版本。

【讨论】:

    【解决方案5】:

    我真的怀疑你是否能够加快 String.compareTo() 的速度。解决方案可能在于减少使用 compareTo() 的频率。但是如果不了解您的算法,就不可能告诉您如何做到这一点。

    【讨论】:

    • 是的,你可能是对的。我会研究是否可以进行预计算和缓存以加快速度。
    【解决方案6】:

    即使您可以从 compareTo() 中获得更多性能,我认为主要问题还是列表的大小。即使假设今天您可以将排序延迟减少到可以接受的程度(1 秒?),如果明年应用程序需要显示两倍大的列表怎么办?排序算法是 O(n log n),所以列表大小加倍会使排序显着变慢。

    要获得可靠的解决方案,请查看虚拟表(使用 SWT.VIRTUAL 属性)。然后你可以实现一个底层数据提供者,它不需要预先进行完整的排序。具体如何实施将取决于您的数据来自何处。如果它来自数据库,您可以考虑在所有可排序字段上放置索引。如果没有办法做到这一点,您可以使用其他策略,例如,如果您有一些将表格分成块的快速方法(例如以“A”开头的行,以“B”开头的行等),那么您可以从提取第一个块中的行开始,对它们进行排序并显示它们,因为用户总是从表的顶部开始。后续块的排序可以在后台线程中继续。

    【讨论】:

    • +1 是的。你说的对。我将不得不考虑将来有一个更大的列表的可能性。不幸的是,目前使用虚拟表进行高级实现的时间非常有限,但将来可能会需要。谢谢你的建议。我将尝试从当前解决方案中榨取一些性能,就像现在一样,并为未来推荐一种不同的方法。
    【解决方案7】:

    如果您需要“文字”和“数字”比较,那么这些字符串包含哪些数据?它们总是代表数字吗?

    如果它们只包含数字,那么将它们存储为数字可能会快得多(而且这样做更简洁)。

    如果您需要“字面”比较(我将其解释为在“20”之前排序“100”),那么您可以轻松地在ints 或longs 上实现它,并使用一些可能仍然更快的数学比字符串比较。

    【讨论】:

    • 字符串中的数据是不可预测的,这意味着它可以是数字和文字、双精度数、整数、名称等。
    【解决方案8】:

    正如 renier 和 Guillaume 已经说过的那样,String.compareTo() 不是罪魁祸首。它应该比数值比较慢,但实际上并没有那么重要。

    即使您的列表长达数百万项,也不会超过一秒钟。

    如果它是一个选项,我会在后台进行搜索,即将某种索引附加到字符串。

    您应该真正分析最常发生哪种操作,单次插入、大量未排序插入、大量部分排序插入、排序、删除等等。

    根据最常见的操作,你会选择更合适的数据结构。

    【讨论】:

      【解决方案9】:

      为什么不在开始时对列表进行一次排序,并通过插入排序保持更新?然后,当您想将排序从升序更改为降序时,信息已经存在。如果您想按另一列排序,那么只需保留列表以防万一您切换回此列?或者这在 SWT 中是不可行的? (我已经有一段时间没有使用它了)

      【讨论】:

      • 也许可以根据列将值附加到每个对象,然后使用这些值来缓存列表中的位置。困难的部分是在一次给定两个项目的比较方法中被调用时找出实际位置。
      【解决方案10】:

      在我看来,您需要做的是避免像您一样经常调用 String.compareTo()。基本上有两种方法可以做到这一点。

      1) 实现某种形式的bucket sort,以避免执行所有这些比较。

      根据要排序的字符串数量(数千?数百万?),使用完整的桶排序可能需要太多开销,在空间和垃圾收集方面。

      为避免您可能执行不断轮次的桶排序,因此将字符串排序到包含所有字符串的列表中,其中前 10 个字母匹配。然后就可以使用内置的 sort 对每个桶进行排序了。

      2) 对每个字符串进行哈希处理并对哈希进行排序(确保处理冲突)。 然后你可以在之后重新排序字符串。这可能是最简单的解决方案。

      使用这些解决方案中的任何一个都可以让您在一秒钟内对数百万个字符串进行排序。

      【讨论】:

      • 我可能有点含糊,但实施该算法对我来说不是一个选择,我毫不怀疑,如果可能的话,它会证明是富有成效的(我的意思是在实际中可能 - 在预算范围内方式)
      【解决方案11】:

      根据您最近的澄清,这里是第二个答案:创建一个类:Item,可用于表示数字或字母数字值,并且可以确定是否是这种情况前期 .这样,您就避免了在 compareTo 方法调用期间解析值和处理任何异常的开销

      public class Item implements Comparable<Item> {
          private final String s;
          private final double d;
          private final boolean numeric;
      
          public Item(String s) {
              double tmpD;
              boolean tmpNumeric;
      
              try {
                  // Do the work of parsing / catching exceptions *upfront*.
                  tmpD = Double.parseDouble(s);
                  tmpNumeric = true;
              } catch(NumberFormatException ex) {
                  // Parse failed so must be a String.
                  tmpD = 0.0;
                  tmpNumeric = false;
              }
      
              this.s = s;
              this.d = tmpD;
              this.numeric = tmpNumeric;
          }
      
          public String asString() {
              return s;
          }
      
          public double asDouble() {
              if (!numeric) {
                  throw new IllegalStateException("Not a numeric value: " + s);
              }
      
              return d;
          }
      
          public boolean isNumeric() {
              return numeric;
          }
      
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (!(o instanceof Item)) return false;
      
              Item item = (Item) o;
      
              return Double.compare(item.d, d) == 0 && s.equals(item.s);
          }
      
          @Override
          public int hashCode() {
              int result;
              long temp;
              result = s.hashCode();
              temp = d != +0.0d ? Double.doubleToLongBits(d) : 0L;
              result = 31 * result + (int) (temp ^ (temp >>> 32));
              return result;
          }
      
          public int compareTo(Item item) {
              int ret;
      
              if (numeric && item.isNumeric()) {
                  // Both items are numeric so do fast comparison.
                  double diff = d - item.asDouble();
                  if (diff > 0.0) {
                      ret = 1;
                  } else if (diff < 0.0) {
                      ret = -1;
                  } else {
                      ret = 0;
                  }
              } else {
                  ret = s.compareTo(item.asString());
              }
      
              return ret;
          }
      }
      

      【讨论】:

      • 即使我要消除所有双重转换和解析以及异常,它也没有太大区别(不是很奇怪)。如果我用一个 compareTo() 调用替换我的所有代码,它的执行速度仍然非常慢。所以我认为解决方案必须要么加快实际的字符串比较,要么减少比较字符串的需要。非常感谢您的回答,谢谢!
      • 如果消除解析不能改善问题,那么您的大多数字符串都无法解析为双精度(在这种情况下,尝试这样做的代码是无用的,应该被消除)或者字符串比较真的没有任何区别,你真的应该去看看排序算法。
      【解决方案12】:

      为什么不试试呢?

      http://algs4.cs.princeton.edu/52trie/

      http://en.wikipedia.org/wiki/Radix_tree

      正如 Robert Sedgewick 所写:“命题 H。在由大小为 R 的字母表上的 N 个随机键构建的 trie 中,检查搜索未命中的平均节点数是 ~logR N。” [塞奇威克,罗伯特;韦恩,凯文(2011-02-21)。算法(第 4 版)(Kindle 位置 12674-12676)。培生教育(美国)。 Kindle 版。]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-11-30
        • 1970-01-01
        • 1970-01-01
        • 2019-03-06
        • 2011-01-04
        • 2013-05-06
        • 1970-01-01
        • 2018-03-14
        相关资源
        最近更新 更多