从Common Lisp中读取文件返回的空字符

时间:2018-03-19 05:11:20

标签: file-io lisp common-lisp unicode-string

我正在阅读文件并使用此函数将它们存储为字符串:

(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。

1 个答案:

答案 0 :(得分:4)

首先,您的关键字参数:external-format放错了地方并且无效。它应该在streampath的内部。但是,这对最终结果没有影响,因为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§§§
"
*