В Powershell, какой самый эффективный способ разбить большой текстовый файл по типу записи? - PullRequest
9 голосов
/ 29 января 2010

Я использую Powershell для некоторых ETL-работ, читаю сжатые текстовые файлы и разбиваю их в зависимости от первых трех символов каждой строки.

Если бы я только фильтровал входной файл, я мог бы передать отфильтрованный поток в Out-File и покончить с этим. Но мне нужно перенаправить вывод более чем в один пункт назначения, и, насколько я знаю, это невозможно сделать с помощью простого канала. Я уже использую .NET streamreader для чтения сжатых входных файлов, и мне интересно, нужно ли мне также использовать потоковую запись для записи выходных файлов.

Наивная версия выглядит примерно так:

while (!$reader.EndOfFile) {
  $line = $reader.ReadLine();
  switch ($line.substring(0,3) {
    "001" {Add-Content "output001.txt" $line}
    "002" {Add-Content "output002.txt" $line}
    "003" {Add-Content "output003.txt" $line}
    }
  }

Это похоже на плохие новости: поиск, открытие, запись и закрытие файла один раз в строке. Входные файлы огромные 500 МБ + монстры.

Существует ли идиоматический способ эффективного управления этими конструкциями с помощью Powershell, или мне следует обратиться к потоковому писателю .NET?

Существуют ли методы объекта (типа "путь" типа "файл"), которые я мог бы использовать для этого?

РЕДАКТИРОВАТЬ для контекста:

Я использую библиотеку DotNetZip для чтения ZIP-файлов в виде потоков; таким образом streamreader, а не Get-Content / gc. Пример кода:

[System.Reflection.Assembly]::LoadFrom("\Path\To\Ionic.Zip.dll") 
$zipfile = [Ionic.Zip.ZipFile]::Read("\Path\To\File.zip")

foreach ($entry in $zipfile) {
  $reader = new-object system.io.streamreader $entry.OpenReader();
  while (!$reader.EndOfFile) {
    $line = $reader.ReadLine();
    #do something here
  }
}

Вероятно, мне следует Dispose() из $ zipfile и $ reader, но это для другого вопроса!

Ответы [ 2 ]

14 голосов
/ 29 января 2010

Чтение

Что касается чтения файла и разбора, я бы пошел с switch оператором:

switch -file c:\temp\stackoverflow.testfile2.txt -regex {
  "^001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $_}
  "^002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $_}
  "^003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $_}
}

Я думаю, что это лучший подход, потому что

  • есть поддержка регулярных выражений, вы не должны сделать подстроку (которая может быть дорогим) и
  • параметр -file довольно удобно;)

Запись

Что касается записи выходных данных, я протестирую использовать streamwriter, однако, если производительность Add-Content для вас достойна, я бы придерживался этого.

Добавлено: Кит предложил использовать оператор >>, однако, похоже, что он очень медленный. Кроме того, он записывает вывод в Unicode, который удваивает размер файла.

Посмотрите на мой тест:

[1]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch ($_.Substring(0,3)) {
>>             '001'{$c >> c:\temp\stackoverflow.testfile.001.txt} `
>>             '002'{$c >> c:\temp\stackoverflow.testfile.002.txt} `
>>             '003'{$c >> c:\temp\stackoverflow.testfile.003.txt}}}
>> }).TotalSeconds
>>
159,1585874
[2]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch ($_.Substring(0,3)) {
>>             '001'{$c | Add-content c:\temp\stackoverflow.testfile.001.txt} `
>>             '002'{$c | Add-content c:\temp\stackoverflow.testfile.002.txt} `
>>             '003'{$c | Add-content c:\temp\stackoverflow.testfile.003.txt}}}
>> }).TotalSeconds
>>
9,2696923

Разница огромна .

Только для сравнения:

[3]: (measure-command {
>>     $reader = new-object io.streamreader c:\temp\stackoverflow.testfile2.txt
>>     while (!$reader.EndOfStream) {
>>         $line = $reader.ReadLine();
>>         switch ($line.substring(0,3)) {
>>             "001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $line}
>>             "002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $line}
>>             "003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $line}
>>             }
>>         }
>>     $reader.close()
>> }).TotalSeconds
>>
8,2454369
[4]: (measure-command {
>>     switch -file c:\temp\stackoverflow.testfile2.txt -regex {
>>         "^001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $_}
>>         "^002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $_}
>>         "^003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $_}
>>     }
>> }).TotalSeconds
8,6755565

Добавлено: мне было интересно узнать производительность письма ... и я был немного удивлен

[8]: (measure-command {
>>     $sw1 = new-object io.streamwriter c:\temp\stackoverflow.testfile.001.txt3b
>>     $sw2 = new-object io.streamwriter c:\temp\stackoverflow.testfile.002.txt3b
>>     $sw3 = new-object io.streamwriter c:\temp\stackoverflow.testfile.003.txt3b
>>     switch -file c:\temp\stackoverflow.testfile2.txt -regex {
>>         "^001" {$sw1.WriteLine($_)}
>>         "^002" {$sw2.WriteLine($_)}
>>         "^003" {$sw3.WriteLine($_)}
>>     }
>>     $sw1.Close()
>>     $sw2.Close()
>>     $sw3.Close()
>>
>> }).TotalSeconds
>>
0,1062315

Это 80 раз быстрее . Теперь вам нужно решить - если скорость важна, используйте StreamWriter. Если ясность кода важна, используйте Add-Content.


Подстрока против регулярного выражения

По словам Кейта Подстрока на 20% быстрее. Это зависит, как всегда. Однако в моем случае результаты выглядят так:

[102]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch ($_.Substring(0,3)) {
>>             '001'{$c | Add-content c:\temp\stackoverflow.testfile.001.s.txt} `
>>             '002'{$c | Add-content c:\temp\stackoverflow.testfile.002.s.txt} `
>>             '003'{$c | Add-content c:\temp\stackoverflow.testfile.003.s.txt}}}
>> }).TotalSeconds
>>
9,0654496
[103]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch -regex ($_) {
>>             '^001'{$c | Add-content c:\temp\stackoverflow.testfile.001.r.txt} `
>>             '^002'{$c | Add-content c:\temp\stackoverflow.testfile.002.r.txt} `
>>             '^003'{$c | Add-content c:\temp\stackoverflow.testfile.003.r.txt}}}
>> }).TotalSeconds
>>
9,2563681

Так что разница не важна, и для меня регулярные выражения более читабельны.

3 голосов
/ 29 января 2010

Учитывая размер входных файлов, вы определенно хотите обрабатывать строку за раз. Я не думаю, что повторное открытие / закрытие выходных файлов будет слишком большим перфомансом. Это, безусловно, делает возможной реализацию с использованием конвейера даже в виде одной строки - на самом деле не слишком отличается от вашей импл Я обернул это здесь, чтобы избавиться от горизонтальной полосы прокрутки:

gc foo.log | %{switch ($_.Substring(0,3)) {
    '001'{$input | out-file output001.txt -enc ascii -append} `
    '002'{$input | out-file output002.txt -enc ascii -append} `
    '003'{$input | out-file output003.txt -enc ascii -append}}}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...