有没有办法摆脱重音并将整个字符串转换为普通字母?

Mar*_*tin 236 java string diacritics

是否有更好的方法来摆脱重音并使这些字母与使用String.replaceAll()方法和逐个替换字母分开?例:

输入: or?pžsíáýd

输出: orcpzsiayd

它不需要包括所有带有重音符号的字母,如俄语字母或中文字母.

Eri*_*son 364

使用java.text.Normalizer来处理这个给你.

string = Normalizer.normalize(string, Normalizer.Form.NFD);
// or Normalizer.Form.NFKD for a more "compatable" deconstruction 
Run Code Online (Sandbox Code Playgroud)

这会将所有重音符号与字符分开.然后,你只需要将每个角色与一个字母进行比较,然后扔掉那些不是.

string = string.replaceAll("[^\\p{ASCII}]", "");
Run Code Online (Sandbox Code Playgroud)

如果你的文字是unicode,你应该使用它:

string = string.replaceAll("\\p{M}", "");
Run Code Online (Sandbox Code Playgroud)

对于unicode,\\P{M}匹配基本字形和\\p{M}(小写)匹配每个重音.

由于GarretWilson的指针和regular-expressions.info为伟大的Unicode指南.

  • 这是一个很好的方法,但删除所有非ASCII字符是过度的,可能会删除你不想要的东西,正如其他人所指出的那样.删除所有Unicode"标记"会更好; 包括非间距标记,间距/组合标记和封闭标记.你可以用`string.replaceAll("\\ p {M}","")`来做到这一点.有关更多信息,请参见http://www.regular-expressions.info/unicode.html. (10认同)
  • 这会每次编译正则表达式,如果你只需要一次就可以了,但是如果你需要用大量的文本来做这件事,那么预编译正则表达式就是一个胜利. (4认同)
  • 您可能希望使用Normalizer.Form.NFKD而不是NFD - NFKD会将连字之类的东西转换为ascii字符(例如fi到fi),NFD不会这样做. (3认同)
  • @chesterm8,有趣的是,NFKD 正在将“fi”转换为“fi”,但并没有将“Æ”转换为“AE”。我想我必须调出 Unicode 数据才能找出原因,但这不是我所期望的。 (3认同)
  • 请注意,并非所有基于拉丁语的字母都会分解为ASCII +重音符号.这会杀死例如."拉丁语{capital,small}字母l中风"用于波兰语. (2认同)

Dav*_*idS 121

截至2011年,您可以使用Apache Commons StringUtils.stripAccents(输入)(自3.0起):

    String input = StringUtils.stripAccents("T?ï? ?š â f???? Š?????");
    System.out.println(input);
    // Prints "This is a funky String"
Run Code Online (Sandbox Code Playgroud)

注意:

接受的答案(Erick Robertson's)不适用于Ø或Ł.Apache Commons 3.5也不适用于Ø,但它确实适用于Ł.在阅读维基百科关于Ø的文章后,我不确定它应该替换为"O":它是挪威语和丹麦语中的单独字母,在"z"之后按字母顺序排列.这是"条带重音"方法局限性的一个很好的例子.

  • Commons Lang 3.5几天前发布了.我确认它现在适用于Ł.它不适用于Ø.阅读[Ø]的维基文章(https://en.wikipedia.org/wiki/%C3%98),我不确定它应该替换为"O":它是挪威语和丹麦语中的一个单独的字母,在"z"之后按字母顺序排列.这是"条带重音"方法局限性的一个很好的例子. (5认同)
  • 我看到有一个[Ł开放式错误报告](https://issues.apache.org/jira/browse/LANG-1120),@KarolS.有人提交了一个拉取请求,但它没有通过一些测试,并且自去年7月以来一直没有更新. (2认同)
  • 如果您不想包含库,可以从https://commons.apache.org/proper/commons-lang/apidocs/src-html/org/apache/轻松地从源代码中获取该功能中涉及的两种方法.公共/ lang3/StringUtils.html (2认同)
  • 作为丹麦语,丹麦语/挪威语 ø 就像法语 œ 和德语/瑞典语/匈牙利语/爱沙尼亚语等一样。 ö 起源于写 oe 的一种简短方式。因此,根据您的目的,这可能是您想要的替代品。 (2认同)

Dav*_*rad 51

@ virgo47的解决方案非常快,但近似.接受的答案使用Normalizer和正则表达式.我想知道Normalizer与正则表达式相比花了多少时间,因为删除所有非ASCII字符都可以在没有正则表达式的情况下完成:

import java.text.Normalizer;

public class Strip {
    public static String flattenToAscii(String string) {
        StringBuilder sb = new StringBuilder(string.length());
        string = Normalizer.normalize(string, Normalizer.Form.NFD);
        for (char c : string.toCharArray()) {
            if (c <= '\u007F') sb.append(c);
        }
        return sb.toString();
    }
}
Run Code Online (Sandbox Code Playgroud)

通过写入char []并且不调用toCharArray()可以获得小的额外加速,尽管我不确定代码清晰度的降低是否值得:

public static String flattenToAscii(String string) {
    char[] out = new char[string.length()];
    string = Normalizer.normalize(string, Normalizer.Form.NFD);
    int j = 0;
    for (int i = 0, n = string.length(); i < n; ++i) {
        char c = string.charAt(i);
        if (c <= '\u007F') out[j++] = c;
    }
    return new String(out);
}
Run Code Online (Sandbox Code Playgroud)

这种变化具有使用Normalizer的正确性以及使用表格的一些速度的正确性的优点.在我的机器上,这个比你接受的答案快4倍,比@ virgo47慢6.6倍到7倍(接受的答案比我机器上的@ virgo47慢约26倍).

  • 我对这个解决方案有异议.想象一下输入"æøåá".当前`flattenToAscii`创建结果"aa ..",其中点代表\ u0000.这是不好的.第一个问题是 - 如何表示"不可规范化"的字符?让我们说它会是?,或者我们可以在那里留下NULL char,但无论如何我们必须保留这些的正确位置(就像正则表达式解决方案一样).为此,循环中的if必须类似于:`if(c <='\ u007F')out [j ++] = c; 否则if(Character.isLetter(c))out [j ++] ='?';`它会慢一点,但首先必须是正确的.;-) (3认同)
  • 在用于构造字符串对象之前,必须调整`out`以匹配有效字符`j`的数量. (2认同)
  • 您可能想使用 Normalizer.Form.NFKD 而不是 NFD - NFKD 会将诸如连字之类的内容转换为 ascii 字符(例如 fi 到 fi),NFD 不会这样做。 (2认同)
  • 对我们来说,我们想完全删除这个角色。为了确保没有尾随空字符,我用一个替代的 String 构造函数删除了它们: return new String(out, 0, j); (2认同)

vir*_*o47 28

编辑:如果你没有坚持Java <6并且速度不重要和/或翻译表太有限,请使用David的回答.关键是使用Normalizer(在Java 6中引入)而不是循环内的转换表.

虽然这不是"完美"的解决方案,但是当您知道范围(在我们的案例中为Latin1,2)时,它可以很好地工作,在Java 6之前工作(虽然不是真正的问题)并且比最建议的版本快得多(可能或可能)不是问题):

    /**
 * Mirror of the unicode table from 00c0 to 017f without diacritics.
 */
private static final String tab00c0 = "AAAAAAACEEEEIIII" +
    "DNOOOOO\u00d7\u00d8UUUUYI\u00df" +
    "aaaaaaaceeeeiiii" +
    "\u00f0nooooo\u00f7\u00f8uuuuy\u00fey" +
    "AaAaAaCcCcCcCcDd" +
    "DdEeEeEeEeEeGgGg" +
    "GgGgHhHhIiIiIiIi" +
    "IiJjJjKkkLlLlLlL" +
    "lLlNnNnNnnNnOoOo" +
    "OoOoRrRrRrSsSsSs" +
    "SsTtTtTtUuUuUuUu" +
    "UuUuWwYyYZzZzZzF";

/**
 * Returns string without diacritics - 7 bit approximation.
 *
 * @param source string to convert
 * @return corresponding string without diacritics
 */
public static String removeDiacritic(String source) {
    char[] vysl = new char[source.length()];
    char one;
    for (int i = 0; i < source.length(); i++) {
        one = source.charAt(i);
        if (one >= '\u00c0' && one <= '\u017f') {
            one = tab00c0.charAt((int) one - '\u00c0');
        }
        vysl[i] = one;
    }
    return new String(vysl);
}
Run Code Online (Sandbox Code Playgroud)

使用32位JDK对我的硬件进行测试表明,这可以在~100ms内执行从àèéľľť89899FDČ到aeelstc89FDC的100万次转换,而Normalizer方式则在3.7s内(37x慢).如果您的需求与性能有关并且您知道输入范围,这可能适合您.

请享用 :-)


Nic*_*ico 21

System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));
Run Code Online (Sandbox Code Playgroud)

为我工作.上面代码片段的输出给出了"aee",这是我想要的,但是

System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""));
Run Code Online (Sandbox Code Playgroud)

没有做任何替代.


Nin*_*Cat 6

根据语言的不同,这些可能不会被视为重音(这会改变字母的声音),而是变音符号

https://en.wikipedia.org/wiki/Diacritic#Languages_with_letters_containing_diacritics

"波斯尼亚语和克罗地亚语有符号č,ć,đ,š和ž,它们被视为单独的字母,并在字典和其他语境中按字母顺序列出.

删除它们可能会固有地改变单词的含义,或者将字母改为完全不同的字母.

  • 它们意味着什么并不重要.问题是如何删除它们. (13认同)
  • 埃里克:重要的是他们所谓的.如果问题是如何删除重音符号,如果这些不是重音符号,那么答案可能不仅仅是如何删除所有看起来像重音符号的内容.虽然这应该是评论而不是答案. (7认同)
  • 同意.例如瑞典语:"höra"(听到) - >"hora"(妓女) (5认同)
  • 我认为这种情况的正常用例是搜索,特别是搜索混合语言,通常使用英语键盘作为输入,在这种情况下,获得误报比假阴性更好. (3认同)