本文将介绍:



  1. 参数个数不定的函数

  2. 查找文件

  3. 内存使用信息

  4. CPU使用信息

  5. 魔法常量

  6. 生成唯一的ID

  7. 序列化

  8. 压缩字符串

  9. 注册关闭函数



1、参数个数不定的函数

你可能已经知道了PHP允许你定义可能参选的函数。但它还是一个接受完整参数个数的方法。


首先,这里有一个可选参数的例子:



// 2个可选参数的函数
function foo($arg1 = '', $arg2 = '') {
echo "参数1: $arg1\n";
echo "参数2: $arg2\n";
}
foo('hello','lorui');
/* 输出:
参数1: hello
参数2: lorui
*/
foo();
/* 输出:
参数1:
参数2:
*/

现在一起看看,如何创建一个接受任意参数个数的函数。这次我们使用func_get_args()函数:



// 是的,参数列表为空
function foo() {
// 将所有传递过来的参数以数组返回
$args = func_get_args();
foreach ($args as $k => $v) {
echo "参数".($k+1).": $v\n";
}
}
foo();
/* 没有输出 */
foo('lorui.com');
/* 输出
参数1: lorui.com
*/
foo('lorui.com', '8310.org', '8ely.com');
/* prints
参数1: lorui.com
参数2: 8310.org
参数3: 8ely.com
*/


2、使用Glob()来查找文件

很多PHP的函数都有一个很长且富有描述性的名字。但是,除非你从其它地方了解到了Glob(),不然你很难知道它的名字所包含的意思


你可以把它视为scandir()函数的增强版。它可以让你使用规则来查找文件:



// 获取所有PHP文件
$files = glob('*.php');
print_r($files);
/* 输出结果看起来像这样:
Array
(
[0] => info.php
[1] => lorui.php
[2] => 8310_org.php
[3] => 8ely_com.php
)
*/

你可以这样来获取多种文件:



// 获取所有PHP文件和txt文件
$files = glob('*.{php,txt}', GLOB_BRACE);
print_r($files);
/* 输出结果看起来像这样:
Array
(
[0] => info.php
[1] => lorui.php
[2] => 8310_org.php
[3] => 8ely_com.php
[4] => ling_in.txt
[5] => sisi_biz.txt
)
*/

注意,根据你的查询,这些文件可以带路径返回:



$files = glob('../images/*_com.jpg');
print_r($files);
/* 输出结果看起来像这样:
Array
(
[0] => ../images/lorui_com.jpg
[1] => ../images/81ly_com.jpg
)
*/

如果你想获得每个文件的完整路径,只要让每个返回值调用realpath()函数即可:



$files = glob('../images/*_com.jpg');
// applies the function to each array element
$files = array_map('realpath',$files);
print_r($files);
/* 输出结果看起来像这样:
Array
(
[0] => /var/www/lorui.com/images/lorui_com.jpg
[1] => /var/www/lorui.com/images/81ly_com.jpg
)
*/


3、内存使用信息

通过监测脚本内存使用情况,可以将代码优化的更好。


PHP提供了一个垃圾收集器(GC,garbage collector)和一个非常复杂的内存管理器。我们可以通过memory_get_usage()来获取当前内存使用情况,同时可以通过memory_get_peak_usage()函数获取内存使用的最高点。



echo "初始: ".memory_get_usage()." 字节 \n";
/* 输出
初始: 361400 字节
*/
// 增加一些内存
for ($i = 0; $i < 100000; $i++) {
$array []= md5($i);
}
// 删除数组
for ($i = 0; $i < 100000; $i++) {
unset($array[$i]);
}
echo "结束: ".memory_get_usage()." 字节 \n";
/* 输出
结束: 885912 字节
*/
echo "峰值: ".memory_get_peak_usage()." 字节 \n";
/* 输出
峰值: 13687072 字节
*/


4、CPU使用信息

这次,我们使用getrusage()函数。注意,这个函数在Windows平台无法使用。


    print_r(getrusage());  
/* 输出
Array
(
[ru_oublock] => 0
[ru_inblock] => 0
[ru_msgsnd] => 2
[ru_msgrcv] => 3
[ru_maxrss] => 12692
[ru_ixrss] => 764
[ru_idrss] => 3864
[ru_minflt] => 94
[ru_majflt] => 0
[ru_nsignals] => 1
[ru_nvcsw] => 67
[ru_nivcsw] => 4
[ru_nswap] => 0
[ru_utime.tv_usec] => 0
[ru_utime.tv_sec] => 0
[ru_stime.tv_usec] => 6269
[ru_stime.tv_sec] => 0
)
*/

如果你没有系统管理的背景,那么这看起来似乎有点神秘。这里对所有的值做个解释(你不需要强记它们):



  • ru_oublock:输出块操作

  • ru_inblock:输入块操作

  • ru_msgsnd:发送信息

  • ru_msgrcv:接收信息

  • ru_maxrss:最大驻留集大小

  • ru_ixrss:整体共享内存的大小

  • ru_idrss:整体未共享数据的大小

  • ru_minflt:页面回收

  • ru_majflt:页面错误

  • ru_nsignals:接收到的信号

  • ru_nvcsw:主动上下文切换

  • ru_nivcsw:非主动上下文切换

  • ru_nswap:交换

  • ru_utime.tv_usec:用户使用时间(毫秒)

  • ru_utime.tv_sec:用户使用时间(秒)

  • ru_stime.tv_usec:系统使用时间(毫秒)

  • ru_stime.tv_sec:系统使用时间(秒)


我们需要通过“用户使用时间”和“系统使用时间”的值来确定脚本使用的CPU资源。默认情况下,通过秒和毫秒分别提供。你可以将毫秒值除1000000然后加上秒值,来获取总秒数。


来看一个例子:



// 延迟3秒
sleep(3);
$data = getrusage();
echo "用户使用时间: ".
($data['ru_utime.tv_sec'] +
$data['ru_utime.tv_usec'] / 1000000);
echo "系统使用时间: ".
($data['ru_stime.tv_sec'] +
$data['ru_stime.tv_usec'] / 1000000);
/* 输出
用户使用时间: 0.011552
系统使用时间: 0
*/

即使脚本运行了大约3秒钟,CPU的使用还是非常非常少。因为延迟(sleep())过程时,脚本实际上并不消耗CPU资源。还有许多像等待磁盘操作这样的任务,可能需要实时运行,但可能不会使用CPU。因此,就像你看到的那样,CPU的使用率和运行时长并不总是相同。


这是另一个例子:



// 循环10000000次 (忙)
for($i=0;$i<10000000;$i++) {
}
$data = getrusage();
echo "用户使用时间: ".
($data['ru_utime.tv_sec'] +
$data['ru_utime.tv_usec'] / 1000000);
echo "系统使用时间: ".
($data['ru_stime.tv_sec'] +
$data['ru_stime.tv_usec'] / 1000000);
/* 输出
用户使用时间: 1.424592
系统使用时间: 0.004204
*/

花了大概1.4秒的CPU时间,几乎所有都是用户使用的时候,因为没有系统调用。


系统使用时间是指CPU花费在程序调用系统内核的时间。这是一个例子:


    
$start = microtime(true);
// 持续调用microtime大概3秒种
while(microtime(true) - $start < 3) {
}
$data = getrusage();
echo "用户使用时间: ".
($data['ru_utime.tv_sec'] +
$data['ru_utime.tv_usec'] / 1000000);
echo "系统使用时间: ".
($data['ru_stime.tv_sec'] +
$data['ru_stime.tv_usec'] / 1000000);
/* 输出
用户使用时间: 1.088171
系统使用时间: 1.675315
*/

现在有相当多的系统使用时间。这是因为脚本多次通过调用microtime()函数来获取操作系统获取时间。


足间舞ROOM


5、魔法常量

PHP提供了实用的魔法常量来获取当前的行(__LINE__)、文件路径(__FILE__)、目录路径(__DIR__)、函数名(__FUNCTION__)、类名(__CLASS__)、方法名(__METHOD__)和命名空间(__NAMESPACE__)。


本文不会逐个介绍这些魔法常量,而是展示一些常用案例。


当包含另一个脚本时,使用__FILE__常量是个好主意(或者PHP 5.3的__DIR__):



// 相对于当前加载的脚本路径
// 当从不同的目录运行脚本时,它可能出现问题
require_once('config/database.php');
// 永远相对于文件的路径
// 无论在哪里,都能正确地包含
require_once(dirname(__FILE__) . '/config/database.php');

__LINE__让调试更容易,你可以跟踪行号:



// 一些代码
// ...
my_debug("一些调试信息", __LINE__);
/* 输出
行 4: 一些调试信息
*/
// 另一些代码
// ...
my_debug("另一些调试信息", __LINE__);
/* 输出
行 11: 另一些调试信息
*/
function my_debug($msg, $line) {
echo "行 $line: $msg\n";
}


6、生成唯一的ID

有时侯需要生成一个唯一字符串。我看到许多人用md5()函数,虽然它并不能完全解决这个问题:



// 生成唯一字符串
echo md5(time() . mt_rand(1,1000000));

其实有一个名叫uniqid()的PHP函数,它的名字就透露出它就是用来解决这个问题的:



// 生成唯一字符串
echo uniqid();
/* 输出
4bd67c947233e
*/
// 生成另一个唯一字符串
echo uniqid();
/* 输出
4bd67c9472340
*/

你可能注意到了,即使字符串是唯一的,但它们的前几个字符是相同的。这是因为生成的字符串和服务器的时间有关。这带来一个很好的用处,可以对生成的ID进行排序。


为了减少出错的可能,你可以传递一个前缀,或者传递第二个参数增加额外的熵:



// 前缀
echo uniqid('lorui_');
/* 输出
lorui_4bd67d6cd8b8f
*/
// 额外的熵
echo uniqid('',true);
/* 输出
4bd67d6cd8b926.12135106
*/
// 前缀和额外的熵
echo uniqid('lorui_',true);
/* 输出
lorui_4bd67da367b650.43684647
*/

此函数会生成比md5()更短的字符串,节省一些空间。



7、序列化

是否有将一个复杂的变量存储到数据库或文本文件中的需求?你不需要设计一个将数组或对象转换成格式化字符的解决方案,因为PHP已经有函数实现此功能。


有两个函数来解决变量的序列化问题。这是一个使用serialize()unserialize()的例子:



// 一个数组
$myvar = array(
'hello',
42,
array(1,'two'),
'apple'
);
// 转换为字符串
$string = serialize($myvar);
echo $string;
/* 输出
a:4:{i:0;s:5:"hello";i:1;i:42;i:2;a:2:{i:0;i:1;i:1;s:3:"two";}i:3;s:5:"apple";}
*/
// 恢复原始变量
$newvar = unserialize($string);
print_r($newvar);
/* 输出
Array
(
[0] => hello
[1] => 42
[2] => Array
(
[0] => 1
[1] => two
)
[3] => apple
)
*/

这是原生的PHP序列化方法。近年来,JSON已经倍受欢迎,于是PHP 5.2中添加了对JSON的支持。所以,你可以使用json_encode()json_decode()函数来实现:



// 一个数组
$myvar = array(
'hello',
42,
array(1,'two'),
'apple'
);
// 转换为字符串
$string = json_encode($myvar);
echo $string;
/* 输出
["hello",42,[1,"two"],"apple"]
*/
// 恢复原始变量
$newvar = json_decode($string);
print_r($newvar);
/* prints
Array
(
[0] => hello
[1] => 42
[2] => Array
(
[0] => 1
[1] => two
)
[3] => apple
)
*/

它更紧凑。最重要的是,它和Javascript及其它很多语言兼容。但是,对于复杂的对象,可能造成一些信息丢失。



8、压缩字符串

当谈及压缩时,人们常常想到的是诸如zip等存档文件。这里要谈的是在PHP里压缩长字符串,而与任何存档文件无关。


在接下来的例子里,将使用gzcompress()函数和gzuncompress()函数:



<?php
$string =
"为何梦见他
那好久好久以前分手的男孩
又来到我梦中
为何梦见他
这男孩在我日记簿里早已不留下痕迹
为何梦见他
为何梦中她的眼神却依然叫我心跳
啊 为何梦见他
为何当我迷蒙醒来却含着眼泪
为何梦见他
那好久好久以前分手的男孩
又来到我梦中
为何梦见他
这男孩在我日记簿里早已不留下痕迹";
$compressed = gzcompress($string);
echo "原始大小: ". strlen($string)."\n";
/* 输出
原始大小: 408
*/
echo "压缩后的大小: ". strlen($compressed)."\n";
/* prints
压缩后的大小: 198
*/
// 还原
$original = gzuncompress($compressed);

上例中,压缩率达到了50%。通过使用不同的算法,可以达到不同的压缩率。



9、注册关闭函数

register_shutdown_function()函数允许你在脚本运行完成前执行一些代码。

假设你想在脚本结束运行时捕捉一些基准统计,比如该脚本运行了多久:



// 启动时间
$start_time = microtime(true);
// 做其它的事
// ...
// 显示脚本运行时间
echo "脚本执行时间: ".
(microtime(true) - $start_time).
" 秒.";

它看起来很正常。你只需要把代码加到脚本的最底部,它就会在结束前运行它。但是,如果你(在脚本其它地方)调用了exit()函数,这段代码将永远不会执行。同样的,如果发生错误,或者脚本被用户中断(按下浏览器的“停止”按键),它可能也不会运行。


当你使用了register_shutdown_function(),无论脚本因为什么而停止运行,你的代码都将执行:


    
$start_time = microtime(true);
register_shutdown_function('my_shutdown');
// 其它代码
// ...
function my_shutdown() {
global $start_time;
echo "脚本执行时间: ".
(microtime(true) - $start_time).
" 秒.";
}

上一篇 下一篇