从 Common Lisp 中读取文件返回空字符
Null char returning from reading a file in Common Lisp
我正在读取文件并使用此函数将它们存储为字符串:
(defun file-to-str (path)
(with-open-file (stream path) :external-format 'utf-8
(let ((data (make-string (file-length stream))))
(read-sequence data stream)
data)))
如果文件只有ASCII字符,我按预期得到了文件的内容;但是如果有超过 127 的字符,对于超过 127 的每个这样的字符,我在字符串的末尾得到一个空字符 (^@
)。所以,在 $ echo "~a^?" > ~/teste
之后我得到
CL-USER> (file-to-string "~/teste")
"~a^?
"
;但是在 echo "aaa§§§" > ~/teste
之后,REPL 给了我
CL-USER> (file-to-string "~/teste")
"aaa§§§
^@^@^@"
等等。我怎样才能解决这个问题?我在 utf-8 语言环境中使用 SBCL 1.4.0。
首先,您的关键字参数 :external-format
放错了地方,没有任何效果。它应该在 stream
和 path
的括号内。但是,这对最终结果没有影响,因为 UTF-8 是默认编码。
这里的问题是在UTF-8编码中,对不同的字符进行编码需要不同的字节数。 ASCII 字符都编码为单个字节,但其他字符需要 2-4 个字节。您现在在字符串中为输入文件的每个 byte 分配数据,而不是其中的每个 character。未使用的字符最终保持不变; make-string
将它们初始化为 ^@
.
(read-sequence)
函数 returns 函数未更改的第一个元素的索引。您目前只是丢弃此信息,但您应该在知道已使用了多少元素后使用它来调整缓冲区大小:
(defun file-to-str (path)
(with-open-file (stream path :external-format :utf-8)
(let* ((data (make-string (file-length stream)))
(used (read-sequence data stream)))
(subseq data 0 used))))
这是安全的,因为文件的长度总是大于或等于其中编码的 UTF-8 字符数。然而,它并不是非常有效,因为它分配了一个不必要的大缓冲区,最后将整个输出复制到一个新的字符串中以返回数据。
虽然这对于学习实验来说很好,但对于 real-world 用例,我推荐 Alexandria 实用程序库,它有一个 ready-made 函数:
* (ql:quickload "alexandria")
To load "alexandria":
Load 1 ASDF system:
alexandria
; Loading "alexandria"
* (alexandria:read-file-into-string "~/teste")
"aaa§§§
"
*
我正在读取文件并使用此函数将它们存储为字符串:
(defun file-to-str (path)
(with-open-file (stream path) :external-format 'utf-8
(let ((data (make-string (file-length stream))))
(read-sequence data stream)
data)))
如果文件只有ASCII字符,我按预期得到了文件的内容;但是如果有超过 127 的字符,对于超过 127 的每个这样的字符,我在字符串的末尾得到一个空字符 (^@
)。所以,在 $ echo "~a^?" > ~/teste
之后我得到
CL-USER> (file-to-string "~/teste")
"~a^?
"
;但是在 echo "aaa§§§" > ~/teste
之后,REPL 给了我
CL-USER> (file-to-string "~/teste")
"aaa§§§
^@^@^@"
等等。我怎样才能解决这个问题?我在 utf-8 语言环境中使用 SBCL 1.4.0。
首先,您的关键字参数 :external-format
放错了地方,没有任何效果。它应该在 stream
和 path
的括号内。但是,这对最终结果没有影响,因为 UTF-8 是默认编码。
这里的问题是在UTF-8编码中,对不同的字符进行编码需要不同的字节数。 ASCII 字符都编码为单个字节,但其他字符需要 2-4 个字节。您现在在字符串中为输入文件的每个 byte 分配数据,而不是其中的每个 character。未使用的字符最终保持不变; make-string
将它们初始化为 ^@
.
(read-sequence)
函数 returns 函数未更改的第一个元素的索引。您目前只是丢弃此信息,但您应该在知道已使用了多少元素后使用它来调整缓冲区大小:
(defun file-to-str (path)
(with-open-file (stream path :external-format :utf-8)
(let* ((data (make-string (file-length stream)))
(used (read-sequence data stream)))
(subseq data 0 used))))
这是安全的,因为文件的长度总是大于或等于其中编码的 UTF-8 字符数。然而,它并不是非常有效,因为它分配了一个不必要的大缓冲区,最后将整个输出复制到一个新的字符串中以返回数据。
虽然这对于学习实验来说很好,但对于 real-world 用例,我推荐 Alexandria 实用程序库,它有一个 ready-made 函数:
* (ql:quickload "alexandria")
To load "alexandria":
Load 1 ASDF system:
alexandria
; Loading "alexandria"
* (alexandria:read-file-into-string "~/teste")
"aaa§§§
"
*