如何在Java中使用StringBuilder重写这段代码?

3

给定一个单词,我需要将其中的某些字母替换为特定的字母,例如用1代替a,用5代替b等。我使用正则表达式来解决这个问题。我知道StringBuilder是处理这个问题的最佳方式,因为我需要进行大量的字符串操作。以下是我的做法:

String word = "foobooandfoo";
String converted = "";
converted = word.replaceAll("[ao]", "1");
converted = converted.replaceAll("[df]", "2");
converted = converted.replaceAll("[n]", "3");

我的问题是如何使用StringBuilder重写这个程序。我尝试了一切但是没有成功。或者对于这个程序使用String也是可以的吗?

9个回答

8

我认为这是一个清晰和性能愉快地相结合的案例。我会使用查找表来进行“翻译”。

  public static void translate(StringBuilder str, char[] table)
  {
    for (int idx = 0; idx < str.length(); ++idx) {
      char ch = str.charAt(idx);
      if (ch < table.length) {
        ch = table[ch];
        str.setCharAt(idx, ch);
      }
    }
  }

如果您的str输入具有较大的字母表,或者您的映射是稀疏的,您可以使用真正的映射,像这样:

  public static void translate(StringBuilder str, Map<Character, Character> table)
  {
    for (int idx = 0; idx < str.length(); ++idx) {
      char ch = str.charAt(idx);
      Character conversion = table.get(ch);
      if (conversion != null) 
        str.setCharAt(idx, conversion);
    }
  }

虽然这些实现可以在原地工作,但你也可以创建一个新的 StringBuilder 实例(或追加到传入的实例中)。


清晰度在观察者的眼中,我的朋友。我发现问题中的代码更容易理解。 - sblundy
没错,这是一个观点问题。如果只有三条规则,那么不是问题,但是如果在大量样板代码中嵌入了很多正则表达式字符类的长列表,我会很难发现其中的错误。 - erickson
这绝对是最好的解决方案。任何能够编写此代码而不使用映射(switch语句)的人都不应该自称为软件工程师。您的代码不应该包含数据(甚至不是'1',"1"或'a')。 - Bill K

2

实际上,我会说在大多数应用程序中代码都很好,尽管从理论上讲,它比其他方法要差。如果你不想使用Matcher,可以这样尝试:

StringBuilder result = new StringBuilder(word.length());

for (char c : word.toCharArray()) {
    switch (c) {
        case 'a': case 'o': result.append('1'); break;
        case 'd': case 'f': result.append('2'); break;
        case 'n': result.append('3'); break;
        default: result.append(c); break;
    }
}

不幸的是,对于(char c:word),它似乎无法工作(至少在我的javac中不行)。哦,对于C#... - Jon Skeet
除此之外,我们的解决方案基本相同 :) - Jon Skeet
在Java 5中,Err,for (char c : word.toCharArray())在这里完美地工作。这已经发布多年了... - JeeBee
如果我们关注性能,我期望显式迭代比将字符串复制到新的字符数组中要快。 - Jon Skeet
感谢sblundy纠正我的代码,StackOverflow变成了一个维基真是太好了! - Konrad Rudolph

1
我不确定StringBuilder是否适合你的情况。我建议看一下Matcher,它是Java正则表达式包中的一部分,如果你真的需要性能,它可能比上面的例子更快。

1

在一些程序中,StringBuilder和StringBuffer可能会有很大的性能差异。请参见: http://www.thectoblog.com/2011/01/stringbuilder-vs-stringbuffer-vs.html 这将是想要保持它的一个强有力的理由。

原帖要求将多个字符替换为单个字符。这会影响调整大小的影响,反过来可能会影响性能。

话虽如此,最简单的方法是使用String。但要注意在何处执行操作,以便在关注性能时最小化gc和其他效果。

我喜欢P Arrayah的方法,但为了更通用的答案,它应该使用LinkedHashMap或保留顺序的其他东西,以防替换有依赖关系。

Map replaceRules = new HashMap();

Map replaceRules = new LinkedHashMap();


1

我不相信你能做到。所有的正则表达式替换API都使用String而不是StringBuilder。

如果你基本上是将每个字符转换为不同的字符,你可以尝试这样做:

public String convert(String text)
{
    char[] chars = new char[text.length()];
    for (int i=0; i < text.length(); i++)
    {
        char c = text.charAt(i);
        char converted;
        switch (c)
        {
            case 'a': converted = '1'; break;
            case 'o': converted = '1'; break;
            case 'd': converted = '2'; break;
            case 'f': converted = '2'; break;
            case 'n': converted = '3'; break;
            default : converted = c; break;
        }
        chars[i] = converted;
    }
    return new String(chars);
}

不过,如果你使用任何复杂的正则表达式,这显然不会有太大帮助。


这似乎是一个很好的解决方案,可能是开销最小的。个人认为,我会将这些case语句合并起来,摆脱转换变量,并且只覆盖c,然后也可以摆脱默认情况。 - Matt Wolfe

0
我看了一下 Matcher.replaceAll(),发现它返回一个 String。因此,我认为你已经拥有了足够快的实现。正则表达式易于阅读和处理速度也很快。
还记得优化的第一条规则:不要优化!

0
我知道StringBuilder是处理这个问题的最佳方式,因为我正在进行大量的字符串操作。
谁告诉你的?最好的方法是那些更容易阅读的,使用StringBuilder的方法。在许多情况下,StringBuilder并不能提供明显的加速效果。
如果值总是被替换,就不应该初始化“converted”。
您可以删除一些样板以改进您的代码:
String word = "foobooandfoo";
String converted = word.replaceAll("[ao]", "1")
                       .replaceAll("[df]", "2")
                       .replaceAll("[n]", "3");

如果你想使用 StringBuilder,你可以使用这个方法。

java.util.regex.Pattern#matcher(java.lang.CharSequence)

接受CharSequence(由StringBuilder实现)的方法。 请参见http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#matcher(java.lang.CharSequence)


0

StringBuilder和正则表达式不是一种二元对立的关系。String#replaceAll()之所以不是正确的工具,是因为每次调用它时都会编译正则表达式并处理整个字符串。你可以通过将所有的正则表达式合并成一个,并使用Matcher中的低级方法而不是replaceAll()来避免所有这些多余的工作,像这样:

String text = "foobooandfoo";
Pattern p = Pattern.compile("([ao])|([df])|n");
Matcher m = p.matcher(text);
StringBuffer sb = new StringBuffer();
while (m.find())
{
  m.appendReplacement(sb, "");
  sb.append(m.start(1) != -1 ? '1' :
            m.start(2) != -1 ? '2' :
                               '3');
}
m.appendTail(sb);
System.out.println(sb.toString());

当然,这还是有点过头了;对于这种简单的任务,我建议使用erickson的方法。

-2
我不建议使用正则表达式来处理这个问题,它们在执行简单操作时速度非常慢。相反,我建议你从类似这样的东西开始:
// usage:
Map<String, String> replaceRules = new HashMap<String, String>();
replaceRules.put("ao", "1");
replaceRules.put("df", "2");
replaceRules.put("n", "3");
String s = replacePartsOf("foobooandfoo", replaceRules);

// actual method
public String replacePartsOf(String thisString, Map<String, String> withThese) {
    for(Entry<String, String> rule : withThese.entrySet()) {
        thisString = thisString.replaceAll(rule.getKey(), rule.getValue());
    }

    return thisString;
}

在你完成这个之后,重构它以使用字符数组。虽然我认为你想做的事情可以用 StringBuilder 来实现,但很可能不值得这样做。


String.replaceAll 实际上使用正则表达式。因此,在 for 循环中调用它并不等同于原始帖子提供的代码。请参阅 Javadoc。 - laz
我只是希望他能够遵循上面代码片段的逻辑,而不是完全使用它。我确实说过他应该“重构代码,改用字符数组”。 - P Arrayah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接