我一直忽略它,但现在我正在努力学习Java中的Unicode。我需要完成一个关于将UTF-16字符串转换为8位ASCII的练习。请问有人能告诉我如何在Java中实现这个功能吗?我知道你不能用ASCII表示所有可能的Unicode值,所以在这种情况下,我希望超过0xFF的代码仍然可以添加(无效数据也应该默默地添加)。
谢谢!
我一直忽略它,但现在我正在努力学习Java中的Unicode。我需要完成一个关于将UTF-16字符串转换为8位ASCII的练习。请问有人能告诉我如何在Java中实现这个功能吗?我知道你不能用ASCII表示所有可能的Unicode值,所以在这种情况下,我希望超过0xFF的代码仍然可以添加(无效数据也应该默默地添加)。
谢谢!
您可以使用java.nio进行简单的解决方案:
// first encode the utf-16 string as a ByteBuffer
ByteBuffer bb = Charset.forName("utf-16").encode(CharBuffer.wrap(utf16str));
// then decode those bytes as US-ASCII
CharBuffer ascii = Charset.forName("US-ASCII").decode(bb);
String input = ... // my UTF-16 string
StringBuilder sb = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
if (ch <= 0xFF) {
sb.append(ch);
}
}
byte[] ascii = sb.toString().getBytes("ISO-8859-1"); // aka LATIN-1
对于大字符串来说,这可能不是最有效的转换方式,因为我们复制字符两次。然而,它有一个直接的优点。
顺便说一下,严格来说,没有8位ASCII字符集。ASCII是一个7位字符集。 LATIN-1是最接近“8位ASCII”字符集的东西(Unicode的块0相当于LATIN-1),所以我会假设这就是你的意思。
编辑:根据问题的更新,解决方案甚至更简单:
String input = ... // my UTF-16 string
byte[] ascii = new byte[input.length()];
for (int i = 0; i < input.length(); i++) {
ascii[i] = (byte) input.charAt(i);
}
这种解决方案更加高效。由于我们现在知道要期望多少字节,因此我们可以预先分配字节数组,并在不使用StringBuilder作为中间缓冲区的情况下复制(截断的)字符。
然而,我并不认为以这种方式处理错误数据是明智的。
编辑2:还有一个更加隐晦的“陷阱”。Unicode实际上将代码点(字符)定义为“大约21位”值... 0x000000到0x10FFFF... 并使用代理表示代码> 0x00FFFF。换句话说,Unicode代码点> 0x00FFFF实际上在UTF-16中表示为两个“字符”。我的答案或其他任何答案都没有考虑到这一点(尽管有些奇特)。事实上,在Java中处理> 0x00FFFF的代码点通常相当棘手。这源于'char'是一个16位类型,而String是以'char'为基础定义的。
编辑3:也许更明智的处理无法转换为ASCII的意外字符的解决方案是用标准替换字符替换它们:
String input = ... // my UTF-16 string
byte[] ascii = new byte[input.length()];
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
ascii[i] = (ch <= 0xFF) ? (byte) ch : (byte) '?';
}
Java在内部使用UTF-16表示字符串。如果您有String对象,可以使用String.getBytes(Charset c)进行编码,其中您可以指定US-ASCII(该字符集可以映射代码点0x00-0x7f)或ISO-8859-1(该字符集可以映射代码点0x00-0xff,并且可能是您所说的“8位ASCII”)。
至于添加“坏数据”... ASCII或ISO-8859-1字符串根本无法表示某个范围外的值。我认为getBytes
会简单地删除不能在目标字符集中表示的字符。
new String(s.getBytes(StandardCharsets.US_ASCII))
将字符串带回我需要的方式。 - Manabu Tokunaga由于这是一项练习,所以听起来您需要手动实现。您可以将编码(例如UTF-16或ASCII)视为查找表,将一系列字节与逻辑字符(代码点)匹配。
Java使用UTF-16字符串,这意味着任何给定的代码点都可以用一个或两个char
变量表示。是否要处理两个char
代理对取决于您认为应用程序遇到它们的可能性有多大(请参见Character类以检测它们)。ASCII仅使用一个八位字节的前7位,因此有效值范围为0到127。UTF-16对于此范围使用相同的值(只是更宽)。可以通过此代码进行确认:
Charset ascii = Charset.forName("US-ASCII");
byte[] buffer = new byte[1];
char[] cbuf = new char[1];
for (int i = 0; i <= 127; i++) {
buffer[0] = (byte) i;
cbuf[0] = (char) i;
String decoded = new String(buffer, ascii);
String utf16String = new String(cbuf);
if (!utf16String.equals(decoded)) {
throw new IllegalStateException();
}
System.out.print(utf16String);
}
System.out.println("\nOK");
char
转换为byte
来将UTF-16转换为ASCII。为了优化已接受的答案并避免支付任何罚款,如果字符串已经全部是ASCII字符,则这里是优化版本。感谢@stephen-c
public static String toAscii(String input) {
final int length = input.length();
int ignoredChars = 0;
byte[] ascii = null;
for (int i = 0; i < length; i++) {
char ch = input.charAt(i);
if (ch > 0xFF) {
//-- ignore this non-ascii character
ignoredChars++;
if (ascii == null) {
//-- first non-ascii character. Create a new ascii array with all ascii characters
ascii = new byte[input.length() - 1]; //-- we know, the length will be at less by at least 1
for (int j = 0; j < i-1; j++) {
ascii[j] = (byte) input.charAt(j);
}
}
} else if (ascii != null) {
ascii[i - ignoredChars] = (byte) ch;
}
}
//-- (ignoredChars == 0) is the same as (ascii == null) i.e. no non-ascii characters found
return ignoredChars == 0 ? input : new String(Arrays.copyOf(ascii, length - ignoredChars));
}