在Delphi中解析一行的最快方法是什么?
我有一个巨大的文件,我必须逐行解析。速度是关键 行的示例: 调用GetToken,返回“这是下一个标记”,并将CurrentPosition设置为标记的最后一个字符的位置,以便为下一次调用GetToken做好准备。令牌由一个或多个空格分隔 假设该文件已在内存中的StringList中。它很容易放入内存,比如说200MB在Delphi中解析一行的最快方法是什么?,delphi,parsing,token,pascal,Delphi,Parsing,Token,Pascal,我有一个巨大的文件,我必须逐行解析。速度是关键 行的示例: 调用GetToken,返回“这是下一个标记”,并将CurrentPosition设置为标记的最后一个字符的位置,以便为下一次调用GetToken做好准备。令牌由一个或多个空格分隔 假设该文件已在内存中的StringList中。它很容易放入内存,比如说200MB 我只担心解析的执行时间。在Delphi(Pascal)中,哪种代码将产生绝对最快的执行速度?滚动自己的代码肯定是最快的方法。有关此主题的更多信息,您可以查看市场上任何语言的Lex
我只担心解析的执行时间。在Delphi(Pascal)中,哪种代码将产生绝对最快的执行速度?滚动自己的代码肯定是最快的方法。有关此主题的更多信息,您可以查看市场上任何语言的Lexer(在项目上下文中称为Highlighter)的内容。我建议您将其中一个词法分析器作为基础,并根据自己的使用情况进行修改。我制作了一个基于状态引擎(DFA)的词法分析器。它与桌子配合使用,速度非常快。但也有可能更快的选择 这也取决于语言。一种简单的语言可能有一个智能算法 该表是一个记录数组,每个记录包含2个字符和1个整数。对于每个令牌,lexer遍历表,从位置0开始:
state := 0;
result := tkNoToken;
while (result = tkNoToken) do begin
if table[state].c1 > table[state].c2 then
result := table[state].value
else if (table[state].c1 <= c) and (c <= table[state].c2) then begin
c := GetNextChar();
state := table[state].value;
end else
Inc(state);
end;
状态:=0;
结果:=tkNoToken;
当(结果=tkNoToken)开始时
如果表[state].c1>表[state].c2,则
结果:=表[状态].值
else if(table[state].c1我认为最大的瓶颈始终是将文件放入内存。一旦您将其放入内存中(显然不是一次完成所有文件,但如果我是您,我会使用缓冲区),实际解析应该是无关紧要的。编写代码的最快方法可能是创建一个TStringList,并将文本文件中的每一行分配给CommaText属性。默认情况下,空格是分隔符,因此每个标记将获得一个StringList项
MyStringList.CommaText := s;
for i := 0 to MyStringList.Count - 1 do
begin
// process each token here
end;
不过,您自己解析每一行可能会获得更好的性能。这里是一个非常简单的lexer的蹩脚实现。这可能会给您一个想法
注意这个例子的局限性——没有缓冲,没有Unicode(这是Delphi7项目的一个摘录)。在一个严肃的实现中,您可能需要这些
{ Implements a simpe lexer class. }
unit Simplelexer;
interface
uses Classes, Sysutils, Types, dialogs;
type
ESimpleLexerFinished = class(Exception) end;
TProcTableProc = procedure of object;
// A very simple lexer that can handle numbers, words, symbols - no comment handling
TSimpleLexer = class(TObject)
private
FLineNo: Integer;
Run: Integer;
fOffset: Integer;
fRunOffset: Integer; // helper for fOffset
fTokenPos: Integer;
pSource: PChar;
fProcTable: array[#0..#255] of TProcTableProc;
fUseSimpleStrings: Boolean;
fIgnoreSpaces: Boolean;
procedure MakeMethodTables;
procedure IdentProc;
procedure NewLineProc;
procedure NullProc;
procedure NumberProc;
procedure SpaceProc;
procedure SymbolProc;
procedure UnknownProc;
public
constructor Create;
destructor Destroy; override;
procedure Feed(const S: string);
procedure Next;
function GetToken: string;
function GetLineNo: Integer;
function GetOffset: Integer;
property IgnoreSpaces: boolean read fIgnoreSpaces write fIgnoreSpaces;
property UseSimpleStrings: boolean read fUseSimpleStrings write fUseSimpleStrings;
end;
implementation
{ TSimpleLexer }
constructor TSimpleLexer.Create;
begin
makeMethodTables;
fUseSimpleStrings := false;
fIgnoreSpaces := false;
end;
destructor TSimpleLexer.Destroy;
begin
inherited;
end;
procedure TSimpleLexer.Feed(const S: string);
begin
Run := 0;
FLineNo := 1;
FOffset := 1;
pSource := PChar(S);
end;
procedure TSimpleLexer.Next;
begin
fTokenPos := Run;
foffset := Run - frunOffset + 1;
fProcTable[pSource[Run]];
end;
function TSimpleLexer.GetToken: string;
begin
SetString(Result, (pSource + fTokenPos), Run - fTokenPos);
end;
function TSimpleLexer.GetLineNo: Integer;
begin
Result := FLineNo;
end;
function TSimpleLexer.GetOffset: Integer;
begin
Result := foffset;
end;
procedure TSimpleLexer.MakeMethodTables;
var
I: Char;
begin
for I := #0 to #255 do
case I of
'@', '&', '}', '{', ':', ',', ']', '[', '*',
'^', ')', '(', ';', '/', '=', '-', '+', '#', '>', '<', '$',
'.', '"', #39:
fProcTable[I] := SymbolProc;
#13, #10: fProcTable[I] := NewLineProc;
'A'..'Z', 'a'..'z', '_': fProcTable[I] := IdentProc;
#0: fProcTable[I] := NullProc;
'0'..'9': fProcTable[I] := NumberProc;
#1..#9, #11, #12, #14..#32: fProcTable[I] := SpaceProc;
else
fProcTable[I] := UnknownProc;
end;
end;
procedure TSimpleLexer.UnknownProc;
begin
inc(run);
end;
procedure TSimpleLexer.SymbolProc;
begin
if fUseSimpleStrings then
begin
if pSource[run] = '"' then
begin
Inc(run);
while pSource[run] <> '"' do
begin
Inc(run);
if pSource[run] = #0 then
begin
NullProc;
end;
end;
end;
Inc(run);
end
else
inc(run);
end;
procedure TSimpleLexer.IdentProc;
begin
while pSource[Run] in ['_', 'A'..'Z', 'a'..'z', '0'..'9'] do
Inc(run);
end;
procedure TSimpleLexer.NumberProc;
begin
while pSource[run] in ['0'..'9'] do
inc(run);
end;
procedure TSimpleLexer.SpaceProc;
begin
while pSource[run] in [#1..#9, #11, #12, #14..#32] do
inc(run);
if fIgnoreSpaces then Next;
end;
procedure TSimpleLexer.NewLineProc;
begin
inc(FLineNo);
inc(run);
case pSource[run - 1] of
#13:
if pSource[run] = #10 then inc(run);
end;
foffset := 1;
fRunOffset := run;
end;
procedure TSimpleLexer.NullProc;
begin
raise ESimpleLexerFinished.Create('');
end;
end.
{实现一个simpe lexer类。}
单元单纯形器;
接口
使用类、sysutil、类型和对话框;
类型
esImplexerFinished=类(异常)结束;
TProcTableProc=对象的过程;
//一个非常简单的lexer,可以处理数字、单词、符号-无注释处理
TSimpleLexer=类(TObject)
私有的
弗林诺:整数;
运行:整数;
偏移量:整数;
fRunOffset:Integer;//偏移的辅助对象
fTokenPos:整数;
来源:PChar;
fProcTable:TProcTableProc的数组[#0..#255];
FuseSimpleString:布尔型;
fIgnoreSpaces:布尔;
程序生成方法表;
程序识别程序;
程序NewLineProc;
程序NullProc;
程序编号PROC;
程序SpaceProc;
程序SymbolProc;
程序未知NPROC;
公众的
构造函数创建;
析构函数销毁;重写;
过程馈送(常量:字符串);
下一步程序;
函数GetToken:string;
函数GetLineNo:Integer;
函数GetOffset:Integer;
属性IgnoreSpaces:布尔读取fIgnoreSpaces写入fIgnoreSpaces;
属性UseSimpleStrings:布尔读取fUseSimpleStrings写入fUseSimpleStrings;
结束;
实施
{TSimpleLexer}
构造函数tsimplexer.Create;
开始
制作方法表;
FuseSimpleString:=false;
fIgnoreSpaces:=假;
结束;
析构函数tsimplexer.Destroy;
开始
继承;
结束;
过程tsimplexer.Feed(常量S:string);
开始
运行时间:=0;
弗林尼诺:=1;
偏移量:=1;
pSource:=PChar(S);
结束;
程序tsimplexer.Next;
开始
fTokenPos:=运行;
偏移量:=运行-frunOffset+1;
fProcTable[pSource[Run]];
结束;
函数tsimplexer.GetToken:string;
开始
设置字符串(结果,(pSource+fTokenPos),运行-fTokenPos);
结束;
函数tsimplexer.GetLineNo:Integer;
开始
结果:=弗林诺;
结束;
函数tsimplexer.GetOffset:整数;
开始
结果:=偏移量;
结束;
程序tsimplexer.MakeMethodTables;
变量
I:半焦;
开始
对于I:=#0到#255 do
案例一
'@', '&', '}', '{', ':', ',', ']', '[', '*',
“^',”),“(”,“;”,“/”,“=”,“-”,“+”,“#”,“>”,”这引出了另一个问题——多大?
给我们一个线索,比如行数或Mb(Gb)?
然后我们将知道它是否适合内存,是否需要基于磁盘等
第一次通过时,我会使用我的单词列表(S:String;AList:TStringlist)
然后您可以访问每个令牌作为列表[n]。。。
或者对它们进行排序或其他任何操作。
- 使用PChar递增以提高处理速度
- 如果不需要某些令牌,只需按需复制令牌数据
- 在实际扫描字符时,将PChar复制到局部变量
- 将源数据保存在单个缓冲区中,除非您必须逐行处理,并且即使如此,也应考虑在Loxer-Algisher
中将行处理作为单独的令牌处理。
- 如果您明确知道编码,请考虑处理直接来自文件的字节数组缓冲区;如果使用Delphi 2009,请使用PAnsiChar而不是PChar,当然,除非您知道编码是UTF16-LE
- 如果您知道唯一的空白是#32(ASCII空格),或者类似的有限字符集,那么可能会有一些巧妙的位处理技巧,可以让您使用整数扫描一次处理4个字节。不过,我不希望在这里取得重大胜利,代码也会像泥一样清晰
这是一个非常有效的示例lexer,但它假设所有源数据都在一个字符串中。由于标记非常长,因此对其进行重新处理以处理缓冲区是相当棘手的
type
TLexer = class
private
FData: string;
FTokenStart: PChar;
FCurrPos: PChar;
function GetCurrentToken: string;
public
constructor Create(const AData: string);
function GetNextToken: Boolean;
property CurrentToken: string read GetCurrentToken;
end;
{ TLexer }
constructor TLexer.Create(const AData: string);
begin
FData := AData;
FCurrPos := PChar(FData);
end;
function TLexer.GetCurrentToken: string;
begin
SetString(Result, FTokenStart, FCurrPos - FTokenStart);
end;
function TLexer.GetNextToken: Boolean;
var
cp: PChar;
begin
cp := FCurrPos; // copy to local to permit register allocation
// skip whitespace; this test could be converted to an unsigned int
// subtraction and compare for only a single branch
while (cp^ > #0) and (cp^ <= #32) do
Inc(cp);
// using null terminater for end of file
Result := cp^ <> #0;
if Result then
begin
FTokenStart := cp;
Inc(cp);
while cp^ > #32 do
Inc(cp);
end;
FCurrPos := cp;
end;
类型
TLexer=类
私有的
FData:字符串;
福托肯斯特:
type
TLexer = class
private
FData: string;
FTokenStart: PChar;
FCurrPos: PChar;
function GetCurrentToken: string;
public
constructor Create(const AData: string);
function GetNextToken: Boolean;
property CurrentToken: string read GetCurrentToken;
end;
{ TLexer }
constructor TLexer.Create(const AData: string);
begin
FData := AData;
FCurrPos := PChar(FData);
end;
function TLexer.GetCurrentToken: string;
begin
SetString(Result, FTokenStart, FCurrPos - FTokenStart);
end;
function TLexer.GetNextToken: Boolean;
var
cp: PChar;
begin
cp := FCurrPos; // copy to local to permit register allocation
// skip whitespace; this test could be converted to an unsigned int
// subtraction and compare for only a single branch
while (cp^ > #0) and (cp^ <= #32) do
Inc(cp);
// using null terminater for end of file
Result := cp^ <> #0;
if Result then
begin
FTokenStart := cp;
Inc(cp);
while cp^ > #32 do
Inc(cp);
end;
FCurrPos := cp;
end;
procedure TMyReader.InitialiseMapping(szFilename : string);
var
// nError : DWORD;
bGood : boolean;
begin
bGood := False;
m_hFile := CreateFile(PChar(szFilename), GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);
if m_hFile <> INVALID_HANDLE_VALUE then
begin
m_hMap := CreateFileMapping(m_hFile, nil, PAGE_READONLY, 0, 0, nil);
if m_hMap <> 0 then
begin
m_pMemory := MapViewOfFile(m_hMap, FILE_MAP_READ, 0, 0, 0);
if m_pMemory <> nil then
begin
htlArray := Pointer(Integer(m_pMemory) + m_dwDataPosition);
bGood := True;
end
else
begin
// nError := GetLastError;
end;
end;
end;
if not bGood then
raise Exception.Create('Unable to map token file into memory');
end;