0%

下载源码(下面指定了版本)

1
2
3
4
5
sudo rm -rf /tmp/mongo-php-driver /usr/src/mongo-php-driver
git clone -c advice.detachedHead=false -b '1.6.1' --single-branch https://github.com/mongodb/mongo-php-driver.git /tmp/mongo-php-driver
sudo mv /tmp/mongo-php-driver /usr/src/mongo-php-driver
cd /usr/src/mongo-php-driver
git submodule update --init

编译安装

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Handle dynamic method calls.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
$start = microtime(true);
$result = call_user_func_array([$this->collection, $method], $parameters);

if ($this->connection->logging()) {
// Once we have run the query we will calculate the time that it took to run and
// then log the query, bindings, and execution time so we will report them on
// the event that the developer needs them. We'll log time in milliseconds.
$time = $this->connection->getElapsedTime($start);

$query = [];

// Convert the query parameters to a json string.
array_walk_recursive($parameters, function (&$item, $key) {
if ($item instanceof ObjectID) {
$item = (string) $item;
}
});

// Convert the query parameters to a json string.
foreach ($parameters as $parameter) {
try {
$query[] = json_encode($parameter);
} catch (Exception $e) {
$query[] = '{...}';
}
}

$queryString = $this->collection->getCollectionName() . '.' . $method . '(' . implode(',', $query) . ')';

$this->connection->logQuery($queryString, [], $time);
}

return $result;
}

启用查询 log

1
2
3
DB::connection('yy')->enableQueryLog();
$model = app(xx::class);
$model->limit(10)->get();

监听事件

在哪里写这个可以参考官方文档,这里只展示具体代码:

1
2
3
\DB::listen(function (QueryExecuted $sql) use ($logSql) {
\Log::info('time: ' . $sql->time . ' ' . $sql->sql);
});

我们知道,在 C 中,函数参数传递只有两种方式,值传递、引用传递。

值传递的时候,会把对应的值复制一份传到函数中,形参和原变量是不同的两个变量,而引用传递只是把原变量的地址传递给形参,形参根据这个地址拿到对应的变量。

引用传递的时候,我们需要通过解引用 (*xxx) 的方式拿到变量,这个时候我们就拿到了原始变量,我们在函数中所有针对该变量的操作都是针对原始变量的操作,因为它们是同一个变量。

那这个规则对结构体变量来说是否还适用呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>

typedef struct User {
char *name;
int age;
} User;

void test1(User user)
{
user.name = "name1";
printf("test1: user address = %p\n", (void*)&user); // 0x7ffee7aae760
printf("test1: name address = %p\n", (void*)&user.name); // 0x7ffee7aae760
}

void test2(User *user)
{
user->name = "name2";
printf("test2: user address = %p\n", (void*)&user); // 0x7ffee7aae768 (这个是参数变量的地址)
printf("test2: name address = %p\n", (void*)&user->name); // 0x7ffee7aae798
}

int main()
{
User user = {.age = 23, .name = "name"};
printf("origin: user address = %p\n", (void*)&user); // 0x7ffee7aae798
printf("origin: name address = %p\n", (void*)&user.name); // 0x7ffee7aae798 (因为 name 是结构体的第一个成员,所以和结构体的地址一样)

test1(user);
printf("test1: user.name = %s\n", user.name); // name

test2(&user);
printf("test2: user.name = %s\n", user.name); // name2

return 0;
}

输出:

1
2
3
4
5
6
7
8
origin: user address = 0x7ffee7aae798
origin: name address = 0x7ffee7aae798
test1: user address = 0x7ffee7aae760
test1: name address = 0x7ffee7aae760
test1: user.name = name
test2: user address = 0x7ffee7aae768
test2: name address = 0x7ffee7aae798 // 和原始 user 的地址一致
test2: user.name = name2

有人会发现 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
2
3
4
5
6
7
// 线性表的双向链表存储结构
typedef struct DulNode
{
ElemType data;
struct DulNode *prior; // 直接前驱指针
struct DulNode *next; // 直接后继指针
} DulNode, *DuLinkList;

既然单链表也可以有循环链表,那么双向链表当然也可以是循环链表。

双向链表的循环带头节点的空链表如下图所示:

3-14-3.png

非空的循环的带头节点的双向链表如下所示:

3-14-4.png

由于这是双向链表,那么对于链表中的某一个节点 p,它的后继的前驱是谁?当然还是它自己。它的前驱的后继自然也是它自己,即:

1
p->next->prior = p = p->prior->next;

双向链表是单链表中扩展出来的结构,所以它的很多操作和单链表相同,比如求长度的 ListLength,查找元素的 GetElem,获得元素位置的 LocateElem 等, 这些操作都只涉及一个方向的指针即可,另一指针多了也不能提供什么帮助。

我们现在假设存储元素 e 的节点为 s,要实现将节点 s 插入到节点 p 和 p->next 之间需要下面几步:

3-14-5.png
1
2
3
4
s->prior = p; // 把 p 赋值给 s 的前驱
s->next = p->next; // 把 p->next 赋值给 s 的后继
p->next->prior = s->next; // 把 s 赋值给 p->next 的前驱
p->next = s; // 把 s 赋值给 p 的后继

总体来说,就是先正向连接起来,再反向连接起来。

关键在于它们的顺序,由于第 2 步和第 3 步都用到了 p->next。如果第 4 步先执行,则会使得 p->next 提前变成 s,使得插入的工作完不成。 因为这样一来我们就没有办法找到原始的 p->next 了。所以我们不妨把上面这张图在理解的基础上记忆,顺序是先搞定 s 的前驱和后继, 再搞定后节点的前驱,最后搞定前节点的后继。

若要删除节点 p,只需要下面两步骤:

3-14-6.png
1
2
3
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);

好了,简单总结一下,双向链表相对于单链表来说,要更复杂一些,毕竟它多了 prior 指针,对于插入和删除时,需要格外小心。 另外它由于每个节点都需要记录两份指针,所以在空间上是要占用多一些的。不过,由于它良好的对称性,使得对某个节点的前后节点的操作, 带来了方便,可以有效提高算法的时间性能。说白了,就是用空间来换时间。

将单链表中终端节点的指针由空指针改为指向头节点,就使整个单链表形成一个环, 这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)

循环链表解决了一个很麻烦的问题。如何从当中一个节点出发,访问到链表的全部节点。

为了使空链表与非空链表处理一致,我们通常设一个头节点,当然,这并不是说, 循环链表一定要头节点,这需要注意。

空循环链表: 3-13-3.png

非空循环链表: 3-13-4.png

其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断 p->next 是否为空, 现在则是 p->next 不等于头节点,则循环未结束。

在单链表中,我们有了头节点时,我们可以用 O(1) 的时间访问第一个节点,但对于要访问到 最后一个节点,却需要 O(n) 时间,因为我们需要将单链表全部扫描一遍。

有没有用 O(1) 的时间由链表指针访问到最后一个节点呢?当然可以。

不过我们需要改造一下这个循环链表,不用头指针,而是用指向终端节点的尾指针来表示循环链表, 此时查找开始节点和终端节点都很方便了。

3-13-5.png

从上图中可以看到,终端节点用尾指针 rear 指示,则查找终端节点是 O(1), 而开始节点,其实就是 rear->next->next,其时间复杂度也为 O(1)。

举个程序的例子,要将两个循环链表合并成一个表时,有了尾指针就非常简单了。 比如下面的这两个循环链表,它们的尾指针分别是 rearA 和 rearB:

3-13-6.png

要想将它们合并,只需要如下的操作即可:

3-13-7.png
1
2
3
4
p = rearA->next; // 保存 A 链表的头节点
rearA->next = rearB->next->next; // 将本是指向 B 的第一个节点(不是头节点)赋值给 rearA->next
rearB->next = p; // 将原 A 表的头节点赋值给 rearB->next
free(p); // 释放 p