yield是什么
生成器是PHP 5.5.0才引入的功能, 生成器函数看上去就像一个普通函数,除了不是返回一个值之外,
生成器会根据需求产生更多的值.
yield特性
- yield只能用于函数内部,在非函数内部运用会抛出错误.
- 如果函数包含了yield关键字的,那么函数执行后的返回值永远都是一个Generator对象.
- 如果函数内部同时包含yield和return,该函数的返回值依然是Generator对象,但是在生成Generator对象时,return语句后的代码被忽略.
- Generator类实现了Iterator接口.
- 可以通过返回的Generator对象内部的方法,获取到函数内部yield后面表达式的值.
- 可以通过Generator的send方法给yield 关键字赋一个值.
- 一旦返回的Generator对象被遍历完成,便不能调用他的rewind方法来重置
- Generator对象不能被clone关键字克隆
yield优点
- 生成器会对PHP应用的性能有非常大的影响
- PHP代码运行时节省大量的内存
- 比较适合计算大量的数据
概念引入
- eg1
function createRange($number){ $data = []; for($i=0;$i<$number;$i++){ $data[] = time(); } return $data; } $result = createRange(10); // 这里调用上面我们创建的函数 foreach($result as $value){ sleep(1);//这里停顿1秒,我们后续有用 echo $value.'<br />'; }
- 代码解读
- 我们创建一个函数.
- 函数内包含一个 for 循环,我们循环的把当前时间放到$data里面
- for循环执行完毕,把 $data 返回出去
- createRange 函数内的 for 循环结果被很快放到 $data 中,并且立即返回.所以, foreach 循环的是一个固定的数组.
- eg2
function createRange($number){ for($i=0;$i<$number;$i++){ yield time(); } } $result = createRange(10); // 这里调用上面我们创建的函数 foreach($result as $value){ sleep(1); echo $value.'<br />'; }
- 代码解读
- createRange函数,传入参数10,但是for值执行了一次然后停止了,并且告诉 foreach 第一次循环可以用的值.
- foreach开始对$result循环,进来首先 sleep(1) ,然后开始使用 for 给的一个值执行输出.
- foreach准备第二次循环,开始第二次循环之前,它向for循环又请求了一次.
- for循环于是又执行了一次,将生成的时间戳告诉foreach .
- foreach拿到第二个值,并且输出.由于foreach中sleep(1) ,所以,for循环延迟了1秒生成当前时间
- createRange的值不是一次性快速生成,而是依赖于foreach循环.foreach循环一次,for执行一次.
实际应用
- 读取超大文件
- 百万级别的访问量
Iterator接口
会发现当对象被foreach的时候, 内部的valid,current,key方法会依次被调用,
其返回值便是foreach语句的key和value.循环的终止条件则根据valid方法的返回而定.
如果返回的是true则继续循环, 如果是false则终止整个循环, 结束遍历.当一次循环体结束之后,
将调用next进行下一次的循环直到valid返回false.而rewind方法则是在整个循环开始前被调用,
这样保证了多次遍历得到的结果都是一致的
- 源码
Iterator extends Traversable { /* Methods */ abstract public mixed current ( void ) //返回当前位置的元素 abstract public scalar key ( void ) //返回当前元素对应的key abstract public void next ( void ) //移到指向下一个元素的位置 abstract public void rewind ( void ) //倒回到指向第一个元素的位置 abstract public boolean valid ( void ) //判断当前位置是否有效 }
- eg
class Number implements Iterator{ protected $i = 1; protected $key; protected $val; protected $count; public function __construct(int $count){ $this->count = $count; echo "第{$this->i}步:对象初始化.\n"; $this->i++; } public function rewind(){ $this->key = 0; $this->val = 0; echo "第{$this->i}步:rewind()被调用.\n"; $this->i++; } public function next(){ $this->key += 1; $this->val += 2; echo "第{$this->i}步:next()被调用.\n"; $this->i++; } public function current(){ echo "第{$this->i}步:current()被调用.\n"; $this->i++; return $this->val; } public function key(){ echo "第{$this->i}步:key()被调用.\n"; $this->i++; return $this->key; } public function valid(){ echo "第{$this->i}步:valid()被调用.\n"; $this->i++; return $this->key < $this->count; } } $number = new Number(5); echo "start...\n"; foreach ($number as $key => $value){ echo "{$key} - {$value}\n"; } echo "...end...\n";
Generator类
- 源码
Generator implements Iterator { /* 方法 */ // 返回当前产生的值 public current ( void ) : mixed // 返回当前产生的键 public key ( void ) : mixed // 生成器继续执行 public next ( void ) : void // 重置迭代器 public rewind ( void ) : void // 向生成器中传入一个值 public send ( mixed $value ) : mixed // 向生成器中抛入一个异常 public throw ( Exception $exception ) : void // 检查迭代器是否被关闭 public valid ( void ) : bool // 序列化回调 public __wakeup ( void ) : void }
传统解决方法: 内存溢出时报 Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) 错误,以往通过php.ini或者ini_set() 来设置一个进程的更大内存,虽然能解决大内存问题但不利于接口的性能。
yield原理:假如有100条数据 用foreach就是一次性把数据放到一个数组,内存长度占100。 用yield是一条一条放进去,而且每次放完一条就“销毁”,然后总内存是一条,内存长度只占1。可以想象yield有点类似 return
常见使用场景
用法1,处理一个返回值或一个键值对
// yield
function createRange($number){
for($i=1;$i<$number;$i++){
yield $i => time();
}
}
$result = createRange(10); // 这里返回生成器对象(里面是我们要的数组data,但内存只占1)
foreach($result as $k=>$value){
echo $value.$k;
}
用法2,处理sql查询出来的数组
public static function yieldData($data){
foreach ($data as $datum){
yield $datum;
}
}
$res = Db()->select();
$list = self::yieldData($res);
// 逻辑处理
foreach($list as $k => &$v){}
// 导出等等
Common::csv($list)
用法3,文件读取/导入功能
function readTxt($path)
{
$handle = fopen($path, 'rb');
while (feof($handle)===false) {
yield fgets($handle);
}
fclose($handle);
}
foreach (readTxt() as $key => $value) {
// 逻辑处理
}
总结
yield生成器允许你 在 foreach 代码块中写代码来迭代一组数据,而不需要在内存中创建一个数组,极大提高了进程的内存使用率