Published: 2018-09-12

Common lisp I/O、流和文件

本文介绍了common lisp I/O操作相关的一些操作符。参考了Hyperspec和Common Lisp Recipes。

Table of Contents

1 with-open-stream

将流绑定到变量上然后在表达式求值,最后关闭这个流。 相当于临时创建一个变量绑定来方便使用传入的流,最后再自动关闭

;; Syntax:

with-open-stream (var stream) declaration* form*

=> result*


;; Examples:

 (with-open-stream (s (make-string-input-stream "1 2 3 4 5"))
    (+ (read s) (read s) (read s))) =>  6

;; Side Effects:

The stream is closed (upon exit).

1.0.1 get-output-stream-string

注意:该方法每次调用会清空stream

Examples:

 (setq a-stream (make-string-output-stream)
        a-string "abcdefghijklm") =>  "abcdefghijklm"
 (write-string a-string a-stream) =>  "abcdefghijklm"
 (get-output-stream-string a-stream) =>  "abcdefghijklm"
 (get-output-stream-string a-stream) =>  ""


2 字符串流

2.1 with-input-from-string

创建一个字符串输入流供在表达式中求值使用。

;; Syntax:

with-input-from-string (var string &key index start end) declaration* form*

=> result* (返回forms的求值结果)


;; Examples:

 (with-input-from-string (s "XXX1 2 3 4xxx"
                             :index ind
                             :start 3 :end 10)
    (+ (read s) (read s) (read s))) =>  6
 ind =>  9


 (with-input-from-string (s "Animal Crackers" :index j :start 6)
   (read s)) =>  CRACKERS
The variable j is set to 15.

2.1.1 index参数

作用:读取内容的时候同时设置index为读取到的位置的后一位,用做标记

(loop with ptr = 0
      with eof = (gensym)
      for from = ptr
      for object = (with-input-from-string
                       (s "41  42 43" :start ptr :index ptr)
                     (read s nil eof))
      until (eq object eof)
      do (format t "~A-~A: ~A~%" from ptr object)
      finally (return (values)))

0-3: 41
3-7: 42
7-9: 43

注意点:

  1. read的行为,为什么ptr依次是3,7,9
  2. eof设置为gensym,来判断是否到达结尾
  3. loop宏里通过设置until来达到遇到eof终止
  4. loop里通过两个with设置初始变量,两个for来设置循环时变量的改动

2.2 with-output-to-string

创建一个输出流,所有输出到该流上的内容转化成字符串。

;; Syntax:

with-output-to-string (var &optional string-form &key element-type) declaration* form*

=> result* 如果提供了string-form, 那么结果是forms的求值结果;如果没有提供参数string-form,那么输出流生成的字符串会被返回

;; Example:
(setq fstr (make-array '(0) :element-type 'base-char
                             :fill-pointer 0 :adjustable t)) =>  ""
 (with-output-to-string (s fstr)
    (format s "here's some output")
    (input-stream-p s)) =>  false
 fstr =>  "here's some output"


2.3 make-string-input-stream, make-string-output-stream

make-string-input-stream : 从字符串创建一个输入流,然后返回这个流

Examples:

 (let ((string-stream (make-string-input-stream "1 one ")))
   (list (read string-stream nil nil)
         (read string-stream nil nil)
         (read string-stream nil nil)))
=>  (1 ONE NIL)


(read (make-string-input-stream "prefixtargetsuffix" 6 12)) =>  TARGET

make-string-output-stream : 生成一个字符串输出流。可以用 get-output-stream-string 取得字符串输出流的内容

Examples:

 (let ((s (make-string-output-stream)))
   (write-string "testing... " s)
   (prin1 1234 s)
   (get-output-stream-string s))

=>  "testing... 1234"

3 let临时改变动态变量绑定

临时将标准输入流和标准输出流绑定到某一流变量上,改变标准输入流的来源和标准输出流去向

CL-USER> (with-input-from-string (s "abcde")
           (let ((*standard-input* s))
             (make-list 3 :initial-element (read))))
=> (ABCDE ABCDE ABCDE)

3.1 Synonym stream

当需要一个根据求值环境动态求值到不同值的流是, synonym stream 就发挥作用。看下面例子:

;; 设置字符串流 a-stream 和 b-stream
 (setq a-stream (make-string-input-stream "a-stream")
        b-stream (make-string-input-stream "b-stream"))
=>  #<String Input Stream>

;; 设置s-stream是一个符号'c-stream的同义流
 (setq s-stream (make-synonym-stream 'c-stream))
=>  #<SYNONYM-STREAM for C-STREAM>

;; 将c-stream 值设为 a-stream
 (setq c-stream a-stream)
=>  #<String Input Stream>

;; 这时读取s-stream 就相当于读取的 c-stream 绑定的 a-stream
 (read s-stream) =>  A-STREAM

;; 又将c-stream设置为b-stream
 (setq c-stream b-stream)
=>  #<String Input Stream>

;; 这时读取s-stream 就相当于读取的 c-stream 绑定的 b-stream
 (read s-stream) =>  B-STREAM

4 Flush output to stream

Lisp输出流通常是buffered,所以发送给输出流的内容不一定会立即在物理设备上显示出来,想立即显示可以用 finish-outputforce-output ,两者区别是前者会等待输出流的buffer清空后再返回,而后者当启动清空的流程就返回了 这导致后者不会报告任何写出到流的错误

5 读取文件相关

5.1 with-open-file

使用open方法打开一个一个文件,绑定到一个流变量上

;; Syntax:

with-open-file (stream filespec options*) declaration* form*

=> results


;; Examples:

 (setq p (merge-pathnames "test"))
=>  #<PATHNAME :HOST NIL :DEVICE device-name :DIRECTORY directory-name
    :NAME "test" :TYPE NIL :VERSION :NEWEST>

 (with-open-file (s p :direction :output :if-exists :supersede)
    (format s "Here are a couple~%of test data lines~%")) =>  NIL

 (with-open-file (s p)
    (do ((l (read-line s) (read-line s nil 'eof)))
        ((eq l 'eof) "Reached end of file.")
     (format t "~&*** ~A~%" l)))

>>  *** Here are a couple
>>  *** of test data lines
=>  "Reached end of file."


5.2 alexandria: read-file-into-byte-vector read-file-into-string

— Function: read-file-into-string pathname &key buffer-size external-format
将pathname指定的文件内容读取,返回一个新建的字符串。内部实际调用with-open-file


— Function: read-file-into-byte-vector pathname
将pathname指定的文件内容读取,返回一个新建的octets。

参考: https://common-lisp.net/project/alexandria/draft/alexandria.html#IO

5.3 自定义函数: file-at-once 一次性读取文件内容

5.3.1 实现

(defun file-at-once (filespec &rest open-args)
           (with-open-stream (stream (apply #'open filespec open-args))
             (let* ((buffer
                      (make-array (file-length stream)
                                  :element-type (stream-element-type stream)
                                  :fill-pointer t))
                    (position (read-sequence buffer stream)))
               (setf (fill-pointer buffer) position)
               buffer)))

5.3.2 stream-element-type

返回可能从流对象读取或写入的对象类型

5.3.3 file-length

输入一个绑定到文件的流,返回它的长度 如果是二进制文件,那么长度是根据流里的 element type 衡量的

6 广播流 make-broadcast-stream

当需要同时向多个流发送数据时,可以采用此函数.

(setq a-stream (make-string-output-stream)
       b-stream (make-string-output-stream)) =>  #<String Output Stream>
(format (make-broadcast-stream a-stream b-stream)
         "this will go to both streams") =>  NIL
(get-output-stream-string a-stream) =>  "this will go to both streams"
(get-output-stream-string b-stream) =>  "this will go to both streams"

注意,如果 make-broadcast-stream 不接受参数,那么发送给该流的内容会被发送给"/dev/null",也就是会丢弃。

7 END

Author: Nisen

Email: imnisen@163.com