【问题标题】:best way to remove the first word in a string in Java在Java中删除字符串中第一个单词的最佳方法
【发布时间】:2014-10-02 09:55:30
【问题描述】:

摆脱字符串中第一个标记的最快方法是什么?到目前为止,我已经尝试过:

String parentStringValue = this.stringValue.split(" ", 2)[1];

而且它的内存和速度非常低效(对于 15 字长的字符串重复数百万次时)。假设字符串由空格分隔的标记组成。

【问题讨论】:

  • 你使用了子字符串吗?问题不清楚

标签: java string performance


【解决方案1】:

StringBuilder vs substring( x ) vs split( x ) vs Regex

答案已编辑:主要缺陷已更正

在纠正了我的基准测试中的一些相当大的缺陷之后(正如 Jay Askren 在 cmets 中指出的那样)。 StringBuilder 方法是最快的(尽管这是假设 StringBuilder 对象是预先创建的),子字符串排在第二位。 split() 排在倒数第二位,比 StringBuilder 方法慢 10 倍。

  ArrayList<String> strings = new ArrayList<String>();
  ArrayList<StringBuilder> stringBuilders = new ArrayList<StringBuilder>();
  for(int i = 0; i < 1000; i++) strings.add("Remove the word remove from String "+i);
  for(int i = 0; i < 1000; i++) stringBuilders.add(new StringBuilder(i+" Remove the word remove from String "+i));
  Pattern pattern = Pattern.compile("\\w+\\s");

  // StringBuilder method
  before = System.currentTimeMillis();
  for(int i = 0; i < 5000; i++){
      for(StringBuilder s : stringBuilders){
          s.delete(0, s.indexOf(" ") + 1);
      }
  }
  after = System.currentTimeMillis() - before;
  System.out.println("StringBuilder Method Took "+after);

  // Substring method
  before = System.currentTimeMillis();
  for(int i = 0; i < 5000; i++){
      for(String s : strings){
          String newvalue = s.substring(s.indexOf(" ") + 1);
      }
  }
  after = System.currentTimeMillis() - before;
  System.out.println("Substring Method Took "+after); 

  //Split method
  before = System.currentTimeMillis();
  for(int i = 0; i < 5000; i++){
      for(String s : strings){
          String newvalue = s.split(" ", 2)[1];
          System.out.print("");
      }
  }
  after = System.currentTimeMillis() - before;
  System.out.println("Your Method Took "+after);

  // Regex method
  before = System.currentTimeMillis();
  for(int i = 0; i < 5000; i++){
      for(String s : strings){
          String newvalue = pattern.matcher(s).replaceFirst("");
      }
  }
  after = System.currentTimeMillis() - before;
  System.out.println("Regex Method Took "+after);

我以随机顺序运行上述程序,在热身后,连续取平均值,将操作次数从 500 万增加到 3000 万,然后每次运行十次,然后再进行下一次。无论哪种方式,最快到最慢的顺序都保持不变。下面是上面代码的一些示例输出;

StringBuilder Method Took 203
Substring Method Took 588
Split Method Took 1833
Regex Method Took 2517

值得一提的是,使用长度大于 1 的 String 调用 split() 只是在其实现中使用 Regex,因此使用 split()Pattern 对象之间应该没有区别。

【讨论】:

  • 这段代码有不少错误。最大的问题之一是您正在比较苹果和橙子。例如,您第一次收集是(之后 = System.currentTimeMillis() - 之前)。您计算时间的剩余时间是(之后 = System.currentTimeMillis() - 之后),其中包括打印内容。如果你解决了这个问题,substring 方法在我的机器上会快 5 倍。此外,您永远不会创建对象 stringBuilders。更不用说,之前和之后没有类型。此外,通常最好将每个测试运行 5 或 10 次,这样您就可以看到异常值。
  • 另一个问题:regex方法每次都编译正则表达式。这不是编写代码的速度。 5000 次迭代乘以 1000 个字符串可能还不够。对于测量时间,我建议System.nanoTime - 它更精确并且保证是单调的。
  • 同意@maaartinus。使用纳米时间的替代方法是使用更多的交互。我建议这个测试至少重复 100,000 次。如果只需要半秒钟,这不是一个很好的性能基准。您希望运行更长时间,以便 JIT 至少有机会发挥作用。
  • @Rudi 我在另一个答案中改进了您的基准。拆分比执行此操作的最快方法慢 5 倍。
  • 每个方法都返回 String 对象,但 StringBuilder 除外,您应该调用 toString() 以使它们均匀。
【解决方案2】:

无需拆分和创建数组,只需使用子字符串

String str="I want to remove I";
String parentStringValue = str.substring(str.indexOf(" ")+1);
System.out.println(parentStringValue);

输出:

want to remove I

【讨论】:

    【解决方案3】:

    您可以为此使用String.substringString.indexOf 的组合。

    类似的东西:

    // TODO check indexOf does not return -1
    this.stringValue.substring(this.stringValue.indexOf(" ") + 1)
    

    【讨论】:

    • 似乎比我目前的做法要慢。
    • @user3639557 您必须提供比“它很慢”更多的信息才能获得进一步的帮助。 IE。堆栈跟踪、大小规格等。这几乎可以保证比将整个 String 拆分为一个数组更快。
    • 它并不慢,它提供几乎相同的速度(快几毫秒)。处理 30000 行所需的时间相同。
    • @user3639557 - 查看我的其他评论。如果您输出每个字符串,那么您将打印到屏幕上,这会限制您的处理速度。
    • 根据我的测试,这比原来的速度快 5 倍左右。
    【解决方案4】:

    试试这个:

      String s = "This is a test";
    
      System.out.println(s.replaceFirst("\\w+\\s", ""));
    

    【讨论】:

    • 还是很慢。而且它似乎比我目前这样做的方式更糟糕。
    • @user3639557 为什么你认为它很慢?
    • 好吧,我可以看到我在文件中的行上迭代的速度有多快。此操作正在每一行上进行。我每 10000 行生成并输出一次。
    • 我在没有读取文件的情况下尝试了循环,我在 405 毫秒内有 10000 条记录。我认为这并不慢。
    • @user3639557 - 如果您正在观看它,那么您的 System.out.println() 语句会减慢您的进程。与大多数进程相比,打印到屏幕确实非常慢。删除它,只输出开始和结束时间。
    【解决方案5】:

    在进行字符串操作时尽量使用StringBuffer或StringBuilder,这样就不会留下很多新的未使用的对象,也不会因为你提到的重复数百万次而导致内存效率低

    【讨论】:

    • 我看不出如何使用 StringBuilder 来解决这个问题?
    • 如何将“this.stringValue”声明为StringBuilder而不是String,并使用它的delete方法删除该StringBuilder中的第一个标记?
    【解决方案6】:

    Rudi 的基准测试存在很多问题,包括不公平和错误地支持拆分方法。所以我采用了他的基准并对其进行了改进。如果碰巧你有一堆 StringBuilder,StringBuilder 方法会稍微快一些,但如果你需要先将它们从字符串转换,它会很慢。子字符串方法是下一个最快的方法,如果您有字符串而不是字符串构建器,您应该使用这种方法。 CommonsLang 次之,substring 方法和 CommonsLang 方法都比使用 split 快 4 到 5 倍。 String.replaceFirst() 使用正则表达式并且非常慢,因为它每次运行都需要编译正则表达式,这使运行时间加倍。即使没有编译步骤,它也比其他步骤慢得多。

    以下是基准测试的代码。您需要将 ApacheCommonsLang 添加到您的类路径中才能运行它。

    import java.util.ArrayList;
    import java.util.List;
    import java.util.regex.Pattern;
    
    import org.apache.commons.lang3.StringUtils;
    
    /**
     *
     */
    public class StringTest {
        public static void main(String[] args) {
            int numIterations = 100000;
            int numRuns = 10;
            ArrayList<String> strings = new ArrayList<String>();
              for(int i = 0; i < 1000; i++) strings.add("Remove the word remove from String "+i);
              //Your method
              long before = 0;
              long after = 0;
              for(int j=0; j < numRuns; j++) {
                  before = System.currentTimeMillis();
                  for(int i = 0; i < numIterations; i++){
                      for(String s : strings){
                          String newvalue = s.split(" ", 2)[1];
        //                System.out.println("split " + newvalue);
                      }
                  }
                  after = System.currentTimeMillis() - before;
                  System.out.println("Split Took "+after + " ms");
              }
    
    
              // Substring method
              for(int j=0; j < numRuns; j++) {
                  before = System.currentTimeMillis();
                  for(int i = 0; i < numIterations; i++){
                      for(String s : strings){
                          String newvalue = s.substring(s.indexOf(" ") + 1);
                      }
                  }
                  after = System.currentTimeMillis() - before;
                  System.out.println("Substring Took "+after + " ms");
              }
    
    
    
              // Apache Commons Lang method
              before = System.currentTimeMillis();
              for(int j=0; j < numRuns; j++) {
                  before = System.currentTimeMillis();
                  for(int i = 0; i < numIterations; i++){
                      for (String s : strings) {
                          String parentStringValue = StringUtils.substringAfter(s, " ");
                      }
                  }
                  after = System.currentTimeMillis() - before;
                  System.out.println("CommonsLang Took "+after + " ms");
              }
    
    
              for(int j=0; j < numRuns; j++) {
                  long deleteTime = 0l;     
                  before = System.currentTimeMillis();
                  for(int i = 0; i < numIterations; i++){
    
                      List<StringBuilder> stringBuilders = new ArrayList<StringBuilder>();
                      for (String s : strings) {
                          stringBuilders.add(new StringBuilder(s));
                      }
                      long beforeDelete = System.currentTimeMillis();
                      for (StringBuilder s : stringBuilders) {
                          s.delete(0, s.indexOf(" ") + 1);
                      }
                      deleteTime+=(System.currentTimeMillis() - beforeDelete);
                  }
                  after = System.currentTimeMillis() - before;
                  System.out.println("StringBuilder Delete " + deleteTime + " ms out of " + after + " total ms");
              }
    
              // Faster Regex method
              Pattern pattern = Pattern.compile("\\w+\\s");
              for(int j=0; j < numRuns; j++) {
                  before = System.currentTimeMillis();
                  for(int i = 0; i < numIterations; i++){
                      for (String s : strings) {
                          String newvalue = pattern.matcher(s).replaceFirst("");
                      }
                  }
                  after = System.currentTimeMillis() - before;
                  System.out.println("Faster Regex Took "+after + " ms");
              }
    
              // Slow Regex method
              for(int j=0; j < numRuns; j++) {
                  before = System.currentTimeMillis();
                  for(int i = 0; i < numIterations; i++){
                      for (String s : strings) {
                          String newvalue = s.replaceFirst("\\w+\\s", "");
                      }
                  }
                  after = System.currentTimeMillis() - before;
                  System.out.println("Slow Regex Took " + after + " ms");
              }
    
        }
    }
    

    在我的带有 I7 处理器的机器上,我得到了以下结果:

    Split Took 10552 ms
    Split Took 10298 ms
    Split Took 10297 ms
    Split Took 10292 ms
    Split Took 10527 ms
    Split Took 10356 ms
    Split Took 10324 ms
    Split Took 10283 ms
    Split Took 10375 ms
    Split Took 10346 ms
    Substring Took 2385 ms
    Substring Took 2354 ms
    Substring Took 2363 ms
    Substring Took 2358 ms
    Substring Took 2361 ms
    Substring Took 2367 ms
    Substring Took 2370 ms
    Substring Took 2350 ms
    Substring Took 2354 ms
    Substring Took 2397 ms
    CommonsLang Took 2462 ms
    CommonsLang Took 2461 ms
    CommonsLang Took 2422 ms
    CommonsLang Took 2426 ms
    CommonsLang Took 2479 ms
    CommonsLang Took 2441 ms
    CommonsLang Took 2440 ms
    CommonsLang Took 2420 ms
    CommonsLang Took 2418 ms
    CommonsLang Took 2421 ms
    StringBuilder Delete 2302 ms out of 5904 total ms
    StringBuilder Delete 2272 ms out of 5908 total ms
    StringBuilder Delete 2241 ms out of 5879 total ms
    StringBuilder Delete 2263 ms out of 5856 total ms
    StringBuilder Delete 2285 ms out of 5858 total ms
    StringBuilder Delete 2305 ms out of 5864 total ms
    StringBuilder Delete 2287 ms out of 5854 total ms
    StringBuilder Delete 2238 ms out of 5890 total ms
    StringBuilder Delete 2335 ms out of 5875 total ms
    StringBuilder Delete 2301 ms out of 5863 total ms
    Faster Regex Took 18387 ms
    Faster Regex Took 18331 ms
    Faster Regex Took 18421 ms
    Faster Regex Took 18356 ms
    Faster Regex Took 18297 ms
    Faster Regex Took 18416 ms
    Faster Regex Took 18338 ms
    Faster Regex Took 18467 ms
    Faster Regex Took 18326 ms
    Faster Regex Took 18355 ms
    Slow Regex Took 35748 ms
    Slow Regex Took 35855 ms
    Slow Regex Took 35924 ms
    Slow Regex Took 35761 ms
    Slow Regex Took 35764 ms
    Slow Regex Took 35698 ms
    Slow Regex Took 35646 ms
    Slow Regex Took 35637 ms
    Slow Regex Took 35871 ms
    Slow Regex Took 35781 ms
    

    【讨论】:

    • +1 很好看,我太快地把这个基准放在一起了。 OP已经接受了我的回答,所以我可能会通过并纠正一些错误。否则我会摆脱它。
    【解决方案7】:

    如果您不反对使用Apache Commons,那么您可以使用StringUtils 类。

    这意味着您不必满足返回 -1 的 String.indexOf :

    String parentStringValue = StringUtils.substringAfter(yourString, " ");
    

    【讨论】:

      猜你喜欢
      • 2010-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多