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);
}
}
///
/// 将字节组转换成为字符串
///
/// 字节数组
///
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);
}
}
发布评论