7.1 流 (Streams)

流是用来表示字符来源或终点的 Lisp 对象。要从文件读取或写入,你将文件作为流打开。但流与文件是不一样的。当你在顶层读入或印出时,你也可以使用流。你甚至可以创建可以读取或写入字符串的流。

输入缺省是从 *standard-input* 流读取。输出缺省是在 *standard-output* 流。最初它们大概会在相同的地方:一个表示顶层的流。

我们已经看过 readformat 是如何在顶层读取与印出。前者接受一个应是流的选择性参数,缺省是 *standard-input*format 的第一个参数也可以是一个流,但当它是 t 时,输出被送到 *standard-output* 。所以我们目前为止都只用到缺省的流而已。我们可以在任何流上面做同样的 I/O 操作。

路径名(pathname)是一种指定一个文件的可移植方式。路径名包含了六个部分:host、device、directory、name、type 及 version。你可以通过调用 make-pathname 搭配一个或多个对应的关键字参数来产生一个路径。在最简单的情况下,你可以只指明名字,让其他的部分留为缺省:

  1. > (setf path (make-pathname :name "myfile"))
  2. #P"myfile"

开启一个文件的基本函数是 open 。它接受一个路径名 [1] 以及大量的选择性关键字参数,而若是开启成功时,返回一个指向文件的流。

你可以在创建流时,指定你想要怎么使用它。 无论你是要写入流、从流读取或者同时进行读写操作,都可以通过 direction 参数设置。三个对应的数值是 :input , :output , :io 。如果是用来输出的流, if-exists 参数说明了如果文件已经存在时该怎么做;通常它应该是 :supersede (译注: 取代)。所以要创建一个可以写至 "myfile" 文件的流,你可以:

  1. > (setf str (open path :direction :output
  2. :if-exists :supersede))
  3. #<Stream C017E6>

流的打印表示法因实现而异。

现在我们可以把这个流作为第一个参数传给 format ,它会在流印出,而不是顶层:

  1. > (format str "Something~%")
  2. NIL

如果我们在此时检查这个文件,可能有输出,也可能没有。某些实现会将输出累积成一块 (chunks)再输出。直到我们将流关闭,它也许一直不会出现:

  1. > (close str)
  2. NIL

当你使用完时,永远记得关闭文件;在你还没关闭之前,内容是不保证会出现的。现在如果我们检查文件 “myfile” ,应该有一行:

  1. Something

如果我们只想从一个文件读取,我们可以开启一个具有 :direction :input 的流 :

  1. > (setf str (open path :direction :input))
  2. #<Stream C01C86>

我们可以对一个文件使用任何输入函数。7.2 节会更详细的描述输入。这里作为一个示例,我们将使用 read-line 从文件来读取一行文字:

  1. > (read-line str)
  2. "Something"
  3. > (close str)
  4. NIL

当你读取完毕时,记得关闭文件。

大部分时间我们不使用 openclose 来操作文件的 I/O 。 with-open-file 宏通常更方便。它的第一个参数应该是一个列表,包含了变数名、伴随着你想传给 open 的参数。在这之后,它接受一个代码主体,它会被绑定至流的变数一起被求值,其中流是通过将剩余的参数传给 open 来创建的。之后这个流会被自动关闭。所以整个文件写入动作可以表示为:

  1. (with-open-file (str path :direction :output
  2. :if-exists :supersede)
  3. (format str "Something~%"))

with-open-file 宏将 close 放在 unwind-protect 里 (参见 92 页,译注: 5.6 节),即使一个错误打断了主体的求值,文件是保证会被关闭的。