【问题标题】:Parsing a very large text file on Windows在 Windows 上解析一个非常大的文本文件
【发布时间】:2012-01-24 23:59:36
【问题描述】:

我有一个 2GB 的文本文件和一个 500MB 的文本文件。 2GB 的格式有点笨拙:例如示例:

光盘 15 IG ABH NU 1223 ** 光盘 17 IG RFT NU 3254 **

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

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

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

我知道一点 VBScript,没有 Perl,没有 .NET,没有 C# 等。我怎样才能编写一个基于 Windows 的程序,该程序将在本地运行,一次加载一行文件,并且不会由于文件大小?

【问题讨论】:

  • 如果你使用fgets(),这是否意味着你知道C?

标签: windows perl vbscript large-files


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    下面声明了一个 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"
    

    【讨论】:

    • OP的算法有两个输入文件,你只读取一个?
    • 好的,这实际上满足了 50% 的要求。后一个组件;给定一张 CD 找到匹配的 NU 记录。既然我已经确定了 File 对象的 OpenTextFile、ReadLine、WriteLine 和 AtEndOfStream 方法,这将是一个相当简单的练习。其余的只是管道。
    【解决方案3】:

    与 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));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-28
      • 2015-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-15
      • 1970-01-01
      • 2015-09-22
      相关资源
      最近更新 更多