Skip to content

LISP - 从流读取数据

输入/输出(I/O)是任何应用程序的基础。在 Common Lisp 中,I/O 通过 streams(流)处理。本教程探讨了从流中读取数据的现代和安全方法,例如从控制台读取用户输入或从文件中读取内容。

Common Lisp 提供了一套分层的读取函数,从单个字符到整个 Lisp 对象。

  • 表达式级别:read:从流中解析一个完整的 Lisp S-expression(S表达式)。它可以读取数字、字符串、符号和列表。安全警告:仅在信任的输入中使用 read,因为它在解析 #.(+ 2 2) 等表达式时可以执行代码。
  • 行级别:read-line:读取字符字符串,直到遇到换行符。这是读取文本数据最常见的方式之一。
  • 字符级别:read-char:从流中读取单个字符。适用于细粒度解析。
  • 二进制级别:read-byte:从二进制流中读取单个字节。对于处理图像或可执行文件等文件至关重要。
  • 预读:peek-char:允许您查看流中的下一个字符,但不消耗它。这对于做出解析决策很有用。

最佳实践:使用 with-open-file 进行安全文件处理

Section titled “最佳实践:使用 with-open-file 进行安全文件处理”

手动打开和关闭文件容易出错。如果在文件打开后但在关闭前发生错误,文件句柄可能会悬空。with-open-file 宏通过保证文件被关闭(即使发生错误)来健壮地处理这种情况。您应该始终优先使用 with-open-file。

假设我们有一个名为 config.txt 的文件,内容如下:

host=localhost
port=8080
user=admin

以下是现代、安全的读取和处理方式:

(defun process-config-file (path)
"Reads a configuration file and prints its contents."
(with-open-file (stream path
:direction :input
:if-does-not-exist :error)
(format t "--- Reading config from ~a ---~%" path)
(loop for line = (read-line stream nil :eof)
until (eq line :eof)
do (format t "Read line: ~s~%" line))
(format t "--- End of file ---~%")))
;; To run it:
;; (process-config-file "config.txt")
--- Reading config from config.txt ---
Read line: "host=localhost"
Read line: "port=8080"
Read line: "user=admin"
--- End of file ---
  • with-open-file:将打开的文件流绑定到变量 stream。当块退出时,它会自动关闭流。
  • :direction :input:指定我们以读取模式打开文件。
  • :if-does-not-exist :error:这是一个安全的默认设置。如果文件未找到,则会发出错误信号。
  • read-line stream nil :eof:这是一个关键的习惯用法。第一个 nil 意味着我们不希望在文件末尾(EOF)时出现错误。第二个参数 :eof 是我们选择的在文件末尾到达时返回的特殊符号。这比检查 nil 更健壮,因为在某些情况下 nil 可能是一个有效的空行。
  • loop ... until (eq line :eof):循环继续,直到 read-line 返回我们特殊的 :eof 符号。

Lisp 的条件系统(Condition System)比传统的异常处理更强大。您可以使用 handler-case 来优雅地处理特定错误,例如文件未找到。

(defun safe-read-config (path)
(handler-case (process-config-file path)
(file-error (c)
(format *error-output* "Error: Could not open file ~a. Details: ~a~%" path c)
nil))) ;; Return nil on failure
;; (safe-read-config "non-existent-file.txt")
Error: Could not open file non-existent-file.txt. Details: The file non-existent-file.txt does not exist.