自学内容网 自学内容网

掌握PHP生成器:中等规模数据集流式处理与CSV导出的开发实战技巧

项目需求

  1. 按照数据模版规范执行如:
$originalData = [
    ['名称' => '南楼_301空调1', '组合名称' => '_电流', '单位类型' => 493, '传感器类型' => 25, '地址号' => 2, '寄存器地址' => 712, '实时数据' => null, '排序' => 1, '公式(上传)' => 'x/1000', '公式(下达)' => null, '配置信息' => '{"fcode":"03","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 490, '指标类别' => 494, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 2, '位置' => 271, '开关记录' => '行政楼', '总高度' => null, '最大值' => 0, '最小值' => null, '数据确认次数' => null],
    ['名称' => '南楼_301空调1', '组合名称' => '_状态', '单位类型' => 508, '传感器类型' => 26, '地址号' => 2, '寄存器地址' => 713, '实时数据' => 1, '排序' => 2, '公式(上传)' => null, '公式(下达)' => null, '配置信息' => '{"fcode":"03","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 484, '指标类别' => 483, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 0, '位置' => 271, '开关记录' => '行政楼', '总高度' => 1, '最大值' => null, '最小值' => null, '数据确认次数' => null],
    ['名称' => '南楼_301空调1', '组合名称' => '_开', '单位类型' => 504, '传感器类型' => 190, '地址号' => 2, '寄存器地址' => 200, '实时数据' => 1, '排序' => 3, '公式(上传)' => null, '公式(下达)' => null, '配置信息' => '{"fcode":"06","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 484, '指标类别' => 524, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 0, '位置' => 271, '开关记录' => '行政楼', '总高度' => 0, '最大值' => null, '最小值' => null, '数据确认次数' => null],
    ['名称' => '南楼_301空调1', '组合名称' => '_关', '单位类型' => 505, '传感器类型' => 190, '地址号' => 2, '寄存器地址' => 201, '实时数据' => 1, '排序' => 4, '公式(上传)' => null, '公式(下达)' => null, '配置信息' => '{"fcode":"06","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 484, '指标类别' => 524, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 0, '位置' => 271, '开关记录' => '行政楼', '总高度' => 0, '最大值' => null, '最小值' => null, '数据确认次数' => null]
];
  1. 根据地址号和排序范围,自动生成对应的数据;
  2. 将生成的数据,导出为csv模版,或者直接入mysql数据库

完整代码

//数据模版规范
$originalData = [
    ['名称' => '南楼_301空调1', '组合名称' => '_电流', '单位类型' => 493, '传感器类型' => 25, '地址号' => 2, '寄存器地址' => 712, '实时数据' => null, '排序' => 1, '公式(上传)' => 'x/1000', '公式(下达)' => null, '配置信息' => '{"fcode":"03","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 490, '指标类别' => 494, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 2, '位置' => 271, '开关记录' => '行政楼', '总高度' => null, '最大值' => 0, '最小值' => null, '数据确认次数' => null],
    ['名称' => '南楼_301空调1', '组合名称' => '_状态', '单位类型' => 508, '传感器类型' => 26, '地址号' => 2, '寄存器地址' => 713, '实时数据' => 1, '排序' => 2, '公式(上传)' => null, '公式(下达)' => null, '配置信息' => '{"fcode":"03","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 484, '指标类别' => 483, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 0, '位置' => 271, '开关记录' => '行政楼', '总高度' => 1, '最大值' => null, '最小值' => null, '数据确认次数' => null],
    ['名称' => '南楼_301空调1', '组合名称' => '_开', '单位类型' => 504, '传感器类型' => 190, '地址号' => 2, '寄存器地址' => 200, '实时数据' => 1, '排序' => 3, '公式(上传)' => null, '公式(下达)' => null, '配置信息' => '{"fcode":"06","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 484, '指标类别' => 524, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 0, '位置' => 271, '开关记录' => '行政楼', '总高度' => 0, '最大值' => null, '最小值' => null, '数据确认次数' => null],
    ['名称' => '南楼_301空调1', '组合名称' => '_关', '单位类型' => 505, '传感器类型' => 190, '地址号' => 2, '寄存器地址' => 201, '实时数据' => 1, '排序' => 4, '公式(上传)' => null, '公式(下达)' => null, '配置信息' => '{"fcode":"06","type":"uint","readType":"0"}', '分组ID' => null, '设备类别' => 484, '指标类别' => 524, 'IP地址' => null, '变化过滤值' => null, '数据精度' => null, '储存策略' => null, '区域' => 0, '位置' => 271, '开关记录' => '行政楼', '总高度' => 0, '最大值' => null, '最小值' => null, '数据确认次数' => null]
];

// 新数据数组
$newData = [];
$sortOrder = 21; // 排序起始值

// 地址号范围
$startAddress = 3;
$endAddress = 6;

// 生成新的数据
for ($address = $startAddress; $address <= $endAddress; $address++) {
    foreach ($originalData as $row) {
        $newRow = [
            '传感器ID' => '',
            '名称' => $row['名称'],
            '组合名称' => $row['组合名称'],
            '单位类型' => $row['单位类型'],
            '传感器类型' => $row['传感器类型'],
            '地址号' => $address,
            '寄存器地址' => $row['寄存器地址'],
            '实时数据' => $row['实时数据'],
            '排序' => $sortOrder,
            '公式(上传)' => $row['公式(上传)'],
            '公式(下达)' => $row['公式(下达)'],
            '配置信息' => $row['配置信息'],
            '分组ID' => $row['分组ID'],
            '设备类别' => $row['设备类别'],
            '指标类别' => $row['指标类别'],
            'IP地址' => $row['IP地址'],
            '变化过滤值' => $row['变化过滤值'],
            '数据精度' => $row['数据精度'],
            '储存策略' => $row['储存策略'],
            '区域' => $row['区域'],
            '位置' => $row['位置'],
            '开关记录' => $row['开关记录'],
            '总高度' => $row['总高度'],
            '最大值' => $row['最大值'],
            '最小值' => $row['总高度'],
            '数据确认次数' => $row['数据确认次数'],
        ];
        $newData[] = $newRow;
        $sortOrder++; // 排序递增
    }
}

/**
 * 获取数据源
 * @param array $data 包含原始数据的数组,是函数处理的基础
 */
function getDataSource(array $data)
{
    foreach ($data as $item) {
        yield $item; // 逐个产生数组中的元素
    }
}

// 将数据写入 CSV 文件
$fp = fopen('output.csv', 'w');
$title = ['传感器ID', '名称', '别称', '单位类型', '传感器类型', '地址号', '寄存器地址', '实时数据', '排序', '公式(上传)', '公式(下达)', '配置信息', '分组ID', '设备类别', '指标类别', 'IP地址', '变化过滤值', '数据精度', '储存策略', '区域', '位置', '开关记录', '总高度', '最大值', '最小值', '数据确认次数'];
fputcsv($fp, $title); // 写入表头

// 使用生成器来逐行处理数据
foreach (getDataSource($newData) as $row) {
    fputcsv($fp, $row);
}

fclose($fp);
echo "数据已成功写入 output.csv 文件。\n";

过程阐释

fputcsv 函数

使用 fputcsv 函数来写入CSV文件是一个常见的做法,对于中等规模的数据集通常是足够的。但是,当处理非常大的数据集时,有一些因素需要考虑:

  1. 内存使用:在循环中直接处理大量数据可能会导致内存消耗增加。如果 $newData 数组非常大,那么将整个数组加载到内存中可能会导致内存不足的问题。

  2. 性能:随着数据量的增加,写入文件的时间也会相应增加。如果数据量极其庞大,可能需要注意脚本执行时间限制(PHP默认的最大执行时间是30秒)。

  3. 锁定问题:如果多个进程同时尝试写入同一个文件,可能会遇到文件锁定的问题。

如果你预计会处理大数据量,可以考虑以下优化措施:

  • 流式处理:如果数据源允许,不要一次性读取所有数据到一个数组中,而是逐行或分批处理数据。
  • 调整PHP配置:你可以通过修改php.ini中的设置来提高脚本执行时间和内存限制,比如max_execution_timememory_limit
  • 分割数据:如果数据量太大,可以考虑将数据分割成多个文件来处理。
  • 错误处理:添加适当的错误处理逻辑,确保在发生异常时能够妥善处理。

这里有一个例子,展示如何使用流式处理来避免将全部数据加载到内存中:

$fp = fopen('output.csv', 'w');
$title = ['传感器ID', '名称', '别称', '单位类型', '传感器类型', '地址号', '寄存器地址', '实时数据', '排序', '公式(上传)', '公式(下达)', '配置信息', '分组ID', '设备类别', '指标类别', 'IP地址', '变化过滤值', '数据精度', '储存策略', '区域', '位置', '开关记录', '总高度', '最大值', '最小值', '数据确认次数'];
fputcsv($fp, $title); // 写入表头

// 假设你有一个函数或者方法可以获取数据流
$dataSource = getDataSource(); // 这应该返回一个迭代器或生成器

foreach ($dataSource as $row) {
    fputcsv($fp, $row);
}

fclose($fp);

function getDataSource() {
    // 实现数据获取逻辑,返回一个迭代器或生成器
    // 例如从数据库查询结果中逐行读取,或者从其他数据源获取
}

在这个例子中,getDataSource 应该被实现为一个返回迭代器或生成器的函数,这样就可以逐步处理数据而不需要将它们全部加载到内存中。如果你是从数据库获取数据,可以利用PDO或MySQLi的游标模式来逐行读取数据。

中等规模的数据集

中等规模的数据集并没有一个严格定义的数量范围,因为它可以根据具体的应用场景、硬件性能和软件实现的不同而有所变化。不过,我们可以给出一个大致的参考范围:

  • 小规模数据集:通常指几千到几万条记录。
  • 中等规模数据集:一般指的是从几十万到几百万条记录。
  • 大规模数据集:可能包含数千万乃至数十亿条记录。

对于PHP脚本处理CSV文件来说,中等规模(例如几十万到几百万条记录)的数据集通常是可以通过适当优化来处理的。但是,如果每条记录包含大量字段或非常大的文本内容,那么即使是中等规模的数据集也可能对内存和执行时间构成挑战。

为了更好地处理这样的数据集,可以采取以下措施:

  1. 增加内存限制:在php.ini中设置更高的memory_limit,或者在脚本开始时使用ini_set('memory_limit', '256M')来临时提高内存限制。

  2. 增加执行时间:在php.ini中设置更高的max_execution_time,或者在脚本开始时使用set_time_limit(0)来取消脚本执行的时间限制。

  3. 流式处理:如前所述,采用逐行处理的方式来减少内存消耗。

  4. 批量处理:将数据分成多个批次处理,并在每个批次后进行适当的清理工作以释放内存。

  5. 错误处理与日志:确保有良好的错误处理机制,以便在出现问题时能够快速定位并解决问题。

  6. 使用更高效的算法:根据具体情况选择合适的算法来处理数据,避免不必要的计算开销。

  7. 多线程/多进程处理:如果条件允许,可以考虑使用多线程或多进程来并行处理数据,加快处理速度。

  8. 数据库优化:如果数据是从数据库获取的,确保查询是经过优化的,并且使用索引等技术来加速数据检索。

总之,对于中等规模的数据集,通过合理的配置和编程技巧,大多数情况下是可以高效处理的。

yield的用法

yield 关键字是 PHP 5.5.0 版本中引入的,用于定义生成器函数。生成器是一种特殊的迭代器,它允许你定义一个可以逐步产生值的函数,而不需要一次性构建整个数组或集合。这使得处理大数据集时非常高效,因为它只在需要时才生成数据。

yield 的基本用法

  1. 创建生成器:使用 yield 关键字可以在函数中返回多个值。
  2. 逐个生成值:每次调用生成器时,它会从上次停止的地方继续执行,直到遇到下一个 yield 语句。
  3. 结束生成:当生成器函数执行完毕或遇到 return 语句时,生成器将停止生成新的值。

示例代码

以下是一个简单的例子,展示如何使用 yield 来创建一个生成器:

function numberGenerator($max) {
    for ($i = 1; $i <= $max; $i++) {
        yield $i;
    }
}

// 使用生成器
foreach (numberGenerator(5) as $number) {
    echo $number . "\n";
}

输出将是:

1
2
3
4
5

在这个例子中,numberGenerator 函数不会立即计算所有数字,而是每次循环时生成一个数字。这样可以节省内存,特别是在处理大量数据时。

生成器的其他功能

  • 传递值给生成器:你可以通过 Generator::send() 方法向生成器发送值,并且该值可以在生成器内部通过 yield 表达式的右侧接收。

示例:

function counter() {
    $count = 0;
    while (true) {
        $receivedValue = (yield $count++);
        if ($receivedValue !== null) {
            $count = $receivedValue;
        }
    }
}

$generator = counter();
echo $generator->current() . "\n"; // 输出 0
$generator->next(); // 跳到下一个值
echo $generator->current() . "\n"; // 输出 1
$generator->send(10); // 发送值 10 到生成器
echo $generator->current() . "\n"; // 输出 10
$generator->next(); // 跳到下一个值
echo $generator->current() . "\n"; // 输出 11
  • 生成器委托:你可以使用 yield from 语法来遍历另一个生成器或可遍历对象。

示例:

function subGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

function mainGenerator() {
    yield from subGenerator();
    yield 4;
    yield 5;
}

foreach (mainGenerator() as $value) {
    echo $value . "\n";
}

支持版本

  • yield 从 PHP 5.5.0 开始支持。
  • yield from 从 PHP 7.0.0 开始支持。

@漏刻有时


原文地址:https://blog.csdn.net/weixin_41290949/article/details/142705382

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!