PHP的日期时间运算总结

 

 

<?php

//GB2312的Encode

header("Cache-Control: no-store, no-cache, must-revalidate");

header("Cache-Control: post-check=0, pre-check=0", false);

 

/*重点了解strtotime()函数

1、strftime比time()好用,可以直接把常用的’2010-02-03‘转成时间戳。

2、date()可以显示1970年前的时间。而不必用负数做参数2

3、日期计算可以用时间戳来中转。计算两个日期相差的天数,可以取得相差的时间戳后除以“24小时*60分*60”秒来得到,但用strtotime()更简洁、

4、了解用PEAR创建日历。这里略去。

知识点:网络上有关于date('Y-m-d',-800)来计算1970年前的时间,但WINDOW系统不支持负值,因此总会返回1970-1-1子夜。

*/

 

#PHP5必须先设置默认区。

date_default_timezone_set('ETC/GMT-8');

$nowdate='2010-02-23';

$lassdate = '2010-02-22';

 

echo 'strftime()函数输出的'.strftime('%Y-%m-%d %H:%M:%S',time()).'<br />';

echo 'date()函数输出的'.date('Y-m-d H:i:s',time()).'<br />';

//检查日期:boolean checkdate(int month,int day,int year)

$d='2010-2-31';

echo $d.'是'.(checkdate(2,31,2010)?'有效日期!':'无效日期!').'<br />';

 

 

//确定当月天数

echo '本月有'.date('t',time()).'天<br />'; //28天

//确定任意给定的月份的天数

$d='2008-02-01'; //闰年,或$d='2008-02';不需要输入天也可以

$d=strtotime($d);

echo '2008年2月有'.date('t',$d).'天<br />'; //29天

 

$d=getdate();

echo '<pre>';

print_r($d);

echo '</pre>';

/*Array(

    [seconds] => 42

    [minutes] => 16

    [hours] => 13

    [mday] => 23

    [wday] => 2

    [mon] => 2

    [year] => 2010

    [yday] => 53

    [weekday] => Tuesday

    [month] => February

    [0] => 1266902202

)

*/

 

//echo date("Y-m-d H:i:s",-8000);

//setlocale(LC_ALL,'zh_CN.gb2312'); //setlocale函数对下面的没有影响。

#测试strftime,mktime函数。

echo strftime('今天是:%Y-%m-%d %H:%M:%S').'<br />';

echo strtotime('now').'<br />'; // 等于time(),但strtotime使用范围更灵活,参下文.

echo '测试还原昨天时间:'.date('Y-m-d',strtotime($lassdate)).'<br />'; //可以把字串型日期转成时间戳再用date转回原格式。

$x=strtotime($lassdate);

$y=mktime(0,0,0,'2','22','2010');

echo 'strtotime()得到的昨天的时间戳是:'.$x.',mktime()得到的昨天时间戳是:'.$y.(($x==$y)?',二者相等':',二者不相同').'<br />'; //相等。

 

#显示1970年前的日期

$time_int=strtotime('1929-2-10');

echo date("Y-m-d ",$time_int).'<br />'; //在MYSQL中与date()函数相同功能的是date_format('1996-02-05 11:07:45','%Y-%m-%d')或for_format()

 

/*时间运算:

*请使用方法三。其它方法只供参考。 *

*/

#1、今天是23号,获得前天的时间,即减两天。

$predate=2;

$pretime=$predate*24*60*60; //2天的时间戳。

echo date('前天是:Y-m-d',time()-$pretime).'<br />';    //前天是:2010-02-21

 

#2、两个日期相差的天数。

$olddate = '2010-02-11'; //如果要用mktime函数,则要用explode拆解日期。

$oldtime = strtotime($olddate);

$passtime = time()-$oldtime; //经过的时间戳。

echo '你在网上泡了'.floor($passtime/(24*60*60)).'天了'.'<br />'; //12天。

 

#3、去年这个时侯。使用时要考虑闰年:平年365天,闰年366天。

#方法一:用减去全年天数的时间戳来获取。

$yDate=1;

$yDate_Y=date('Y',time())-1; //年份-1,即去年

$yDateYMD="$yDate_Y-01-01";

$yYMD=strtotime($yDateYMD); //去年的1月1号时间戳。

$d=date('L',$yYMD)?366:365; //是否是闰年

$yYearTime=$d*24*60*60;

 

$yYear=date('Y-m-d',time()-$yYearTime);

echo "去年的今天:$yYear<br />"; //2009-02-23

#方法二:用直接截取当前日期的年份减一,但不严谨,没有考虑到闰年。

#计算60年前的今天。忽略当中经过的闰年。

$yDate_Y=$yDate_Y-59;

$md=explode('-',date('Y-m-d'));

$yYMD="$yDate_Y-{$md[1]}-{$md[2]}";

echo "60年前的今天:$yYMD <br />"; //1950-02-23

 

#方法三:用strtotime()和GNU日期语法---------推荐!

//3天后; //当前时间为2010-02-23

$d=strtotime('3 days');

echo '3天后'.date('Y-m-d',$d)."<br />";

//3天前:

$d=strtotime('-3 days');

echo '3天前'.date('Y-m-d',$d)."<br />"; //2010-02-20

//一个月前:

$d=strtotime('-1 months');

echo '一个月前'.date('Y-m-d',$d)."<br />"; //2010-01-23

 

//2个月后:

$d=strtotime('2 months');

echo '二个月后'.date('Y-m-d',$d)."<br />"; //2010-04-23

 

//1年前:

$d=strtotime('-1 years');

echo '1年前'.date('Y-m-d',$d)."<br />"; //2009-02-23

 

//2小时前:

$d=strtotime('-2 hours');

echo '目前:'.date('Y-m-d H:i:s',time()).',2小时前'.date('Y-m-d H:i:s',$d)."<br />"; //目前:2010-02-23 13:38:49,2小时前2010-02-23 11:38:49

 

#DateTime构造函数:object DateTime([string $time [,dateTimeZone $timezone])

$date = new DateTime('2010-02-23 12:26:36');

echo $date->format('Y-m-d H:i:s')."<br />"; //和date()函数相同。2010-02-23 12:26:36

//重设时间:

//1、重设日期: boolean setDate(int year,int month,int day)

//2、重设时间: boolean setDate(int hour,int minute[,int second])

$date->setDate(2010,2,28);

echo $date->format('Y-m-d H:i:s')."<br />"; //2010-02-28 12:26:36

//日期计算,相当于上面的strtotime()

$date->modify("+7 hours");

echo $date->format('Y-m-d H:i:s')."<br />"; //2010-02-28 19:26:36

$date->modify("3 days");

echo $date->format('Y-m-d H:i:s')."<br />"; //2010-03-03 19:26:36 //从上面被改过的28号开始

 

/*PHP5在WIN不支持money_format函数?

setlocale(LC_MONETARY,'zh_CN');

echo money_format("%i",786.56);//?Fatal error: Call to undefined function money_format()

*/

?>

php json函数在jquery中的应用

 

本文来自小屋sjolzy.cn【原创】

 

鉴于JSON的娇小性和通用性 PHP在5.2的版本之后 默认加上一组针对数据进行JSON互转的函数: 

json_encode :数据到JSON 

json_decode :JSON到PHP变量的转换 

这两个函数在使用Ajax诸如:jquery和Ext框架的时候显得尤为重要,Ext默认接受的数据就是JSON格式,而jquery如果让JSON接入之后就显得更加轻松和简洁。本文将用一个实例讨论一下PHP 的JSON数据函数在jquery中的应用。 

Jquery做为一个轻量级的Ajax框架确实显得短小而精悍,比较适合Web编程的前台页面,更加适合和PHP结合。其中jquery中有一个方法是:$.getJSON,这个用来从服务器脚本页面取得JSON格式的数据的。我们按顺序来,首先建立一个名为json.php的PHP脚本,具体程序如下: 

<?php 

//创建一个联合数组,这里我们尽量真实的模拟了一个从数据库中读取的记录 

$a=array('name'=>'dog','sex'=>'f','par'=>array('li','wang','lian')); 

$b=array('name'=>'pig','sex'=>'m','par'=>array('liv','wange','liang')); 

$array = array(0=>$a,1=>$b); 

//将数组转化为一个json数据串 

echo json_encode($array); 

?>

 

前台页面jquery代码: 

<script> 

$(document).ready(function(){ 

    $.getJSON( 

    'json.php', 

    function(data){ 

       var content="";         

       for(var i=0;i<data.length;i++){//循环读取后台的取得的JSON数据 

        content+=data[i].name; 

        var par="排挡:"; 

        for(var j=0;j<data[i].par.length;j++){//循环读取数据中的par集合 

            par+=data[i].par[j]+"、"; 

        } 

        content+=par+"<br/>"; 

      } 

    alert(content); 

    } 

   );     

}); 

</script>

 

本例在循环读取数据的时候应用了js的基本流程控制for循环方法,在jquery中也提供了一个遍历方法each,底层实现也是基于for循环的。 

但按由于json_encode json_decode两个函数是在PHP5.2之后才有的,早于此前的版本使用将会报一个找不到json_encode函数的错误!

所以为了适用性,做了一下修改:

 

/*

 * 自定义my_json_encode函数

 * @params array $arr 欲转json的数组

 */

function my_json_encode($arr){

if (!function_exists('json_encode')){

return json_encode($arr);

}else{

require_once 'lib/json.class.php';

$json = new Services_JSON();

return $json->encode($arr);

}

}

自定义一个my_json_encode函数,当php版本为5.2之前,找不到son_encode函数时,载入json.class.php类文件。使用my_json_encode等价于json_encode。(json.class.php下载地址

PHP:$_SERVER中,QUERY_STRING、REQUEST_URI、SCRIPT_NAME与PHP_SELF四个变量的区别

,$_SERVER["QUERY_STRING"]
说明:查询(query)的字符串

2,$_SERVER["REQUEST_URI"]
说明:访问此页面所需的URI

3,$_SERVER["SCRIPT_NAME"]
说明:包含当前脚本的路径

4,$_SERVER["PHP_SELF"]
说明:当前正在执行脚本的文件名

实例:
1,http://www.sjolzy.com/ (直接打开主页)
结果:
$_SERVER["QUERY_STRING"] = ""
$_SERVER["REQUEST_URI"] = "/"
$_SERVER["SCRIPT_NAME"] = "/index.php"
$_SERVER["PHP_SELF"]     = "/index.php"

2,http://www. sjolzy.com/?p=222 (附带查询)
结果:
$_SERVER["QUERY_STRING"] = "p=222"
$_SERVER["REQUEST_URI"] = "/?p=222"
$_SERVER["SCRIPT_NAME"] = "/index.php"
$_SERVER["PHP_SELF"]     = "/index.php"

3,http://www. sjolzy.com/index.php?p=222&q=sjolzy
结果:
$_SERVER["QUERY_STRING"] = "p=222&q=sjolzy"
$_SERVER["REQUEST_URI"] = "/index.php?p=222&q= sjolzy"
$_SERVER["SCRIPT_NAME"] = "/index.php"
$_SERVER["PHP_SELF"]     = "/index.php"

$_SERVER["QUERY_STRING"]获取查询语句,实例中可知,获取的是?后面的值
$_SERVER["REQUEST_URI"] 获取http://www.biuuu.com后面的值,包括/
$_SERVER["SCRIPT_NAME"] 获取当前脚本的路径,如:index.php
$_SERVER["PHP_SELF"] 当前正在执行脚本的文件名

 

当前url:"http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']

 

总结一下,对于QUERY_STRING,REQUEST_URI,SCRIPT_NAME和PHP_SELF,深入了解将有利于我们在$_SERVER函数中正确调用这四个值。通过实例详解$_SERVER函数中QUERY_STRING,REQUEST_URI,SCRIPT_NAME和PHP_SELF掌握四个变量之间的区别。

PHPDocument 代码注释规范总结

PHPDocument 代码注释规范

1. 安装phpDocumentor(不推荐命令行安装)
在http://manual.phpdoc.org/下载最新版本的PhpDoc
放在web服务器目录下使得通过浏览器可以访问到
点击files按钮,选择要处理的php文件或文件夹
还可以通过该指定该界面下的Files to ignore来忽略对某些文件的处理。
然后点击output按钮来选择生成文档的存放路径和格式.
最后点击create,phpdocumentor就会自动开始生成文档了。

2.如何写PHP规范注释

所有的文档标记都是在每一行的 * 后面以@开头。如果在一段话的中间出来@的标记,这个标记将会被当做普通内容而被忽略掉。
@access        该标记用于指明关键字的存取权限:private、public或proteced 使用范围:class,function,var,define,module
@author        指明作者
@copyright    指明版权信息
@const        使用范围:define 用来指明php中define的常量
@final            使用范围:class,function,var 指明关键字是一个最终的类、方法、属性,禁止派生、修改。
@global        指明在此函数中引用的全局变量
@name            为关键字指定一个别名。
@package    用于逻辑上将一个或几个关键字分到一组。
@abstrcut    说明当前类是一个抽象类
@param        指明一个函数的参数
@return        指明一个方法或函数的返回值
@static            指明关建字是静态的。
@var            指明变量类型
@version        指明版本信息
@todo            指明应该改进或没有实现的地方
@link            可以通过link指到文档中的任何一个关键字
@ingore        用于在文档中忽略指定的关键字

一些注释规范
a.注释必须是
/**
* XXXXXXX
*/
的形式
b.对于引用了全局变量的函数,必须使用glboal标记。
c.对于变量,必须用var标记其类型(int,string,bool...)
d.函数必须通过param和return标记指明其参数和返回值
e.对于出现两次或两次以上的关键字,要通过ingore忽略掉多余的,只保留一个即可
f.调用了其他函数或类的地方,要使用link或其他标记链接到相应的部分,便于文档的阅读。
g.必要的地方使用非文档性注释(PHPDOC无法识别的关键字前的注释),提高代码易读性。
h.描述性内容尽量简明扼要,尽可能使用短语而非句子。
i.全局变量,静态变量和常量必须用相应标记说明

能够被phpdoc识别的关键字:
Include
Require
include_once
require_once
define
function
global
class

3. 规范注释的php代码 :

<?php
/**
* 文件名(sample2.php)
*
* 功能描述(略)
*
* @author steve <liuzhiqun@facedoing.com>
* @version 1.0
* @package sample2
*/

/**
* 包含文件
*/
include_once 'sample3.php';

/**
* 声明全局变量
* @global integer $GLOBALS['_myvar']
* @name $_myvar
*/
$GLOBALS['_myvar'] = 6;

/**
* 声明全局常量
*/
define('NUM', 6);

/**
* 类名
*
* 类功能描述
*
* @package sample2
* @subpackage classes(如果是父类 就添加)
*/
class myclass {

/**
* 声明普通变量
*
* @accessprivate
* @var integer|string
*/
var $firstvar = 6;

/**
* 创建构造函数 {@link $firstvar}
*/
function myclass() {
$this->firstvar = 7;
}

/**
* 定义函数
*
* 函数功能描述
*
* @global string $_myvar
* @staticvar integer $staticvar
* @param string $param1
* @param string $param2
* @return integer|string
*/
function firstFunc($param1, $param2 = 'optional') {
static $staticvar = 7;
global $_myvar;
return $staticvar;
}
}
?>

在数据库中存储层次数据

 

无论你要构建自己的论坛,在你的网站上发布消息还是书写自己的cms [1]程序,你都会遇到要在数据库中存储层次数据的情况。同时,除非你使用一种像XML [2]的数据库,否则关系数据库中的表都不是层次结构的,他们只是一个平坦的列表。所以你必须找到一种把层次数据库转化的方法。

存储树形结构是一个很常见的问题,他有好几种解决方案。主要有两种方法:邻接列表模型和改进前序遍历树算法

在本文中,我们将探讨这两种保存层次数据的方法。我将举一个在线食品店树形图的例子。这个食品店通过类别、颜色和品种来组织食品。树形图如下:

1105_tree

本文包含了一些代码的例子来演示如何保存和获取数据。我选择PHP [3]来写例子,因为我常用这个语言,而且很多人也都使用或者知道这个语言。你可以很方便地把它们翻译成你自己用的语言。

邻接列表模型(The Adjacency List Model)

我们要尝试的第一个——也是最优美的——方法称为“邻接列表模型”或称为“递归方法”。它是一个很优雅的方法因为你只需要一个简单的方法来在你的树中进行迭代。在我们的食品店中,邻接列表的表格如下:

1105_table1

如你所见,对每个节点保存一个“父”节点。我们可以看到“Pear [4]”是“Green”的一个子节点,而后者又是“Fruit”的子节点,如此类推。根节点,“Food”,则他的父节点没有值。为了简单,我只用了“title”值来标识每个节点。当然,在实际的数据库中,你要使用数字的ID。

显示树

现在我们已经把树放入数据库中了,得写一个显示函数了。这个函数将从根节点开始——没有父节点的节点——同时要显示这个节点所有的子节点。对于这些子节点,函数也要获取并显示这个子节点的子节点。然后,对于他们的子节点,函数还要再显示所有的子节点,然后依次类推。

也许你已经注意到了,这种函数的描述,有一种普遍的模式。我们可以简单地只写一个函数,用来获得特定节点的子节点。这个函数然后要对每个子节点调用自身来再次显示他们的子节点。这就是“递归”机制,因此称这种方法叫“递归方法”。

<?php
// $parent 是我们要查看的子节点的父节点
// $level 会随着我们深入树的结构而不断增加,
//        用来显示一个清晰的缩进格式
function display_children($parent, $level) {
    // 获取$parent的全部子节点
    $result = mysql_query('SELECT title FROM tree '.
                           'WHERE parent="'.$parent.'";'); 

    // 显示每个节点
    while ($row = mysql_fetch_array($result)) {
        // 缩进并显示他的子节点的标题
        echo str_repeat('  ',$level).$row['title']."\n"; 

        // 再次调用这个函数来显着这个子节点的子节点
        display_children($row['title'], $level+1);
    }
}
?>

要实现整个树,我们只要调用函数时用一个空字符串作为$parent 和$level = 0: display_children('',0); 函数返回了我们的食品店的树状图如下:

Food
  Fruit
    Red
      Cherry
    Yellow
      Banana
  Meat
    Beef
    Pork

注意如果你只想看一个子树,你可以告诉函数从另一个节点开始。例如,要显示“Fruit”子树,你只要display_children('Fruit',0); while ($row = mysql_fetch_array($result)) { // 检查栈里面有没有元素 if (count($right)>0) { // 检查我们是否需要从栈中删除一个节点 while ($right[count($right)-1]<$row['rgt']) { array_pop($right); } } // 显示缩进的节点标题 echo str_repeat(' ',count($right)).$row['title']."\n"; // 把这个节点添加到栈中 $right[] = $row['rgt']; } } ?>

节点的路径

利用差不多的函数,我们也可以查询某个节点的路径如果你只知道这个节点的名字或者ID。例如,“Cherry”的路径是“Food”>“Fruit”>“Red”。要获得这个路径,我们的函数要获得这个路径,这个函数必须从最深的层次开始:“Cheery”。但后查找这个节点的父节点,并添加到路径中。在我们的例子中,这个父节点是“Red”。如果我们知道“Red”是“Cherry”的父节点。

<?php
// $node 是我们要查找路径的那个节点的名字
function get_path($node) {
    // 查找这个节点的父节点
    $result = mysql_query('SELECT parent FROM tree '.
                           'WHERE title="'.$node.'";');
    $row = mysql_fetch_array($result); 

    // 在这个array[5] 中保存数组
    $path = array(); 

    // 如果 $node 不是根节点,那么继续
    if ($row['parent']!='') {
        //  $node 的路径的最后一部分是$node父节点的名称
        $path[] = $row['parent']; 

        // 我们要添加这个节点的父节点的路径到现在这个路径
        $path = array_merge(get_path($row['parent']), $path);
    } 

    // 返回路径
    return $path;
}
?>

这个函数现在返回了指定节点的路径。他把路径作为数组返回,这样我们可以使用print_r(get_path('Cherry')); 来显示,其结果是:

Array
(
    [0] => Food
    [1] => Fruit
    [2] => Red
)

不足

正如我们所见,这确实是一个很好的方法。他很容易理解,同时代码也很简单。但是邻接列表模型的缺点在哪里呢?在大多数编程语言中,他运行很慢,效率很差。这主要是“递归”造成的。我们每次查询节点都要访问数据库。

每次数据库查询都要花费一些时间,这让函数处理庞大的树时会十分慢。

造成这个函数不是太快的第二个原因可能是你使用的语言。不像Lisp这类语言,大多数语言不是针对递归函数设计的。对于每个节点,函数都要调用他自己,产生新的实例。这样,对于一个4层的树,你可能同时要运行4个函数副本。对于每个函数都要占用一块内存并且需要一定的时间初始化,这样处理大树时递归就很慢了。

改进前序遍历树

现在,让我们看另一种存储树的方法。递归可能会很慢,所以我们就尽量不使用递归函数。我们也想尽量减少数据库查询的次数。最好是每次只需要查询一次。

我们先把树按照水平方式摆开。从根节点开始(“Food”),然后他的左边写上1。然后按照树的顺序(从上到下)给“Fruit”的左边写上2。这样,你沿着树的边界走啊走(这就是“遍历”),然后同时在每个节点的左边和右边写上数字。最后,我们回到了根节点“Food”在右边写上18。下面是标上了数字的树,同时把遍历的顺序用箭头标出来了。

1105_numbering

我们称这些数字为左值和右值(如,“Food”的左值是1,右值是18)。正如你所见,这些数字按时了每个节点之间的关系。因为“Red”有3和6两个值,所以,它是有拥有1-18值的“Food”节点的后续。同样的,我们可以推断所有左值大于2并且右值小于11的节点,都是有2-11的“Food”节点的后续。这样,树的结构就通过左值和右值储存下来了。这种数遍整棵树算节点的方法叫做“改进前序遍历树”算法。

在继续前,我们先看看我们的表格里的这些值:

1105_table2

注意单词“left”和“right”在SQL中有特殊的含义。因此,我们只能用“lft”和“rgt”来表示这两个列。(译注——其实Mysql中可以用“`”来表示,如“`left`”,MSSQL中可以用“[]”括出,如“[left]”,这样就不会和关键词冲突了。)同样注意这里我们已经不需要“parent”列了。我们只需要使用lft和rgt就可以存储树的结构。

获取树

如果你要通过左值和右值来显示这个树的话,你要首先标识出你要获取的那些节点。例如,如果你想获得“Fruit”子树,你要选择那些左值在2到11的节点。用SQL语句表达:

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11;

这个会返回:

1105_table3

好吧,现在整个树都在一个查询中了。现在就要像前面的递归函数那样显示这个树,我们要加入一个ORDER BY子句在这个查询中。如果你从表中添加和删除行,你的表可能就顺序不对了,我们因此需要按照他们的左值来进行排序。

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;

就只剩下缩进的问题了。

要显示树状结构,子节点应该比他们的父节点稍微缩进一些。我们可以通过保存一个右值的一个栈。每次你从一个节点的子节点开始时,你把这个节点的右值添加到栈中。你也知道子节点的右值都比父节点的右值小,这样通过比较当前节点和栈中的前一个节点的右值,你可以判断你是不是在显示这个父节点的子节点。当你显示完这个节点,你就要把他的右值从栈中删除。要获得当前节点的层数,只要数一下栈中的元素。

<?php
function display_tree($root) {
    // 获得$root节点的左边和右边的值
    $result = mysql_query('SELECT lft, rgt FROM tree '.
                           'WHERE title="'.$root.'";');
    $row = mysql_fetch_array($result); 

    // 以一个空的$right栈开始
    $right = array(); 

    // 现在,获得$root节点的所有后序
    $result = mysql_query('SELECT title, lft, rgt FROM tree '. 

      'WHERE lft BETWEEN '.$row['lft'].' AND '.
                           $row['rgt'].' ORDER BY lft ASC;'); 

    // 显示每一行

如果运行这段代码,你可以获得和上一部分讨论的递归函数一样的结果。而这个函数可能会更快一点:他不采用递归而且只是用了两个查询

节点的路径

有了新的算法,我们还要另找一种新的方法来获得指定节点的路径。这样,我们就需要这个节点的祖先的一个列表。

由于新的表结构,这不需要花太多功夫。你可以看一下,例如,4-5的“Cherry”节点,你会发现祖先的左值都小于4,同时右值都大于5。这样,我们就可以使用下面这个查询:

SELECT title FROM tree WHERE lft < 4 AND rgt > 5 ORDER BY lft ASC;

注意,就像前面的查询一样,我们必须使用一个ORDER BY子句来对节点排序。这个查询将返回:

+-------+
| title |
+-------+
| Food  |
| Fruit |
| Red   |
+-------+

我们现在只要把各行连起来,就可以得到“Cherry”的路径了。

有多少个后续节点?How Many Descendants

如果你给我一个节点的左值和右值,我就可以告诉你他有多少个后续节点,只要利用一点点数学知识。

因为每个后续节点依次会对这个节点的右值增加2,所以后续节点的数量可以这样计算:

descendants = (right – left - 1) / 2

利用这个简单的公式,我可以立刻告诉你2-11的“Fruit”节点有4个后续节点,8-9的“Banana”节点只是1个子节点,而不是父节点。

自动化树遍历

现在你对这个表做一些事情,我们应该学习如何自动的建立表了。这是一个不错的练习,首先用一个小的树,我们也需要一个脚本来帮我们完成对节点的计数。

让我们先写一个脚本用来把一个邻接列表转换成前序遍历树表格。

<?php
function rebuild_tree($parent, $left) {
    // 这个节点的右值是左值加1
    $right = $left+1; 

    // 获得这个节点的所有子节点
    $result = mysql_query('SELECT title FROM tree '.
                           'WHERE parent="'.$parent.'";');
    while ($row = mysql_fetch_array($result)) {
        // 对当前节点的每个子节点递归执行这个函数
        // $right 是当前的右值,它会被rebuild_tree函数增加
        $right = rebuild_tree($row['title'], $right);
    } 

    // 我们得到了左值,同时现在我们已经处理这个节点我们知道右值的子节点
    mysql_query('UPDATE tree SET lft='.$left.', rgt='.
                 $right.' WHERE title="'.$parent.'";'); 

    // 返回该节点的右值+1
    return $right+1;
}
?>

这是一个递归函数。你要从rebuild_tree('Food',1); 开始,这个函数就会获取所有的“Food”节点的子节点。

如果没有子节点,他就直接设置它的左值和右值。左值已经给出了,1,右值则是左值加1。如果有子节点,函数重复并且返回最后一个右值。这个右值用来作为“Food”的右值。

递归让这个函数有点复杂难于理解。然而,这个函数确实得到了同样的结果。他沿着树走,添加每一个他看见的节点。你运行了这个函数之后,你会发现左值和右值和预期的是一样的(一个快速检验的方法:根节点的右值应该是节点数量的两倍)。

添加一个节点

我们如何给这棵树添加一个节点?有两种方式:在表中保留“parent”列并且重新运行rebuild_tree()

函数——一个很简单但却不是很优雅的函数;或者你可以更新所有新节点右边的节点的左值和右值。

第一个想法比较简单。你使用邻接列表方法来更新,同时使用改进前序遍历树来查询。如果你想添加一个新的节点,你只需要把节点插入表格,并且设置好parent列。然后,你只需要重新运行rebuild_tree() 函数。这做起来很简单,但是对大的树效率不高。

第二种添加和删除节点的方法是更新新节点右边的所有节点。让我们看一下例子。我们要添加一种新的水果——“Strawberry”,作为“Red”的最后一个子节点。首先,我们要腾出一个空间。“Red”的右值要从6变成8,7-10的“Yellow”节点要变成9-12,如此类推。更新“Red”节点意味着我们要把所有左值和右值大于5的节点加上2。

我们用一下查询:

UPDATE tree SET rgt=rgt+2 WHERE rgt>5;
UPDATE tree SET lft=lft+2 WHERE lft>5;

现在我们可以添加一个新的节点“Strawberry”来填补这个新的空间。这个节点左值为6右值为7。

INSERT INTO tree SET lft=6, rgt=7, title='Strawberry';

如果我们运行display_tree() 函数,我们将发现我们新的“Strawberry”节点已经成功地插入了树中:

Food
  Fruit
    Red
      Cherry
      Strawberry
    Yellow
      Banana
  Meat
    Beef
    Pork

缺点

首先,改进前序遍历树算法看上去很难理解。它当然没有邻接列表方法简单。然而,一旦你习惯了左值和右值这两个属性,他就会变得清晰起来,你可以用这个技术来完成临街列表能完成的所有事情,同时改进前序遍历树算法更快。当然,更新树需要很多查询,要慢一点,但是取得节点却可以只用一个查询。

总结

你现在已经对两种在数据库存储树方式熟悉了吧。虽然在我这儿改进前序遍历树算法性能更好,但是也许在你特殊的情况下邻接列表方法可能表现更好一些。这个就留给你自己决定了

最后一点:就像我已经说得我部推荐你使用节点的标题来引用这个节点。你应该遵循数据库标准化的基本规则。我没有使用数字标识是因为用了之后例子就比较难读。

进一步阅读

数据库指导 Joe Celko写的更多关于SQL数据库中的树的问题:

http://searchdatabase.techtarget.com/tip/1,289483,sid13_gci537290,00.html [6]

另外两种处理层次数据的方法:

http://www.evolt.org/article/Four_ways_to_work_with_hierarchical_data/17/4047/index.html [7]

Xindice, “本地XML数据库”:

http://xml.apache.org/xindice/ [8]

递归的一个解释:

http://www.strath.ac.uk/IT/Docs/Ccourse/subsection3_9_5.html [9]

[1] http://www.sitepoint.com/glossary.php?q=C#term_28

[2] http://www.sitepoint.com/glossary.php?q=X#term_3

[3] http://www.sitepoint.com/glossary.php?q=P#term_1

[4] http://www.sitepoint.com/glossary.php?q=P#term_50

[5] http://www.sitepoint.com/glossary.php?q=%23#term_72

[6] http://searchdatabase.techtarget.com/tip/1,289483,sid13_gci537290,00.html

[7] http://www.evolt.org/article/Four_ways_to_work_with_hierarchical_data/17/4047/index.html

[8] http://xml.apache.org/xindice/

[9] http://www.strath.ac.uk/IT/Docs/Ccourse/subsection3_9_5.html

http://www.sitepoint.com/article/hierarchical-data-database/2/

http://dev.mysql.com/tech-resources/articles/hierarchical-data.html

PHP关于引用的理解

php引用(&)详解 php的引用(就是在变量或者函数、对象等前面加上&符号) 在PHP 中引用的意思是:不同的名字访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的指针里面存储的是变量的内容在内存中存放的地址 变量的引用 PHP 的引用允许你用两个变量来指向同一个内容 [php] <? $a="ABC"; $b =&$a; echo $a;//这里输出:ABC echo $b;//这里输出:ABC $b="EFG"; echo $a;//这里$a的值变为EFG 所以输出EFG echo $b;//这里输出EFG ?> [/php] 函数的传址调用 传址调用我就不多说了 下面直接给出代码 [php] function test(&$a) { $a=$a+100; } $b=1; echo $b;//输出1 test($b); //这里$b传递给函数的其实是$b的变量内容所处的内存地址,通过在函数里改变$a的值 就可以改变$b的值了 echo "
"; echo $b;//输出101 [/php] 要注意的是,在这里test(1);的话就会出错,原因自己去想 函数的引用返回 先看代码 [php] function &test() { static $b=0;//申明一个静态变量 $b=$b+1; echo $b; return $b; } $a=test();//这条语句会输出 $b的值 为1 $a=5; $a=test();//这条语句会输出 $b的值 为2 $a=&test();//这条语句会输出 $b的值 为3 $a=5; $a=test();//这条语句会输出 $b的值 为6 [/php] 下面解释下:  通过这种方式$a=test();得到的其实不是函数的引用返回,这跟普通的函数调用没有区别 至于原因: 这是PHP的规定 PHP规定通过$a=&test(); 方式得到的才是函数的引用返回 至于什么是引用返回呢(PHP手册上说:引用返回用在当想用函数找到引用应该被绑定在哪一个变量上面时。) 这句狗屁话 害我半天没看懂 用上面的例子来解释就是 $a=test()方式调用函数,只是将函数的值赋给$a而已, 而$a做任何改变 都不会影响到函数中的$b 而通过$a=&test()方式调用函数呢, 他的作用是 将return $b中的 $b变量的内存地址与$a变量的内存地址 指向了同一个地方 即产生了相当于这样的效果($a=&b;) 所以改变$a的值 也同时改变了$b的值 所以在执行了 $a=&test(); $a=5; 以后,$b的值变为了5 这里是为了让大家理解函数的引用返回才使用静态变量的,其实函数的引用返回多用在对象中 对象的引用 [php] <? class a{ var $abc="ABC"; } $b=new a; $c=$b; echo $b->abc;//这里输出ABC echo $c->abc;//这里输出ABC $b->abc="DEF"; echo $c->abc;//这里输出DEF ?> [/php] 以上代码是在PHP5中的运行效果 在PHP5中 对象的复制 是通过引用来实现的。上列中$b=new a; $c=$b; 其实等效于$b=new a; $c=&$b; PHP5中默认就是通过引用来调用对象, 但有时你可能想建立一个对象的副本,并希望原来的对象的改变不影响到副本 . 为了这样的目的,PHP定义了一个特殊的方法,称为__clone. 引用的作用 如果程序比较大,引用同一个对象的变量比较多,并且希望用完该对象后手工清除它,个人建议用 "&" 方式,然后用$var=null的方式清除. 其它时候还是用php5的默认方式吧. 另外, php5中对于大数组的传递,建议用 "&" 方式, 毕竟节省内存空间使用。 取消引用 当你 unset 一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了。例如: <?php $a = 1; $b =& $a; unset ($a); ?> 不会 unset $b,只是 $a。 global 引用 当用 global $var 声明一个变量时实际上建立了一个到全局变量的引用。也就是说和这样做是相同的: <?php $var =& $GLOBALS["var"]; ?> 这意味着,例如,unset $var 不会 unset 全局变量。 $this 在一个对象的方法中,$this 永远是调用它的对象的引用。 //下面再来个小插曲 php中对于地址的指向(类似指针)功能不是由用户自己来实现的,是由Zend核心实现的,php中引用采用的是“写时拷贝”的原理,就是除非发生写操作,指向同一个地址的变量或者对象是不会被拷贝的。 通俗的讲 1:如果有下面的代码 [php] $a="ABC"; $b=$a; [/php] 其实此时 $a与$b都是指向同一内存地址 而并不是$a与$b占用不同的内存 2:如果在上面的代码基础上再加上如下代码 [php] $a="EFG"; [/php] 由于$a与$b所指向的内存的数据要重新写一次了,此时Zend核心会自动判断 自动为$b生产一个$a的数据拷贝,重新申请一块内存进行存储 一个例子 <?php $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8) ?>

标签: php, 函数, 变量, 引用