为了账号安全,请及时绑定邮箱和手机立即绑定

将 Golang rune 转换为 utf-8 结果与 js string.fromCharCode

将 Golang rune 转换为 utf-8 结果与 js string.fromCharCode

Go
慕后森 2023-02-21 16:42:59
去var int32s = []int32{  8, 253, 80, 56, 30, 220, 217, 42, 235, 33, 211, 23, 231, 216, 234, 26,}fmt.Println("word: ", string(int32s))jslet int32s = [8, 253, 80, 56, 30, 220, 217, 42, 235, 33, 211, 23, 231, 216, 234, 26]str = String.fromCharCode.apply(null, int32s);console.log("word: " + String.fromCharCode.apply(null, int32s))对于一些空字符,上面的 2 个结果是不一样的。是否有任何解决方案可以修改 go 代码以生成与 js 相同的结果?
查看完整描述

1 回答

?
森林海

TA贡献2011条经验 获得超2个赞

引用文档String.fromCharCode

静态方法返回从指定的UTF-16String.fromCharCode()代码单元序列创建的字符串。

因此,数组中的每个数字int32s都被解释为提供 Unicode 代码单元的 16 位整数,因此整个序列被解释为形成 UTF-16 编码字符串的一系列代码单元
我要强调最后一点,因为从变量的命名来看—— int32s,——无论 JS 代码的作者是谁,他们似乎对那里发生的事情有错误的想法。

现在回到 Go 的对应部分。Go 没有内置对 UTF-16 编码的支持;它的字符串通常使用UTF-8编码(虽然它们不是必需的,但我们不要离题),并且 Go 还提供数据rune类型,它是int32. 符文是一个 Unicode 代码点,即一个能够包含完整 Unicode 字符的数字。(稍后我会回到这个事实及其与 JS 代码的关系。)

现在,你的问题在于它以与(记住 a是 的别名)相同的方式string(int32s)插入你的 s 切片,因此它采用切片中的每个数字来表示单个 Unicode 字符并生成它们的字符串。(这个字符串在内部编码为 UTF-8,但这个事实与问题无关。)int32[]runeruneint32

换句话说,区别在于:

  • JS 代码将数组解释为表示 UTF-16 编码字符串的 16 位值序列,并将其转换为某种内部字符串表示形式。

  • Go 代码将切片解释为 32 位 Unicode 代码点序列,并生成包含这些代码点的字符串。

Go 标准库生成了一个处理 UTF-16 编码的包:encoding/utf16,我们可以使用它来执行 JS 代码编码的操作——将 UTF-16 编码的字符串解码为一系列 Unicode 代码点,然后我们可以转换为 Go 字符串:

package main


import (

    "fmt"

    "unicode/utf16"

)


func main() {

    var uint16s = []uint16{

        8, 253, 80, 56, 30, 220, 217, 42, 235, 33, 211, 23, 231, 216, 234, 26,

    }


    runes := utf16.Decode(uint16s)


    fmt.Println("word: ", string(runes))

}

游乐场


(请注意,我已将切片的类型更改为[]unit16并相应地重命名。此外,我已将源切片解码为明确命名的变量;这样做是为了清楚起见——突出显示正在发生的事情。)

此代码会产生与 Firefox 控制台中的 JS 代码相同的乱码。

更新

对于一些空字符,上面的 2 个结果是不一样的。

我没有碰过的一点。

据我了解,问题是您的 Go 代码打印出类似的东西,
ýP8ÜÙ*ë!ÓçØê
而 JS 代码打印
�ýP8�ÜÙ*ë!Ó�çØê�
正确吗?

这里的问题在于对结果字符串的不同解释fmt.Printlnconsole.log做。

首先让我声明,您的 Go 代码恰好在没有使用我建议的正确解码的情况下正常工作——因为切片中的所有整数都是“基本”范围内的 UTF-16 代码单元,所以“哑”转换有效,并且生成与 JS 代码相同的字符串。
要“按原样”查看这两个字符串,您可以这样做:

  1. fmt.Printf对于 Go,与动词一起使用%q以在打印输出中使用 Go 规则查看“转义”的“特殊”Unicode(和 ASCII)字符:

    fmt.Println("%q\n", string(int32s))
    产生
    "\býP8\x1eÜÙ*ë!Ó\x17çØê\x1a"

    注意这些 '\b'、'\x1e' 和其他转义符:

    如您所见,这些是不可打印的控制字符。

    • '\b' 是 ASCII BS(退格)控制字符,代码 0x08 — 请参阅http://man-ascii.com/

    • '\x1e'是一个字节,代码为0x1E,是ASCII RS(记录分隔符)。

    • …等等。

  2. 对于 JS,无需使用即可打印结果字符串的值console.log——只需将其值保存在变量中,然后在控制台输入其名称并按 Enter——“按原样”打印其值:

    > let int32s = [8, 253, 80, 56, 30, 220, 217, 42, 235, 33, 211, 23, 231, 216, 234, 26]
    > str = String.fromCharCode.apply(null, int32s);
    > str"\u0008ýP8\u001eÜÙ*ë!Ó\u0017çØê\u001a"

    请注意,该字符串包含“\uXXXX”转义符。它们定义了 Unicode 代码点(BTW Go 支持相同的语法),并且这些转义定义了与 Go 示例中相同的代码点:

    • “\u0008”是一个代码为 8 或 0x08 的字符。

    • "\u001e" 是一个代码为 0x1E 的字符。

    • …等等。

如您所见,生成的字符串是相同的,唯一的区别是 Go 的字符串是用 UTF-8 编码的,因此,使用并查看编码字节来查看其内容,这就是 Gofmt.Printf打印它们%q的原因“转义”使用“最小”编码,但我们也可以使用 JS 示例中的转义:您可以检查而不是运行 prints 。
fmt.Println("\býP8\x1eÜÙ*ë!Ó\x17çØê\x1a" == "\u0008ýP8\u001eÜÙ*ë!Ó\u0017çØê\u001a")
true

因此,正如您现在看到的,console.log用特殊的 Unicode 代码点 U+FFFD 替换每个不可打印的字符,这称为 Unicode 替换字符,通常呈现为带有白色问号的黑色菱形。
Gofmt.Println不会那样做:它只是将这些字节“按原样”发送到输出。

希望这可以解释观察到的差异。


查看完整回答
反对 回复 2023-02-21
  • 1 回答
  • 0 关注
  • 209 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信