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

分类栏目:gamemaker教程

442

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

4、基本流程

①打开文件,并确定打开模式(二进制还是文本)和操作模式(输入还是输出)。
②依次读取/写入数据或字符。
③关闭文件。注意,文件从被打开到被关闭之前中,会处于“被占用”的状态,处于该状态的文件只能被其他程序以只读方式打开。另外,在文件流操作中,为了安全起见,输出流并不是将数据直接输出到目标文件中的,而是保存在缓存之中,直到关闭文件时才把数据从缓存中复制到文件中,如果不关闭文件,写入的数据会丢失。

5、二进制文件流

①打开文件
file_bin_open(file, mode) 以二进制文件流打开一个文件,参数file为文件的完整路径,参数mode取0表示只读,取1表示只写,取2表示既读也写。该函数会创建一个二进制文件流,并且返回它的索引。

②关闭文件
file_bin_close(file id) 关闭索引为file id的二进制文件流。

③写入文件
file_bin_write_byte(file id, val) 将数据val写入索引为file id的二进制文件流中。注意,一次只能写一个字节,即val只能取0~255。
如果我们要储存大于255的数据,那么就要把它分段储存。例如有一个变量value,可能的取值范围是0~1654558,它可能的最大值大于65535(256²-1),小于16777215(256³-1),因此在储存value时,我们需要这样写:
var f, temp;
f = file_bin_open(working_directory + "\SaveData", 1);
temp = value;

file_bin_write_byte(f, temp div (256 * 256));
temp -= (temp div (256 * 256)) * 256 * 256;
file_bin_write_byte(f, temp div 256);
temp -= (temp div 256) * 256;
file_bin_write_byte(f, temp);

file_bin_close(f);
即把变量value拆成value = a * 256² + b * 256 + c的形式,再分别储存a,b,c三个值。
对于任意大小的一个整数,我们都可以把它拆成a1 * 256^(n - 1) + a2 * 256^(n - 2) + ...... + an-1 * 256 + an一共n个数据来储存。
注意,由于上述例子中使用了-=,因此要用temp来替代value进行操作,否则会改变value的值。
④读取文件
file_bin_read_byte(file id) 从索引为file id的二进制流中读出正在处理的那个字节的数据,返回该数据,一定是0~255中的一个数值。
继续上述例子,我们把变量value分成了三个0~255的字节数据储存在了文件中,那么我们要怎么读出这三个数据还原出value呢?
var f;
f = file_bin_open(working_directory + "\SaveData", 0);

value = file_bin_read_byte(f) * 256 * 256;
value += file_bin_read_byte(f) * 256;
value += file_bin_read_byte(f);

file_bin_close(f);
注意第一个是=,之后都是+=。可见,读取数据就是写入数据的反操作。在这里我们明确的知道我们写入了三个字节来保存value变量的值,因此我们可以通过读取三个字节来还原value变量的值。但是这并不是硬性的规定,如果你需要,也可以把这三个字节拆成三个数据或两个数据来使用。
⑤面向初学者的简化
假如你对二进制感到十分吃力,我们也可以换一种办法来储存实数,那就是不以256为拆分点,而是以100为拆分点。比如value的值是114253,我们可以把它拆成114253 = 11 * 10000 + 42 * 100 + 53,这样我们就得到了三个数据:11,42,53,将这三个数据分别写入二进制文档即可。代码就是:
var f, temp;
f = file_bin_open(working_directory + "\SaveData", 1);
temp = value;

file_bin_write_byte(f, temp div 10000);
temp -= (temp div 10000) * 10000;
file_bin_write_byte(f, temp div 100);
temp -= (temp div 100) * 100;
file_bin_write_byte(f, temp);

file_bin_close(f);
对于任何一个数据,都可以隔两位拆分一个数据,例如655641213拆成6,55,64,12,13一共五个数据来保存。本质上其实与以256作为基数储存数据并没有什么不同,硬要说的话就是更占内存了。但是使用我们熟悉的10进制拆分数据,比起陌生的16进制,可能会更有助于理解二进制文件流。同样的,在读取数据时,基数256也要改为100,这里不再累赘的写出完整代码。注意,你选择的基数可以是2~256之间的任意值,但是一定不能大于256。
⑥储存负数小数
GM的二进制流储存的是单字节无符号的整数,并不提供储存负数和小数,因此,我们需要自己来实现这个功能。
如果一个变量value既可能是正数也可能是负数,取值范围假设是-1654558~1654558,那么需要这样来储存:
var f, temp;
f = file_bin_open(working_directory + "\SaveData", 1);
temp = abs(value);

file_bin_write_byte(f, sign(value) + 1);
file_bin_write_byte(f, temp div (256 * 256));
temp -= (temp div (256 * 256)) * 256 * 256;
file_bin_write_byte(f, temp div 256);
temp -= (temp div 256) * 256;
file_bin_write_byte(f, temp);

file_bin_close(f);
即占用一个字节来储存value的正负,而temp则取value的绝对值。
注意,由于sign(value)的返回值为-1(value为负数),0(value==0),1(value为正数),而-1本身依然是个负数,不能用来储存,因此我们要储存sign(value) + 1,即用0来代表负数,1代表0,2代表正数。
同样的,在读取这个数据时,我们也需要自行判断value的正负:
var f, sig;
f = file_bin_open(working_directory + "\SaveData", 0);

sig = file_bin_read_byte(f);
value = file_bin_read_byte(f) * 256 * 256;
value += file_bin_read_byte(f) * 256;
value += file_bin_read_byte(f);
if(!sig)
 value = -value;

file_bin_close(f);
而对于储存小数,个人建议使用数据结构+文本文件流,但是要用二进制流储存也不是没有办法。
(此段不重要,可跳过不看)在其他语言中,通常将负数拆分为四部分:阶符,阶码,数符,数码。数符就是指整个数据的正负,而阶符是指阶码的正负。阶码和数码如何构成数据?假设为a,b,则数据=1.b * 2^a。1.b表示把b的数据放在小数点后,小数点后补1,比如假设a的二进制是10110,那么1.a就是1.10110,这个数据就是二进制的小数,可以类比十进制来理解。由于二进制下第一位一定是1,所以第一位1通常都不写。a表示2的多少次方,a的正负由阶码决定。不知道各位是否学过科学计数法,即用x * 10^y来表示一个数,其中a是整数部分只有一位的实数,用科学计数法可以表示任意整数和小数。1.a * 2^b就是二进制形式下的科学计数法了,同样可以表示出任意整数和小数。
但是这种表示方法实在不适合初学者,因此这里给出一种十分简单的方法:放缩。说白了,就是把小数乘1000...(省略多个0),让这个小数变成一个整数,或者虽然还不是整数,但是得到的整数部分已经足够精确了,然后再把得到的整数储存在文件中。例如假设value的取值范围变成了0~100之间的任意小数,要求储存的小数的小数点后至少有六位,那么就把value放大1000000倍,变成0~100000000之间的一个很大的数,这个很大的数的小数部分则不再考虑,接下来把这个很大的数按照上面的办法拆为256的倍数和储存在文件中即可。而在读取这个数据时,只要在读取数据结束后,再执行value = value / 1000000即可还原原本的小数。
⑦判断文件结束
一般来讲由于二进制文件流写入和读出有极大的关联性,要精确读出每一个数据,就必须知道写入每一个数据的字节数,所以很少会出现要手动判断文件结束的情况,不过姑且还是说一说比较好。
file_bin_size(file id) 返回索引为file id的二进制文件流所处理的文件的大小(字节)。
file_bin_position(file id) 返回索引为file id的二进制文件流正在处理第几个字节(注意,从0开始)。
判断二进制文件流结束的标准就是file_bin_position(f) == file_bin_size(f)。
⑧定位
file_bin_seek(file id, pos) 令索引为file id的二进制文件流从pos位置的字节处理。与file_bin_position一样,是从0开始的。
也是一个基本不会用的上的函数,不过总归聊胜于无。

6、文本文件流

①打开文件
file_text_open_read(file) 以文本文件输入流打开一个文件。参数file为文件完整路径,返回该文件流的id。
file_text_open_write(file) 以文本文件输出流打开一个文件。参数file为完整路径,如果不存在则会被创建,如果存在,则会先清空原文件的内容。返回该文件流的id。
file_text_open_append(file) 以文本文件输出流打开一个文件。参数file为完整路径,如果不存在则会被创建,与上面的函数不同的是,如果存在文件,则会把数据接在原文件数据的后面,而不是清空原文件。返回该文件流的id。

②关闭文件
file_text_close(file id) 关闭索引为file id的文本文件流。

③写入文件
file_text_write_string(file id, str) 将字符串str写入到索引为file id的文本文件流中。
file_text_write_real(file id, val) 将一个实数写入到索引为file id的文本文件流中。注意,与二进制文件流不同的是,文本文件流将实数转换为ASCII码的形式储存,比如,实数255在二进制流中只占一个字节,但是在文本文件流中会被转化为"255"字符串来储存,每个字符占一个字节,所以一共占四个字节(还有一个多出来的字节是ASCII码为20的字符,这个字符不会被显示,GM8用它来隔断不同的实数)。
file_text_writeln(file id) 向文件索引写入一个换行符。
这里我不得不骂一句yoyo game萨比,因为GM8在写入字符串的时候并不会写入任何分割符。也就是说,如果你写入三个字符串,在读取的时候却会被当做一整个字符串读取出来。因此,这就迫使GMer在写入字符串的时候,每写一个字符串都要写一个换行符才行。你以为这就结束了?Too young!你在读取字符串的时候,GM8确实会以换行符作为分界线,但是,它不会自动跳行,也就是说,你读完一个字符串之后,还要手动调用函数跳过换行符。意不意外?惊不惊喜?另外还有一点,读取字符串并不会因为遇到了数字字符,或者ASCII码为20的字符而停止,因此,你甚至有可能会把写入的实数附在字符串里给一并读取出来。
但是,不管yoyo game多么的萨比,我们机智的GMer总有办法可以拯救一波,详情请参考下一节内容。
④读取文件
file_text_read_string(file id) 从索引为file id的文本文件流中读取一个字符串。字符串以换行符作为结束标志。
file_text_read_real(file id) 从索引为file id的文本文件流中读取一个实数。一个实数是指一串连续的字符,且均为'0'~'9','.','-'中的某一个。当遇到这些字符以外的字符时,GM会认为属于这个实数的字符结束了。
file_text_readln(file id) 跳过一行字符串。正如前文所述,你在读取完一行字符串之后,不得不手动跳行来读取下一行的字符串。

⑤判断文件结束
file_text_eof(file id) 判断索引为file id的文本文件流是否已经结束。

⑥后话
GM8的文本文件流可以说是差到令人失望,标准的文本文件流应当是逐字符处理,而不是逐字符串处理,再加上GM8并不是按照传统的以ASCII码为0的字符作为字符串的结束,而是以ASCII码为10的换行符,这就导致GM8的文本文件流几乎无法和其他语言的文本文件流交互,你用其他语言写的文本文件GM8读不来,你用GM8写的文本文件其他语言也读不来。
但是我这并不是在说二进制文件流比文本文件流好,事实上,只要没有跳过二进制文件流一节的人都能看出来二进制文件流的操作十分不便,典型的牺牲代码换取效率。
因此,下一节就是我们要讲的重点,如何活用文本文件流来储存数据。