c#发展

首页 » 常识 » 常识 » u0002Cu0003u0001
TUhjnbcbe - 2025/5/8 17:02:00
中科医院专家微信 http://hunan.ifeng.com/a/20170626/5773085_0.shtml
文件格式我们在编写程序的时候,经常需要保存自己的程序产生的一些数据,然后在下次程序运行的时候,读取出来,这就涉及到文件的读写操作。一般情况下,我们搜索C#文件读写,经常是这样的一段代码:FileStreamfs=newFileStream(C:\\test.dat,FileMode.Open,FileAccess.Read);byte[]data=newbyte[fs.Length];fs.Read(data,0,fs.Length);fs.Close();上面的这段代码就是经典的文件读取代码,代码没错,但是很多时候,这段代码并不能满足我们的需要,因为这段代码,是将文件整个的读取到一个byte数组里。我们想有格式的读取文件,怎么办?比如有个兄弟给了我这么一个需求:将一个float数组写入文件,然后再将文件读到到一个float数组。这就涉及到自定义文件格式的读写了。当然也有人会抬杠说可以保存为xml文件格式,可以保存为json格式,可以保存为文本格式,没错,这些都是解决方案,但是我这个兄弟还有个要求,用二进制而不是文本格式保存,因为他不想让用户直接看到里面保存的内容。所以,以上的方案就不适合了。而且,我本质上,是希望通过这个简单的例子,让大家融会贯通,能够设计出自己的更复杂的文件格式。好了,现在让我们来设计一下文件格式。需求很明确,也很简单,文件就是保存一个float数组,我们可以将文件分为三部分:1.文件标识区。2.数组长度区。3.数组数据区。至于文件扩展名,自己定义一个,只要不和常见的文件扩展名相同就可以了,比如本文,我们用.dat作为自己定义的文件的扩展名。文件标识区这个是自己来定义的,也就是用来识别是不是自己的文件格式,有很多文件格式都会有自己的文件标识区,比如exe文件格式,早期的dos系统的可执行程序,文件头部有个MZ标识。这里我用我的V号作为我的文件标识:jidi,用utf8编码的话,占8个字节。数组长度区数组长度区就是告诉我们,文件里保存的这个float数组的长度。虽然说每个float都是占4个字节,是定长的,但是我们还是浪费4个字节来存储数组的长度,用途等下再说。数组数据区这个区域存储的就是float数组的具体数据。一个接一个的float数组依次存放,每个float占4个字节。写入文件至此,我们的文件格式已经定义好了,是时候写入数据了。在写入数据之前,我们看一下FileStream有哪些写入函数:1、publicoverridevoidWrite(byte[]array,intoffset,intcount);2、publicoverrideTaskWriteAsync(byte[]buffer,intoffset,intcount,CancellationTokencancellationToken);3、publicoverridevoidWriteByte(bytevalue);有这么三个写入函数,第一个是写入一个byte数组,第二个函数是异步写入,我们这里不做讨论,第三个是写入一个byte类型的数据。所以,我们用第一个函数,而且要将数据转换成byte数组写入,无论是字符串string,还是原始类型,比如int,float这些,都要先转换成byte数组,然后调用这个函数写入到文件中。首先我们写入文件标识区,也就是“jidi”这串文本,先把这串文本转换成byte数组,然后写入这个数组到文件byte[]bytes=System.Text.Encoding.UTF8.GetBytes(jidi);fs.Write(bytes,0,bytes.Length);然后写入数组长度区,我们用int类型,4个字节来表示这个长度。intlength=10;byte[]bytes=BitConverter.GetBytes(length);fs.Write(bytes,0,bytes.Length);最后一步,逐个写入float数组里的数据,结束。for(inti=0;idataArr.Length;i++){byte[]data=BitConverter.GetBytes(dataArr);fs.Write(data,0,data.Length);}文件写入完毕。读取文件现在,我们看看如何读取这个文件。在此之前,我们先来了解一下FileStream有关读取的几个函数:1、publicoverrideintRead(byte[]array,intoffset,intcount);2、publicoverrideTaskintReadAsync(byte[]buffer,intoffset,intcount,CancellationTokencancellationToken);3、publicoverrideintReadByte();第一个函数是将数据读入到一个byte数组中,返回值是实际读取的字节数,第二个函数是异步读取数据,不在本文的讨论范围中,第三个函数是读取一个byte数据。所以,我们一般都是使用第一个函数进行文件读取,读取数据到byte数组,然后再将这个byte数组转换成我们需要的类型,比如string,int,float等等。根据文件格式,首先,我们要读取文件的标识区,看看这个文件是不是我们定义的文件格式,如果不是,那么我们就识别不了,不继续读取了。文件读取要比文件写入要麻烦一些,因为我们要做更多的判断。在刚才我们写入文件标识区的时候,我们已经知道了文件标识区jidi这串文本,占8个字节,所以,我们要先判断文件长度是否有8个字节,如果没有,那肯定不是我们的文件格式:if(fs.Length8)return;然后定义一个长度为8的byte数组,将文件标识区读入到这个数组byte[]bytes=newbyte[8];intreadBytes=fs.Read(bytes,0,8);if(readBytes8)return;然后将这个数组转换成文本,并和jidi比较是否相同stringstr=System.Text.Encoding.UTF8.GetString(bytes);if(!str.Equals(jidi))return;然后,我们在读取数组的长度,并且计算一下如果是这个数组长度,那么文件大小应该是多少个字节,确保我们能读入这么多字节。byte[]bytes=newbyte[4];intreadBytes=fs.Read(bytes,0,4);if(readBytes4)return;intlength=BitConverter.ToInt32(bytes,0);如果文件大小,小于我们计算的文件大小,说明文件要么被破坏了,要么就是一个巧合,这不是我们定义的文件格式。这个就是数组长度区的作用,能够确保有足够的数据给我们读入,不会引发异常。intfileSize=8+length+4*length;//文件尺寸=文件标识区长度+4个字节的数组长度区+4*数组长度if(fs.LengthfileSize)return;最后就简单了,依次读入float数据,然后显示出来,和之前写入的数据做对比。//逐一读取浮点数组数据byte[]data=newbyte[4];float[]dataArr=newfloat[length];for(inti=0;ilength;i++){fs.Read(data,0,4);dataArr=BitConverter.ToSingle(data,0);}
1
查看完整版本: u0002Cu0003u0001