从US-ASCII编码字符串获取UTF-8编码

3

我有一个字符串 "Château",它是使用UTF-8编码的,并且在我的应用程序的底层库中被转换为US-ASCII格式为 "Ch??teau"。

现在,我想从 "U-ASCII" 转换后的字符串 "Ch??teau" 中获取原始字符串 "Château"。但是,我无法使用以下代码实现。

StringBuilder masterBuffer = new StringBuilder();
byte[] rawDataBuffer = (Read from InputStream) // say here it is "Château"
String rawString = new String(rawDataBuffer, "UTF-8");
masterBuffer.append(rawString);
onMessageReceived(masterBuffer.toString().getBytes()) => Here, getBytes() uses the platform's default charset 'US-ASCII.

我的应用程序接收到US-ASCII编码的字节数组。 在应用程序端,即使我尝试获取UTF-8字符串,也没有用。 转换尝试仍然会返回“Ch??teau”。

String asciiString = "Ch??teau";
String originalString = new String(asciiString.getBytes("UTF-8"), "UTF-8");
System.out.println("orinalString: " + originalString);

'originalString'的值仍然是“Château”。

这样做正确吗?

谢谢。


好的,首先要明确一点:UTF-8编码的字符串并不存在。Java中的String存储文本数据时不考虑字符编码,这意味着你的问题不在你发布的代码中。请贴出完整的代码。 - fge
Java的String(就像C#,JavaScript等)是一个UTF-16代码单元的计数序列,其中一个或两个代码单元编码一个Unicode代码点。(而且,在计算机世界中有一些字符不在Unicode字符集中。) - Tom Blodget
1
@TomBlodget:在明年即将发布的Java 9中,字符串不再总是内部存储UTF-16。它们将使用ISO-8859-1来在可能的情况下压缩内存使用。当然,公共接口仍然期望charString方法对UTF-16数据进行操作,因此必须在运行时执行额外的转换以便在基于UTF-16的代码逻辑中使用基于ISO-8859-1的字符串。 - Remy Lebeau
@TomBlodget 但这只是一个实现细节。就其价值而言,String 的元素可以是信鸽;String 没有编码。 - fge
@fge 不完全是这样。如果使用字符串的代码是这样编写的,那就太理想了,但是一旦您获取长度或使用索引或其他与“char”相关的操作,就必须处理每个单独 Unicode 代码点中有多少个 UTF-16 代码单元。 - Tom Blodget
2个回答

3
您无法做到。将文本转换为US-ASCII格式时,会丢失信息。您无法恢复已经丢失的内容。

1
你的代码接收一个UTF-8编码的字节数组,正确地将其转换为Java字符串,但随后将该字符串转换为ASCII编码的字节数组。 ASCII不支持“Ô和“¢”字符,这就是它们被转换为“?”的原因。一旦进行了该转换,就无法回退。 ASCII是UTF-8的子集,在ASCII中,“?”也是UTF-8中的“?”。
解决方案是停止转换为ASCII。你应该转换回UTF-8。
StringBuilder masterBuffer = new StringBuilder();
byte[] rawDataBuffer = ...; // Read from InputStream
String rawString = new String(rawDataBuffer, "UTF-8");
masterBuffer.append(rawString);
onMessageReceived(masterBuffer.toString().getBytes("UTF-8"));

至少这样做,对于真正的ASCII字符,接收者将永远不会知道差异(因为ASCII是UTF-8的子集),非ASCII字符也不会再丢失。接收者只需要知道要期望UTF-8而不是ASCII。而且,您的代码将更具可移植性,因为您将不再依赖于特定平台的默认字符集(并非所有平台都默认使用ASCII)。
当然,在您的示例中,您的StringBuilder是多余的,因为您没有添加任何其他内容,所以您可以将其删除:
byte[] rawDataBuffer = ...; // Read from InputStream
String rawString = new String(rawDataBuffer, "UTF-8");
onMessageReceived(rawString.getBytes("UTF-8"));

然后,String也变得多余了:
byte[] rawDataBuffer = ...; // Read from InputStream
onMessageReceived(rawDataBuffer);

如果 onMessageReceived() 需要字节作为输入,为什么要浪费开销将字节转换为字符串再转换回字节呢?

解决方案是从一开始就停止转换为ASCII。这是在我无法控制的底层第三方库中发生的问题。这就是问题所在。这就是为什么我想从US-ASCII编码的字节数组(@app level)中获取UTF-8字符串。看起来,这是不可能的。 - bms

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