2017-09-20 66 views
2

我有兩個XML文件:A.xml和B.xml。
每個XML包含類似事件的數量:如何在大型XML文件中查找事件對象

<Event> 
    <EventData Name="Time">09/10/2017 12:54:16</EventData> 
    <EventData Name="WorkstationName">USER2-PC</EventData> 
    <EventData Name="UserName">user2</EventData> 
    </Event> 

的值的例子。 。

我要搜索如果在A.XML已在B.XML相同"WorkstationName「和"UserName"值的事件

例如,這是個XML:
A.XML

<?xml version="1.0" encoding="UTF-8"?> 
<Events> 
    <Event> 
    <EventData Name="Time">09/10/2017 12:54:16</EventData> 
    <EventData Name="WorkstationName">USER2-PC</EventData> 
    <EventData Name="UserName">user2</EventData> 
    </Event> 
</Events> 

B.XML

<?xml version="1.0" encoding="UTF-8"?> 
<Events> 
    <Event> 
    <EventData Name="Time">09/10/2017 14:54:16</EventData> 
    <EventData Name="WorkstationName">USER1-PC</EventData> 
    <EventData Name="UserName">user1</EventData> 
    </Event> 
    <Event> 
    <EventData Name="Time">09/10/2017 13:54:16</EventData> 
    <EventData Name="WorkstationName">USER2-PC</EventData> 
    <EventData Name="UserName">user2</EventData> 
    </Event>  
</Events> 

預期結果:

<Event> 
    <EventData Name="Time">09/10/2017 13:54:16</EventData> 
    <EventData Name="WorkstationName">USER2-PC</EventData> 
    <EventData Name="UserName">user2</EventData> 
    </Event> 

我寫的確實是代碼:
算法:

$fileA = "C:\tmp\A.xml" 
$fileB = "C:\tmp\B.xml" 

$a = New-Object Xml.XmlDocument 
$a.Load($fileA) 

$b = New-Object Xml.XmlDocument 
$b.Load($fileB) 

$pc = ($event.EventData | Where-Object {$_.Name -eq "WorkstationName"})."#text" 
$username = ($event.EventData | Where-Object {$_.Name -eq "UserName"})."#text" 

$result = $b.Events.Event | Where-Object { 

(($_.EventData | where-object {$_.Name -eq "WorkstationName"})."#text" -eq $pc) -and 
(($_.EventData | where-object {$_.Name -eq "UserName"})."#text" -eq $username) 

} 

$result.EventData 

問題是,當我與大B.XML文件工作(〜25萬線)。
我寫了一個代碼,將創建兩個XML實例(小A.XML文件和大B.XML文件):

function createXMLFiles($numberOfLinesToCreateInB){ 
    $legitXmlPrefix = @(0xef, 0xbb, 0xbf, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x0d, 0x0a, 0x3c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x3e, 0x0d, 0x0a) 
    $XMLEnd = @(0x0d, 0x0a, 0x3c, 0x2f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x3e) 
    $enc = [system.Text.Encoding]::UTF8 

    $aXML = @" 
    <Event> 
    <EventData Name="Time">09/10/2017 12:54:16</EventData> 
    <EventData Name="WorkstationName">USER2-PC</EventData> 
    <EventData Name="UserName">user2</EventData> 
    </Event> 
"@ 

    $data1 = $enc.GetBytes($aXML) 
    $newXmlFile = "c:\tmp\A.xml" 
    $newArr = $legitXmlPrefix + $data1 + $XMLEnd 
    [io.file]::WriteAllBytes($newXmlFile, $newArr) 

    $bXML = @" 
    <Event> 
    <EventData Name="Time">09/10/2017 14:54:16</EventData> 
    <EventData Name="WorkstationName">USER1-PC</EventData> 
    <EventData Name="UserName">user1</EventData> 
    </Event> 
    <Event> 
    <EventData Name="Time">09/10/2017 13:54:16</EventData> 
    <EventData Name="WorkstationName">USER2-PC</EventData> 
    <EventData Name="UserName">user2</EventData> 
    </Event>  
"@ 


    $newXmlFile = "c:\tmp\B.xml" 
    $data1 = $enc.GetBytes($bXML) 
    $newArr = $legitXmlPrefix 

    $additionals = @" 
    <Event> 
    <EventData Name="Time">09/10/2017 14:54:16</EventData> 
    <EventData Name="WorkstationName">USER1-PC</EventData> 
    <EventData Name="UserName">user1</EventData> 
    </Event> `n 
"@ 


    $data2 = $enc.GetBytes($additionals) 
    if($numberOfLinesToCreateInB -gt 0){ 
     $data2 = $data2 * $numberOfLinesToCreateInB 
     $newArr += $data2 
    } 
    $newArr += $data1 
    $newArr += $XMLEnd 
    [io.file]::WriteAllBytes($newXmlFile, $newArr) 
} 

createXMLFiles 50000 

如果你運行我寫,你會看到,它需要很長時間才能算法在B.XML中從A.XML中查找事件。
這是因爲A.XML中的事件是B.XML中的最後一個事件,所以只有當它遇到B.XML中的最後一個節點時它纔會完成。

有沒有讓它更有效率的選擇?
我雖然也許使用多線程劃分的部分:一個線程將搜索事件0..1000之間,第二將搜索1001..2000等
但也許你有更好的解決方案。

參考文獻:
How can i use XmlReader in PowerShell to stream big/huge XML files?
How to create a new System.Xml.Linq.XElement with PowerShell

EDIT(FAST):
我試着用XPATH。它越過所有的事件時仍能正常工作緩慢:

Select-Xml -Path $fileB -XPath "/Events/Event" | Where-Object { 

    (($events[0].Node.EventData | where-object {$_.Name -eq "WorkstationName"})."#text" -eq $pc) -and 
    (($events[0].Node.EventData | where-object {$_.Name -eq "UserName"})."#text" -eq $username) 

} 

繼@Tomalak的建議,我刪除了幾乎所有的Where-Object管道,事情開始以更快的速度運行。

Measure-Command { 
    Select-Xml -Path $fileB -XPath "/Events/Event" | Where-Object { 

     ($_.Node.EventData[1]."#text" -eq $pc) -and 
     ($_.Node.EventData[2]."#text" -eq $username) 

    } 
} 

Days    : 0 
Hours    : 0 
Minutes   : 0 
Seconds   : 6 
Milliseconds  : 253 
Ticks    : 62535333 
TotalDays   : 7.23788576388889E-05 
TotalHours  : 0.00173709258333333 
TotalMinutes  : 0.104225555 
TotalSeconds  : 6.2535333 
TotalMilliseconds : 6253.5333 

Measure-Command { 
    $result = $b.Events.Event | Where-Object { 
     ($_.EventData[1]."#text" -eq $pc) -and 
     ($_.EventData[2]."#text" -eq $username) 
    } 
} 


Days    : 0 
Hours    : 0 
Minutes   : 0 
Seconds   : 17 
Milliseconds  : 700 
Ticks    : 177006124 
TotalDays   : 0.000204868199074074 
TotalHours  : 0.00491683677777778 
TotalMinutes  : 0.295010206666667 
TotalSeconds  : 17.7006124 
TotalMilliseconds : 17700.6124 

它的工作速度更快,並且還使用-XPath更快(6秒)比普通的傳遞方式(17秒)。
測試是在50,000個事件中,250,000個XML行。

EDIT(非常快):
的結局是這樣的:

Measure-Command { 
    Select-Xml -Path $fileB -XPath "/Events/Event[EventData[@Name = 'WorkstationName'] = '$pc' and EventData[@Name = 'UserName'] = '$username']" 
} 

Days    : 0 
Hours    : 0 
Minutes   : 0 
Seconds   : 0 
Milliseconds  : 609 
Ticks    : 6099484 
TotalDays   : 7.05958796296296E-06 
TotalHours  : 0.000169430111111111 
TotalMinutes  : 0.0101658066666667 
TotalSeconds  : 0.6099484 
TotalMilliseconds : 609.9484 

,這使得它非常快(小於1秒!)。

+0

首先,測量XML文件加載需要多長時間。只需調用'$ b.Load($ fileB)'。如果可以接受的話,切換到使用XPath查找您的節點,而不是Powershell循環,看看是否改善了事情。如果即使加載時間本身是不可接受的,切換到[XMLReader](https://msdn.microsoft.com/en-us/library/system.xml.xmlreader(v = vs.110).aspx)來處理文件,就像你已經建議的那樣([C#examples](https://stackoverflow.com/q/2441673/18771),[Powershell example](https://stackoverflow.com/q/26820590/18771))。 – Tomalak

+0

@Tomalak,用Load()加載大文件很快(1秒,甚至更少)。 – E235

+0

@Tomalak我嘗試使用XPath,它在傳遞所有事件時仍然工作緩慢:'Select-Xml -Path $ fileB -XPath「/ Events/Event」| Where-Object {({$ events [0] .Node.EventData | where-object {$ _。Name -eq「WorkstationName」})。「#text」-eq $ pc)和 (($ events [0] .Node.EventData | where-object {$ _。Name -eq「UserName」})。「#text」-eq $ username)' – E235

回答

1

這種方法的緩慢部分是複雜的PowerShell Where-Object管道。對於大量輸入文檔來說,這不是非常有效,因爲它涉及創建一整套整體的Powershell特定的臨時包裝對象,這些對象實際上可以使用Where-Object

最適合從XML文檔中有效選擇特定節點的工具是XPath。使用Select-Xml cmdlet可以針對XML文檔運行XPath篩選器。

您的代碼:

$result = $b.Events.Event | Where-Object { 
    (($_.EventData | where-object {$_.Name -eq "WorkstationName"})."#text" -eq $pc) -and 
    (($_.EventData | where-object {$_.Name -eq "UserName"})."#text" -eq $username)  
} 

翻譯成英語如下:

    所有 <Event>節點的 <Events>
  • ,挑選那些
    • <EventData>@NameWorkstationName和值$pc AND
    • <EventData>UserName一個@Name$username

它轉換爲XPath的直向前如下值:

$events_by_user_and_pc = " 
    /Events/Event[ 
     EventData[@Name = 'WorkstationName'] = '$pc' and 
     EventData[@Name = 'UserName'] = '$username' 
    ] 
" 

$result = Select-Xml -Path $fileB -XPath $events_by_user_and_pc 

...這看起來非常酷似Powershell代碼。 XPath不關心換行符和空格,所以可以很好地格式化它。


String functions可用於進行部分比較。是選擇任何一臺PC,其@WorkstationName開始與USER2看起來像這樣對一個特定用戶的所有事件的路徑:

/Events/Event[ 
    starts-with(EventData[@Name = 'WorkstationName'], 'USER2') and 
    EventData[@Name = 'UserName'] = '$username' 
]