首先讓我告訴你,你在這裏優化錯誤的東西:除非你是爲了娛樂的目的而這樣做,那麼你的方法是錯的。 XML並不是一個困難的格式,但它確實有它的怪癖,它需要它自由。這是一種爲外國應用程序之間的數據交換而設計的格式,因此需要將重點放在兼容性上,而不是速度上!一個非標準的超快速解析器在遇到輕微改變的XML文件時會給出錯誤的結果有什麼好處?
如果您可以找到一個XML解析庫,保證與任何可以解析您的數據的硬件解析數據的速度相同,那麼只需實現一個生產者 - 消費者多線程應用程序,其中一個線程不斷從磁盤讀取數據,而另外兩個只是解析。最後,您只能在保持兼容性的同時受到HDD速度的限制。如果你只是在尋找速度,你可能會犯錯,跳過XML功能,取決於你正在處理的示例XML文件的某些特殊性。您的應用程序可能因多種原因而中斷。
請記住,應用程序最昂貴的週期是維護,而不是生產。當計算機速度提高50%(使競爭對手無能爲力)時,如果將速度提高50%,難度維持在200%以上,那麼今後可能會失去一年左右的時間。此外,對於這樣的流程,例如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.
你如何測試這個,你想要多快?我的意思是,即使你只是從磁盤讀取8Mb,對於8Mb沒有任何幫助,115ms是相當不錯的。如果你能夠快速解析硬盤可以讀取的內容,它會對你有什麼好處? –
時間如下:從磁盤加載爲Unicode字符串:26ms,解析節點邊界:7ms,加載節點並填充數據記錄:76ms。這是在一臺i5 4Ghz計算機上,從ramdisk加載XML文件。然而,磁盤加載部分在這裏沒有任何含義,我需要改進分析過程。 – hikari
「加載節點」(c)你需要_load_節點嗎?似乎你應該使用SAX模型解析器,而不需要像DOM模型解析器那樣的「真實加載」節點。 無論如何,至於我,155ms是一個非常好的速度。您可以嘗試使用OmniXML或NativeXML解析器,但我不知道它們是否實現了SAX-model – teran