如何从字符串中删除非ASCII字符?

102

我有两个字符串 "A função", "Ãugent",需要用空字符串替换其中的çãÃ等非ASCII字符。

如何从字符串中删除这些非ASCII字符?

我尝试使用下面的函数实现,但它不能正确地工作。一个问题是不想要的字符被替换成了空格字符。

public static String matchAndReplaceNonEnglishChar(String tmpsrcdta) {
    String newsrcdta = null;
    char array[] = Arrays.stringToCharArray(tmpsrcdta);
    if (array == null)
        return newsrcdta;

    for (int i = 0; i < array.length; i++) {
        int nVal = (int) array[i];
        boolean bISO =
                // Is character ISO control
                Character.isISOControl(array[i]);
        boolean bIgnorable =
                // Is Ignorable identifier
                Character.isIdentifierIgnorable(array[i]);
        // Remove tab and other unwanted characters..
        if (nVal == 9 || bISO || bIgnorable)
            array[i] = ' ';
        else if (nVal > 255)
            array[i] = ' ';
    }
    newsrcdta = Arrays.charArrayToString(array);

    return newsrcdta;
}

10个回答

181

这将搜索并替换所有非ASCII字母:

String resultString = subjectString.replaceAll("[^\\x00-\\x7F]", "");

3
A是一个完全合法的ASCII字符。为什么需要替换它呢? - FailedDev
@Dev 我认为它不可见,但这是一个拉丁字符,其 Unicode 值为 "\u00c3"。 - rahulsri
@rahulsri \u00c3 == Ã,是的,它被替换了。你在其他地方有问题。 - FailedDev
53
最有可能你想要去除不可打印和控制字符。这种情况下,你可以使用以下正则表达式:"[^\\x20-\\x7E]" 或者简单地使用 "[^ -~]" - Zouppen
3
"[^\p{ASCII}]" 是 "[^\\x00-\\x7F]" 的等效替代。 - M. Justin
显示剩余2条评论

97
< p> FailedDev 的回答很好,但还有改进的空间。 如果你想保留ASCII等效项,需要先进行规范化: < /p>
String subjectString = "öäü";
subjectString = Normalizer.normalize(subjectString, Normalizer.Form.NFD);
String resultString = subjectString.replaceAll("[^\\x00-\\x7F]", "");

=> will produce "oau"

这样,像 "öäü" 这样的字符将被映射为 "oau",这至少保留了一些信息。如果没有规范化,结果字符串将为空。


6
你的答案不错,但还有改进的余地。用for循环替换代码中的Regex会大大提升速度(20-40倍)。更多信息请参见:https://dev59.com/qHA75IYBdhLWcg3wYYBQ#15191508 - Saket
谢谢你的提示。性能差异的程度出乎意料。 - Michael Böckling
4
你可能希望使用Normalizer.Form.NFKD而不是NFD - NFKD会将连字号之类的字符转换为ASCII字符(例如,将fi转换为fi),而NFD则不会这样做。 - chesterm8
Normalizer.normalize("ãéío – o áá", Normalizer.Form.NFD).replaceAll("[^\x00-\x7F]", ""); 这段代码输出结果为"aeio o aa",但是 echo "ãéío – o áá" | iconv -f utf8 -t ascii//TRANSLIT 的输出结果为"aeio - o aa"。 有没有一种方法可以让Java像iconv一样用"-"替换"–"? - dvlcube

27

这将是Unicode解决方案

String s = "A função, Ãugent";
String r = s.replaceAll("\\P{InBasic_Latin}", "");

\p{InBasic_Latin} 是 Unicode 范围 U+0000..U+007F 中包含所有字母的 Unicode 块(详见regular-expression.info

\P{InBasic_Latin}\p{InBasic_Latin} 的否定形式


7
请将英语翻译为中文。仅返回已翻译的文本:(注:大写\P代表否定。) - ShreevatsaR
2
@user1187719,你可以更准确地描述问题,而不是只说“这个不行”。这个回答已经得到了一些赞,所以它肯定不是完全无用的。当然,如果你使用的是Java 7之前的版本,那么我同意。在正则表达式中使用Unicode在那里是不起作用的。 - stema
@stema - 我在Java 6中运行了它,所以你的Java 7理论是正确的。 - Entropy
它会删除特殊字符,并将它们替换为ASCII等价字符,而不是保留它们。 - AL̲̳I
@Ali,是的,你完全理解了我的回答。这就是5年前被要求的内容。如果这不是你需要的,请选择Michael Böckling的答案。 - stema

3
您可以尝试类似于这样的方法。字母的特殊字符范围从192开始,因此您可以在结果中避免使用这些字符。
String name = "A função";

StringBuilder result = new StringBuilder();
for(char val : name.toCharArray()) {
    if(val < 192) result.append(val);
}
System.out.println("Result "+result.toString());

为什么要检查192而不是128(这将是ASCII表)?您假定某种编码(我认为是ISO-8859-1),但如果编码是ISO-8859-2/3/4/5/7等呢?表的那个区域中有字母。 - stema
是的,它取决于我们想允许的字符数量以及编码方式。这只是一个例子。我们可以根据所需的字符和编码添加条件。 - mmodi

2
String s = "A função";
String stripped = s.replaceAll("\\P{ASCII}", "");
System.out.println(stripped); // Prints "A funo"

或者

private static final Pattern NON_ASCII_PATTERN = Pattern.compile("\\P{ASCII}");

public static String matchAndReplaceNonEnglishChar(String tmpsrcdta) {
    return NON_ASCII_PATTERN.matcher(s).replaceAll("");
}

public static void main(String[] args) {
    matchAndReplaceNonEnglishChar("A função"); // Prints "A funo"
}

解释

String.replaceAll(String regex, String replacement) 方法用给定的替换字符串(replacement)替换所有匹配给定正则表达式(regex)的子串。

用给定的替换字符串替换与给定正则表达式匹配的此字符串的每个子字符串。

Java中有 "\p{ASCII}" 正则表达式构造,它匹配任何ASCII字符,以及它的反义词 "\P{ASCII}",它匹配任何非ASCII字符。然后可以用空字符串替换匹配的字符,从而将其从结果字符串中删除。

String s = "A função";
String stripped = s.replaceAll("\\P{ASCII}", "");
System.out.println(stripped); // Prints "A funo"

完整的有效正则表达式构建列表记录在Pattern类中。
注意:如果您在运行中要多次调用此模式,则直接使用已编译的Pattern会更有效,而不是使用String.replaceAll。这样,该模式仅被编译一次并重复使用,而不是每次调用replaceAll时都重新编译。
public class AsciiStripper {
    private static final Pattern NON_ASCII_PATTERN = Pattern.compile("\\P{ASCII}");
    
    public static String stripNonAscii(String s) {
        return NON_ASCII_PATTERN.matcher(s).replaceAll("");
    }
}

2

或者您可以使用下面的函数从字符串中删除非ASCII字符。您将了解内部工作原理。

private static String removeNonASCIIChar(String str) {
    StringBuffer buff = new StringBuffer();
    char chars[] = str.toCharArray();

    for (int i = 0; i < chars.length; i++) {
        if (0 < chars[i] && chars[i] < 127) {
            buff.append(chars[i]);
        }
    }
    return buff.toString();
}

2

[更新的解决方案]

可以与“Normalize”(规范分解)和“replaceAll”一起使用,将其替换为适当的字符。

import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.regex.Pattern;

public final class NormalizeUtils {

    public static String normalizeASCII(final String string) {
        final String normalize = Normalizer.normalize(string, Form.NFD);

        return Pattern.compile("\\p{InCombiningDiacriticalMarks}+")
                      .matcher(normalize)
                      .replaceAll("");
    } ...

1

ASCII表包含128个代码,其中有95个可打印字符,仅有52个字符是字母:

  • [0-127] ASCII代码
    • [32-126] 可打印字符
      • [48-57] 数字[0-9]
      • [65-90] 大写字母[A-Z]
      • [97-122] 小写字母[a-z]

你可以使用String.codePoints方法获得此字符串的字符int值流,并filter非ASCII字符:
String str1 = "A função, Ãugent";

String str2 = str1.codePoints()
        .filter(ch -> ch < 128)
        .mapToObj(Character::toString)
        .collect(Collectors.joining());

System.out.println(str2); // A funo, ugent

或者您可以明确指定字符范围。例如,过滤掉除字母以外的所有内容

String str3 = str1.codePoints()
        .filter(ch -> ch >= 'A' && ch <= 'Z'
                || ch >= 'a' && ch <= 'z')
        .mapToObj(Character::toString)
        .collect(Collectors.joining());

System.out.println(str3); // Afunougent

另请参阅:如何在不使用正则表达式的情况下进行密码验证时不包含特殊字符?


1
一个易于阅读、可打印的 ASCII 流解决方案:
String result = str.chars()
    .filter(c -> isAsciiPrintable((char) c))
    .mapToObj(c -> String.valueOf((char) c))
    .collect(Collectors.joining());

private static boolean isAsciiPrintable(char ch) {
    return ch >= 32 && ch < 127;
}

要转换为“_”:.map(c -> isAsciiPrintable((char) c) ? c : '_') 32到127相当于正则表达式[^\\x20-\\x7E](来自对正则表达式解决方案的评论)
isAsciiPrintable的源代码:http://www.java2s.com/Code/Java/Data-Type/ChecksifthestringcontainsonlyASCIIprintablecharacters.htm

0

如果你正在使用Google Guava库,可以使用CharMatcher.retainFrom

String s = "A função";
String stripped = CharMatcher.ascii().retainFrom(s);
System.out.println(stripped); // Prints "A funo"

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