登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP XMLWriter 怎样分段取出内存缓冲区的内容

来源:17golang原创

时间:2026-11-10 08:15:50 189浏览 收藏

需要把大量记录写成 XML 时,XMLWriter 的内存模式可以按批次取出已经生成的字符串。关键不是“每写一个标签就调用一次输出”,而是先用 openMemory() 创建内存写入器,再在合适的批次边界调用 outputMemory(true) 或 flush(true)。参数为 true 时,取出的内容会从内部缓冲区移走;如果传入 false,内容仍会保留,下一次取出可能再次包含它。

本文只讨论 XMLWriter 的内存输出,不把中间片段冒充成完整 XML 文档。官方参考地址:https://www.php.net/manual/en/book.xmlwriter.php

先分清 outputMemory 与 flush

XMLWriter::outputMemory(bool $flush = true) 返回当前缓冲区字符串,$flush 控制返回后是否清空缓冲区;XMLWriter::flush(bool $empty = true) 也会刷新当前缓冲区,在内存模式下同样返回生成的 XML 字符串,第二个参数控制是否清空。两者最容易混淆的地方是:方法名不同不代表一个会“自动分批”、另一个不会,真正决定下一批是否重复旧内容的是清空参数。

调用取出结果内部缓冲区适用场景
outputMemory(true)当前 XML 字符串清空分批消费,控制内存
outputMemory(false)当前 XML 字符串保留只读取快照,继续拼同一份结果
flush(true)当前 XML 字符串清空使用 flush 语义输出批次
flush(false)当前 XML 字符串保留查看当前内容但不移动游标
XMLWriter 从内存写入到取出片段再清空后继续写入的流程结构图
图1:XMLWriter 内存模式按批次写入、取出并清空缓冲区的静态说明图,不是运行截图。

按批次取出内存缓冲区

最稳妥的分段模式是把“生成一批元素”和“取出这一批字符串”放在同一个循环边界。下面的示例每两条记录取一次,outputMemory(true) 让已经交给下游的内容不再留在 XMLWriter 的内部缓冲区。

openMemory();
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('items');

$records = [
    ['id' => 101, 'name' => '键盘'],
    ['id' => 102, 'name' => '鼠标'],
    ['id' => 103, 'name' => '显示器'],
    ['id' => 104, 'name' => '耳机'],
];

$chunks = [];
foreach ($records as $index => $record) {
    // 每个 item 是一个完整子元素,避免在元素内部截断。
    $writer->startElement('item');
    $writer->writeAttribute('id', (string) $record['id']);
    $writer->writeElement('name', $record['name']);
    $writer->endElement();

    // 每两条记录取出一次,并清空已交付的缓冲区。
    if (($index + 1) % 2 === 0) {
        $chunks[] = $writer->outputMemory(true);
    }
}

// 处理不足一个批次的尾部记录。
$tail = $writer->outputMemory(true);
if ($tail !== '') {
    $chunks[] = $tail;
}

// 关闭根元素和文档;这部分属于文档收尾,不再当作业务批次。
$writer->endElement();
$writer->endDocument();
$documentEnd = $writer->outputMemory(true);

foreach ($chunks as $chunk) {
    // 这里可以写入队列、响应流或临时文件。
    processXmlChunk($chunk);
}
processXmlChunk($documentEnd);
?>

这个示例的重点是边界:批次中拿到的是已经生成的 XML 片段,根元素尚未闭合,所以它们未必能单独作为完整文档交给严格的 XML 解析器。如果下游要求“每次都是完整 XML”,应让每个批次拥有自己的根元素,或者在业务层先封装后再发送。

为什么 outputMemory(true) 能控制内存

当 true 传给 outputMemory() 时,方法返回当前缓冲区并清空它。下一轮写入从空缓冲区继续,因而适合生成大量数据时把片段写到队列、文件或网络层。它不会替你关闭尚未结束的 XML 元素,也不会替你判断片段是否满足下游协议;它只负责返回并按参数处理当前缓冲区。

调用时机也很重要。刚创建 writer 或刚清空缓冲区时调用,得到空字符串是正常现象;如果在 startElement() 之后立刻取出,拿到的可能只有未闭合的开始标签。把取出点放在一个完整业务元素之后,通常更容易让下游按片段处理。

flush=true 与 flush=false 怎么选

flush(true) 与 outputMemory(true) 都适合“取出后继续写新内容”的分段场景;而 flush(false) 与 outputMemory(false) 更像读取当前快照。后者不会清掉旧内容,若循环中反复读取,返回字符串会包含之前保留的内容,不能直接当作“下一批”。

XMLWriter flush true 和 false 在清空缓冲区、内存增长与文档完整性上的差异结构图
图2:flush 参数对缓冲区清空与分批生成策略的静态结构图,不是运行截图。
openMemory();
$writer->startElement('root');
$writer->writeElement('message', '第一段');

$snapshot = $writer->flush(false);

// 继续写入后,flush(true) 会返回保留的旧内容加上新内容,并清空缓冲区。
$writer->writeElement('message', '第二段');
$chunk = $writer->flush(true);

// 结束元素后再取收尾内容,避免把根元素的关闭标签遗漏。
$writer->endElement();
$writer->endDocument();
$end = $writer->flush(true);

consume($snapshot, $chunk, $end);
?>

如果只是想把同一份完整结果不断追加到一个变量,保留缓冲区可以工作,但内存会随着输出增长。若目标是流式传输或大批量导出,通常应优先清空,并把返回值及时交给下游。

文档完整性要自己设计

XMLWriter 是向前写入器,分段取出并不等于自动生成多个独立文档。像 这样的根元素如果在所有业务数据完成后才关闭,那么中间片段只是一份连续输出流的一部分。可以采用三种策略:

  • 下游接受 XML 片段流:保持一个根元素,按完整子元素分批取出。
  • 下游要求独立文档:每批重新创建 writer,并为每批写入独立根元素。
  • 下游要求一个完整文件:不清空,最终统一取出,或直接使用 openUri() 写入目标 URI。

不要把“已经取到字符串”误认为“已经完成 XML 文档”。尤其是把片段放进消息队列时,要同时传递批次序号、是否首批、是否末批等元数据,消费端才能正确拼接或解析。

常见问题:为什么拿到空字符串或重复内容

为什么刚调用 outputMemory 就是空的

调用时缓冲区可能还没有产生可输出内容,或者上一次调用使用了默认的 true 并已清空。把调用点移到写入完整元素之后,并确认是否有意清空。

为什么每次取出都包含上一批

通常是使用了 outputMemory(false) 或 flush(false)。这两个调用只返回当前内容,不负责删除旧内容;分批消费时改成 true,或在业务层自行记录已处理偏移。

为什么片段无法直接解析

片段可能缺少根元素、关闭标签或 XML 声明。分段输出时要明确下游协议:接受连续片段,还是要求每一批都是独立文档;两者的 writer 生命周期不同。

小结

PHP XMLWriter 分段取内存内容的核心只有两点:先用 openMemory() 建立内存写入器,再根据消费模型选择是否清空。outputMemory(true) 与 flush(true) 适合把已生成片段交给下游并控制内存;false 适合查看快照,但会保留旧内容。最后,缓冲区边界和 XML 文档边界不是一回事,根元素的生命周期必须由业务代码明确管理。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>