Unicode替换为ASCII

3
我在Windows系统上创建了一个文本文件,我认为默认的编码方式是ANSI,文件内容如下:
This is\u2019 a sample text file \u2014and it can ....

我使用了 Windows 的默认编码方式保存了这个文件,尽管也有其他的编码方式可用,比如 UTF-8、UTF-16 等。

现在我想写一个简单的 Java 函数,其中我将传入一些输入字符串,并替换所有的 Unicode 字符为相应的 ASCII 值。

例如:\u2019 应该被替换为 "'",\u2014 应该被替换为 "-" 等等。

观察: 当我创建一个像这样的字符串文字时

  String s = "This is\u2019 a sample text file \u2014and it can ....";

我的代码运行得很好,但是当我从文件中读取它时,它就无法运行。我知道在Java中String使用UTF-16编码。
下面是我用来读取输入文件的代码。
FileReader fileReader  = new FileReader(new File("C:\\input.txt"));
BufferedReader bufferedReader = new BufferedReader(fileReader)
String record = bufferedReader.readLine();

我也尝试使用InputStream并将字符集设置为UTF-8,但结果仍然相同。 替换代码:
public static String removeUTFCharacters(String data){      
        for(Entry<String,String> entry : utfChars.entrySet()){
            data=data.replaceAll(entry.getKey(), entry.getValue());
        }
        return data;
    }

地图:
    utfChars.put("\u2019","'");
    utfChars.put("\u2018","'");
    utfChars.put("\u201c","\"");
    utfChars.put("\u201d","\"");
    utfChars.put("\u2013","-");
    utfChars.put("\u2014","-");
    utfChars.put("\u2212","-");
    utfChars.put("\u2022","*");

有人能帮我理解这个问题的概念和解决方案吗?


只是为了明确,您是说您的文件中有六个字符,它们字面上是 ''、'u'、'2'、'0'、'1'、'9' 吗? - ajb
在现实世界中,我将从一些外部系统接收此文件,并告诉我您将在输入文本文件中收到这些Unicode,例如"\u2019"。为了进行单元测试,我尝试创建与我将要接收的相同类型的文件。 - saurav
你能展示一下读取后的String中实际显示了哪些16位字符吗?就像这样:for (i=0; i<record.length(), i++) System.out.printf("%04X ",(int)record.charAt(i)); - ajb
0054 0068 0069 0073 0020 0069 0073 005C 005C 0075 0032 0030 0031 0039 0020 0061 0020 0073 0061 006D 0070 006C 0065 0020 0074 0065 0078 0074 0020 0066 0069 006C 0065 0020 005C 005C 0075 0032 0030 0031 0034 0061 006E 0064 0020 0069 0074 0020 0063 0061 006E 002E 002E 002E - saurav
2个回答

10

将转义序列\uXXXX与正则表达式匹配。然后使用替换循环,将每个出现的转义序列替换为字符的解码值。

由于Java字符串文字使用\来引入转义,所以序列\\用于表示\。此外,Java正则表达式语法特殊处理序列\ u(表示Unicode转义)。因此,必须再次转义\\,添加一个额外的\\。因此,在模式中,"\\\\u"实际上意味着“在输入中匹配\u”。

为了匹配数字部分,即四个十六进制字符,请使用模式\p{XDigit},并用额外的\转义\。我们想要轻松地将十六进制数提取为一组,因此它被括在括号中以创建一个捕获组。这样,在模式中"(\\p{XDigit}{4})"的意思是,“在输入中匹配4个十六进制字符,并对其进行捕获。”

在循环中,我们搜索模式的出现次数,用解码字符值替换每个出现的转义序列。通过解析十六进制数来解码字符值。Integer.parseInt(m.group(1), 16)的意思是“将前一个匹配中捕获的组作为base-16数字进行解析”。然后使用该字符创建替换字符串。替换字符串必须进行转义或引用,以防它是$,因为在替换文本中具有特殊含义。

String data = "This is\\u2019 a sample text file \\u2014and it can ...";
Pattern p = Pattern.compile("\\\\u(\\p{XDigit}{4})");
Matcher m = p.matcher(data);
StringBuffer buf = new StringBuffer(data.length());
while (m.find()) {
  String ch = String.valueOf((char) Integer.parseInt(m.group(1), 16));
  m.appendReplacement(buf, Matcher.quoteReplacement(ch));
}
m.appendTail(buf);
System.out.println(buf);

谢谢,它起作用了。如果您能解释一下背后实际发生了什么,那将非常有帮助。 - saurav
@Saurav 注意,我对代码进行了小的修改,以修复在输入中找到序列 \u0024 ($) 时出现的错误。我将对示例进行注释,以解释发生了什么。 - erickson
还有一件事,如果我将文件的编码格式从默认更改为UTF-8或UTF-16,保存后会产生什么影响? - saurav
@Saurav 我之前也没有正确处理十六进制,所以请同时应用这个更改。当你创建Reader时,你应该使用 InputStreamReader 并指定编码为你用来保存文件的编码。现在,你正在使用系统默认编码来读取文件,如果你使用不同的编码进行编写,则可能会出错。但是,我猜测使用输入文件中的转义序列的整个目的是为了能够对它们进行 US-ASCII 编码;也就是说,它们永远不应该包含“特殊”字符,对吗? - erickson
是的,我理解了。 非常感谢您清晰的解释。 您的示例激励我学习正则表达式。 - saurav
@BhavinChauhan,你的问题不够清晰。无论如何,它似乎与我的答案无关,而是一个全新的问题。请发布一个新问题,并提供足够的细节以便理解你的问题。 - erickson

6

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