我在utf8中被刺痛了:
“红色??r”ö“塞斯”
我需要将其转换为有效的ucs-2(或固定大小的utf-16be没有bom,他们是一样的东西)编码,所以输出将是:“红色röses“作为”?“超出ucs-2的范围。
我尝试过:
@Test
public void testEncodeProblem() throws CharacterCodingException {
String in = "Red\uD83C\uDF39\uD83C\uDF39Röses";
ByteBuffer input = ByteBuffer.wrap(in.getBytes());
CharsetDecoder utf8Decoder = StandardCharsets.UTF_16BE.newDecoder();
utf8Decoder.onMalformedInput(CodingErrorAction.REPLACE);
utf8Decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
utf8Decoder.replaceWith(" ");
CharBuffer decoded = utf8Decoder.decode(input);
System.out.println(decoded.toString()); // 剥擰龌맰龌륒쎶獥
}
不。
@Test
public void testEncodeProblem() {
String in = "Red\uD83C\uDF39\uD83C\uDF39Röses";
byte[] bytes = in.getBytes(StandardCharsets.UTF_16BE);
String res = new String(bytes);
System.out.println(res); // Red�<�9�<�9Röses
}
不。
请注意”ö" 是有效的ucs-2符号。
有什么想法/图书馆吗?
1条答案
按热度按时间fv2wmkja1#
不幸的是,这两个代码段实际上都不起作用,这是因为您误解了utf-16编码。utf-16可以对这些表情进行编码,它不是固定宽度的。没有“用utf-16编码修复”这样的东西。有。。ucs2。不是utf-16。be部分并没有使它成为“固定宽度”,它只是锁定在末端。这就是为什么这两个都印玫瑰。不幸的是,java没有附带ucs2编码系统,这使得这项工作更加困难和丑陋。
此外,这两个代码段都会失败,因为您正在调用禁止的方法。
任何时候你把字节转换成字符,反之亦然,字符转换就发生了。你不能选择退出。然而,仍然存在一堆方法,它们不接受任何参数来指示要使用哪个字符集编码。这些是被禁止的方法:这些默认为“系统默认值”,看起来像是有人挥舞着魔杖,这样我们就可以将字符转换成字节,反之亦然,而不用担心字符编码。
解决办法是永远不要使用被禁止的方法。更好的是,告诉ide它应该将它们标记为错误。唯一的例外是,您知道api的默认值不是“平台默认值”,而是一些正常的东西-我所知道的唯一一个是
Files.*
api,默认为utf-8,而不是平台默认值。因此,使用无字符集变体是可以接受的。如果确实必须使用平台默认值(仅适用于命令行工具),请通过传递
Charset.defaultCharset()
.禁止使用的方法列表很长,但是
new String(bytes)
以及string.getBytes()
都在上面。不要使用这些方法/构造函数。永远不会。此外,你的第一个片段是各种困惑。您想要对字符串进行编码(字符串已经是个字符,没有编码。就是这样。那么,为什么你要在没有什么可解码的情况下制作解码器?)到utf-16,而不是解码它:
或第二个代码段:
但是,正如我所说的,两者都只是打印玫瑰,因为它们可以用utfè16表示。
那么,如何完成这项工作呢?如果java有一个内置的ucs2编码,它将是一个简单的替换
StandardCharsets.UTF_16BE
与StandardCharsets.UCS2
,但没有这样的运气。所以,我想。。。可能是“用手”:耶!成功!
注意:
codePointAt
可以在这里工作并避免难看的流,但是cpa的输入不是在“codepoint index”中,而是在“char index”中,这使得事情变得相当复杂;对于任何代理项对,都必须增加2。对unicode、ucs2和utf-16的一些反思:
unicode是一个巨大的表,它将0到1112064之间的任何数字(约为20位半)Map到字符、控件概念、货币、标点符号、表情符号、方框图或其他类似字符的概念。
像utf-8或us\u ascii这样的编码定义了将这些数字的一部分或全部转换成一系列字节,这样它也可以被解码回通常存储在32位中的一系列码点,因为它们不适合16位,并且没有任何体系结构可以有意义地处理例如24位之类的内容。
为了容纳ucs2/utf-16,unicode规范中没有从0xd800到0xdfff的字符,这是有意的,而且永远不会有。
这意味着ucs2和utf-16或多或少是一回事,有一个“窍门”:
对于任何低于65536的unicode数字(因此理论上可以容纳2个字节),对于utf-16编码(可以编码emoji等),utf-16编码只是。。号码。直截了当。为2字节。d800 dfff不可能发生,因为那些代码点不是故意的。
对于65536以上的任何内容,使用d800到dfff的空闲块来生成所谓的代理项对。第二个“字符”(2字节的第二个块)与我们可以用d800 dfff范围存储的11位数据组合,总共16+11=27位,足以覆盖其余的数据。
因此,utf-16将任何unicode编码点编码为2字节或4字节。
ucs-2作为一个术语已经失去了它的意义。最初,它的意思是每个“字符”正好有2个字节,不多也不少,现在仍然是这个意思,但“字符”的含义已经扭曲得无法辨认:那是玫瑰?它计为2个字符。用java试试-
x.length()
返回2,而不是1。ucs-2的一个合理定义是:1 char实际上意味着1 char,每个char由2个字节表示,如果您试图存储一个不合适的char(可能是一个代理项对),那么,这些字符就不能被编码,所以崩溃或应用on unprestable character代替占位符。不幸的是,这并不是ucs-2的意思,这让我们不得不重新编写应用此操作的任何代码(放弃/替换任何代理项对,以便字节长度正好是2*码点数)。注意,基于java
char
非常接近ucs2的理想(因为它是一个16位的数字,在java规范中是硬编码的):您可以循环遍历所有字符(如java的char
)丢弃任何c >= 0xD800 && c < 0xE000
,以及紧接着的人物,这将摆脱玫瑰。