如何使用php订购大型csv文件?

时间:2019-05-06 12:30:54

标签: php csv

我正在寻找一种算法策略。我有一个162列和55000行的csv文件。 我想订购一个日期的数据(在第3列)。

首先,我尝试直接将所有内容放入数组,但内存爆炸。

所以我决定: 1 /将前三列放在一个数组中。 2 /使用usort排序此数组 3 /读取csv文件以恢复其他列 4 /在新的csv文件中添加整行 5 /用读取的csv文件上的空字符串替换该行

//First read of the file
while(($data = fgetcsv($handle, 0,';')) !== false)
{
    $tabLigne[$columnNames[0]] = $data[0];
    $tabLigne[$columnNames[1]] = $data[1];
    $tabLigne[$columnNames[2]] = $data[2];

    $dateCreation = DateTime::createFromFormat('d/m/Y', $tabLigne['Date de Création']);



    if($dateCreation !== false)
    {
        $tableauDossiers[$row] = $tabLigne;
    }
    $row++; 

    unset($data);
    unset($tabLigne);
}

//Order the array by date

usort(
    $tableauDossiers,
    function($x, $y) {
        $date1 = DateTime::createFromFormat('d/m/Y', $x['Date de Création']);
        $date2 = DateTime::createFromFormat('d/m/Y', $y['Date de Création']);

        return $date1->format('U')> $date2->format('U');

    }
);


fclose($handle);
copy(PATH_CSV.'original_file.csv', PATH_CSV.'copy_of_file.csv');



for ($row = 3; $row <= count($tableauDossiers); $row++)

{
    $handle = fopen(PATH_CSV.'copy_of_file.csv', 'c+');
    $tabHandle = file(PATH_CSV.'copy_of_file.csv');
    fgetcsv($handle);
    fgetcsv($handle);
    $rowHandle = 2;
    while(($data = fgetcsv($handle, 0,';')) !== false)
    {

        if($tableauDossiers[$row]['Caisse Locale Déléguée'] == $data[0]
                && $tableauDossiers[$row]['Date de Création'] == $data[1]
                && $tableauDossiers[$row]['Numéro RCT'] == $data[2])
        {
            fputcsv($fichierSortieDossier, $data,';');
            $tabHandle[$rowHandle]=str_replace("\n",'', $tabHandle[$rowHandle]);
            file_put_contents(PATH_CSV.'copy_of_file.csv', $tabHandle);
            unset($tabHandle);

            break;
        }
        $rowHandle++;
        unset($data);
        unset($tabLigne);
    }

    fclose($handle);
    unset($handle);
}

该算法确实执行时间太长,但是可以正常工作

有什么想法要改进吗?

谢谢

4 个答案:

答案 0 :(得分:1)

您有大量数据要处理,因此您需要做一些 来对其进行优化。

您可以增加内存,但这只会推迟错误,当文件更大时,它将崩溃(或使waaaayy太慢)。

第一个选择是尝试最小化数据量。从文件中删除所有不相关的列。无论采用哪种解决方案,较小的数据集总是更快。

我建议您将其放入数据库并对其应用需求,然后使用该结果创建一个新文件。建立了一个数据库来管理大型数据集,因此将花费更少的时间。

从PHP接收大量数据并将其写入文件仍然会很慢,但是可以管理。另一种策略可能是使用命令行,使用.sh文件。如果您具有基本的终端/ ssh技能,则具有基本的.sh编写功能。在该文件中,您可以使用mysqldump to export as csv like this。 Mysqldump将显着提高速度,但是当您习惯使用PHP时,操作起来会有些棘手。


要改善当前代码,请执行以下操作:
-第一个结尾处的unset不会做任何有用的事情。它们几乎不会存储数据,并且无论何时开始发出下一个提示时,它们仍然会重置。
-使用纪元值代替所有事情的DateTime(),这更容易使用,但速度较慢。我不知道它现在以什么格式出现,但是如果您使用纪元秒(例如time()的结果),则会有两个数字。您的usort()将大大改善,因为它不再需要使用繁重的DateTime类,而只是简单的数字比较。


这全部假设您需要做多次。如果没有,只需在Excel或Numbers中将其打开,然后使用该排序并将其另存为副本即可。

答案 1 :(得分:1)

假设您仅限于使用PHP,并且不能按照注释中的建议使用数据库来实现它,那么下一个最佳选择是使用external sorting算法。

  1. 将文件拆分为小文件。这些文件应足够小以在内存中对其进行排序。
  2. 将所有这些文件分别在内存中排序。
  3. 通过比较每个文件的第一行将已排序的文件合并为一个大文件。

可以非常高效地完成排序文件的合并:只需在任何给定时间在内存中保存每个文件的第一行。时间戳最小的第一行应转到生成的文件。

对于真正的大文件,您可以层叠合并,即:如果有10,000个文件,则可以先合并100个文件的组,然后合并结果的100个文件。


示例

为了便于阅读,我使用逗号来分隔值而不是换行符。

未排序的文件(想象它太大而无法容纳到内存中):

1, 6, 2, 4, 5, 3

将文件拆分成足够小以适合内存的方式:

1, 6, 2
4, 5, 3

分别对它们进行排序:

1, 2, 6
3, 4, 5

现在合并:

  1. 比较1和3→取1
  2. 比较2和3→取2
  3. 比较6和3→取3
  4. 比较6和4→取4
  5. 比较6和5→取5
  6. 采取6。

答案 2 :(得分:0)

我会将数据加载到数据库中,然后再担心底层算法。

如果这是一次性的问题,我建议不要自动执行,而是使用电子表格。

答案 3 :(得分:0)

我仅在一个小文件上尝试过此操作,但是原理与您读取文件,存储日期然后对其进行排序的想法非常相似。然后读取原始文件并写出排序的数据。

在此版本中,加载仅读取日期并创建一个数组,该数组保存日期和行首在文件中的位置(每次读取后使用ftell()来获取文件指针)

然后对这个数组进行排序(因为日期首先只是使用常规排序)。

然后,它遍历已排序的数组,并且对于每个条目,它使用fseek()在文件中定位记录,并读取行(使用fgets())并将此行写入输出文件。

$file = "a.csv";
$out = "sorted.csv";

$handle = fopen($file, "r");
$tabligne = [];
$start = 0;
while ( $data = fgetcsv($handle) )    {
    $tabligne[] = ['date' => DateTime::createFromFormat('d/m/Y', $data[2]),
        'start' => $start ];
    $start = ftell($handle);
}

sort($tabligne);

$outHandle = fopen( $out, "w" );
foreach ( $tabligne as $entry ) {
    fseek($handle, $entry['start']);
    $copy = fgets($handle);
    fwrite($outHandle, $copy);
}

fclose($outHandle);
fclose($handle);