内容简介:迭代器和生成器
如果让你用 PHP 生成从 1 到100 万个数值,请问怎么做才能最省内存?
没错,这是一道面试题,如果让你写出答案,你会有什么样的思路呢?请先独自思考几分钟。
可能你想到的会是这种方式:
<?php // 我自己本地测试大概用了 34MB 内存 function makeLargeArray($length){ $dataset = []; for ($i = 0; $i < $length; $i++) { $dataset[] = $i; } return $dataset; } $customRange = makeLargeArray(1000000); foreach ($customRange as $i) { echo $i, PHP_EOL; } // 记录使用的内存 function formatBytes($bytes, $precision =2){ $units = array("b", "kb", "mb", "gb", "tb"); $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, $precision) . " " . $units[$pow]; } print formatBytes(memory_get_peak_usage());
你如果给出这种答案的话,肯定不会让面试官满意的。因为这道题的重点是节省内存,如果是如上的程序,虽然能够实现题目的前半部分要求,但实际运行起来却是一个非常吃内存的老虎。因为从 PHP 底层分析, makeLargeArray()
会预先创建一个由一百万个整数组成的数组分配内存!
那么到底要怎么做才能既节省内存,又能实现这道题的目标呢?
正确答案是,使用 PHP生成器 ,代码如下:
<?php // 我本地测试大概用了 718KB 内存 function makeLargeArray($length){ for ($i = 0; $i < $length; $i++) { yield $i; } } foreach (makeLargeArray(1000000) as $i) { echo $i, PHP_EOL; }
很少用 PHP 生成器的同学是不是看不太懂?没关系,本文才刚刚开始。
PHP 迭代器
在讲 PHP 生成器之前,我觉得有必要给大家讲一讲迭代器的概念。如果你对 PHP 迭代器的概念很了解,可以直接跳过这个章节,直接前往文中下半部分。
迭代是指反复执行一个过程,每执行一次叫做一次迭代。这么说你可能不是很理解,事实上,我们每天都在和迭代打交道,就比如 PHP 的 foreach()
函数,像这样:
<?php $home = [ 'bed', 'television', 'computer' ]; foreach ($home as $furniture) { echo 'There is a '.$furniture.'in my home!'; }
上面一个简单的 foreach()
就是一个迭代器,它将 home
一次又一次的遍历,输出三个家具 furniture
。实际上,发生变化的是 home
这个数组,你可以把它当做是一个对象, foreach
在每次遍历它时,都会调用这个对象里的一个方法,让数组在自己内部的指针发生一次变化(迭代)。
所以,我们可以称 home
数组为 迭代器对象,而 foreach
就是一个 迭代器接口( Iterator
)。
PHP 生成器
概念
通过上面的例子,想必大家对生成器有一个大概的理解,先来看看官方的解释(慢点儿度读,好好理解):生成器允许你在 foreach
代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield
多次,以便生成需要迭代的值。
生成器( Generator
)是 PHP5.5 引入的功能,往往没有被大家充分利用。其实这是一个非常有用的功能,我相信大多数开发者和我一样不知道有生成器这种东西,因为平时工作中不常用,其实很简单,生成器就是迭代器,仅此而已。
作用
生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator
接口的方式,性能开销和复杂性大大降低。
生成器的用法
我觉得除了我给的第一个例子,官方给的用例是也能很好的解释生成器的益处。比如 PHP 的一个函数:range ,它可以建立一个包含指定范围单元的数组,标准的 range()
函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000)
将导致内存占用超过 100 MB
。
做为一种替代方法, 我们可以实现一个 xrange()
生成器, 只需要足够的内存来创建 Iterator
对象并在内部跟踪生成器的当前状态,这样只需要不到 1K
字节的内存。
<?php function xrange($start, $limit, $step =1){ if ($start < $limit) { if ($step <= 0) { throw new LogicException('Step must be +ve'); } for ($i = $start; $i <= $limit; $i += $step) { yield $i; } } else { if ($step >= 0) { throw new LogicException('Step must be -ve'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; } } } /* 注意下面range()和xrange()输出的结果是一样的。 */ echo 'Single digit odd numbers from range(): '; foreach (range(1, 9, 2) as $number) { echo "$number "; } echo "\n"; echo 'Single digit odd numbers from xrange(): '; foreach (xrange(1, 9, 2) as $number) { echo "$number "; } // 以上例程会输出: Single digit odd numbers from range(): 1 3 5 7 9 Single digit odd numbers from xrange(): 1 3 5 7 9 ?>
用法
我们需要注意的关键是 yield
,这是生成器的关键。我们通过上面例子,可以看得出, yield
会将当前一个值传递给 foreach
,换句话说, foreach
每一次迭代过程都会从 yield
处取一个值,直到整个遍历过程不再存在 yield
为止的时候,遍历结束。
我们也可以发现, yield
和 return
都会返回值,但区别在于一个 return
是返回既定结果,一次返回完毕就不再返回新的结果,而 yield
是 不断产出 直到无法产出为止。
实际上存在 yield
的函数返回值返回的是一个 Generator
对象(这个对象不能手动通过 new 实例化),该对象实现了 Iterator
接口。
你可能觉得以上例子没啥实际用途,我再举一个比较有用的例子(来自 Modern PHP 一书),比如我想导入一个大小约为 4GB 的 CSV 文件(你可以理解为 Excel),而且我们的服务器运行在一个共享的 VPS 中,只提供了 1 GB 的内存,所以不能直接把 CSV 这个生成的数组直接都放在内存里。那我们怎么用生成器+迭代器来实现呢?代码如下:
// 生成器产出 function getRows($file){ $handel = fopen($file, 'rb'); if ($handel === false) { throw new Exception("Error Processing", 1); } while (feof($handel) === false) { yield fgetcsv($handel); } fclose($handel); } // 迭代器读取 foreach (getRows('./data.csv') as $row) { print_r($row); }
导入导出这种需求在现实场景中是不是很常见,大家可以在项目里尝试使用生成器来取代传统的数组声明,可以帮公司省掉一大笔内存购买费用~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 可迭代对象,迭代器(对象),生成器(对象)
- 搞清楚 Python 的迭代器、可迭代对象、生成器
- 深入理解python的迭代器,生成器,可迭代对象区别
- 生成器与迭代器
- 【重温基础】13.迭代器和生成器
- Python 迭代器、生成器和列表解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
游戏编程权威指南
Mike McShaffry 麦克沙福瑞、David “Rez” Graham 格雷海姆 / 师蓉、李静、李青翠 / 人民邮电 / 2016-3 / 99.00元
全书分为4个部分共24章。首部分是游戏编程基础,主要介绍了游戏编程的定义、游戏架构等基础知识。 第二部分是让游戏跑起来,主要介绍了初始化和关闭代码、主循环、游戏主题和用户界面等。 第三部分是核心游戏技术,主要介绍了一些*为复杂的代码 示例,如3D编程、游戏音频、物理和AI编程等。 第四部分是综合应用,主要介绍了网络编程、多道程序设计和用C#创建工具等,并利用前面所讲的 知识开发出......一起来看看 《游戏编程权威指南》 这本书的介绍吧!