我正在阅读文件并使用此函数将它们存储为字符串:
(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。
答案 0 :(得分:4)
首先,您的关键字参数:external-format
放错了地方并且无效。它应该在stream
和path
的内部。但是,这对最终结果没有影响,因为UTF-8是默认编码。
这里的问题是,在UTF-8编码中,编码不同字符需要不同的字节数。 ASCII字符全部编码为单个字节,但其他字符占用2-4个字节。您现在在字符串中为输入文件的每个字节分配数据,而不是在其中的每个字符。未使用的字符最终没有变化; make-string
将其初始化为^@
。
(read-sequence)
函数返回函数未更改的第一个元素的索引。您目前只是丢弃此信息,但在知道已使用了多少元素后,应使用它来调整缓冲区的大小:
(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字符的数量。但是,它不是非常有效,因为它分配了一个不必要的大缓冲区,最后将整个输出复制到一个新的字符串中以返回数据。
虽然这对于学习实验来说很好,但对于实际用例,我建议Alexandria实用程序库具有现成的功能:
* (ql:quickload "alexandria")
To load "alexandria":
Load 1 ASDF system:
alexandria
; Loading "alexandria"
* (alexandria:read-file-into-string "~/teste")
"aaa§§§
"
*