示例中的 UTF-8 字符串似乎是用 太多字节!
编码的输入字符串:“👉TEST📍TEST”
此字符串存储在 UTF-8 编码文件中,当我使用十六进制编辑器时,我会按预期看到下面的 16 个字节。当我将字符串复制到在线工具时,我发现相同的 16 个字节。
f0 9f 91 89 54 45 53 54 f0 9f 93 8d 54 45 53 54
\_______/ \_______/ \_______/ \_______/
U+1F449 T E S T U+1F4CD T E S T
“👉” “📍”
但是,函数 babel:string-to-octets 的结果是不同的,我得到 20 个字节:
(defun print-hex (octets)
(dotimes (offset (length octets))
(let ((byte (aref octets offset)))
(format t "~2,'0x " byte)))
(format t "(~A bytes)~%" (length octets)))
(let ((string "👉TEST📍TEST"))
(format t "TEST STRING [~A]~%" string)
(print-hex (babel:string-to-octets string))
(print-hex (babel:string-to-octets string :encoding :UTF-8)))
TEST STRING [👉TEST📍TEST]
ED A0 BD ED B1 89 54 45 53 54 ED A0 BD ED B3 8D 54 45 53 54 (20 bytes)
ED A0 BD ED B1 89 54 45 53 54 ED A0 BD ED B3 8D 54 45 53 54 (20 bytes)
如果我们进一步分析:
ED A0 BD ED B1 89 54 45 53 54 ED A0 BD ED B3 8D 54 45 53 54
\_____________/ \_______/ \_____________/ \_______/
??? T E S T ??? T E S T
^^^ ^^^
UTF-16 surrogate pair? UTF-16 surrogate pair?
如何从输入字符串中获取 16 个字节?
突出显示同一问题的另一个有趣的行为,转换为八位字节然后返回原始字符串会导致第一个字符出现编码错误。
(let ((string "👉TEST📍TEST"))
(babel:octets-to-string (babel:string-to-octets string)))
debugger invoked on a BABEL-ENCODINGS:CHARACTER-OUT-OF-RANGE in thread
#<THREAD "main thread" RUNNING {100F080003}>:
Illegal :UTF-8 character starting at position 0.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
我非常确定这是 SBCL repl 本身的问题,也可能是您将字符串引入代码的方式问题。
就repl而言,SBCL repl并没有真正得到积极的开发;大多数 lispers 可能使用 Slime 或类似的东西来进行 repl 开发。这比与 SBCL 代表一起工作要好得多。我无法让发布的代码在 Slime repl 中行为不当。
我能够使用 SBCL repl 重现该问题。在我的 Windows 计算机上,将发布的字符串文字粘贴到 SBCL repl 窗口中似乎会生成 UTF-16 编码的字符串。这是我怀疑 SBCL repl 存在问题的地方。正如 OP 所指出的,在粘贴的字符串上调用
babel:string-to-octets
会产生错误的结果。 SBCL 有自己的 sb-ext:string-to-octets
过程,在粘贴的字符串上调用该过程会进入调试器并出现 SB-IMPL::OCTETS-ENCODING-ERROR
错误。这让我觉得问题出在 SBCL 方面。
作为解决方法,我可以使用
babel
: 通过 UTF-16 编码来回传输粘贴的字符串
;; Calling on a pasted string literal:
* (print-hex (babel:string-to-octets "��TEST��TEST"))
ED A0 BD ED B1 89 54 45 53 54 ED A0 BD ED B3 8D 54 45 53 54 (20 bytes)
NIL
;; Round-tripping the pasted string literal:
* (print-hex (babel:string-to-octets
(babel:octets-to-string
(babel:string-to-octets "��TEST��TEST" :encoding :utf-16)
:encoding :utf-16)))
F0 9F 91 89 54 45 53 54 F0 9F 93 8D 54 45 53 54 (16 bytes)
NIL
请注意,我无法使用 SBCL 的
sb-ext:string-to-octets
和 sb-ext:octets-to-string
程序进行相同的往返工作。
OP 说: “此字符串存储在 UTF-8 编码文件中。” 其意义尚不清楚。发布的代码是否保存在文件中并加载到 repl 中?我使用 Emacs 和 Slime、使用带有 UTF-8 编码的 Windows 记事本以及使用带有 UTF-16 编码的 Windows 记事本将发布的代码保存在文件中。每次我将这些文件中的代码加载到 SBCL repl 或 Slime repl 中时,它都会按预期工作。这让我相信这个问题可能是在repl中玩的不便,但对于真正的程序来说不是问题。