PHP标准库(PHP SPL)详解


Posted in PHP onMarch 16, 2019

什么是SPL?

SPL,PHP 标准库(Standard PHP Library) ,此从 PHP 5.0 起内置的组件和接口,并且从 PHP5.3 已逐渐的成熟。SPL 其实在所有的 PHP5 开发环境中被内置,同时无需任何设置。

似乎众多的 PHP 开发人员基本没有使用它,甚至闻所未闻。究其原因,可以追述到它那阳春白雪般的说明文档,使你忽略了「它的存在」。SPL 这块宝石犹如铁达尼的「海洋之心」般,被沉入海底。而现在它应该被我们捞起,并将它穿戴在应有的位置 ,而这也是这篇文章所要表述的观点。

那么,SPL 提供了什么?

SPL 对 PHP 引擎进行了扩展,例如 ArrayAccess、Countable 和 SeekableIterator 等接口,它们用于以数组形式操作对象。同时,你还可以使用 RecursiveIterator、ArrayObejcts 等其他迭代器进行数据的迭代操作。

它还内置几个的对象例如 Exceptions、SplObserver、Spltorage 以及 splautoloadregister、splclasses、iteratorapply 等的帮助函数(helper functions),用于重载对应的功能。

这些工具聚合在一起就好比是把多功能的瑞士军刀,善用它们可以从质上提升 PHP 的代码效率。那么,我们如何发挥它的威力?

如何使用SPL?

SPL提供了一组标准数据结构:

双向链表

SplDoublyLinkedList

  • SplStack
  • SplQueue

双链表是一种重要的线性存储结构,对于双链表中的每个节点,不仅仅存储自己的信息,还要保存前驱和后继节点的地址。

PHP SPL中的SplDoublyLinkedList类提供了对双链表的操作。

SplDoublyLinkedList类摘要如下:

SplDoublyLinkedList implements Iterator , ArrayAccess , Countable { 
  public __construct ( void )
  public void add ( mixed $index , mixed $newval )
  //双链表的头部节点
  public mixed top ( void )
  //双链表的尾部节点
  public mixed bottom ( void )
  //双联表元素的个数
  public int count ( void )
  //检测双链表是否为空
  public bool isEmpty ( void )
  //当前节点索引
  public mixed key ( void )
  //移到上条记录
  public void prev ( void )
  //移到下条记录
  public void next ( void )
  //当前记录
  public mixed current ( void )
  //将指针指向迭代开始处
  public void rewind ( void )
  //检查双链表是否还有节点
  public bool valid ( void )  
  //指定index处节点是否存在
  public bool offsetExists ( mixed $index )
  //获取指定index处节点值
  public mixed offsetGet ( mixed $index )
  //设置指定index处值
  public void offsetSet ( mixed $index , mixed $newval )
  //删除指定index处节点
  public void offsetUnset ( mixed $index ) 
  //从双链表的尾部弹出元素
  public mixed pop ( void )
  //添加元素到双链表的尾部
  public void push ( mixed $value )  
  //序列化存储
  public string serialize ( void )
  //反序列化
  public void unserialize ( string $serialized )
  //设置迭代模式
  public void setIteratorMode ( int $mode )
  //获取迭代模式SplDoublyLinkedList::IT_MODE_LIFO (Stack style) SplDoublyLinkedList::IT_MODE_FIFO (Queue style)
  public int getIteratorMode ( void )
  //双链表的头部移除元素
  public mixed shift ( void )
  //双链表的头部添加元素
  public void unshift ( mixed $value )
 }

使用起来也比较简单

$list = new SplDoublyLinkedList();
 $list->push('a');
 $list->push('b');
 $list->push('c');
 $list->push('d'); 
 $list->unshift('top');
 $list->shift(); 
 $list->rewind();//rewind操作用于把节点指针指向Bottom所在的节点
 echo 'curren node:'.$list->current()."<br />";//获取当前节点
 $list->next();//指针指向下一个节点
 echo 'next node:'.$list->current()."<br />";
 $list->next();
 $list->next();
 $list->prev();//指针指向上一个节点
 echo 'next node:'.$list->current()."<br />";
 if($list->current())
   echo 'current node is valid<br />';
 else
   echo 'current node is invalid<br />';
 if($list->valid())//如果当前节点是有效节点,valid返回true
   echo "valid list<br />";
 else
   echo "invalid list <br />";
 var_dump(array(
   'pop' => $list->pop(),
   'count' => $list->count(),
   'isEmpty' => $list->isEmpty(),
   'bottom' => $list->bottom(),
   'top' => $list->top()
 ));
 $list->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO);
 var_dump($list->getIteratorMode());
 for($list->rewind(); $list->valid(); $list->next()) {
   echo $list->current().PHP_EOL;
 }
 var_dump($a = $list->serialize());
 //print_r($list->unserialize($a));
 $list->offsetSet(0,'new one');
 $list->offsetUnset(0);
 var_dump(array(
   'offsetExists' => $list->offsetExists(4),
   'offsetGet' => $list->offsetGet(0),
 ));
 var_dump($list);
 //堆栈,先进后出
 $stack = new SplStack();//继承自SplDoublyLinkedList类
 $stack->push("a<br />");
 $stack->push("b<br />");
 echo $stack->pop();
 echo $stack->pop();
 echo $stack->offsetSet(0,'B');//堆栈的offset=0是Top所在的位置,offset=1是Top位置节点靠近bottom位置的相邻节点,以此类推
 $stack->rewind();//双向链表的rewind和堆栈的rewind相反,堆栈的rewind使得当前指针指向Top所在的位置,而双向链表调用之后指向bottom所在位置
 echo 'current:'.$stack->current().'<br />';
 $stack->next();//堆栈的next操作使指针指向靠近bottom位置的下一个节点,而双向链表是靠近top的下一个节点
 echo 'current:'.$stack->current().'<br />';
 echo '<br /><br />';
 //队列,先进先出
 $queue = new SplQueue();//继承自SplDoublyLinkedList类
 $queue->enqueue("a<br />");//插入一个节点到队列里面的Top位置
 $queue->enqueue("b<br />");
 $queue->offsetSet(0,'A');//堆栈的offset=0是Top所在的位置,offset=1是Top位置节点靠近bottom位置的相邻节点,以此类推
 echo $queue->dequeue();
 echo $queue->dequeue();
 echo "<br /><br />";

重载 autoloader

如果你是位「教科书式的程序员」,那么你保证了解如何使用 __autoload 去代替 includes/requires 操作惰性载入对应的类,对不?

但久之,你会发现你已经陷入了困境,首先是你要保证你的类文件必须在指定的文件路径中,例如在 Zend 框架中你必须使用「_」来分割类、方法名称(你如何解决这一问题?)。

另外的一个问题,就是当项目变得越来越复杂, __autoload 内的逻辑也会变得相应的复杂。到最后,甚至你会加入异常判断,以及将所有的载入类的逻辑如数写到其中。

大家都知道「鸡蛋不能放到一个篮子中」,利用 SPL 可以分离 __autoload 的载入逻辑。只需要写个你自己的 autoload 函数,然后利用 SPL 提供的函数重载它。

例如上述 Zend 框架的问题,你可以重载 Zend loader 对应的方法,如果它没有找到对应的类,那么就使用你先前定义的函数。

<?php
class MyLoader {
  public static function doAutoload($class) {
    // 本模块对应的 autoload 操作
  }
}
spl_autoload_register( array('MyLoader', 'doAutoload') );
?>

正如你所见, spl autoload register 还能以数组的形式加入多个载入逻辑。同时,你还可以利用spl autoload unregister 移除已经不再需要的载入逻辑,这功能总会用到的。

迭代器

迭代是常见设计模式之一,普遍应用于一组数据中的统一的遍历操作。可以毫不夸张的说,SPL 提供了所有你需要的对应数据类型的迭代器。

有个非常好的案例就是遍历目录。常规的做法就是使用 scandir ,然后跳过「.「 和 「..」,以及其它未满足条件的文件。例如你需要遍历个某个目录抽取其中的图片文件,就需要判断是否是 jpg、gif 结尾。

下面的代码就是使用 SPL 的迭代器执行上述递归寻找指定目录中的图片文件的例子:

<?php
class RecursiveFileFilterIterator extends FilterIterator {
  // 满足条件的扩展名
  protected $ext = array('jpg','gif');
  /**
   * 提供 $path 并生成对应的目录迭代器
   */
  public function __construct($path) {
    parent::__construct(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)));
  }
  /**
   * 检查文件扩展名是否满足条件
   */
  public function accept() {
    $item = $this->getInnerIterator();
    if ($item->isFile() && 
        in_array(pathinfo($item->getFilename(), PATHINFO_EXTENSION), $this->ext)) {
      return TRUE;
    }
  }
}
// 实例化
foreach (new RecursiveFileFilterIterator('/path/to/something') as $item) {
  echo $item . PHP_EOL;
}
?>

你可能会说,这不是花了更多的代码去办同一件事情吗?那么,查看上面的代码,你不是拥有了具有高度重用而且可以测试的代码了吗 :)

下面是 SPL 提供的其他的迭代器:

  • RecursiveIterator
  • RecursiveIteratorIterator
  • OuterIterator
  • IteratorIterator
  • FilterIterator
  • RecursiveFilterIterator
  • ParentIterator
  • SeekableIterator
  • LimitIterator
  • GlobIterator
  • CachingIterator
  • RecursiveCachingIterator
  • NoRewindIterator
  • AppendIterator
  • RecursiveIteratorIterator
  • InfiniteIterator
  • RegexIterator
  • RecursiveRegexIterator
  • EmptyIterator
  • RecursiveTreeIterator
  • ArrayIterator

自 PHP5.3 开始,会内置其他更多的迭代器,我想你都可以尝试下,或许它能改变你编写传统代码的习惯。

SplFixedArray

SPL 还内置了一系列的数组操作工具,例如可以使用 SplFixedArray 实例化一个固定长度的数组。那么为什么要使用它?因为它更快,甚至它关系着你的工资问题 :)

我们知道 PHP 常规的数组包含不同类型的键,例如数字、字符串等,并且长度是可变的。正是因为这些「高级功能」,PHP 以散列(hash)的方式通过键得到对应的值 -- 其实这在特定情况这会造成性能问题。

而 SplFixedArray 因为是使用固定的数字键,所以它并没有使用散列存储方式。不确切的说,甚至你可以认为它就是个 C 数组。这就是为什么 SplFixedArray 会比通常数组要快的原因(仅在 PHP5.3 中)。

那到底有多快呢,下面的组数据可以让你窥其究竟。

PHP标准库(PHP SPL)详解

如果你需要大量的数组操作,那么你可以尝试下,相信它是值得信赖的。

数据结构

同时 SPL 还提供了些数据结构基本类型的实现 。虽然我们可以使用传统的变量类型来描述数据结构,例如用数组来描述堆栈(Strack)-- 然后使用对应的方式 pop 和 push(arraypop()、arraypush()),但你得时刻小心,·因为毕竟它们不是专门用于描述数据结构的 -- 一次误操作就有可能破坏该堆栈。

而 SPL 的 SplStack 对象则严格以堆栈的形式描述数据,并提供对应的方法。同时,这样的代码应该也能理解它在操作堆栈而非某个数组,从而能让你的同伴更好的理解相应的代码,并且它更快。

最后,可能上述那些惨白的例子还不足矣「诱惑你」去使用 SPL。实践出真知,SPL 更多、更强大的功能需要你自己去挖掘。而它正如宝石般的慢慢雕砌,才能散发光辉。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

PHP 相关文章推荐
destoon网站转移服务器后搜索汉字出现乱码的解决方法
Jun 21 PHP
php 使用file_get_contents读取大文件的方法
Nov 13 PHP
ThinkPHP中处理表单中的注意事项
Nov 22 PHP
php数组冒泡排序算法实例
May 06 PHP
Yii2中如何使用modal弹窗(基本使用)
May 30 PHP
php实现的统计字数函数定义与使用示例
Jul 26 PHP
PHP文件系统管理(实例讲解)
Sep 19 PHP
PHP读取并输出XML文件数据的简单实现方法
Dec 22 PHP
PHP实现的字符串匹配算法示例【sunday算法】
Dec 19 PHP
PHP微信H5支付开发实例
Jul 25 PHP
PHP从零开始打造自己的MVC框架之入口文件实现方法详解
Jun 03 PHP
深入学习微信网址链接解封的防封原理visit_type
Aug 15 PHP
PHP PDO数据库操作预处理与注意事项
Mar 16 #PHP
php生成word并下载代码实例
Mar 15 #PHP
PHP-FPM的配置与优化讲解
Mar 15 #PHP
php-fpm中max_children的配置
Mar 15 #PHP
使用Zookeeper分布式部署PHP应用程序
Mar 15 #PHP
php根据命令行参数生成配置文件详解
Mar 15 #PHP
详解PHP的抽象类和抽象方法以及接口总结
Mar 15 #PHP
You might like
CI操作cookie的方法分析(基于helper类库)
2016/03/28 PHP
用php+ajax新建流程(请假、进货、出货等)
2017/06/11 PHP
php-app开发接口加密详解
2018/04/18 PHP
传智播客学习之JavaScript基础篇
2009/11/13 Javascript
JQuery中对服务器控件 DropdownList, RadioButtonList, CheckboxList的操作总结
2011/06/28 Javascript
select标签模拟/美化方法采用JS外挂式插件
2013/04/01 Javascript
JS getAttribute和setAttribute(取得和设置属性)的使用介绍
2013/07/10 Javascript
jquery利用ajax调用后台方法实例
2013/08/23 Javascript
关于js内存泄露的一个好例子
2013/12/09 Javascript
ztree获取当前选中节点子节点id集合的方法
2015/02/12 Javascript
nodejs连接mongodb数据库实现增删改查
2016/12/01 NodeJs
详解Angular 4.x Injector
2017/05/04 Javascript
微信小程序request出现400的问题解决办法
2017/05/23 Javascript
Node.js连接mongodb实例代码
2017/06/06 Javascript
node.js 发布订阅模式的实例
2017/09/10 Javascript
JavaScript数组去重的多种方法(四种)
2017/09/19 Javascript
使用python验证代理ip是否可用的实现方法
2018/07/25 Python
Python实现的读取文件内容并写入其他文件操作示例
2019/04/09 Python
详解python中eval函数的作用
2019/10/22 Python
Python+OpenCV+图片旋转并用原底色填充新四角的例子
2019/12/12 Python
matlab 计算灰度图像的一阶矩,二阶矩,三阶矩实例
2020/04/22 Python
HTML5 Canvas实现图片缩放、翻转、颜色渐变的代码示例
2016/02/28 HTML / CSS
日本最大的眼镜购物网站:Oh My Glasses
2016/11/13 全球购物
iHerb俄罗斯:维生素、补品和天然产品
2020/07/09 全球购物
自我评价中英文语句
2013/11/30 职场文书
大学生的网上创业计划书
2013/12/31 职场文书
会计自荐信范文
2014/03/09 职场文书
2014年团总支工作总结
2014/11/21 职场文书
房产证明范本
2015/06/19 职场文书
图书借阅制度范本
2015/08/06 职场文书
数据结构课程设计心得体会
2016/01/15 职场文书
微信小程序实现录音Record功能
2021/05/09 Javascript
pytorch 一行代码查看网络参数总量的实现
2021/05/12 Python
MySQL 隔离数据列和前缀索引的使用总结
2021/05/14 MySQL
PHP面试题 wakeup魔法 Ezpop pop序列化与反序列化
2022/04/11 PHP
在Windows Server 2012上安装 .NET Framework 3.5 所遇到的问题
2022/04/29 Servers