1、行情文件说明
沪市行情文件show2003.dbf
深市行情文件sjshq.dbf
2、行情文件格式
说明:
(1)、表文件由头记录及数据记录组成。头记录定义该表的结构及与表相关的其他信息。数据记录紧接在头记录之后,包含字段中实际的文本。记录的长度等于所有字段定义的长度之和(以字节为单位)。
(2)、头记录以终止符(0x0D)结束,数据记录以终止符(0x1A)结束。
(3)、表文件中存储整数时低位字节在前。
(4)、数据记录从删除标记字节开始。如果删除标记字节为 ASCII 空格 (0x20),则表示该记录未被删除,如果该字节为星号 (0x2A),则表示该记录被删除。在删除标记之后是字段记录中所命名的各字段的数据。
(5)、数据记录都是用ASCII码形式存放的,所以只要读出文件头和字段类型描述区的内容,就可以直接读取dbf文件中的每条记录。
文件头部结构(32字节)
位置 | 长度 | 含义 | 备注 |
0 | 1 | 文件类型 | 0x03,FoxBASE+/dBASE III PLUS,无备注 |
1 - 3 | 3 | 最近一次更新的时间(YYMMDD) |
|
4 - 7 | 4 | 文件中的记录数目 |
|
8 - 9 | 2 | 文件中的第一个数据记录的位置 |
|
10 - 11 | 2 | 每个数据记录的长度(包括删除标记) |
|
12 - 31 | 20 | 保留 |
|
字段记录结构(32字节)
位置 | 长度 | 说明 | 备注 |
0 - 10 | 11 | 字段名 | 最多10个字符,若少于10则用空字符填充 |
11 | 1 | 字段类型 | C-字符型 Y-货币型 N-数值型 F-浮点型 D-日期型 T-日期时间型 B-双精度型 I-整型 L-逻辑型 M-备注型 G-通用型 C-字符型(二进制) M-备注型(二进制) P-图片型 |
12 - 15 | 4 | 记录中该字段的偏移量(16进制) |
|
16 | 1 | 字段长度 | 以字节为单位 |
17 | 1 | 小数位数 | 以字节为单位 |
18 - 31 | 14 | 保留 |
|
示例代码
示例:显示DBF文件信息
说明:delphi编写的示例,显示DBF文件中头部信息、字段信息和数据信息。
unit uDataBuffer;
interface
uses
SysUtils,Classes;
type
TCustomStringBuffer = class(TObject)
private
FBuffer: PChar;
FBufferSize: Integer;
FBufferContent: string;
function GetBufferContent: string;
procedure SetBufferSize(Value: Integer);
procedure SetBufferContent(const Value: string);
protected
procedure DoBufferChange; virtual;
procedure ClearBuffer; virtual;
public
constructor Create;
//---
function ReadFile(const AFileName: string): Boolean;
function SaveFile(const AFileName: string; const Append: Boolean = false):
Boolean;
//---
property Buffer: PChar read FBuffer;
property BufferSize: Integer read FBufferSize write SetBufferSize;
property BufferContent: string read GetBufferContent write SetBufferContent;
end;
TRecordStream = class(TCustomStringBuffer)
private
FCount: Integer;
FDataSize: Cardinal;
function GetDatas(Index: Integer): Pointer;
procedure SetCount(const Value: Integer);
protected
procedure ClearBuffer; override;
procedure DoBufferChange; override;
//---
property Datas[Index: Integer]: Pointer read GetDatas;
property DataSize: Cardinal read FDataSize write FDataSize;
public
property Count: Integer read FCount write SetCount;
end;
function OpenToFile(const AFileName: string; const AMode: Integer; const AIsAppend: Boolean = false): integer;
function ReadFormFile(const AFileName: string): string;
function SaveToFile(const AFileName,AContent: string; const AIsAppend: Boolean
= false): Boolean;
implementation
function OpenToFile(const AFileName: string; const AMode: Integer; const AIsAppend: Boolean = false): integer;
const
CNT_OpenCount = 20;
CNT_OpenOutTime = 30;
//---
function _OpenWrite: integer;
var
i,AFileHandle: integer;
APath: string;
begin
if FileExists(AFileName) then
begin
for i := 0 to CNT_OpenCount do
begin
if AIsAppend then
AFileHandle := FileOpen(AFileName,fmOpenWrite or fmShareExclusive)
else
AFileHandle := FileCreate(AFileName);
if AFileHandle < 0 then
sleep(CNT_OpenOutTime)
else
Break;
end;
end
else
begin
APath := ExtractFilePath(AFileName);
if not DirectoryExists(APath) then
ForceDirectories(APath);
//---
AFileHandle := FileCreate(AFileName);
end;
//---
Result := AFileHandle;
end;
//---
function _OpenRead: integer;
var
i,AFileHandle: integer;
begin
Result := -1;
//---
if not FileExists(AFileName) then
Exit;
//---
for i := 0 to CNT_OpenCount do
begin
AFileHandle := FileOpen(AFileName,fmOpenRead or fmShareDenyNone);
if AFileHandle < 0 then
sleep(CNT_OpenOutTime)
else
Break;
end;
//---
Result := AFileHandle;
end;
begin
Result := -1;
//---
if Trim(AFileName) = '' then
Exit;
//---
if AMode = fmOpenWrite then
Result := _OpenWrite
else
Result := _OpenRead;
end;
function ReadFormFile(const AFileName: string): string;
var
AFileHandle,ABufferSize: integer;
begin
Result := '';
//---
AFileHandle := OpenToFile(AFileName,fmOpenRead);
if AFileHandle < 0 then
Exit;
//---
try
ABufferSize := FileSeek(AFileHandle,0,soFromEnd);
if ABufferSize = 0 then
Exit;
//---
SetLength(Result,ABufferSize);
//---
FileSeek(AFileHandle,0,soFromBeginning);
FileRead(AFileHandle,Pchar(Result)^,ABufferSize);
finally
FileClose(AFileHandle);
end;
end;
function SaveToFile(const AFileName,AContent: string; const AIsAppend: Boolean
= false): Boolean;
var
AFileHandle: integer;
begin
result := false;
//---
if Length(AContent) = 0 then
Exit;
//---
try
AFileHandle := OpenToFile(AFileName,fmOpenWrite,AIsAppend);
if AFileHandle < 0 then
Exit;
//---
try
if AIsAppend then
FileSeek(AFileHandle,0,soFromEnd)
else
FileSeek(AFileHandle,0,soFromBeginning);
FileWrite(AFileHandle,Pchar(AContent)^,Length(AContent));
finally
FileClose(AFileHandle);
end;
//---
Result := true;
except
on E: Exception do
begin
Result := false;
end;
end;
end;
procedure TCustomStringBuffer.ClearBuffer;
begin
FBufferSize := 0;
end;
constructor TCustomStringBuffer.Create;
begin
FBufferSize := 0;
end;
procedure TCustomStringBuffer.DoBufferChange;
begin
FBuffer := PChar(FBufferContent);
end;
function TCustomStringBuffer.GetBufferContent: string;
begin
if FBufferSize = 0 then
Result := ''
else
Result := FBufferContent;
end;
function TCustomStringBuffer.ReadFile(const AFileName: string): Boolean;
begin
self.BufferContent := ReadFormFile(AFileName);
Result := self.BufferSize > 0;
end;
function TCustomStringBuffer.SaveFile(const AFileName: string; const Append:
Boolean = false): Boolean;
begin
result := SaveToFile(AFileName,self.BufferContent,Append);
end;
procedure TCustomStringBuffer.SetBufferSize(Value: Integer);
begin
FBufferSize := Value;
if FBufferSize < 0 then
FBufferSize := 0;
//---
if FBufferSize > 0 then
SetLength(FBufferContent,Value);
//---
DoBufferChange;
end;
procedure TCustomStringBuffer.SetBufferContent(const Value: string);
begin
FBufferContent := Value;
FBufferSize := Length(FBufferContent);
//---
DoBufferChange;
end;
procedure TRecordStream.ClearBuffer;
begin
inherited;
//---
FCount := 0;
end;
procedure TRecordStream.DoBufferChange;
//---
procedure _RefreshCount;
begin
FCount := FBufferSize div FDataSize;
if FDataSize * FCount <> FBufferSize then
self.ClearBuffer;
end;
begin
inherited;
//---
if FDataSize <= 0 then
self.ClearBuffer
else
_RefreshCount;
end;
function TRecordStream.GetDatas(Index: Integer): Pointer;
begin
Result := FBuffer + Index * FDataSize;
end;
procedure TRecordStream.SetCount(const Value: Integer);
begin
FCount := Value;
if FCount < 0 then
FCount := 0;
//---
self.BufferSize := FDataSize * FCount;
end;
end.
unit uStockDbfData;
interface
uses
uDataBuffer;
type
TDbfHead = packed record
FileKind: byte; //--文件类型
FileDate: array[0..2] of byte; //--文件更新时间(YYMMDD)
RecordCount: Cardinal; //--记录数目
RecordStart: Word; //--文件第一个记录的起始位置
RecordLength: Word; //--记录长度(包括删除标记)
Reserve: array[0..19] of Char; //--保留
end;
PTDbfHead = ^TDbfHead;
TDbfField = packed record
Name: array[0..10] of Char; //--字段名
Kind: Char; //--字段类型
Start: Cardinal; //--记录中该字段的偏移量(16进制)
Len: byte; //--字段长度
Decimal: byte; //--小数位数
Reserve: array[0..13] of Char; //--保留
end;
TDbfFields = array of TDbfField;
TDbfRecordStream = class(TRecordStream)
private
function GetItems(Index: Integer): string;
public
property Items[Index: Integer]: string read GetItems; default;
end;
TStockDbfStream = class(TCustomStringBuffer)
private
FHead: TDbfHead;
FFields: TDbfFields;
FRecords: TDbfRecordStream;
function GetHead: PTDbfHead;
protected
procedure DoBufferChange; override;
public
constructor Create;
destructor Destroy; override;
//---
function GetFieldValue(const ARecord: string; const AFieldIndex: integer): string;
//---
property Head: PTDbfHead read GetHead;
property Fields: TDbfFields read FFields;
property Records: TDbfRecordStream read FRecords;
end;
implementation
constructor TStockDbfStream.Create;
begin
inherited;
//---
FRecords := TDbfRecordStream.Create;
end;
destructor TStockDbfStream.Destroy;
begin
FRecords.Free;
//---
inherited;
end;
procedure TStockDbfStream.DoBufferChange;
const
CNT_HeadSize = $00018;
CNT_DataStartAddress = $41000;
//---
function _ReadHead(var AReadSize: integer): Boolean;
begin
Result := self.BufferSize - AReadSize > SizeOf(FHead);
if Result then
begin
Move(Buffer^,FHead,SizeOf(FHead));
AReadSize := SizeOf(FHead);
end;
end;
//---FHead.RecordStart = 文件头部大小(32个字节) + 字段信息大小 + 终止符大小(1个字节)
function _ReadFields(var AReadSize: integer): Boolean;
var
AFieldBufferSize,AFieldCount: integer;
begin
AFieldBufferSize := FHead.RecordStart - AReadSize - 1;
AFieldCount := AFieldBufferSize div sizeof(TDbfField);
if sizeof(TDbfField) * AFieldCount <> AFieldBufferSize then
AFieldCount := 0;
//---
Result := AFieldCount > 0;
if Result then
begin
setlength(FFields,AFieldCount);
Move((Buffer + AReadSize)^,FFields[0],AFieldBufferSize);
//---
AReadSize := FHead.RecordStart;
end;
end;
//---
function _ReadRecords(var AReadSize: integer): Boolean;
var
ARecordBuffer: string;
ARecordBufferSize: integer;
begin
with FHead do
ARecordBufferSize := RecordLength * RecordCount;
//---
Result := ARecordBufferSize <= (self.BufferSize - AReadSize);
if Result then
begin
setlength(ARecordBuffer,ARecordBufferSize);
Move((Buffer + AReadSize)^,ARecordBuffer[1],ARecordBufferSize);
//---
with FRecords do
begin
DataSize := FHead.RecordLength;
BufferContent := ARecordBuffer;
end;
end;
end;
//---
var
AReadSize: integer;
begin
inherited;
//---
FillChar(FHead,SizeOf(FHead),0);
setlength(FFields,0);
FRecords.Count := 0;
//---
AReadSize := 0;
if not (_ReadHead(AReadSize) and _ReadFields(AReadSize) and _ReadRecords(AReadSize)) then
self.ClearBuffer;
end;
function TStockDbfStream.GetFieldValue(const ARecord: string;
const AFieldIndex: integer): string;
var
AData:string;
begin
//---数据记录的第1个字节为删除标记,空格 (0x20) -> 未删除,星号 (0x2A) -> 删除
AData := Copy(ARecord,2,Length(ARecord) - 1);
//---
with FFields[AFieldIndex] do
Result := Copy(AData,Start,Len);
end;
function TStockDbfStream.GetHead: PTDbfHead;
begin
Result := @FHead;
end;
function TDbfRecordStream.GetItems(Index: Integer): string;
var
ARecord: string;
begin
setlength(Result,DataSize);
Move((Buffer + Index * DataSize)^,PChar(Result)^,DataSize);
//---
//Result := Copy(BufferContent,Index * DataSize + 1,DataSize);
end;
end.
unit Unit1;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,
Dialogs,StdCtrls,ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
ListBox1: TListBox;
GroupBox1: TGroupBox;
OpenDialog1: TOpenDialog;
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
procedure ShowData(const AFile: string; const AListBox: TListBox);
end;
var
Form1: TForm1;
implementation
uses uStockDbfData;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
SendMessage(ListBox1.Handle,LB_SetHorizontalExtent,2000,longint(0));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
with self.OpenDialog1 do
begin
if Execute then
ShowData(FileName,ListBox1);
end;
end;
procedure TForm1.ShowData(const AFile: string; const AListBox: TListBox);
var
AStream: TStockDbfStream;
//---
procedure _ShowHead;
begin
with AListBox.Items,AStream.Head^ do
begin
Add('--------文件头部---------');
Add(Format('文件类型:%d 文件更新时间:%d.%d.%d 记录数目:%d 记录起始位置:%d 记录长度:%d',
[FileKind,FileDate[0],FileDate[1],FileDate[2],RecordCount,RecordStart,RecordLength]));
end;
end;
//---
procedure _ShowFields;
var
i: Integer;
begin
with AListBox.Items,AStream do
begin
Add('--------字段信息---------');
//---
for i := low(Fields) to high(Fields) do
begin
with Fields[i] do
begin
Add(Format('字段名:%s 字段类型:%s 字段的偏移量:%d 字段长度:%d 小数位数:%d',
[Name,Kind,Start,Len,Decimal]));
end;
end;
end;
end;
//---
{procedure _ShowRecords;
var
i: Integer;
begin
with AListBox.Items,AStream do
begin
Add('--------记录信息---------');
//---
for i := 0 to Records.Count - 1 do
Add(Records[i]);
end;
end;}
//---
procedure _ShowRecords;
var
i,j: Integer;
AText: string;
begin
with AListBox.Items,AStream do
begin
Add('--------记录信息---------');
//---
for i := 0 to Records.Count - 1 do
begin
AText := '';
for j := low(Fields) to high(Fields) do
AText := AText + ' ' + GetFieldValue(Records[i],j);
//---
Add(AText);
end;
end;
end;
begin
AStream := TStockDbfStream.Create;
try
with AListBox.Items do
begin
BeginUpdate;
Clear;
with AStream do
begin
if ReadFile(AFile) then
begin
_ShowHead;
_ShowFields;
_ShowRecords;
end;
end;
EndUpdate;
end;
finally
AStream.Free;
end;
end;
end.