1.基于C#的Dbf读写(文件结构概述)

愿你出走半生,归来仍是少年! 

环境:.NET FrameWork4.5、.Net 6

1.简述

        在地理信息系统中,有很多的常用数据格式,类似Shapefile、Dxf等等,在不同的商业或开源平台中都有对其可靠的支持。DBF数据文件作为Shapefile文件的属性存储文件,出现的情况特别多;除此之外,现如今特别多的测绘公司对于数据的内业处理也还是保存在DBF中通过VFP进行处理。

        虽然都是DBF文件,但是其内部存在不同的文件类型,这个会导致现如今在Git上找到的开源库有时候会出现有些dbf在打开时出现错误的情况。为了能够方便、快捷的读写DBF数据,不少开发者需要安装驱动,通过OLE DB的方式连接操作。

        本文通过二进制流的方式进行DBF文件的解析、读取、写入,同时说明DBF文件的构成。

1.1.C#依赖知识

C#数据类型字节数
数据类型所占字节数
Byte1
Short2
Int4
Char1

        后续的文件读写操作都是基于C#中的BinaryReader进行。

        

2.DBF构成

        每个DBF文件依次存在以下几个部分:头文件区域字段描述区域后链信息(VFP存在,其它版本没发现)数据内容区域

        通过一下代码创建面向指定DBF文件的流读取器:

new BinaryReader(new FileStream(Filename, FileMode.Open, FileAccess.Read, FileShare.Read, 100000));

2.1.头文件区域

        头文件区域共计占32字节,具体分布如下。

文件头结构
字节范围字节数读取类型说明
01Byte文件类型
11Byte文件最后修改日期,年 YY
21Byte文件最后修改日期,月 MM
31Byte文件最后修改日期,日 DD
4-74Int数据记录数量,总行数
8-92Short文件头的字节长度(头文件区域字段描述区域、结束符(0x0D后链信息(VFP存在,其它版本没发现)
10-112short每一行数据记录的字节长度
12-2716未使用保留区域
281Byte表的标记                
291Byte Language Driver ID (LDID),文件编码代码页标记
30-312未使用保留区域

2.1.1.文件类型读取

 // 获取Dbf的第一个字节,代表dbf文件类型,参照 DbfFileType 0
 FileType = reader.ReadByte();

        创建BinaryReader后,通过 ReadByte()方法读取文件第一个字节,获取到Dbf的文件类型,具体类型清单如下,通过对比可得知当前的DBF类型。

DBF文件类型
编码说明
0x02 FoxBASE
0x03FoxBASE+/dBASE III PLUS,无备注
0x30 Visual FoxPro
0x43 dBASE IV SQL 表文件,无备注
0x63 dBASE IV SQL 系统文件,无备注
0x83 FoxBASE+/dBASE III PLUS,有备注
0x8B dBASE IV 有备注
0xCB dBASE IV SQL 表文件,有备注
0xF5 FoxPro 2.x(或更早版本)有备注
0xFB FoxBASE

     2.1.2.最后修改日期读取

 // 获取更新日期:年 1
 int year = reader.ReadByte();

 // 获取更新日期:月 2
 int month = reader.ReadByte();


 // 获取更新日期:日 3
 int day = reader.ReadByte();

 try
 {
     //转换为日期
     UpdateDate = new DateTime(year + 1900, month, day);
 }
 catch
 {
     //获取文件的最后一次编辑时间
     UpdateDate = new FileInfo(Filename).LastWriteTime;
 }

        通过三次的ReadByte()方法依次读取出年月日,并转换为对应的日期。当出错时就以文件的最后编辑时间作为参照。

2.1.3.数据记录数量/总行数

  // 读取数据总数,int 4字节长度  4-7
  NumRecords = reader.ReadInt32();

        通过ReadInt32()方法一次性读取4个字节,获得该DBF文件中现有的数据记录总数。 

2.1.4.文件头总字节长度

  //  读取文件的头文件长度 short 2字节长度 8-9
  HeaderLength = reader.ReadInt16();

        通过ReadInt16()方法一次性读取2个字节,获得该DBF文件的头文件总字节长度。 

2.1.5.每行数据的字节长度

 // 读取每行记录的长度 short 2字节长度 10-11
 RecordLength = reader.ReadInt16();

         通过ReadInt16()方法一次性读取2个字节,获得该DBF文件中每一行数据记录的长度。 

2.1.6.保留区、标记

 // 跳过系统保留区域 12-27  ;表的标记 28
 reader.ReadBytes(17);

2.1.7.代码页标记

 // 读取 Language Driver ID (LDID)  byte 1字节长度 29
 LanguageDriverId = reader.ReadByte();

         通过ReadByte()方法读取代码页标记,用于设置编码。 

2.1.8.保留区

//  跳过系统保留区 30-31 
reader.ReadBytes(2);

2.2.字段描述区域

        字段描述区域包含了该DBF中的所有字段信息。每个字段的信息占用共计32字节长度,然后以0x0d结束。若该DBF包含后链信息,则后续是后链信息;若不包含后链信息,则后续是数据内容区域

        每个字段的信息32字节长度分布结构如下:

2.2.1.字段名称读取

// 读取字段名称 :字段名,10字节,0-9;保留区,默认为0,1字节,10
string name = Encoding.GetString(reader.ReadBytes(11));

//获取保留区序号
int nullPoint = name.IndexOf((char)0);

//裁剪
if (nullPoint != -1)
{
    name = name.Substring(0, nullPoint);
}

        通过读取前面11个字节,然后获取第一个0的位置进行裁剪获得名称。

2.2.2.字段类型读取

  // 字段类型,1字节,11
  char code = (char)reader.ReadByte();

        获取字段类型后可得知其对应的数据类型,具体类型如下:

2.2.3.字段偏移量读取

// 字段偏移量,int 4字节,12-15 
int dataAddress = reader.ReadInt32();

 2.2.4.字段长度读取

// 字段长度,byte 1字节 16
byte tempLength = reader.ReadByte();

 2.2.5.字段小数位数读取

// 小数位数,byte 1字节 17
byte decimalcount = reader.ReadByte();

  2.2.6.标记及保留区读取

  //  字段标记及保留区 ,字段标记 byte 1字节 18,;保留区  19-31
  reader.ReadBytes(14);

   2.2.7.字段描述区整体读取

 Fields = new List<DbfField>();

 bool readFieldFinish = false;



 while (!readFieldFinish)
 {
     // 读取字段名称 :字段名,10字节,0-9;保留区,默认为0,1字节,10
     string name = Encoding.GetString(reader.ReadBytes(11));

     //获取保留区序号
     int nullPoint = name.IndexOf((char)0);

     //裁剪
     if (nullPoint != -1)
     {
         name = name.Substring(0, nullPoint);
     }

     // 字段类型,1字节,11
     char code = (char)reader.ReadByte();

     // 字段偏移量,int 4字节,12-15 
     int dataAddress = reader.ReadInt32();

     // 字段长度,byte 1字节 16
     byte tempLength = reader.ReadByte();

     // 小数位数,byte 1字节 17
     byte decimalcount = reader.ReadByte();

     //  字段标记及保留区 ,字段标记 byte 1字节 18,;保留区  19-31
     reader.ReadBytes(14);

      

     DbfField myField = new DbfField(name, code, tempLength, decimalcount)
     {
         DataAddress = dataAddress
     };

     Fields.Add(myField);

     dt.Columns.Add(myField);

     long pst = reader.BaseStream.Position;


     //_numFields = (HeaderLength - FileDescriptorSize - 1) / FileDescriptorSize;
     //  字段描述区的结束符  0x0d,有些版本后面有263个字节包含后链信息(相关数据库 (.dbc) 的相对路径)。
     // 计算字段数量:【头长度-文件描述长度(32)-结束符长度(1)】/单字段描述区长度(32)进行计算会忽略263字符的情况
     //  如果第一个字节为 0x00,则该文件不与数据库关联。因此数据库文件本身总是包含 0x00。
     byte last = reader.ReadByte();

     if (last == 0x0d)
     {
         readFieldFinish = true;
     }
     else
     {
         reader.BaseStream.Position = pst;
     }
 }

        整体读取时需要注意的是每次读取完成后需要进行下一个字节的判断,当这个字节是0x0d时就代表字段描述区域已经完结了。这样的话可以避过后链信息区域的干扰,获取到正确的字段集合。

2.3.数据内容区域

        字段描述区域包含了该DBF中的所有数据记录,每条记录的字节长度固定为2.1.5中获取到的每行数据的字节长度,每个字段的长度为2.2.4.中获取到的字段长度。通过字节长度读取每个字段对应的数据内容,并根据在2.2.2.中获取的字段类型进行转换,便可以获取到对应的数据。

        每条记录的第一个字节都是该记录的删除标记,当内容为空格 (0x20)时,表示该记录未标记删除,当内容为星号 (0x2A)时,表示该记录已标记为删除。

        

  • 43
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

就是那个帕吉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值