【问题标题】:Break A Large File Into Many Smaller Files With PHP使用 PHP 将一个大文件分成许多小文件
【发布时间】:2011-01-14 18:10:47
【问题描述】:

我有一个 209MB 的 .txt 文件,它包含大约 95,000 行,每周会自动推送到我的服务器一次,以更新我网站上的一些内容。问题是我无法分配足够的内存来处理这么大的文件,所以我想将大文件分成小文件,每个文件 5,000 行。

在文件被分解成更小的部分之前,我根本无法使用 file(),所以我一直在使用 SplFileObject。但我没有得到它。这是我想要完成的一些伪代码:

read the file contents

while there are still lines left to be read in the file
    create a new file
    write the next 5000 lines to this file
    close this file

for each file created
    run mysql update queries with the new content

delete all of the files that were created

文件为 csv 格式。

编辑:这是逐行读取文件的解决方案,给出以下答案:

function getLine($number) {
    global $handle, $index;
    $offset = $index[$number];
    fseek($handle, $offset);
    return explode("|",fgets($handle));
}

$handle = @fopen("content.txt", "r");

while (false !== ($line = fgets($handle))) {
    $index[] = ftell($handle);
}

print_r(getLine(18437));

fclose($handle);

【问题讨论】:

  • 您要进行什么样的处理?通过fopenfgets 读取应该可以正常工作,除非您尝试将其全部存储在一个数组中。
  • 第二个fgets。这样您就可以逐行读取,而无需将整个文件加载到内存中。
  • 我可以使用 fgets 按字节输出文件。我可以使用 fgets 按行号获取文件内容吗?
  • 另外,您可以使用 unix split 命令来拆分文件。可能会快一点。但正如@mfonda 所说,您没有任何理由需要这样做。

标签: php file memory-management pseudocode


【解决方案1】:

如果你的大文件是 CSV 格式,我猜你需要逐行处理它,实际上不需要将它分解成更小的文件。无需一次在内存中保存 5.000 行或更多行!为此,只需使用 PHP 的“低级”文件函数:

$fp = fopen("path/to/file", "r");

while (false !== ($line = fgets($fp))) {
    // Process $line, e.g split it into values since it is CSV.
    $values = explode(",", $line);

    // Do stuff: Run MySQL updates, ...
}

fclose($fp);

如果您需要随机访问,例如逐行读取,您可以为您的文件创建一个“行索引”:

$fp = fopen("path/to/file", "r");

$index = array(0);

while (false !== ($line = fgets($fp))) {
    $index[] = ftell($fp);  // get the current byte offset
}

现在$index 将行号映射到字节偏移量,您可以使用fseek() 导航到一行:

function get_line($number)
{
    global $fp, $index;
    $offset = $index[$number];
    fseek($fp, $offset);
    return fgets($fp);
}

$line10 = get_line(10);

// ... Once you are done:
fclose($fp);

请注意,与文本编辑器不同,我从 0 开始计算行数。

【讨论】:

  • 谢谢,我使用了这个解决方案。
【解决方案2】:

这应该对你有用,我没有一个非常大的文本文件,但我用一个 1300 行长的文件进行了测试,它将文件分成 3 个文件:

    // Store the line no:
    $i = 0;
    // Store the output file no:
    $file_count = 1;
    // Create a handle for the input file:
    $input_handle = fopen('test.txt', "r") or die("Can't open output file.");
    // Create an output file:
    $output_handle = fopen('test-'.$file_count.'.txt', "w") or die("Can't open output file.");

    // Loop through the file until you get to the end:
    while (!feof($input_handle)) 
    {
        // Read from the file:
        $buffer = fgets($input_handle);
        // Write the read data from the input file to the output file:
        fwrite($output_handle, $buffer);
        // Increment the line no:
        $i++;
        // If on the 5000th line:
        if ($i==5000)
        {
            // Reset the line no:
            $i=0;
            // Close the output file:
            fclose($output_handle);
            // Increment the output file count:
            $file_count++;
            // Create the next output file:
            $output_handle = fopen('test-'.$file_count.'.txt', "w") or die("Can't open output file.");
        }
    }
    // Close the input file:
    fclose($input_handle);
    // Close the output file:
    fclose($output_handle);

您现在可能会发现的问题是,当您谈论 200+mb 的文件时,脚本的执行时间太长了。

【讨论】:

    【解决方案3】:
    //MySQL Connection Stuff goes here
    
    $handle = fopen('/path/to/bigfile.txt','r');  //open big file with fopen
    $f = 1; //new file number
    
    while(!feof($handle))
    {
        $newfile = fopen('/path/to/newfile' . $f . '.txt','w'); //create new file to write to with file number
        for($i = 1; $i <= 5000; $i++) //for 5000 lines
        {
            $import = fgets($handle);
            fwrite($newfile,$import);
            if(feof($handle))
            {break;} //If file ends, break loop
        }
        fclose($newfile);
        //MySQL newfile insertion stuff goes here
        $f++; //Increment newfile number
    }
    fclose($handle);
    

    这应该可行,大文件每个文件应该经过 5000 行,并且像 newfile1.txt、newfile2.txt 等输出文件可以通过 for 循环中的$i &lt;= 5000 位进行调整。

    哦,我明白了,您想插入大文件中的数据,而不是存储有关文件的信息。然后只需使用 fopen/fgets 并插入直到 feof。

    【讨论】:

      【解决方案4】:

      您可以使用fgets逐行读取。

      您需要创建一个函数来将读取的内容放入一个新文件中。示例:

      function load(startLine) {
          read the original file from a point startline
          puts the content into new file
      }
      

      在此之后,您可以递归调用该函数,将startline传递给每个阅读循环中的函数。

      【讨论】:

        【解决方案5】:

        如果这是在 linux 服务器上运行,只需让 php 让命令行执行以下命令:

        split -l 5000 -a 4 test.txt out

        然后 glob 可以打开的文件名的结果。


        我认为您的算法很尴尬,看起来您正在无缘无故地分解文件。 如果您只是打开初始数据文件并逐行读取它,您仍然可以执行 mysql 插入,然后只需删除该文件。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-09-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多