文件I/O

来源:互联网 发布:开淘宝店经验 编辑:程序博客网 时间:2024/06/06 03:46

Common Lisp 为读写数据提供了一个流的抽象和一个称为路径名(pathname)的抽象,他们以一种跟操作系统无关的方式来管理文件

特有:读写S表达式

S基本元素是列表和原子(非列表或空列表),列表是括号包围,并且包含任意数量的以空格分开的元素。我们平常说的列表(1 2 3)就是一个S表达式。

一:读取文件数据

Open 基于字符的输入流,你可以将它传给函数以便读取一个或多个字符。你打一个文件就会返回给你一个抽象的流,首先把这个流接收,然后我们对这个流进行操作。

Read-char  读取单个字符

Read-line  读取一行文本,去掉行结束符后作为一个字符串返回。

Read       读取单一的s表达式并返回一个lisp对象。

注意点:

1:有一种情况就是,当你打开的文件不存在时,我们可以设置关键字:if-does-not-exist来指定不同行为,有3个可能的值. :error 报错(默认),:create继续并创建,:NIL返回NIL代替一个流。

2:当不同的格式输出的时候,显示不一样,~s用一种可以再读回去的格式显示输出

CL-USER> (let ((in (open "d:/emacs/io3.db")))   (loop for c = (read-char in nil)  while c      do(format t "~a" c))   (close in))heal the worldmay TCL-USER> (let ((in (open "d:/emacs/io3.db")))   (loop for c = (read-char in nil)     while c      do(format t "~s" c))       (close in))#\h#\e#\a#\l#\ #\t#\h#\e#\ #\w#\o#\r#\l#\d#\Return#\Newline#\m#\a#\y#\ T

3:Read-char,read-line,read函数都接受一个可选参数,如下面的(read-line in nil).默认情况下为真,用于指示当遇到文件尾的时候是否报错。一般情况下都设置为NIL,返回他们第三个参数的值。


4:Read方式他跟repl中读取器R的函数是同一个,用于读取lisp源代码,每次调用他会读取单一的S表达式(列表或者非列表及空列表)并跳过空格和注释。然后返回由S表达式代表的lisp对象。字符串中的对象d:/slime/保持原样,而其他的都变成大写。

你如果用read-line读取lisp源文件的话,中文字符会被显示成乱码,因为open的时候是按字符进行读取的流。

CL-USER> (let ((in (open "d:/emacs/.emacs")))   (when in     (loop for line = (read in nil)    while line do(format t "~a~%" line))  (close in)))(ADD-TO-LIST 'LOAD-PATH D:/slime/)(SETQ INFERIOR-LISP-PROGRAM D:/sbcl/sbcl.exe)(REQUIRE 'SLIME-AUTOLOADS)(SLIME-SETUP '(SLIME-FANCY))T
二:读取二进制流

刚才我们已经说了open是返回字符流,也就是一个字节的读,因为最底层肯定都是字节,而现在他是根据一定的编码规则把字节转换为字符,然后作为流再输出来。现在如果你想看最原始的字节,你需要为open指定一个值为'(unsigned-byte 8):element-type参数。然后将打开得到的流给read-byte.它将在每次调用时返回0-255的整数,它本身是一个二进制流了,是二进制01格式,只是我们在输出显示的时候将它转化为了易读的十进制数,但是你可以转换成01格式了。

CL-USER> (let ((in (open "d:/emacs/io3.db" :element-type '(unsigned-byte 8))))   (loop for c = (read-byte in nil)while cdo(format t "~a" c))        (close in))10910132116111111131098101T


三:批量读取

read-sequence可以接受字符流和二进制流。他接受一个序列和流,他会试着用来自流的数据来填充该序列。它返回序列中第一个没有被填充的元素的索引,或是在完全填充的情况下返回该序列的长度。

(read-sequence  (list)  stream)

四:文件输出

首先还是需要先打开一个文件,只是现在你要标明它是用于output的,:direction :output 来设置。默认情况下他会假设这个文件不存在,如果存在的话,就报错,你可以通过设置符号:if-exists来改变这个行为。如果没有的话,就建立,注意下面四个命令只是对文件不存在时而言。

:supersede 替换以前的文件

:append    保文件写到文件尾

:overwrite  从文件头覆盖原文件

NIL        如果文件存在的话,返回NIL而不是流。

Write-char 回向流中写入一个单一字符

Write-line  写个字符并且紧跟一个换行符。

Write-string 写入一个字符串而不会添加任何行结束符

Prinl       generates output for programs, 

princ      generates output for people.

有两个不同函数只打印一个换行:

Terpri "终止打印" (terminal print)的简称,即无条件打印一个换行。

Fresh-line 打印一个换行,除非流已经在文件一行的开始处。流在文件一行的开始处,就是说你现在往里写,光标在文件的一行的开始。因为其实流就是一个文件的抽象,比方说(read-line in)他会从流中读一行数据,实际上就是从一个文件中读取一行数据,然后每读取一条文件中的光标就执行回车回到下一行的开头,所以我们说(read-line in)会返回一条记录,而对于写的话,我们肯定是往流里面写,也就相当与写到了文件里面。比如执行完(write-string "sb" stream) 字符串sb就到输出文件中了,然后光标在文件中sb的后面。所以一旦碰到换行符,光标就会跳到文件的下一行。

CL-USER> (prin1 "hello world")"hello world""hello world"CL-USER> (princ "hello world")hello world"hello world"

注意当你进行输出的时候千万别忘了关闭当前流,否则你打开文件里面没有任何内容。

CL-USER> (let ((stream (open "d:/emacs/out.db" :direction :output :if-exists :supersede))) (write-string "sb" stream))   "sb"CL-USER> (let ((stream (open "d:/emacs/out.db" :direction :output :if-exists :supersede)))  write-string "sb" stream)   (close stream))   TCL-USER> (let ((stream (open "d:/emacs/out.db" :direction :output      :if-exists :supersede      :element-type '(unsigned-byte 8))))             (write-byte 97 stream)                (close stream))T  并且文件中显示a
五:宏封装文件流

通过上面的例子,你应该明白了,对于文件的处理关键是得到流。像上面每一次的结束都得自己手动关闭流,否则会造成文件句柄不够。并且有的时候没有执行到文件的close程序出错了,就会造成文件同样没有被安全关闭。于是Common lisp定义了一个特殊操作符Unwind-protect来解决了这个问题。With-open-fileUnwind-protect上面的的宏。得到流以后你就可以往里面write和从里面read了。

CL-USER> (with-open-file (stream "d:/emacs/io.db"))NILCL-USER> (with-open-file (stream "d:/emacs/io.db" :direction :output))
六:其他IO

Common lisp还支持其他IO,string-stream从一个字符串中读取或写入数据。

make-string-input-stream  里面包含一个string对象,然后返回一个指向这个对象的stream 

make-string-output-stream 不需要参数,返回一个stream

你比如下面的在创建一个流的时候,对于文件IO,你要么有个地方可以读到数据如文件,或者你可以直接传递给数据也行,相当于一个水管直接接到了string对象。input的话,可以把string作为对象,然后生成一个指向他的流,但是现在对于output,你没有文件中可以存放数据的地方,那么你把这个流指向什么地方呢?现在的处理就是调用上面的两个构造函数,返回一个流指向所给string,对于这个流无论你写了什么,字符串输出流都将被积累到字符串中,你随后必须调用函数get-output-stream-string来获取该串。我感觉这种类型肯定只是针对小数据而言,肯定有个上限的。

CL-USER> (let ((s (make-string-input-stream "1.23")))     (unwind-protect (read s)      (close s)))1.23CL-USER> (let ((s (make-string-output-stream)))   (format s "string")   (format t "~s" (get-output-stream-string s)))         "string"

宏封装的string stream 现在关键是with-output-to-string会返回由get-output-stream-string返回的值。

CL-USER> (with-output-to-string (out)   (format out " hello world")   (format out "~s" (list 1 3)))" hello world(1 3)"CL-USER> (with-input-from-string (s "1.23")   (read s))1.23
七:流的拼接技术

提供多种形式的流拼接技术,允许你以几乎任何配置将流拼接在一起

make-broadcast-stream 输出流 make-broadcast-stream构造流

concatenated-stream   输入流 make-concatenated-stream 接受任何数量的输入流作为参数

CL-USER> (let ((in (open "d:/emacs/io.db"))(in2 (open "d:/emacs/io2.db")))   (let ((merge (make-concatenated-stream in in2)))     (loop for line = (read-char merge nil)while linedo(format t "~a" line))     (close in)     (close out)     (close merge)))hello worldi  am ryuthanks.good bye.(+ 1 3)ni haomaduo xieqi duo



原创粉丝点击