Разбор очень большого текстового файла в Windows - PullRequest
2 голосов
/ 25 января 2012

У меня есть текстовый файл 2 ГБ и текстовый файл 500 МБ. 2 ГБ в слегка ненормальном формате: например, образец:

CD 15
IG ABH
NU 1223
**
CD 17
IG RFT
NU 3254
**

Где ** - маркер между записями.

Мне нужно извлечь все значения NU, где CD - это определенное значение; Затем мне нужно пройти через текстовый файл размером 500 МБ, а затем сопоставить все записи в нем со значениями NU из файла 2 ГБ, а затем записать их в новый файл.

Я знаю PHP. Это тривиально в PHP, кроме размера файла. Даже использование fgets для чтения строки за раз, на самом деле не работает, как это требуется навсегда, а затем приводит к сбою моего компьютера на локальном хосте (под XAMPP apache.exe увеличивается, чтобы использовать всю системную память). Кроме того, делать это на PHP было бы непросто (для тех, кто не из тех, кто работает), поэтому им нужно будет загружать 2 ГБ и 500 МБ с FTP-сервера, когда они становятся доступными каждую неделю; загружать их на мой FTP-сервер, который является нестабильным для файлов такого большого размера: запустите на моем сервере скрипт, который занимает много лет и т. д.)

Я немного знаю VBScript, нет Perl, нет .NET, нет C # и т. Д. Как я могу написать программу для Windows, которая будет запускаться локально, загружать файлы по очереди за один раз и не зависать из-за размер файла?

Ответы [ 3 ]

2 голосов
/ 25 января 2012

Следующее создаст хеш (тип ассоциативного массива) с одним (маленьким) элементом для каждого NU, чтобы найти во втором файле. Насколько большим будет этот хеш, зависит от того, сколько у вас соответствующих записей в первом файле.

Если это все еще занимает слишком много памяти, разбейте первый файл на более мелкие части, запустите программу более одного раза и объедините результаты.

use strict;
use warnings;

my $qfn_idx = '...';
my $qfn_in  = '...';
my $qfn_out = '...';

my $cd_to_match = ...;

my %nus;
{
   open(my $fh_idx, '<', $qfn_idx)
      or die("Can't open \"$qfn_idx\": $!\n");

   local $/ = "\n**\n";
   while (<$fh_idx>) {
      next if !( my ($cd) = /^CD ([0-9]+)/m );
      next if $cd != $cd_to_match;
      next if !( my ($nu) = /^NU ([0-9]+)/m );
      ++$nus{$nu};
   }
}

{
   open(my $fh_in, '<', $qfn_in)
      or die("Can't open \"$qfn_in\": $!\n");
   open(my $fh_out, '>', $qfn_out)
      or die("Can't create \"$qfn_out\": $!\n");

   local $/ = "\n**\n";
   while (<$fh_in>) {
      next if !( my ($nu) = /^NU ([0-9]+)/m );
      next if !$nus{$nu};
      print($fh_out $_);
   }
}
0 голосов
/ 25 января 2012

В основном та же идея, что и у ikegami, но с подпрограммой и некоторой удобной обработкой аргументов.

Основная идея - прочитать полную запись, установив в качестве разделителя записи $/ входной разделитель записей "\n**\n", превратить эту запись в хеш, сохранить значения NU и использовать их для последующего поиска , Обратите внимание на использование режима переключения на eof.

Я жестко закодировал ввод для CD, но изменив его на my $CD = shift;, вы сможете сделать:

script.pl 15 CD.txt NU.txt > outputfile

Я не слишком люблю использовать разделитель входных записей, так как он довольно негибкий и чувствителен к повреждению данных, такому как пропущенные символы новой строки в eof. Но до тех пор, пока данные непротиворечивы, проблем не должно быть.

Использование:

script.pl CD.txt NU.txt > outputfile

Где CD.txt - это файл, в который вы извлекаете значения NU для поиска в NU.txt.

Код:

use strict;
use warnings;

my $CD = 15;
my %NU;
my $read = 1;
local $/ = "\n**\n";
while (<>) {
    next unless /\S/; # no blank lines
    my %check = record($_);
    if ($read) {
        if ($check{'CD'} == $CD) {
            $NU{$check{'NU'}}++;
        }
    } else {
        if ($NU{$check{'NU'}}) {
            print;
        }
    }
    $read &&= eof;
}

sub record {
    my $str = shift;
    chomp $str;  # remove record separator **
    return map(split(/ /, $_, 2), split(/\n/, $str));
}
0 голосов
/ 25 января 2012

Следующее объявляет функцию VBScript для чтения исходного файла по одной строке за раз и записи конечного файла, только если строка cdfilter соответствует cd в записи:

Option Explicit

Const ForReading = 1
Const ForWriting = 2

Sub Extract(srcpath, dstpath, cdfilter)
  Dim fso, src, dst, txt, cd, nu
  Set fso = CreateObject("Scripting.FileSystemObject")
  Set src = fso.OpenTextFile(srcpath, ForReading)
  Set dst = fso.OpenTextFile(dstpath, ForWriting, True)
  While (not src.AtEndOfStream)
    txt = ""
    While (not src.AtEndOfStream) and (txt <> "**")
      txt = src.ReadLine
      If Left(txt, 3) = "CD " Then
        cd = mid(txt, 4)
      End If
      If Left(txt, 3) = "NU " Then
        nu = mid(txt, 4)
      End If
      If txt = "**" Then
        If cd = cdfilter Then
          dst.WriteLine nu
          cd = ""
          nu = ""
        End If
      End If
    Wend
  Wend
End Sub

Convert "input.txt", "output.txt", "17"
...