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
注意点:
- read的行为,为什么ptr依次是3,7,9
- eof设置为gensym,来判断是否到达结尾
- loop宏里通过设置until来达到遇到eof终止
- 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-output
和 force-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",也就是会丢弃。