【问题标题】:Null char returning from reading a file in Common Lisp从 Common Lisp 中读取文件返回的空字符
【发布时间】:2018-03-19 05:11:20
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

    首先,您的关键字参数:external-format 放错了位置,没有任何效果。它应该在 streampath 的父代内。但是,这对最终结果没有影响,因为 UTF-8 是默认编码。

    这里的问题是,在 UTF-8 编码中,编码不同的字符需要不同的字节数。 ASCII 字符全部编码为单个字节,但其他字符占用 2-4 个字节。您现在在字符串中为输入文件的每个 byte 分配数据,而不是其中的每个 character。未使用的字符最终保持不变; 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§§§
    "
    *
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-24
      • 1970-01-01
      • 1970-01-01
      • 2012-12-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多