除了使用String.replaceAll()
方法并逐个替换字母外,是否有更好的方法来去除重音并使这些字母规范化?
例如:
输入:orčpžsíáýd
输出:orcpzsiayd
它不需要包括所有带重音符号的字母,如俄语或中文字母。
除了使用String.replaceAll()
方法并逐个替换字母外,是否有更好的方法来去除重音并使这些字母规范化?
例如:
输入:orčpžsíáýd
输出:orcpzsiayd
它不需要包括所有带重音符号的字母,如俄语或中文字母。
使用 java.text.Normalizer
来处理这个问题。
string = Normalizer.normalize(string, Normalizer.Form.NFD);
// or Normalizer.Form.NFKD for a more "compatible" deconstruction
这将从字符中分离出所有的重音符号。然后,您只需要将每个字符与字母进行比较并丢弃不是字母的字符。
string = string.replaceAll("[^\\p{ASCII}]", "");
如果您的文本是Unicode编码,则应改用此方法:
string = string.replaceAll("\\p{M}", "");
\\P{M}
匹配基本字形,而\\p{M}
(小写)匹配每个重音符号。从2011年开始,你可以使用Apache Commons StringUtils.stripAccents(input) (自3.0版本起):
String input = StringUtils.stripAccents("Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ");
System.out.println(input);
// Prints "This is a funky String"
注意:
被接受的答案(Erick Robertson的)不能处理 Ø 或 Ł。Apache Commons 3.5也不能处理 Ø,但是它可以处理Ł。阅读了关于Ø的维基百科文章后,我不确定是否应该用“O”替换它:在挪威和丹麦,Ø是一个单独的字母,在“z”之后排列。这是“去除重音”方法的限制很好的例子。
@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();
}
}
通过将数据写入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);
}
这种变体的优点是使用Normalizer的正确性和使用表格的一些速度。在我的机器上,这个版本比被接受的答案快4倍左右,比@virgo47的慢6.6倍到7倍(被接受的答案在我的机器上比@virgo47的慢26倍左右)。
out
构建字符串对象之前,必须将其重新调整大小以匹配有效字符数 j
。 - Lefteris EflattenToAscii
创建了结果“aa..”,其中点表示\u0000
。这不好。第一个问题是如何表示“无法规范化”的字符?假设它将是“?”或者我们可以在那里留下NULL字符,但无论如何,我们都必须保留这些字符的正确位置(就像正则表达式解决方案所做的那样)。为此,在循环中的if语句必须像这样:if (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';
它会使它变慢一些,但首先必须是正确的。;-) - virgo47isLetter
)不是正确的选择,但我没有找到更好的。我不是Unicode专家,所以我不知道如何更好地识别代替原始字符的单个字符的类别。对于大多数应用/用途,字母运作良好。 - virgo47编辑:如果你不受限于Java <6且速度不是关键,或者翻译表太受限制,请使用David的答案。关键是在循环内部使用Java 6中引入的Normalizer
而不是翻译表。
虽然这不是“完美”的解决方案,但在你知道范围(在我们的情况下是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);
}
我的硬件上使用32位JDK进行测试表明,该方法将从àèéľšťč89FDČ转换为aeelstc89FDC,在1百万次内约需要100毫秒,而Normalizer方式则需要3.7秒(慢37倍)。如果您的需求是关于性能,并且您了解输入范围,那么这个方法可能适合您。
祝您愉快 :-)
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));
这对我有用。上面示例的输出给出了我想要的"aee",但是
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""));
没有进行任何替换。
根据语言的不同,这些可能不被视为重音(改变字母的发音),而是变音符号。
https://en.wikipedia.org/wiki/Diacritic#Languages_with_letters_containing_diacritics
"Bosnian and Croatian have the symbols č, ć, đ, š and ž, which are considered separate letters and are listed as such in dictionaries and other contexts in which words are listed according to alphabetical order."i.e., Non-breaking space - [Hex - A0] Space [Hex - 20]. To show Non-breaking space over HTML. I have used the following
spacing entities
. Their character and its bytes are like&emsp is very wide space[ ]{-30, -128, -125}, &ensp is somewhat wide space[ ]{-30, -128, -126}, &thinsp is narrow space[ ]{32} , Non HTML Space {}
String s1 = "My Sample Space Data", s2 = "My Sample Space Data"; System.out.format("S1: %s\n", java.util.Arrays.toString(s1.getBytes())); System.out.format("S2: %s\n", java.util.Arrays.toString(s2.getBytes()));
Output in Bytes:
S1: [77, 121,
32
, 83, 97, 109, 112, 108, 101,32
, 83, 112, 97, 99, 101,32
, 68, 97, 116, 97] S2: [77, 121,-30, -128, -125
, 83, 97, 109, 112, 108, 101,-30, -128, -125
, 83, 112, 97, 99, 101,-30, -128, -125
, 68, 97, 116, 97]
使用以下代码来获取不同空格及其字节码: wiki for List_of_Unicode_characters
String spacing_entities = "very wide space,narrow space,regular space,invisible separator";
System.out.println("Space String :"+ spacing_entities);
byte[] byteArray =
// spacing_entities.getBytes( Charset.forName("UTF-8") );
// Charset.forName("UTF-8").encode( s2 ).array();
{-30, -128, -125, 44, -30, -128, -126, 44, 32, 44, -62, -96};
System.out.println("Bytes:"+ Arrays.toString( byteArray ) );
try {
System.out.format("Bytes to String[%S] \n ", new String(byteArray, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
➩ ASCII transliterations of Unicode string for Java. unidecode
String initials = Unidecode.decode( s2 );
➩ using Guava
: Google Core Libraries for Java
.
String replaceFrom = CharMatcher.WHITESPACE.replaceFrom( s2, " " );
For URL encode for the space use Guava laibrary.
String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);
➩ To overcome this problem used String.replaceAll()
with some RegularExpression
.
// \p{Z} or \p{Separator}: any kind of whitespace or invisible separator.
s2 = s2.replaceAll("\\p{Zs}", " ");
s2 = s2.replaceAll("[^\\p{ASCII}]", " ");
s2 = s2.replaceAll(" ", " ");
➩ Using java.text.Normalizer.Form. This enum provides constants of the four Unicode normalization forms that are described in Unicode Standard Annex #15 — Unicode Normalization Forms and two methods to access them.
s2 = Normalizer.normalize(s2, Normalizer.Form.NFKC);
测试字符串和不同方法的输出,如 ➩ Unidecode,正规化器,StringUtils。
String strUni = "Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß";
// This is a funky String AE,O,D,ss
String initials = Unidecode.decode( strUni );
// Following Produce this o/p: Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß
String temp = Normalizer.normalize(strUni, Normalizer.Form.NFD);
Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
temp = pattern.matcher(temp).replaceAll("");
String input = org.apache.commons.lang3.StringUtils.stripAccents( strUni );
使用Unidecode是最佳选择
,下面是我的最终代码。
public static void main(String[] args) {
String s1 = "My Sample Space Data", s2 = "My Sample Space Data";
String initials = Unidecode.decode( s2 );
if( s1.equals(s2)) { //[ , ] %A0 - %2C - %20 « http://www.ascii-code.com/
System.out.println("Equal Unicode Strings");
} else if( s1.equals( initials ) ) {
System.out.println("Equal Non Unicode Strings");
} else {
System.out.println("Not Equal");
}
}
由于该解决方案已经在Maven Repository的StringUtils.stripAccents()中提供,并且像@DavidS所提到的那样对于Ł有效。
但我需要它可以处理Ø和Ł。 因此进行了修改,如下所示。 可能对他人也有帮助。
这是StringUtils.stripAccents(String obj)的修改版本,包含旧功能以及处理Ø和Ł字符的功能。
public static String stripAccents(final String input) {
if (input == null) {
return null;
}
final StringBuilder decomposed = new StringBuilder(Normalizer.normalize(input, Normalizer.Form.NFD));
for (int i = 0; i < decomposed.length(); i++) {
if (decomposed.charAt(i) == '\u0141') {
decomposed.setCharAt(i, 'L');
} else if (decomposed.charAt(i) == '\u0142') {
decomposed.setCharAt(i, 'l');
}else if (decomposed.charAt(i) == '\u00D8') {
decomposed.setCharAt(i, 'O');
}else if (decomposed.charAt(i) == '\u00F8') {
decomposed.setCharAt(i, 'o');
}
}
// Note that this doesn't correctly remove ligatures...
return Pattern.compile("\\p{InCombiningDiacriticalMarks}+").matcher(decomposed).replaceAll("");
}
输入字符串 Ł Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Ø ø
输出字符串 L This is a funky String O o
tLibraryLoad
组件。将其连接到tJava
组件后,在高级设置中添加以下行import static net.gcardone.junidecode.Junidecode.*;
。从那里,您可以调用该方法来转换您的字符串。 - Alexander Dixon如果你没有库的话,使用正则表达式和规范化是最好的方法之一:
public String flattenToAscii(String s) {
if(s == null || s.trim().length() == 0)
return "";
return Normalizer.normalize(s, Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", "");
}
string.replaceAll("\\p{M}", "")
来完成此操作。有关更多信息,请参见 http://www.regular-expressions.info/unicode.html。 - Garret Wilson