2023年12月22日发(作者:)

DBF文件格式详细说明及程序设计

三峡大学水利与环境学院 肖泽云

1 DBF文件格式说明

DBF文件是一种以二进制进行存储的表格数据文件,其文件内部有着严格的格式要求,具体由文件头和记录项组成。其中文件头中包括字段的相关信息。DBF文件的数据结构如下表所示:

组成 内容

文件头 文件头定义

位置(Byte)

0-31

说明

包括版本信息、更新时间、记录条数、文件头长度等

字段名称、类型、字段长度(Byte)、精度等

同上

同上

同上

表示终止字段定义

表示第1行数据

表示第2行数据

字段1定义 32-64

字段2定义

……

字段n定义

值为0x0D

表格记录数据 第1行数据

第2行数据

……

65-97

-n*32+31

n*32+32

n*32+33-X

注意,在表格记录数据中每行数据具体占多长字节,这个由文件头中定义的字段数目以及字段长度来决定,如果该文件一共只有两个字段,其中第一个字段为数值,其长度为4,第二个字段为字符串,长度为50,则每一行数据占的字节长度为4+50=54,在读取数据时也是读取前4个为第一个字段对应的值,读取第5-54个为第二个字段对应的值。

另外,为便于理解表格与下面内容的关系,特说明字段即是指表格中的列,记录指表格中的行数据,DBF按行数据方式来存储,即在文件头中定义了列数、列的名称、列的数据类型、列长度等等,然后在后面的记录数据中插入每行数据。

文件头中格式及说明如下:

位置

0

类型

1个字节 表示当前的版本信息:

0x02 FoxBASE

0x03 FoxBASE+/Dbase III plus, no memo

0x30 Visual FoxPro

说明

0x31 Visual FoxPro, autoincrement enabled

0x43 dBASE IV SQL table files, no memo

0x63 dBASE IV SQL system files, no memo

0x83 FoxBASE+/dBASE III PLUS, with memo

0x8B dBASE IV with memo

0xCB dBASE IV SQL table files, with memo

0xF5 FoxPro 2.x (or earlier) with memo

0xFB FoxBASE

1-3 3个字节 表示最近的更新日期,按照YYMMDD格式,以1900年为起始,即第一个字节表示文件最后保存时的年份-1900,第二个字节的值为保存时的月,第三个字节的值为保存时的日。

4-7

8-9

10-11

12-13

Int32

Int16

Int16

文件中的记录条数,即表格的行数。

文件头中的字节数,在此之后的字节为表格记录数据

一条记录中的字节长度,即每行数据所占的长度

2个字节 保留字节,用于以后添加新的说明性信息时使用,这里用0来填写。

14

15

1个字节 表示未完成的操作。

1个字节 dBASE IV编密码标记。

16-27 12个字节 保留字节,用于多用户处理时使用。

28 1个字节 DBF文件的MDX标识。在创建一个DBF 表时 ,如果使用了MDX 格式的索引文件,那么 DBF 表的表头中的这个字节就自动被设置了一个标志,当你下次试图重新打开这个DBF表的时候,数据引擎会自动识别这个标志,如果此标志为真,则数据引擎将试图打开相应的MDX 文件。

29

30-31

1个字节 页码标记.

2个字节 保留字节,用于以后添加新的说明性信息时使用,这里用0来填写。

32-N (x*32)个字节

这段长度由表格中的列数(即字段数,Field Count)决定,每个字段的长度为32,如果有x列,则占用的长度为x*32,这每32个字节里面又按其规定包含了每个字段

的名称、类型等信息,具体见下面的表。

N+1 1个字节 作为字段定义的终止标识,值为0x0D。

每个字段定义格式如下表,每个字段定义都用32个字节来完成:

位置

0-10

11

内容 说明

11个字节 字段的名称,是ASCII码值。

1个字节 字段的数据类型,为ASCII码值。每个值对应不同的字段数据类型,如N表示数值,C表示字符串,关于具体的数据类型说明见下表。

12-15 4个字节 保留字节,用于以后添加新的说明性信息时使用,默认为0。

16 1个字节 字段的长度,表示该字段对应的值在后面的记录中所占的长度。

17

18-19

1个字节 字段的精度。

2个字节 保留字节,用于以后添加新的说明性信息时使用,默认为0。

20 1个字节 工作区ID。

21-31 11个字节 保留字节,用于以后添加新的说明性信息时使用,默认为0。

字段数据类型:

代码

B

C

D

数据类型

二进制型

字符型

日期型

各种字符。

各种字符。

用于区分年、月、日的数字和一个字符,内部存储按照YYYYMMDD格式。

G (General or OLE)

N

L

M

数值型(Numeric) - . 0 1 2 3 4 5 6 7 8 9

逻辑型(Logical) ? Y y N n T t F f (? 表示没有初始化)。

(Memo) 各种字符。

各种字符。

允许输入的数据

2 DBF文件数据结构实例分析

下面以一个具体实例来分析DBF数据结构:

该表格数据为:

列1

1

2

3

4

5

6

7

8

9

10

列2

2

4

6

8

10

12

14

16

18

20

用UltraEdit打开该dbf文件,其内容如下:

现在先分解一下,找出文件头,并分析一下文件头的内容。首先看第一个字节,值为03,这个是16进制的数据,第一个字节表示数据库类型,值03即0x03,对应FoxBASE+/Dbase III plus,。然后看第4个字节到第7个字节,这一段表示文件中的记录条数,即表格的行数,其Byte值为0A 00 00 00,转换成Int32即为10,即表格的行数为10。关于Byte数组转换成数值类型,其代码如下:

///

/// 将字节组转换成为整型

///

/// 字节数组

///

public static Int32 ConvertBytesToInt32(byte[] tempBytes)

{

Int32 result = 32(tempBytes, 0);

return result;

}

接着看第8个和第9字节,其值为61 00,转换成Int16其值即为97,意思就是说文件头所占字节长度为97,所以文件头的范围就是下面红色框内:

蓝色框为左边红色框对应的值,这个仅供参考。下面来仔细分析红色框中的数据:

前面的第1个字节到第8个和第9个字节我们前面都已经分析过了。第10和11个字节对应值表示一行数据的长度,其值为13 00,转换为Int其值为19,即每行数据占19个字节的长度,这个值应该等于接下来定义字段长度之和+1(每行最前面还有一个16进制值为20的字节)。

接下来的从第12个到31个都为保留字节,可以不管。因为前面已知文件头的长,为97,而用于文件头定义就占了32个,文件头最后一个字节用于表示字段定义结束,也占了一个,于是就只剩下97-32-1=64,而每定义一个字段占

32个字节,所以就只有2个字段。第一个字段就是从第33个字节开始到第33+32个字节,如下面黄色框中:

下面参考第三个表来分析这段数据,已知从第0开始到10这11个字节表示的字段的名称,将这11个字节数组转换成字符串即为字段名称。将字节数组转换成字符串代码如下:

///

/// 将字节组转换成为字符串

///

/// 字节数组

///

public static string ConvertBytesToString(byte[] tempBytes)

{

string result =

oding("gb2312").GetString(tempBytes);

return result;

}

将第0到第10个字节值转换成字符串,其值为“列1”,即第一个字段的名称。

第11个字节为字段数据类型,该值为4E(上图中用绿色标出),转换为char即为N,N表示数值类型,即第一列的数据类型为数值类型。

第12个到第15个为保留字节,暂不管。第16个字节表示字段的长度,其值为09(上图中用绿色标出),转换为Int其值即为9,即表示该字段长度为9个字节。后面从第17到31个字节都可暂不管。这样就分析完了一个字段的定义,安装同样的思路分析第二个字段,可以得到第二字段名称为“列2”,字段类型也为数值类型(N),字段长度也为9个字节。如下图所示,注意绿色框中的数据:

在第二个字段定义完成后,接下来的一个字节值为0D,即表示结束了字段定义,刚好在此也是97个字节。从第98个字节开始就是表格的数据对象了,每行占19个字节,其中第一个字节值为20,不属于表格单元的值,即从第99个字节开始,前面9个字节对应的值为第一行第一列单元的值,接着的9个字节是第一行第二列单元的值,在接下来的一个字节为默认字节,接着的9个字节就是第2行第一列单元的值,接下来的9个字节就是第2行第2列单元的值,依次类推,直到结束位置。

3 DBF文件读取与写入程序设计

下面介绍使用程序来读取和写入DBF文件:

首先新建一个窗体项目,各主要控件如下图所示:

3.1 读取DBF文件

读取DBF文件的内容并写到一个DataGridView控件中,采用如下函数:

///

/// 读取DBF文件

///

/// DBF文件路径

/// DataGridView控件

private void ReadDBFFile(string filepath, DataGridView dataGridView)

{

try

{

();

();

//打开文件

FileStream fs = new FileStream(filepath, ,

);

BinaryReader br = new BinaryReader(fs);

//跳过前面4个

tes(4);

//文件中的记录条数,即行数

int rowCount = t32();//4

//文件头的长度

int headLength = t16();//2

//每行的长度

int rowLength = t16();//2

//计算字段数目

int columnCount = (headLength - 33) / 32;

//跳过32-4-4-2-2=20个,直接到32

tes(20);

int i, j;

byte[] tempBytes = null;

byte tempByte;

int[] fieldLength = null;

//添加表格的列

if (columnCount > 0)

{

string tempColumnName;

fieldLength = new int[columnCount];

for (i = 0; i < columnCount; i++)

{

//读取前11个,为列名称

tempBytes = tes(11);

tempColumnName = ConvertBytesToString(tempBytes);

(tempColumnName,

tempColumnName);

//跳过5个

tes(5);

//获取字段长度

tempByte = te();

fieldLength[i] = (int)tempByte;

//跳过剩下的字节,有32-11-5-1=15个

tes(15);

}

}

//获取文件头的最后一个字节,值应该为0D

tempByte = te();

if (tempByte == 0x0D)

{

//添加表格的行及数据

if (rowCount > 0)

{

string tempStr;

for (i = 0; i < rowCount; i++)

{

();

//每行数据中第一个20,跳过

te();

for (j = 0; j < columnCount; j++)

{

tempBytes = tes(fieldLength[j]);

tempStr = ConvertBytesToString(tempBytes);

dataGridView[j, i].Value = tempStr;

}

}

}

}

else

{

("文件头定义出现错误!",

"提示!", , g);

}

();

();

}

catch (Exception except)

{

(e,

"提示!", , g);

}

}

///

/// 将字节组转换成为字符串

///

/// 字节数组

/// DataGridView控件

public static string ConvertBytesToString(byte[] tempBytes)

{

string result =

oding("gb2312").GetString(tempBytes);

return result;

}

///

/// 将字节组转换成为整型

///

/// 字节数组

///

public static Int32 ConvertBytesToInt32(byte[] tempBytes)

{

Int32 result = 32(tempBytes, 0);

return result;

}

所以,在按钮“读取DBF文件button1”的单击事件添加调用上面读取DBF文件的代码:

private void 读取DBF文件button1_Click(object sender,

EventArgs e)

{

if (())

{

ReadDBFFile(, dataGridView1);

}

}

3.2 写入DBF文件

同样,编写用于存储DBF文件的函数,如下:

///

/// 保存DBF文件

///

/// DBF文件保存路径

/// 表格对象

private void WriteDBFFile(string filepath, DataGridView dataGridView)

{

try

{

int rowCount = nt;

int columnCount = Count;

if (rowCount > 0 && columnCount > 0)

{

FileStream fs = new FileStream(filepath, ,

);

BinaryWriter bw = new BinaryWriter(fs);

byte tempByte;

byte[] tempBytes;

int i, j, tempInt;

int[] fieldLength = new int[columnCount];

int tempMax = 0;

for (i = 0; i < columnCount; i++)

{

tempMax = 0;

for (j = 0; j < rowCount; j++)

{

if (dataGridView[i, j].Value != null

&& dataGridView[i, j].ng() != "")

{

if (dataGridView[i,

j].ng().Length > tempMax)

{

tempMax = dataGridView[i,

j].ng().Length;

}

}

}

fieldLength[i] = tempMax * 2;

}

//写入文件头

{

//第0个字节为数据库类型

tempByte = 0x03;

(tempByte);

tempBytes = new byte[3];

//第1个字节为保存时的年份-1990

tempInt = - 1900;

tempBytes[0] = (tempInt);

//第2个字节为保存时的月份

tempBytes[1] =

();

//第3个字节为保存时的日

tempBytes[2] = (byte)();

(tempBytes);

// (rowCount);

// tempInt = 33 + columnCount * 32;

((Int16)tempInt);

// tempInt = 1;

for (i = 0; i < columnCount; i++)

{

tempInt += fieldLength[i];

}

((Int16)tempInt);

// tempBytes = new byte[20];

(tempBytes);

string tempColumnName;

// for (i = 0; i < columnCount; i++)

{

tempColumnName = s[i].Name;

tempBytes

11);

(tempBytes);

// tempByte = (byte)('C');// (tempByte);

// tempBytes = new byte[4];

(tempBytes);

// tempByte = (byte)fieldLength[i];

第4-7个字节为行数

第8-9字节为文件头的长度

第10-11为每行数据所占长度

第12-31为保留字段,默认为0

开始写字段

= ConvertStringToBytes(tempColumnName,

第11个字节为数据类型

数据类型为字符串

第12-15为保留字节

第16个字节为字段长度

(tempByte);

//接着第17-31都为保留字节

tempBytes = new byte[15];

(tempBytes);

}

//最后以0D结尾

tempByte = 0x0D;

(tempByte);

}

object tempValue;

//写入单元格数据

for (i = 0; i < rowCount; i++)

{

//每一行第一个字节默认为20

tempByte = 0x20;

(tempByte);

for (j = 0; j < columnCount; j++)

{

tempValue = dataGridView[j, i].Value;

if (tempValue != null)

{

tempBytes =

ConvertStringToBytes(ng(), fieldLength[j]);

(tempBytes);

}

else

{

tempBytes = ConvertStringToBytes("",

fieldLength[j]);

(tempBytes);

}

}

}

();

();

}

else

{

("表格中没有数据",

"提示!", , g);

}

}

catch (Exception except)

{

(e,

"提示!", , g);

}

}

///

/// 将字符串转换成为字节数组

///

///

///

///

public static byte[] ConvertStringToBytes(string tempStr,int

limitLength)

{

byte[] result = null;

byte[] tempBytes =

oding("gb2312").GetBytes(tempStr);

if ( == limitLength)

{

result = tempBytes;

}

else if ( > limitLength)

{

for (int i = 0; i < limitLength; i++)

{

result[i] = tempBytes[i];

}

}

else if ( < limitLength)

{

result = new byte[limitLength];

for (int i = 0; i < ; i++)

{

result[i] = tempBytes[i];

}

}

return result;

}

同样,在按钮“保存DBF文件button1”的Click事件中添加调用上面的函数:

private void 保存DBF文件button1_Click(object sender, EventArgs e)

{

SaveFileDialog saveDg = new SaveFileDialog();

= "保存DBF文件";

= "DBF文件(*.dbf)|*.dbf|所有文件(*.*)|*.*";

if (alog() == )

{

WriteDBFFile(me, dataGridView1);

}

}