首先让我告诉您,您在这里优化了错误的事情:除非您出于娱乐目的这样做,否则您的方法是错误的。XML 不是一种困难的格式,但它确实有它的怪癖并且它需要它的自由。它是一种为国外应用程序之间的数据交换而设计的格式,所以重点需要放在兼容性上,而不是速度上!当遇到稍微改变的 XML 文件时给出错误结果的非标准超快速解析器有什么好处?
如果你能找到一个 XML 解析库,它可以保证与任何能够以 HDD 读取数据的速度一半的速度解析数据的东西兼容,那么只需实现一个生产者-消费者多线程应用程序,其中一个线程不断读取数据从磁盘,而其他两个只是进行解析。最后,在保持兼容性的同时,您只会受到 HDD 速度的限制。如果您只追求速度,您可能会犯错误,请跳过 XML 功能,这取决于您正在处理的示例 XML 文件的某些特殊性。您的应用程序可能会因多种原因而中断。
请记住,应用程序最昂贵的周期是维护,而不是生产。你今天通过制造速度提高 50% 的东西而获得的东西,但在一年左右的时间里,当计算机速度提高 50% 时(使你在竞争中的优势消失),将在一年左右的时间里失去。此外,超出此类过程的自然限制是没有意义的,例如 HDD 的速度。使用 RAM 驱动器中的文件进行测试无关紧要 - 当应用程序投入生产时,它将与 HDD 中的文件一起使用,并且您的应用程序的性能将受到 HDD 速度的限制。
无论如何,我偶尔会喜欢挑战,我真的很喜欢解析器。下面是一个非常简单的解析器实现,它只查看输入字符串中的每个字符一次,并且只在需要的地方复制内容:复制节点的名称以便决定下一步做什么,并在适当的时候复制节点的“有效负载”,为了将其推入阵列。在我的“适度”i7 @ 3.4 Ghz 上,解析通过复制样本数据 10,000 次构建的字符串需要 63 毫秒。它显然比你的时间要好,但要提醒一句,这段代码很脆弱:它取决于有一个特定形式的 XML 文件。没办法。
program Project28;
{$APPTYPE CONSOLE}
uses SysUtils, DateUtils, Windows;
const SampleData =
'<node>'#13#10+
' <datatype1>randomdata</datatype1>'#13#10+
' <datatype2>randomdata</datatype2>'#13#10+
' <datatype3>randomdata</datatype3>'#13#10+
' <datatype4>randomdata</datatype4>'#13#10+
' <datatype5>randomdata</datatype5>'#13#10+
' <datatype6>randomdata</datatype6>'#13#10+
' <datatype7>randomdata</datatype7>'#13#10+
' <datatype8>randomdata</datatype8>'#13#10+
' <datatype9>randomdata</datatype9>'#13#10+
' <datatype10>randomdata</datatype10>'#13#10+
' <datatype11>randomdata</datatype11>'#13#10+
' <datatype12>randomdata</datatype12>'#13#10+
' <datatype13>randomdata</datatype13>'#13#10+
' <datatype14>randomdata</datatype14>'#13#10+
' <datatype15>randomdata</datatype15>'#13#10+
' <datatype16>randomdata</datatype16>'#13#10+
' <datatype17>randomdata</datatype17>'#13#10+
' <datatype18>randomdata</datatype18>'#13#10+
' <datatype19>randomdata</datatype19>'#13#10+
' <datatype20>randomdata</datatype20>'#13#10+
'</node>'#13#10;
const NodeIterations = 10000;
type
TDummyRecord = record
D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13,
D14, D15, D16, D17, D18, D19, D20: string;
end;
TDummyRecordArray = array[1..NodeIterations] of TDummyRecord;
procedure ParseDummyXMLToRecordArray(const InputText:string; var A: TDummyRecordArray);
var PInputText: PChar;
cPos, TextLen: Integer;
C: Char;
State: Integer;
tag_starts_at: Integer;
last_payload_starts_at: Integer;
FlagEndTag: Boolean;
NodeName, Payload: string;
cNode: Integer;
const st_not_in_node = 1;
st_in_node = 2;
begin
cPos := 1;
TextLen := Length(InputText);
PInputText := @InputText[1];
State := st_not_in_node;
last_payload_starts_at := 1;
cNode := 0;
// This is the lexer/parser loop. It's a finite-state machine with only
// two states: st_not_in_node and st_in_node
while cPos < TextLen do
begin
C := PInputText[cPos-1];
case State of
// What happens when we're NOT currently inside a node?
// Not much. We only jump to st_in_node if we see a "<"
st_not_in_node:
case C of
'<':
begin
// A node starts here. Switch state and set up some simple
// flags.
state := st_in_node;
tag_starts_at := cPos + 1;
FlagEndTag := False;
end;
end;
// What happens while inside a node? Again, not much. We only care about
// the "/" - as it signals an closing tag, and we only care about the
// ">" because that means the end of the ndoe.
st_in_node:
case C of
'/': FlagEndTag := True;
'>':
begin
// This is where the magic haepens. We're in one of possibly two states:
// We're ither seeing the first <name> of a pair, or the second </name>
//
if FlagEndTag then
begin
// This is the closing pair of a tag pair, ie, it's the </NodeName> What we'll do
// depends on what node is closing, so we retreive the NodeName:
NodeName := System.Copy(InputText, tag_starts_at+1, cPos - tag_starts_at-1);
if NodeName <> 'node' then // SAMPLE-DATA-SPECIFIC: I know I don't care about "node" tags.
begin
// SAMPLE-DATA-SPECIFIC: I know there are only two kinds of nodes:
// "node" and "datatypeN". I retreive the PAYLOAD for the node because
// I know it's not "ndoe" and I know I'll need it.
Payload := System.Copy(InputText,last_payload_starts_at, tag_starts_at - last_payload_starts_at -1);
// Make sure we're dealing with a valid node
if (cNode > 0) and (cNode <= High(A)) then
begin
// Based on NodeName, copy the Payload into the appropriate field.
if NodeName = 'datatype1' then A[cNode].D1 := Payload
else if NodeName = 'datatype2' then A[cNode].D2 := Payload
else if NodeName = 'datatype3' then A[cNode].D3 := Payload
else if NodeName = 'datatype4' then A[cNode].D4 := Payload
else if NodeName = 'datatype5' then A[cNode].D5 := Payload
else if NodeName = 'datatype6' then A[cNode].D6 := Payload
else if NodeName = 'datatype7' then A[cNode].D7 := Payload
else if NodeName = 'datatype8' then A[cNode].D8 := Payload
else if NodeName = 'datatype9' then A[cNode].D9 := Payload
else if NodeName = 'datatype10' then A[cNode].D10 := Payload
else if NodeName = 'datatype11' then A[cNode].D11 := Payload
else if NodeName = 'datatype12' then A[cNode].D12 := Payload
else if NodeName = 'datatype13' then A[cNode].D13 := Payload
else if NodeName = 'datatype14' then A[cNode].D14 := Payload
else if NodeName = 'datatype15' then A[cNode].D15 := Payload
else if NodeName = 'datatype16' then A[cNode].D16 := Payload
else if NodeName = 'datatype17' then A[cNode].D17 := Payload
else if NodeName = 'datatype18' then A[cNode].D18 := Payload
else if NodeName = 'datatype19' then A[cNode].D19 := Payload
else if NodeName = 'datatype20' then A[cNode].D20 := Payload
else
raise Exception.Create('Unknown node: ' + NodeName);
end
else
raise Exception.Create('cNode out of bounds.');
end;
// Repeat :-)
state := st_not_in_node;
end
else
begin
// Node start. Retreive node name. I only care about the start of the "NODE" - if I see that
// I'll increment the current node counter so I'll go on filling the next position in the array
// with whatever I need.
NodeName := System.Copy(InputText, tag_starts_at, cPos - tag_starts_at);
last_payload_starts_at := cPos+1;
if NodeName = 'node' then Inc(cNode);
state := st_not_in_node;
end;
end;
end;
end;
Inc(cPos);
end;
end;
var DataString: string;
SB: TStringBuilder;
i: Integer;
DummyArray: TDummyRecordArray;
T1, T2, F: Int64;
begin
try
try
// Prepare the sample string; 10.000 iterations of the sample data.
SB := TStringBuilder.Create;
try
for i:=1 to NodeIterations do
SB.Append(SampleData);
DataString := SB.ToString;
finally SB.Free;
end;
// Invoke the simple parser using the string constant.
QueryPerformanceCounter(T1);
ParseDummyXMLToRecordArray(DataString, DummyArray);
QueryPerformanceCounter(T2);
QueryPerformanceFrequency(F);
WriteLn(((T2-T1) * 1000) div F);
// Test parse validity.
for i:=1 to NodeIterations do
begin
if DummyArray[i].D1 <> 'randomdata' then raise Exception.Create('Bug. D1 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D2 <> 'randomdata' then raise Exception.Create('Bug. D2 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D3 <> 'randomdata' then raise Exception.Create('Bug. D3 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D4 <> 'randomdata' then raise Exception.Create('Bug. D4 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D5 <> 'randomdata' then raise Exception.Create('Bug. D5 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D6 <> 'randomdata' then raise Exception.Create('Bug. D6 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D7 <> 'randomdata' then raise Exception.Create('Bug. D7 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D8 <> 'randomdata' then raise Exception.Create('Bug. D8 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D9 <> 'randomdata' then raise Exception.Create('Bug. D9 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D10 <> 'randomdata' then raise Exception.Create('Bug. D10 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D11 <> 'randomdata' then raise Exception.Create('Bug. D11 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D12 <> 'randomdata' then raise Exception.Create('Bug. D12 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D13 <> 'randomdata' then raise Exception.Create('Bug. D13 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D14 <> 'randomdata' then raise Exception.Create('Bug. D14 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D15 <> 'randomdata' then raise Exception.Create('Bug. D15 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D16 <> 'randomdata' then raise Exception.Create('Bug. D16 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D17 <> 'randomdata' then raise Exception.Create('Bug. D17 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D18 <> 'randomdata' then raise Exception.Create('Bug. D18 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D19 <> 'randomdata' then raise Exception.Create('Bug. D19 doesn''t have the proper value, i=' + IntToStr(i));
if DummyArray[i].D20 <> 'randomdata' then raise Exception.Create('Bug. D20 doesn''t have the proper value, i=' + IntToStr(i));
end;
except on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
finally
WriteLn('ENTER to Exit');
ReadLn;
end;
end.