GameMaker8.0 :新手教程 Part 26 -文件流(上)-

分类栏目:gamemaker教程

463

GameMaker8.0 :新手教程 Part 26 -文件流(上)-

1、数据流

数据流(Data Stream)的现代概念最早在1998年被定义为“只能以事先规定好的顺序被读取一次的数据的一个序列”,之后又被修改为“只能被读取一次或少数几次的点的有序序列”,即将“一次”放松为了“几次”。你可能会觉得看不懂什么意思,但是没关系,等看完这章之后你就会对这个定义恍然大悟,现在我们先了解数据流有什么特性。
①一般,数据流都分为输入流(Input Stream)和输出流(Output Stream),输入流只读不写,而输出流只写不读。有些数据流为了操作方便,还提供第三种,输入输出流(Input/Output Stream),既可读又可写。
②处理系统不能控制数据的处理顺序。
这里就拿普通字符串处理和字符串流处理来举例。普通字符串处理我们前面也讲过,我们可以用string_char_at来获取任意位置的字符,可以用string_delete删除任意位置的字符,也可以用string_insert在任意位置插入字符,或者string_replace替换任意位置的字符。对于字符串流处理来说,这里假设是输入流,假如现在轮到处理第五个字符了,那我能不能读第四个字符?抱歉不能。我能不能读第六个字符?抱歉不能。你能且只能读第五个字符,因为现在字符串流正在处理第五个字符。读完第五个字符后,字符串流又会自动地跳到下一个,即第六个字符的处理,这时候才能读取第六个字符。那我能不能只读第五个字符,但是字符串流处理的位置不跳到第六个字符,而是保留在第五个字符处?抱歉也不能,因为处理系统没有权限控制数据的处理顺序。由于这种顺序处理不太方便操作,所以很多流处理都会提供seek函数,该类函数可以精确跳跃到流的某一个位置,但是要清楚,其本质还是把数据流从头开始再处理一遍,直到处理你给定的数据位置为止,再将处理权交还给你。
③数据流的处理十分高效。假设数据是一个湖泊,我们想把里面的水移动到另一个湖里,普通的数据处理就像是一个巨大无比的袋子,一口气装下整个湖泊,转移到另一个地方,因此其负担与数据量有很大的关系。而数据流处理就是一个抽水机,我不管这个湖到底有多少水,我只抽取水管口附近的水,因为水有流动性,所以其他地方的水又会接二连三地移动到水管口,直到抽完所有水为止。理论上,数据流可以处理无穷数据的数据源。
④数据流中的单个数据一旦被处理完就会被丢弃。这一点在第二点的例子中就可以看出来。在大型数据流处理中,通常数据的总量远远大于处理器的内存,因此,已经处理过的数据不应当还留在处理器中。注意,这里说的是从数据流中丢弃,这里依然假设是字符串流,字符串本身是占用内存的,而字符串流每次只获取字符串的一个字符保存在自己的内存中,这个内存和字符串占用的内存是分开的。处理完的字符会被丢弃,指的是字符从字符串流的内存中丢弃,而字符串本身还有这个字符。

2、文件

文件(File)定义为储存在磁盘上,以实现某种功能、或某个软件的部分功能为目的一个单位。例如文档文件,图像文件,音乐文件,可执行文件(程序)等。通常也会根据尾缀来命名,如jpg文件,mp3文件,exe文件等等。
文件的本质是将数据转换为二进制储存在磁盘之中。
字节(Byte)是文件大小的基本单位,1字节等于8比特,即相当于能储存0~255一共256个整数数据。任何文件的大小都必须是字节的整数倍。
在电脑发展早期,曾经出现过一套用二进制储存英文字符的标准,ASCII,它规定了每一个英文字符或控制字符在内存中对应的二进制形式,如下图所示:
GameMaker8.0 :新手教程 Part 26 -文件流(上)-
ASCII码只对应0~127,后来又提出了IBM扩展字符集,将编码扩展到了0~255一共256个字符,前127位与ASCII相同,在内存中用一个字节表示一个字符,充分利用了一个字节所能表示的最多数据。而之后发展出的Unicode则使用两个字节表示一个字符(此处指UTF-16),因此最多可以表示出65536个不同的字符。
对于任意一个文件,都可以用IBM扩展字符集或者unicode表示出来。当你右键“用记事本打开”任意类型的文件时,通常会显示一堆乱码,如下图所示:
GameMaker8.0 :新手教程 Part 26 -文件流(上)-
这就是记事本用unicode编码(此处通常是UTF-8)将文件的数据全部翻译为了字符。由于这个文件本身并不是文本文件,所以就全是乱码了。
如果使用winHex或其他软件,则可以看到IBM扩展字符集翻译的文件内容(红框部分):
GameMaker8.0 :新手教程 Part 26 -文件流(上)-
通常情况下,我们把用来储存字符数据的文件,如txt文件,能在正确的编码下打开看到文本信息的,称为文本文件(Text File),而本质是储存实数数据,或者程序指令(指exe文件),不管在什么编码下用记事本打开是一片乱码的,称之为二进制文件(Binary File)。注意,文件类型与后文提到的文件流没有直接的关系,二进制文件也可以用文本文件流打开,文本文件也可以用二进制流打开。

3、文件流

文件流(File stream)处理是数据流处理的一种。文件流按操作方式可以分为输入文件流(Input File Stream),输出文件流(Output File Stream),输入输出文件流(Input/Output file Stream)三种,但是一般建议输入和输出分开操作。文件流按照文件打开模式又可分为二进制文件流(Binary File Stream)和文本文件流(Text File Stream)。
二进制文件流是变长度的数据处理,假如我先写入175(二进制10101111),再写入4546(二进制00010001 11000010),那么文件的内容就是10101111 00010001 11000010一共三字节的大小。但是在读取这个二进制文件流的时候,我却可以先读取两个字节,再读取一个字节,即把10101111 00010001当成第一个数据读取出44817,再把11000010当成第二个数据读取出194。或者我也可以把三个字节当成一整个数据11473346来读出。因此,二进制文件流写入的数据和读出的数据可以不相同,如果要保证写入和读出的数据是一样的,那么就要明确每一个数据所占用的字节数。
文本文件流是定长度的数据处理,每一次数据处理所占用的字节数只和编码类型有关,比如ASCII一次处理一字节,Unicode(此处指UTF-16)处理一次两个字节。因此,只要写入和读出使用同一个编码,就能保证读出的数据就是写入的数据。如果读出和写入使用不同的编码,或者用文本文件流读取非文本文件(如图像文件,视频文件),那么读出的数据一般都是无意义的乱码(但是在二进制层面上,二者还是一样的)。
注意,GM只有字符串类型,并没有字符类型,因此将文本文件流进一步封装,每次读出/写入一整个字符串,而不能做到单字符处理。GM在做文本文件处理时,会把换行符当做字符串的结束,因此GM大致上做了下面这种封装:
var c, str;
c = 读取一个字符;
while(c != chr(10))
{
str += c;
c = 读取一个字符;
}
return str;
因此,GM8的文本文件流本质上还是一次只处理一个字符。
根据第二节的知识,任何一个文件都可以用二进制文件流和文本文件流的方式打开,不管它本身到底是二进制文件还是文本文件。数据类型之间的转换是很随意的,你用txt随便写点文本,可以用二进制文件流打开读取实数出来用,反之亦然。
EOF:End Of File的缩写,在ASCII文本文件流中表示文件的结束,本质是-1。为什么必须是ASCII文本文件流呢?因为文件到达末尾之后,文件流会向处理系统返回-1(二进制11111111,此为负数的补码形式,二进制的第一位用来表示正负),而ASCII码的范围是0~127(二进制00000000~01111111),二进制第一位永远是0,因此如果得到-1就可以肯定的说明文件结束了。但是在二进制流中,完全有可能存在-1或者无符号的255(“无符号”的意思是不使用第一位二进制表示正负,因此也只能表示正数,无符号的255二进制也是11111111),所以,在读取到11111111时,我们无法肯定到底是文件结束了呢,还是真的读到了-1或者无符号的255的数据呢。在绝大多数流处理中,都将EOF封装为一个函数(GM8为file_text_eof(file id)),其本质类似于
if(当前字符 == EOF)
return true;
else
return false;
当文件结束时,函数返回true,否则返回false。其本质还是判断EOF,故而只能用于ASCII文本文件流。
值得注意的是,如果用文本文件流处理非ASCII文件(包括其他编码的文本文件,以及二进制文件)时,由于非ASCII文件有可能会储存11111111这个二进制数据,因此,当处理到这个字节时,即使离文件流结束还有十万八千里,eof函数也会返回true,故而,用文本文件流打开二进制文件并不是一个好的选择。
关于补码(two's complement):我们之前只提到过整数转换为二进制,但是准确的说,我们提到的是无符号的整数转换为二进制。需要考虑正负的情况下,要把第一个二进制位用来表示符号,0表示正,1表示负,因此,1字节储存的无符号的整数范围为0~255,有符号的整数却是-128~127。负数的补码并不是单纯的正数第一位改成1,比如1的二进制是00000001,-1的二进制却是11111111而不是10000001,具体的算法这里不展开来讲。想要了解负数的二进制表示,甚至于小数的二进制表示方式,还请百度“补码”以进一步了解。