替换字符串中多个字符的有效方法是什么?

29

我希望学习在Java中如何处理字符串。目前我想要输入一个字符串并替换掉其中的任何字符。

以下是我当前的低效(而且我认为有点儿傻)函数。这个函数只是为了能够工作而被编写出来的。

public String convertWord(String word)
{
    return word.toLowerCase().replace('á', 'a')
                             .replace('é', 'e')
                             .replace('í', 'i')
                             .replace('ú', 'u')
                             .replace('ý', 'y')
                             .replace('ð', 'd')
                             .replace('ó', 'o')
                             .replace('ö', 'o')
                             .replaceAll("[-]", "")
                             .replaceAll("[.]", "")
                             .replaceAll("[/]", "")
                             .replaceAll("[æ]", "ae")
                             .replaceAll("[þ]", "th");
}

我运行了100万次,并花费8182毫秒的时间。那么,我应该如何改变这个函数,使其更加高效?

找到解决方案:

将函数转换为以下形式

public String convertWord(String word)
{
    StringBuilder sb = new StringBuilder();

    char[] charArr = word.toLowerCase().toCharArray();

    for(int i = 0; i < charArr.length; i++)
    {
        // Single character case
        if(charArr[i] == 'á')
        {
            sb.append('a');
        }
        // Char to two characters
        else if(charArr[i] == 'þ')
        {
            sb.append("th");
        }
        // Remove
        else if(charArr[i] == '-')
        {
        }
        // Base case
        else
        {   
            sb.append(word.charAt(i));
        }
    }

    return sb.toString();
}

执行这个函数100万次需要518毫秒,所以我认为它已经足够高效了。感谢大家的帮助 :)


3
部分工作在这里:https://dev59.com/8XNA5IYBdhLWcg3wVcFx。我不确定`æ`和`þ`应该如何处理。 - Kobi
9个回答

20

你可以创建一个长度为Character.MAX_VALUE的String[]表格(包括小写映射),随着替换变得更加复杂,执行它们所需的时间将保持不变。

private static final String[] REPLACEMENT = new String[Character.MAX_VALUE+1];
static {
    for(int i=Character.MIN_VALUE;i<=Character.MAX_VALUE;i++)
        REPLACEMENT[i] = Character.toString(Character.toLowerCase((char) i));
    // substitute
    REPLACEMENT['á'] =  "a";
    // remove
    REPLACEMENT['-'] =  "";
    // expand
    REPLACEMENT['æ'] = "ae";
}

public String convertWord(String word) {
    StringBuilder sb = new StringBuilder(word.length());
    for(int i=0;i<word.length();i++)
        sb.append(REPLACEMENT[word.charAt(i)]);
    return sb.toString();
} 

这是帮助我创建当前代码的解决方案。所以我接受了这个。但是mikera也提供了很多帮助。 - Ólafur Waage
1
起初似乎有点疯狂,但事实证明这个数组只有64KB,其实并不那么糟糕。 - Kobi
@Kobi,这个查找非常快速,不需要任何对象。convertWord()函数只创建了一个临时对象(StringBuilder)。 - Peter Lawrey
那个评论实际上是我表达支持的奇怪方式!(我只是想在投票之前检查一下大小是否合理) - Kobi
@Kobi,使用Map<Character,String>的最大大小相比,大小会小得多。 ;) - Peter Lawrey
@Peter - 这取决于情况。如果您默认删除字符(即删除未映射的字符,这可能是大多数字符的情况),则不需要在地图中使用所有字符。不过关于装箱问题,我会听从您的建议。 - Kobi

8
我的建议是:
  • 将字符串转换为char[]数组
  • 逐个遍历数组,测试每个字符(例如使用switch语句)并在需要时替换它
  • 将char[]数组转换回字符串
我认为这可能是Java中最快的性能。
编辑:我注意到您正在进行一些更改,这些更改会更改字符串的长度。在这种情况下,相同的原则适用,但您需要保留两个数组,并分别递增源索引和目标索引。如果您用完目标空间(即重新分配一个较大的数组并将现有的目标数组复制到其中),还可能需要调整目标数组的大小。

1
基本上,您应该遍历字符并使用 StringBuilder - Kobi
1
如果您不介意使用更多的空间,我会做同样的事情,只有一个小变化:我会使用Map<Character, Character>,其中键是要替换的字符集,值是相应的替换。这避免了switch语句。 - MarcoS
Kobi / MarcoS - 同意你们两种方法都更加简洁/优雅 :-) 不过它们也会稍微慢一些。我想这取决于你真正关心“效率”有多少...... - mikera
Map<Char, String> 可以工作,例如我将 æ 替换为 ae。 - Ólafur Waage
使用字符作为键可能会导致为字符串中的每个字符创建一个对象。 ;) - Peter Lawrey

5

我的实现基于查找表。

public static String convertWord(String str) {
    char[] words = str.toCharArray();
    char[] find = {'á','é','ú','ý','ð','ó','ö','æ','þ','-','.',
            '/'};
    String[] replace = {"a","e","u","y","d","o","o","ae","th"};
    StringBuilder out = new StringBuilder(str.length());
    for (int i = 0; i < words.length; i++) {
        boolean matchFailed = true;
        for(int w = 0; w < find.length; w++) {
            if(words[i] == find[w]) {
                if(w < replace.length) {
                    out.append(replace[w]);
                }
                matchFailed = false;
                break;
            }
        }
        if(matchFailed) out.append(words[i]);
    }
    return out.toString();
}

2
我的第一选择是使用 StringBuilder,因为您需要从字符串中删除一些字符。
第二选择是迭代字符数组并将处理后的字符添加到另一个大小与原始字符串相同的数组中。然后您需要复制该数组以裁剪可能未使用的位置。
之后,我会进行一些性能测试,以确定哪种方法更好。

0

我认为效率低下的地方在于你会再次检查已经被替换的字符,这是无用的。

我会获取字符串实例的charArray,对其进行迭代,并针对每个字符使用一系列if-else,如下所示:

char[] array = word.toCharArray();
for(int i=0; i<array.length; ++i){
    char currentChar = array[i];
    if(currentChar.equals('é'))
        array[i] = 'e';
    else if(currentChar.equals('ö'))
        array[i] = 'o';
    else if(//...
}

0
我怀疑你确实无法加快“字符替换”的速度。至于正则表达式替换的情况,你可以事先编译正则表达式。

0
使用函数String.replaceAll。 类似于您想要的好文章:link

0

每当我们遇到这样的问题时,我们都会使用正则表达式,因为它们是处理您尝试解决的问题最快捷的方式。

您已经尝试过正则表达式了吗?


0
我刚刚实现了这个实用类,它可以替换字符串中的一个字符或一组字符。它相当于bash中的tr和perl中的tr///,也就是转换字符。希望能对某些人有所帮助!
package your.package.name;

/**
 * Utility class that replaces chars of a String, aka, transliterate.
 * 
 * It's equivalent to bash 'tr' and perl 'tr///'.
 *
 */
public class ReplaceChars {

    public static String replace(String string, String from, String to) {
        return new String(replace(string.toCharArray(), from.toCharArray(), to.toCharArray()));
    }

    public static char[] replace(char[] chars, char[] from, char[] to) {

        char[] output = chars.clone();
        for (int i = 0; i < output.length; i++) {
            for (int j = 0; j < from.length; j++) {
                if (output[i] == from[j]) {
                    output[i] = to[j];
                    break;
                }
            }
        }
        return output;
    }

    /**
     * For tests!
     */
    public static void main(String[] args) {

        // Example from: https://en.wikipedia.org/wiki/Caesar_cipher
        String string = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG";
        String from = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        String to = "XYZABCDEFGHIJKLMNOPQRSTUVW";

        System.out.println();
        System.out.println("Cesar cypher: " + string);
        System.out.println("Result:       " + ReplaceChars.replace(string, from, to));
    }
}

这是输出内容:

Cesar cypher: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Result:       QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD

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