2012-01-24 84 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)); 
} 
相關問題