文件I/O

深入探讨Common Lisp的文件流与路径名管理
本文详细介绍了Common Lisp中流的抽象、路径名管理、读写S表达式、文件读取与输出操作,包括字符流与二进制流的使用,以及宏封装文件流的方法。此外,文章还涵盖了其他输入输出技术,如字符串流的使用,并提供了流拼接技术的实例。

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 world
may 
T

CL-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 c
				do(format t "~a" c))
	        (close in))
10910132116111111131098101
T


三:批量读取

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))	   
T
CL-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"))
NIL
CL-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.23
CL-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 line
		do(format t "~a" line))
	     (close in)
	     (close out)
	     (close merge)))

hello world
i  am ryu
thanks.
good bye.
(+ 1 3)ni haoma
duo xie
qi duo



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值