2012-01-24 39 views
2

我有一个2GB的文本文件和一个500MB的文本文件。这个2GB的格式稍微不明显:例如样本:在Windows上解析一个非常大的文本文件

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

其中**是记录之间的标记。

我需要提取NU的所有值,其中CD是一个特定的值;然后我需要浏览500MB的文本文件,然后用2GB文件中的NU值匹配其中的所有记录,然后将它们写入新文件。

我知道PHP。除了文件的大小外,这在PHP中是微不足道的。即使使用fgets一次读取一行也不会真正起作用,因为它需要耗费时间,然后在本地主机上崩溃我的计算机(在XAMPP下apache.exe增长以用完所有系统内存)。另外在PHP中做这件事会很痛苦(非技术人员需要运行,所以当他们每周都可用时,他们需要从FTP服务器下载2GB和500MB;将它们上传到我的FTP服务器,在这样大的文件大小;运行一个脚本在我的服务器,需要年龄等)。

我知道一点VBScript,没有Perl,没有.NET,没有C#等。我如何编写一个基于Windows的程序,将在本地运行,一次加载文件一行,而不是因为文件大小?

+0

如果你使用'与fgets()',这是否意味着你懂C? –

回答

0

以下声明一个VBScript函数以一次读源文件1线和写入目标文件仅当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" 
+0

太棒了!非常感谢! – Apemantus

+0

OP的算法有两个输入文件,但你只能读一个? – ikegami

+0

好的,这实际上满足了50%的要求。后一部分;给CD找到匹配的NU记录。现在我已经确定了File对象的OpenTextFile,ReadLine,WriteLine和AtEndOfStream方法,这将是一个相当直接的练习。其余的只是管道。 –

2

下面将创建一个散列(一种关联数组),其中每个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

基本上相同的作为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)); 
}