下载源码(下面指定了版本)
1 | sudo rm -rf /tmp/mongo-php-driver /usr/src/mongo-php-driver |
编译安装
1 | phpize && ./configure && make && make install |
下载源码(下面指定了版本)
1 | sudo rm -rf /tmp/mongo-php-driver /usr/src/mongo-php-driver |
编译安装
1 | phpize && ./configure && make && make install |
jenssegers mongodb
是否记录查询语句依赖于 \Illuminate\Database\Connection
里面的 loggingQueries
属性是否为 true, 而这个属性可以使用 DB::connection('xx')->enableQueryLog()
来设置,我们调用了这个方法之后,该扩展包底层会 fire 一个 \Illuminate\Database\Events\QueryExecuted
事件,所以如果我们想把查询语句记录到日志文件,可以考虑,监听这个事件,然后在 回调里面写入日志。
\Jenssegers\Mongodb\Collection
1 | /** |
1 | DB::connection('yy')->enableQueryLog(); |
在哪里写这个可以参考官方文档,这里只展示具体代码:
1 | \DB::listen(function (QueryExecuted $sql) use ($logSql) { |
我们知道,在 C 中,函数参数传递只有两种方式,值传递、引用传递。
值传递的时候,会把对应的值复制一份传到函数中,形参和原变量是不同的两个变量,而引用传递只是把原变量的地址传递给形参,形参根据这个地址拿到对应的变量。
引用传递的时候,我们需要通过解引用 (*xxx) 的方式拿到变量,这个时候我们就拿到了原始变量,我们在函数中所有针对该变量的操作都是针对原始变量的操作,因为它们是同一个变量。
那这个规则对结构体变量来说是否还适用呢?
1 | #include <stdio.h> |
输出:
1 | origin: user address = 0x7ffee7aae798 |
有人会发现 printf("test2: user address = %p\n", (void*)&user);
输出的地址并不是原始 user
的地址,这是为什么呢?
其实实际上 void test2(User *user)
test2(&user);
这两句还是值传递,只是传递的值是一个指针的地址。而上面的打印 &user
的地址实际上 是打印保存这个指针地址变量的地址,听起来有点拗口,但实际上就是这样,我们在 test2
内拿到了一个地址,我们会根据这个地址拿到原始的变量。
如何证实这一点呢?
在 test2
里面加上这一行,获取解引用后的 user
的地址,发现指向是原始 user
的地址。
1 | printf("test2: user address = %p\n", (void*)&(*user)); // 0x7ffee7aae798 |
结论:值传递、引用传递的规则对于结构体变量传参同样适用。
我们在单链表中,有了 next 指针,这就使得我们要查找下一节点的时间复杂度为 O(1)。 可是如果我们要查找的是上一节点的话,那最坏的时间复杂度就是 O(n) 了,因为我们每次都要从头开始遍历查找。
为了克服单向性这一缺点,我们的老科学家们,设计出了双向链表。
双向链表(double linked list)是在单链表的每个节点中,再设置一个指向其前驱节点的指针域。 所以在双向链表中的节点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
1 | // 线性表的双向链表存储结构 |
既然单链表也可以有循环链表,那么双向链表当然也可以是循环链表。
双向链表的循环带头节点的空链表如下图所示:
非空的循环的带头节点的双向链表如下所示:
由于这是双向链表,那么对于链表中的某一个节点 p,它的后继的前驱是谁?当然还是它自己。它的前驱的后继自然也是它自己,即:
1 | p->next->prior = p = p->prior->next; |
双向链表是单链表中扩展出来的结构,所以它的很多操作和单链表相同,比如求长度的 ListLength,查找元素的 GetElem,获得元素位置的 LocateElem 等, 这些操作都只涉及一个方向的指针即可,另一指针多了也不能提供什么帮助。
我们现在假设存储元素 e 的节点为 s,要实现将节点 s 插入到节点 p 和 p->next 之间需要下面几步:
1 | s->prior = p; // 把 p 赋值给 s 的前驱 |
总体来说,就是先正向连接起来,再反向连接起来。
关键在于它们的顺序,由于第 2 步和第 3 步都用到了 p->next。如果第 4 步先执行,则会使得 p->next 提前变成 s,使得插入的工作完不成。 因为这样一来我们就没有办法找到原始的 p->next 了。所以我们不妨把上面这张图在理解的基础上记忆,顺序是先搞定 s 的前驱和后继, 再搞定后节点的前驱,最后搞定前节点的后继。
若要删除节点 p,只需要下面两步骤:
1 | p->prior->next = p->next; |
好了,简单总结一下,双向链表相对于单链表来说,要更复杂一些,毕竟它多了 prior 指针,对于插入和删除时,需要格外小心。 另外它由于每个节点都需要记录两份指针,所以在空间上是要占用多一些的。不过,由于它良好的对称性,使得对某个节点的前后节点的操作, 带来了方便,可以有效提高算法的时间性能。说白了,就是用空间来换时间。
将单链表中终端节点的指针由空指针改为指向头节点,就使整个单链表形成一个环, 这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)
循环链表解决了一个很麻烦的问题。如何从当中一个节点出发,访问到链表的全部节点。
为了使空链表与非空链表处理一致,我们通常设一个头节点,当然,这并不是说, 循环链表一定要头节点,这需要注意。
空循环链表:
非空循环链表:
其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断 p->next 是否为空, 现在则是 p->next 不等于头节点,则循环未结束。
在单链表中,我们有了头节点时,我们可以用 O(1) 的时间访问第一个节点,但对于要访问到 最后一个节点,却需要 O(n) 时间,因为我们需要将单链表全部扫描一遍。
有没有用 O(1) 的时间由链表指针访问到最后一个节点呢?当然可以。
不过我们需要改造一下这个循环链表,不用头指针,而是用指向终端节点的尾指针来表示循环链表, 此时查找开始节点和终端节点都很方便了。
从上图中可以看到,终端节点用尾指针 rear 指示,则查找终端节点是 O(1), 而开始节点,其实就是 rear->next->next,其时间复杂度也为 O(1)。
举个程序的例子,要将两个循环链表合并成一个表时,有了尾指针就非常简单了。 比如下面的这两个循环链表,它们的尾指针分别是 rearA 和 rearB:
要想将它们合并,只需要如下的操作即可:
1 | p = rearA->next; // 保存 A 链表的头节点 |