开发者学堂课程【PHP 进阶教程-由浅入深掌握面向对象开发-第二阶段:Generator 生成器】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/712/detail/12717
Generator 生成器
内容介绍:
一、目标
二、概念
三、步骤
四、示例
五、小结
一、目标
了解生成器的作用和语法,掌握生成器对于内存的优化
二、概念
如果要生成一个复杂的数据,取出或遍历,需要先将其变为二维数组保存,再将二维数组进行 foreach 遍历。但是生成大量的数组需要大量时间并占用大量内存空间,此时对服务器的运行有明显的效率降低。生成器是一种使代码的使用方式变得简单,使内存优化的方法。
生成器: Generator,生成器提供了一种更容易的方法来实现简单的对象迭代
1.相比较定义类实现 lterator 接口的方式,性能开销和复杂性大大降低
2.生成器是一个类 Generator 实现了 lterator 接口,并且实现了 lterator 方法(修改了内部逻辑)
3.生成器是暂停循环执行逻辑,等到使用到的时候才触发循环再次执行。比如一个一万次的循环,当使用了生成器之后执行第一次就停,除非有第二次的输出需求才会执行第二次,一次只取一个,不像数组一次性把一万个全部取到数组里。逻辑是有一个一万次的循环,每次需要把 i 放到数组里存放,此时用到 yield 暂停,如果执行此时只会执行一次。
for($i = 0;$i < 10000; $i++){
#此时循环只执行一次:除非有内容触发循环再次执行(需要 $i)循环才会开始下一次
yield $i;
}
4.yield 关键字代表暂停代码继续向下执行:直到 yield 代码被使用(循环遍历)
5.生成器对象遍历:使用 yield 后,函数就会返回一个 Generator 的对象,此时就可以针对对象进行遍历。只要有了 yield 系统会自动匹配 Generator。
此时需要一个函数让对象返回。需要函数对 yield 进行包装:函数才有返回值。
三、步骤
1、需要使用大规模数据产生(数组或者对象,一般针对数组,因为对象不会很大不会有很多数据和属性,而数组是专门用来解决大数据的问题)
2、需要对数据产生后进行遍历,因为获取就是为了输出
3、为了节省内存的使用:使用生成器
四、示例
1. 需要产生10000个数据的数组并进行遍历
传统实现方式:函数+遍历。先定义一个函数,循环一万次,任何把数据存到数组里,把数组再返回,外部拿到数组后再 foreach 取出来。
function getArr(
)
{
for($i =
0
;$i < 10000; $i++){
$arr[] = $i;
}
return $arr;
}
$arr = getArr
()
;
foreach($arr as $v){
echo $v . ' ';
}
这种方式消耗的内存可以通过此方法获得:
#普通方式进行数组遍历
function getArr(){
for($i = 0;$i < 1000;$i++){
$arr[] = $i;
}
return $arr;
}
echo memory_get_usage() , '
'; #取出当前 PHP 所占用的内存
$arr = getArr();
foreach($arr as $v){
}
echo memory_get_usage(),'
';
结果为
此时表示1000个数据用了37兆多的内存,10000个数据则是用到了几百 k,这是因为数组在内存里存着很占空间。
使用生成器:函数(生成器)+遍历: for 里不用数组来保存,直接 yield $i,到这里就会暂停,然后用 $g 获取内容,获取到了进行遍历。但是遍历不再使用原来的方式。
function getArr
()
{
for($i = 0;$i < 10000;$i++){
yield $i;
}
}
$g = getArr(
)
;
foreach($g as $v){
echo $v . ' ';
}
遍历之前可以打印看到内容,生成器的对象,一定要用函数来做才能得到对应生成器的对象,如果只用 yield 没办法返回。
为避免重复改 Arr 为 Ger:
function getGer({
for($i = 0;$i < 10000;$i++){
yield $i;
}
}
echo memory get_usage(), '
';
$g = getGer();
var_dump($g);
foreach($g as $v){
#echo $v . ' ';
}
echo memory get_usage(), '
';
此时再来看它所占用的内存 可以看到同样10000个数据只占了两百兆。
原因是当我们定义函数运行时函数不会运行,但是调用函数时就会运行,此时运行到 yield 会暂停,所以当拿到 $g 对象的时候函数运行停留在 yield $i; 直到把 Generator 生成器进行遍历,取 $v,这意味着要用一次 yield,给到 $i 之后输出,再进行下一次 foreach。当取出之后系统就会自动执行,再按照函数循环的方式执行,然后到 yield 暂停,等待下一次使用。言外之意在内存里面根本没有10000个数据的数组,始终只有 $i 变量从1到9999,保存一个整数变量和保存10000个元素数组的变量占用的空间完全不同,所以它的核心在于因为 yield 空间被释放。
2. 生成器实际运用
从数据库获取一张表所有记录,然后输出所有记录到表格。正常做法是连接认证,设置字符集,写一个函数 query,再全部放到数组里面。如果记录非常大,就可以使用 yield 来做一次就取一条记录然后暂停,在下一次要的时候调用 query 把数据查出来,然后进行遍历输出。一个 v 就是一个 row,就是一个数组,一个数组里面取出下标,然后取出对应元素。
$conn = @mysq1i_connect(' localhost' , ' root' , ' root ' , 'db_2' , '3306') or die ('数据库连 mysqli_set_charset($conn , 'utf8') or die ('字符集设置失败! ');
function query($conn, $sq1){
$res = mysqli_query($conn,$sq1);
while($row = mysqli_fetch_assoc($res)){
yield $row;
#这样就不需要数组保存大数据了,如果数据量够大会产生很大的消耗
}
}
#遍历输出
$l
ist = query($conn, 'select * from t_40");
echo '';
foreach($list as $v){
echo <<
在里面运用了一个函数来封装了查询操作,本来是给出二位数组,然后遍历成一维再去输出,可以达到同样的效果。
然后 echo memory__get__usage(),"
' ;
可以看到内存多少,大概使用了 16k 的内存
结果为
如果用 yield 的方式来实现:
yield $row; #这样就不需要数组保存大数据了,如果数据量够大会产生很大的消耗
}
# return $list;
}
echo memory_get_usage(o, '
";
结果为
只用了不到2k的内存,实现了内存消耗的减少。也可以做时间的判定,看哪个时间少效率高,用对应的 time 相减。在后续进行大数据的使用时,从数据库取出显示的时候可以使用生成器来优化内存占用。
五、小结
1、生成器是一种实现了 lterator 迭代器接口的类,这种类不需要我们主动调用,它会自动地触发,一旦某一个函数里面用到了 yield 关键字,系统就会自动返回 Generator 的对象,对象就接管了 foreach 的内部运行原理,自己去控制。
2、生成器的目的是利用 yield 关键字实现循环内部的暂停(这种暂停让以前必须把所有东西取出来存放到很大的变量里面变成数组或二位数组的方式被 yield 打断,简化成一个简单变量),而直到 yield 被使用使用循环才会继续执行,从而节省通过循环产生一个大数组的过程(函数内部生成数组本身也是这样内存,但是过程检测不到,因为函数的执行是内部的过程,这个过程把生成大数组,函数生成和外面保存两部分对内存的消耗都解决掉),最终实现内存优化
3、在大型数据展示的时候(数据库数据操作、文件读取)都建议使用生成器来实现内存解析(面试题常问:一个 10G 的文件,但只有 2G 内存,应该怎么读。应该写一个函数,里面用生成器,保证其他程序正常运行,不占用内存)