msa*_*gel 0 java unicode encoding utf-8 utf-16
我对 UTF8 感到困惑:
\n\n\n“RedR\xc3\xb6ses”
\n
我需要将其转换为有效的 UCS-2(或没有 BOM 的固定大小 UTF-16BE,它们是相同的东西)编码,因此输出将是:\n“Red R\xc3\xb6ses”作为“”输出UCS-2 的射程。
\n我尝试过的:
\n @Test\npublic void testEncodeProblem() throws CharacterCodingException {\n String in = "Red\\uD83C\\uDF39\\uD83C\\uDF39R\xc3\xb6ses";\n ByteBuffer input = ByteBuffer.wrap(in.getBytes());\n\n CharsetDecoder utf8Decoder = StandardCharsets.UTF_16BE.newDecoder();\n utf8Decoder.onMalformedInput(CodingErrorAction.REPLACE);\n utf8Decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);\n utf8Decoder.replaceWith(" ");\n\n CharBuffer decoded = utf8Decoder.decode(input);\n\n System.out.println(decoded.toString()); // \xe5\x89\xa5\xe6\x93\xb0\xe9\xbe\x8c\xeb\xa7\xb0\xe9\xbe\x8c\xeb\xa5\x92\xec\x8e\xb6\xe7\x8d\xa5 \n}\nRun Code Online (Sandbox Code Playgroud)\n没有。
\n @Test\npublic void testEncodeProblem() {\n String in = "Red\\uD83C\\uDF39\\uD83C\\uDF39R\xc3\xb6ses";\n byte[] bytes = in.getBytes(StandardCharsets.UTF_16BE);\n String res = new String(bytes);\n System.out.println(res); // Red\xef\xbf\xbd<\xef\xbf\xbd9\xef\xbf\xbd<\xef\xbf\xbd9R\xc3\xb6ses\n}\nRun Code Online (Sandbox Code Playgroud)\n没有。
\n请注意,“\xc3\xb6”是有效的 UCS-2 符号。
\n有什么想法/图书馆吗?
\n不幸的是,这两个片段实际上都不起作用,那是因为您误解了 UTF-16 编码。UTF-16可以编码这些表情符号,它不是固定宽度。不存在“用 UTF-16 编码修复”这样的东西。有..UCS2。这不是 UTF-16。BE 部分并不使其成为“固定宽度”,它只是锁定字节顺序。这就是为什么这两个都打印玫瑰。不幸的是,Java 没有附带 UCS2 编码系统,这使得这项工作变得更加困难和丑陋。
\n此外,两个片段都会失败,因为您正在调用禁止的方法。
\n每当您将字节转换为字符或反之亦然时,都会发生字符转换。你不能选择不这样做。尽管如此,仍然存在一些方法,它们不带任何参数来指示您想要使用哪种字符集编码。这些是被禁止的方法:这些默认为“系统默认”,看起来就像有人挥舞着魔杖,这样我们就可以将字符转换为字节,反之亦然,而不必担心字符编码。
\n解决方案是永远不要使用禁止的方法。更好的是,告诉您的 IDE 应该将它们标记为错误。唯一的例外是您知道 API 默认值不是“平台默认值”,而是某种理智的东西 - 我所知道的唯一一个是 API,Files.*它默认为 UTF-8 而不是平台默认值。因此,使用无字符集变体是可以接受的。
如果您确实必须具有平台默认值(仅适用于命令行工具),请通过传递 使其明确Charset.defaultCharset()。
禁止方法的列表很长,但是new String(bytes)和string.getBytes()都在上面。不要使用这些方法/构造函数。曾经。
此外,您的第一个片段很混乱。您想要将一个字符串(字符串已经是字符并且没有编码。它就是它的本质。那么,当没有什么可解码时,为什么要制作解码器?)到 UTF-16,而不是解码它:
\nString in = "Red\\uD83C\\uDF39\\uD83C\\uDF39R\xc3\xb6ses";\nCharBuffer input = CharBuffer.wrap(in);\nCharsetEncoder utf16Encoder = StandardCharsets.UTF_16BE.newEncoder();\nutf16Encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);\nutf16Encoder.replaceWith(" ");\nByteBuffer encoded = utf16Encoder.encode(input);\n\nSystem.out.println(new String(encoded.array(), StandardCharsets.UTF16_BE));\nRun Code Online (Sandbox Code Playgroud)\n或第二个片段:
\n@Test\npublic void testEncodeProblem() {\n String in = "Red\\uD83C\\uDF39\\uD83C\\uDF39R\xc3\xb6ses";\n byte[] bytes = in.getBytes(StandardCharsets.UTF_16BE);\n String res = new String(bytes, StandardCharsets.UTF_16BE);\n System.out.println(res);\n}\nRun Code Online (Sandbox Code Playgroud)\n但是,正如我所说,两者都只打印玫瑰,因为它们可以用 UTF_16 表示。
\n那么,如何完成工作呢?如果 java 内置了 UCS2 编码,那么将很简单,只需替换StandardCharsets.UTF_16BE为StandardCharsets.UCS2,但没有这样的运气。所以,我想......可能是“手工”:
String in = "Red\\uD83C\\uDF39\\uD83C\\uDF39R\xc3\xb6ses";\nByteArrayOutputStream out = new ByteArrayOutputStream();\nin.codePoints()\n .filter(a -> a < 65536)\n .forEach(a -> {\n out.write(a >> 8);\n out.write(a);\n });\n\n// stream is ugly, but, because codePoints() was added in a time\n// when oracle had just invented the shiny hammer, they are using it\n// here for smearing butter on their sandwich. Silly geese. Oh well.\n\nbyte[] result = out.toByteArray();\n// given that java has no way of reading UCS2, and UTF16BE doesn't fit,\n// as there are chars representable in 2 bytes in UCS2 that take 3+ in\n// UTF16BE, it's not possible to print this without another loop similar to above. \n// Let's just print the bytes and check em, by hand:\n\nfor (byte r : result) System.out.print(" " + (r & 0xFF));\nSystem.out.println();\n// For the roses string, printing with UTF-16BE does actually work,\n// but it won't be true for all input strings...\nSystem.out.println(new String(result, StandardCharsets.UTF_16BE));\nRun Code Online (Sandbox Code Playgroud)\n耶!成功!
\n注意:codePointAt这里可以工作并避免丑陋的流,但是 cPA 的输入不在“代码点索引”中,而是在“字符索引”中,这使得事情变得相当复杂;对于任何代理对,您都必须增加 2。
对 unicode、UCS2 和 UTF-16 的一些反思:
\nUnicode 是一个巨大的表,它将 0 到 1,112,064(大约 20 位半)之间的任何数字映射到字符、控制概念、货币、标点符号、表情符号、方框图或其他字符概念。
\n像 UTF-8 或 US_ASCII 这样的编码定义了将这些数字中的部分或全部转换为一系列字节,以便它也可以解码回代码点序列,这些代码点通常存储在 32 位中,因为它们不适合 16 位,并且没有任何架构能够有意义地处理 24 位或其他类似的问题。
\n为了适应 UCS2/UTF-16,unicode 规范中从 0xD800 到 0xDFFF 之间没有任何字符,这是故意的,而且永远不会有。
\n这意味着 UCS2 和 UTF-16 或多或少是相同的,只是有一个“技巧”:
\n对于任何低于 65536 的 unicode 数字(理论上可以容纳 2 个字节),对于 UTF-16 编码(可以编码表情符号等),UTF-16 编码只是......数字。直起来。作为2字节。D800-DFFF 不可能发生,因为这些代码点是故意不存在的。
\n对于任何高于65536 的数据,D800 到 DFFF 的空闲块将用于生成所谓的代理对。第二个“字符”(第二个 2 字节块)与我们可以用 D800-DFFF 系列存储的 11 位数据相结合,总共 16+11 = 27 位,足以覆盖其余部分。
\n因此,UTF-16 会将任何 unicode 代码点编码为 2 字节或 4 字节。
\nUCS-2 作为一个术语已经基本失去了它的意义。最初,它的意思是每个“字符”正好 2 个字节,不多也不少,现在仍然是这个意思,但是“一个字符”的含义已经被扭曲得面目全非:那朵玫瑰?它算作 2 个字符。在 java 中尝试一下 -x.length()返回 2,而不是 1。 UCS-2 的一个比较合理的定义是:1 个字符实际上意味着 1 个字符,每个字符由 2 个字节表示,如果您尝试存储一个不适合的字符(将是一个代理对),好吧,这些只是无法编码,因此崩溃或应用 on-unreprestable-character-instead 占位符。不幸的是,这并不是 UCS-2 的意思,这让我们不得不编写任何应用此操作的代码(丢弃/用占位符替换任何代理项对,以便字节长度恰好为 2*number的代码点)我们自己。
请注意,这个代理对的东西为您提供了不同的策略,基于 javachar非常接近 UCS2 的理想(因为它是一个 16 位数字,在 java 规范中硬编码)的事实:您可以循环遍历所有字符(如 java 中的char)并丢弃任何c >= 0xD800 && c < 0xE000,以及紧随其后的字符,这将消除玫瑰。
| 归档时间: |
|
| 查看次数: |
1850 次 |
| 最近记录: |