# Introduction

长期的积累终将通向高手之路。


# First Chapter

GitBook allows you to organize your book into chapters, each chapter is stored in a separate file like this one.


# 基础


# 数据类型和常量

> 在PHP中，存在8种变量类型，分为三类
>
> * 标量类型： boolean、integer、float(double)、string
> * 复合类型： array、object
> * 特殊类型： resource、NULL

## 浮点类型

浮点类型不能运用到比较相等运算

举例：

```php
$a = 0.1;
$b = 0.7;
$c = $a + $b;

if($a + $b == 0.8){
    echo 'true1';
}

if($c == 0.8){
    echo 'true2';
}

$a = 1;
$b = 7;
if($a+$b == 8){
    echo 'true3';
}
```

结果：

```
true3
```

结果只输出了整型的计算比较。

为什么浮点数计算会有问题，因为`$a+$b`是交给cpu进行计算，cpu计算时只能转成二进制，转二进制时浮点数就会有一定的损耗，所以`$a+$b`，最终就不在是0.8,导致判断有问题。

## 布尔类型

**false的7种情况：**&#x20;

* 整型0
* 浮点`0.0`(当然`0.000`也都算)
* 布尔`false`
* 空字符串
* 0字符串：\`'0'
* 空数组：`array()`
* NULL

## 数组类型

超全局数组

* $GLOBALS - 引用全局作用域中可用的全部变量（包含后面所有的数据）
* $\_SERVER - 保存关于报头、路径和脚本位置的信息。
* $\_REQUEST - 用于收集 HTML 表单提交的数据（包含$\_GET,$\_POST数据和$\_COOKIE数据）。
* $\_POST
* $\_GET
* $\_FILES
* $\_ENV
* $\_COOKIE
* $\_SESSION

示例：

访问url：`http://127.0.0.1/test.php/user/reg?status=open`

```php
echo 'SERVER_ADDR:' . $_SERVER['SERVER_ADDR'] . '<br>';
echo 'SERVER_NAME:' . $_SERVER['SERVER_NAME'] . '<br>';
echo 'REQUEST_TIME:' . $_SERVER['REQUEST_TIME'] . '<br>';
echo 'QUERY_STRING:' . $_SERVER['QUERY_STRING'] . '<br>';
echo 'HTTP_REFERER:' . isset($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'] . '<br>';
echo 'HTTP_USER_AGENT:' . $_SERVER['HTTP_USER_AGENT'] . '<br>';
echo 'REMOTE_ADDR:' . $_SERVER['REMOTE_ADDR'] . '<br>';
echo 'REQUEST_URI:' . $_SERVER['REQUEST_URI'] . '<br>';
echo 'PATH_INFO:' . $_SERVER['PATH_INFO'] . '<br>';
```

结果：

```
SERVER_ADDR:127.0.0.1
SERVER_NAME:127.0.0.1
REQUEST_TIME:1504965126
QUERY_STRING:status=open
HTTP_USER_AGENT:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
REMOTE_ADDR:127.0.0.1
REQUEST_URI:/test.php/user/reg?status=open
PATH_INFO:/user/reg
```

> 为什么使用$\_SERVER\['HTTP\_REFERER']会出现这个报错的原因。
>
> 我们大家都知道$\_SERVER\['HTTP\_REFERER']用来获取当前页面的上一个页面地址，只有上一个页面存在，$\_SERVER\['HTTP\_REFERER']才会被设置，如果没有上一个页面（比如用户直接在浏览器上输入当前页面地址打开页面，这时候就没有上一个页面了），$\_SERVER\['HTTP\_REFERER']变量不会被设置。所以在使用这个变量时会报错。

下表列出了您能够在 $\_SERVER 中访问的最重要的元素：

| 元素/代码                               | 描述                                                                          |
| ----------------------------------- | --------------------------------------------------------------------------- |
| $\_SERVER\['PHP\_SELF']             | 返回当前执行脚本的文件名。                                                               |
| $\_SERVER\['GATEWAY\_INTERFACE']    | 返回服务器使用的 CGI 规范的版本。                                                         |
| $\_SERVER\['SERVER\_ADDR']          | 返回当前运行脚本所在的服务器的 IP 地址。                                                      |
| $\_SERVER\['SERVER\_NAME']          | 返回当前运行脚本所在的服务器的主机名（比如 [www.w3school.com.cn）。](http://www.w3school.com.cn）。) |
| $\_SERVER\['SERVER\_SOFTWARE']      | 返回服务器标识字符串（比如 Apache/2.2.24）。                                               |
| $\_SERVER\['SERVER\_PROTOCOL']      | 返回请求页面时通信协议的名称和版本（例如，“HTTP/1.0”）。                                           |
| $\_SERVER\['REQUEST\_METHOD']       | 返回访问页面使用的请求方法（例如 POST）。                                                     |
| $\_SERVER\['REQUEST\_TIME']         | 返回请求开始时的时间戳（例如 1577687494）。                                                 |
| $\_SERVER\['QUERY\_STRING']         | 返回查询字符串，如果是通过查询字符串访问此页面。                                                    |
| $\_SERVER\['HTTP\_ACCEPT']          | 返回来自当前请求的请求头。                                                               |
| $\_SERVER\['HTTP\_ACCEPT\_CHARSET'] | 返回来自当前请求的 Accept\_Charset 头（ 例如 utf-8,ISO-8859-1）                           |
| $\_SERVER\['HTTP\_HOST']            | 返回来自当前请求的 Host 头。                                                           |
| $\_SERVER\['HTTP\_REFERER']         | 返回当前页面的完整 URL（不可靠，因为不是所有用户代理都支持）。                                           |
| $\_SERVER\['HTTPS']                 | 是否通过安全 HTTP 协议查询脚本。                                                         |
| $\_SERVER\['REMOTE\_ADDR']          | 返回浏览当前页面的用户的 IP 地址。                                                         |
| $\_SERVER\['REMOTE\_HOST']          | 返回浏览当前页面的用户的主机名。                                                            |
| $\_SERVER\['REMOTE\_PORT']          | 返回用户机器上连接到 Web 服务器所使用的端口号。                                                  |
| $\_SERVER\['SCRIPT\_FILENAME']      | 返回当前执行脚本的绝对路径。                                                              |
| $\_SERVER\['SERVER\_ADMIN']         | 该值指明了 Apache 服务器配置文件中的 SERVER\_ADMIN 参数。                                    |
| $\_SERVER\['SERVER\_PORT']          | Web 服务器使用的端口。默认值为 “80”。                                                     |
| $\_SERVER\['SERVER\_SIGNATURE']     | 返回服务器版本和虚拟主机名。                                                              |
| $\_SERVER\['PATH\_TRANSLATED']      | 当前脚本所在文件系统（非文档根目录）的基本路径。                                                    |
| $\_SERVER\['SCRIPT\_NAME']          | 返回当前脚本的路径。                                                                  |
| $\_SERVER\['SCRIPT\_URI']           | 返回当前页面的 URI。                                                                |

## NULL

三种null情况

* 直接赋值为NULL
* 未定义的变量
* unset销毁的变量

```php
<?php

$a;
var_dump($a);
var_dump($b);
$c = 'c';
unset($c);
var_dump($c);
```

结果

```
null
null
null
```

> 注意如果错误级别高的话，会提示notice错误，关闭掉 NOTICE错误的警告 error\_reporting(E\_ALL ^ E\_NOTICE);

**null的特殊性**

1. 递减NULL 没有效果,還是null
   1. 递增NULL值为1
2. 当变量为null值，像数组一样取键值不会报错

```
$result = null;
//$result = array($result); // Notice: Undefined index: city ....
echo $result['city'];
```

## 常量

**两种定义方式：**

* const
* define

const 是语言结构，define是函数，所以const更快

**区别与共同：**  <br>

* define不能用于类常量，const可以
* 常量一经修改，不能被修改，不能被删除

**魔术常量：**

PHP中有八个魔术常量，它们的值会随着它们在代码中的位置的改变而改变。这些特殊的常量不区分大小写。

* **\_\_LINE\_\_**：返回文件中的当前行号。也可写成\_\_line\_\_。
* **\_\_FILE\_\_**：返回当前文件的绝对路径（包含文件名）。
* **\_\_DIR\_\_**：返回当前文件的绝对路径（不包含文件名），等价于 dirname(\_\_FILE\_\_)。
* **\_\_FUNCTION\_\_**：返回当前函数（或方法）的名称。
* **\_\_CLASS\_\_**：返回当前的类名（包括该类的作用区域或命名空间）。
* **\_\_TRAIT\_\_**：返回当前的trait名称（包括该trait的作用区域或命名空间）。
* **\_\_METHOD\_\_**：返回当前的方法名（包括类名）。
* **\_\_NAMESPACE\_\_**：返回当前文件的命名空间的名称。

**预定义常量：**

**内核预定义常量：是在PHP的内核中就定义好了的常量。区分大小写。**

PHP\_VERSION：返回PHP的版本。

PHP\_OS：返回执行PHP解释器的操作系统名称。

PHP\_EOL：系统换行符，Windows是（\r\n），Linux是（\n），MAC是（\r）。

**标准预定义常量：PHP默认定义的常量。区分大小写。**

M\_PI：返回圆周率π的值。


# 字符串

## **字符串的3种定义方式**

* 1、单引号
* 2、双引号
* 3、heredoc 和nowdoc

## 区别

**单引号：**

* 1、单引号不解析变量
* 2、单引号不能解析转移字符，只能解析单引号和反斜线本身
* 3、变量和变量、变量和字符串、字符串和字符串之间可以用.连接

**双引号:**&#x20;

* 1、双引号可以解析**变量**，变量可以使用特殊字符和{ }包含
* 2、双引号可以解析所有转义字符
* 3、也可以使用.来连接

> 双引号到底解析哪些字符?
>
> 1、当字符串用双引号或 heredoc 结构定义时，其中的变量将会被解析。
>
> 2、\n , \r , \t , \v , \e , \f , , $ , \“
>
> 3、\\\[0-7]{1,3} 符合该正则表达式序列的是一个以八进制方式来表达的字符
>
> 4、\x\[0-9A-Fa-f]{1,2} 符合该正则表达式序列的是一个以十六进制方式来表达的字符

**Heredoc :**

* 1、开始标记和结束标记使用相同的字符串，通常以大写字母来写。
* 2、开始标记后不能出现空格或多余的字符。
* 3、结束标记必须顶头写，不能有缩进和空格，且在结束标记末尾要有分号 。
* 4、位于开始标记和结束标记之间的**变量**可以被正常解析，但是函数则不可以。在heredoc中，变量不需要用连接符.或,来拼接 。

**Nowdoc：**

* 1、其用法和heredoc相同，两者都是处理大文本，不同的是Nowdoc 结构是类似于单引号字符串的。
* 2、其语法和单引号字符类似。

**区别：**&#x20;

* 单引号效率更高。
* Heredoc 类似于双引号
* Newdoc 类似于单引号

## 单引号，双引号区别示例

```
echo "this is a pen , \nthis is a pen." .'<br>';
echo 'this is a pen, \nthis is a pen.'.'<br>';

echo "\101\102".'<br>';
echo "\x41\x42".'<br>';

echo  "\\".'<br>';
echo  '\\'.'<br>';
```

结果：

```
this is a pen , this is a pen.
this is a pen, \nthis is a pen.
AB
AB
\
\
```

示例：

```
$a = 'world';
echo 'hello $a \n\'\\';
echo '<br>';
echo "hello '{$a}' '$a' $a \n\'\\";
```

结果：

```
hello $a \n'\
hello 'world' 'world' world &world& \'\
```

1.单引号，只解析了 单引号和反斜线本身，变量和其他转义字符都没有被解析

2.而双引号则全部进行了解析

## 用法

**单引号双引号：**

```
$sql = "select * FROM user WHERE name='$name'";
```

单引号效率更高。并且独立的变量可读性会更高。应该写成：

```
$sql = 'select * FROM user WHERE name=\'' . $name . '\'';
```

**Heredoc 用法:**

```php
<?php

$a = 'world';
echo <<<EOT
   <html>
   <head><title>主页</title></head>
   <body>hello $a</body>
   </html>
EOT;
```

自 PHP 5.3.0 起还可以在 Heredoc 结构中用双引号来声明标识符，所以开头这句也可以写为`echo <<<"EOT"`

**Nowdoc用法**

在 PHP 5.3.0 及其以后的版本中增加了nowdoc结构，其用法和heredoc相同，不同的是Nowdoc 结构是类似于单引号字符串的。nowdoc 中不进行解析操作。这种结构很适合用于嵌入 PHP 代码或其它大段文本而无需对其中的特殊字符进行转义。

一个 nowdoc 结构也用和 heredocs 结构一样的标记`<<<`， 但是跟在后面的标识符要用单引号括起来，即`<<<'EOT'`。

```php
<?php
$str = <<<'EOD'
Example of string
spanning multiple lines
using nowdoc syntax.
EOD;
```

> ```
> 在使用heredoc和nowdoc时，经常会遇到如下报错：
> Parse error: syntax error, unexpected T_SL in php
>
> 一段遇到该问题的原因是定界符之后空格的问题引起的。如的EOT后面有空格就会报这个错。
> ```

## 字符串“串行化”

大部分的 PHP 值可以转变成 string 来永久保存，串行化的目的是把一些其他的数据类型，不方便存储的数据类型（比如数组和对象无法直接保存入mysql或者memcache中的）。

* 方法一：函数 serialize() 序列化可以实现
* 方法二：函数 json\_encode ()可以实现，常用
* 方法三：函数 var\_export($items, true);　参数为true,将一个数据返回给变量，并且内容和php语法一致。

> ### **serialize() 与　json\_encode的区别，在于对一个对象的操作**
>
> １.序列化反序列化后仍然是相同的对象
>
> ２．json\_\_encode(),再json\_\_ddecode(),后对象有可能发生变化
>
> 所以如果要存储一个对象并且还需要能够将对象重新实例化的话则需要serialize
>
> 性能方面json\_\_encode() 比 \_serialize()高些，但表现不是很明显
>
> ```php
> class User {
>     function getUserName($name) {
>         echo $name;
>     }
> }
>
>
> $user = new User();
> $serializeUser = serialize($user);
> $unserializeUser = unserialize($serializeUser);
> var_dump($unserializeUser);
> $unserializeUser->getUserName('revin');
>
> $user1 = new User();
> $jsonEncodeUser1 = json_encode($user1);
> $jsonDecodeUser1 = json_decode($jsonEncodeUser1);
> var_dump($jsonDecodeUser1);
> $jsonDecodeUser1->getUserName('revin');
> ```
>
> 结果：
>
> ```
> object(User)[2]
> revin
> object(stdClass)[4]
> atal error: Call to undefined method stdClass::getUserName() in xxx
> ```
>
> 例子很明显。
>
> ### var\_export 用法：
>
> ```php
> $str = [1,2,3,4,5,6];
> $str = var_export($str, true);
> $str = '<?php return ' . $str .';';
> file_put_contents('test.php', $str);
>
>
> $test = include 'test.php';
> var_dump($test);
> ```
>
> 结果：
>
> ```
> array (size=6)
>   0 => int 1
>   1 => int 2
>   2 => int 3
>   3 => int 4
>   4 => int 5
>   5 => int 6
> ```
>
> 开发思路：比如从数据库提取的配置文件，组成数组的形式放入到一个文件中，当下次访问include从文件提取即可,文件缓存方式。

## 其他

**底层C语言中怎么表示PHP字符串？**

```
struct {
    char *val;
    int len;
} str;
```

**字符串的存取**

* 字节组成的数组可以用\[]或者{}访问某个字符(也就是像操作一个数组的方式操作字符串)
* 字符串长度可以达到2G(内存)
* 常见函数都是单字节处理方式
* 可以用\[]或者{}访问某个字符
* PHP字符串是二进制安全的，而在ｃ语言中不是安全的。

> 为什么在ｃ语言中不安全?
>
> C语言中字符串会逐个扫描字符，直到遇见 '\0' 就会结束
>
> 而在中不会因为你加入了\0 就会导致字符串被截断输出。
>
> 更多请看[C语言字符数组和字符串](http://c.biancheng.net/cpp/html/2921.html)

示例:

```php
$str = '123456';
echo $str[1] .'<br>';
echo $str{0} .'<br>';
$str[100] = 'e';
echo ($str) .'<br>';
echo (strlen($str)) .'<br>';
```

结果：

```
2
1
123456 e
101
```

字符串存取总结:

* string 中的字符可以通过一个从 0 开始的下标，用类似 array 结构中的方括号包含对应的数字来访问和修改，比如 $str\[42]。
* 也可用花括号访问，比如 $str{42}。数组可以这么访问么？&#x20;
* 用超出字符串长度的下标写入将会拉长该字符串并以空格填充。
* 非整数类型下标会被转换成整数。非法下标类型会产生一个 E\_NOTICE 级别错误。


# 字符编码

## 位、字节、字符

* 位（bit b）指二进制中的一位，是二进制最小信息单位。   &#x20;
* 字节（Byte B）是计算机信息技术用于计量存储容量的一种计量单位。   &#x20;
* 字符（ Character ）是指计算机中使用的字母、数字、汗字和符号等。

## 字符集和字符编码

* 字符集（Charset）：是一个系统支持的所有抽象字符的集合。
* 字符编码（Character Encoding）：是一套法则，使字符集与计算机之间建立对应关系，就是将字符转换为计算机可以接受的数字代码。就是以二进制的数字来对应字符集的字符。

## 常用字符集

### ASCII

ASCII（American Standard Code for Information Interchange，美国信息交换标准代码）是基于拉丁字母的一套电脑编码系统。

ASCII字符集：主要包括控制字符（回车键、退格、换行键等）；可显示字符（英文大小写字符、阿拉伯数字和西文符号）。

ASCII编码：将ASCII字符集转换为计算机可以接受的数字系统的数的规则。**使用7位（bits）表示一个字符，共128字符。**

> ASCII只是为英语国家的人使用的编码，因为里面只是用到了英语常用的字符

![](https://timgsa.baidu.com/timg?image\&quality=80\&size=b9999_10000\&sec=1505479856914\&di=b49f4569d4c1dd9d03b261f9606820b0\&imgtype=0\&src=http%3A%2F%2Fs1.knowsky.com%2F20170221%2F4fdfzprpy1j41.jpg)

### ISO-8859-1

**ISO-8859-1编码是单字节编码，向下兼容ASCII**，是ASCII 的扩展的一种，其编码范围是0x00-0xFF，0x00-0x7F之间完全和ASCII一致，0x80-0x9F之间是控制字符，0xA0-0xFF之间是文字符号。

此字符集支持部分于**欧洲**使用的语言，包括阿尔巴尼亚语、巴斯克语、布列塔尼语、加泰罗尼亚语、丹麦语、荷兰语、法罗语、弗里西语、加利西亚语、德语、格陵兰语、冰岛语、爱尔兰盖尔语、意大利语、拉丁语、卢森堡语、挪威语、葡萄牙语、里托罗曼斯语、苏格兰盖尔语、西班牙语及瑞典语。

**Latin1是ISO-8859-1的别名，有些环境下写作Latin-1。**

### GB 2312

《信息交换用汉字编码字符集》是由中国国家标准总局1980年发布，1981年5月1日开始实施的一套国家标准，标准号是GB 2312—1980。

GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换，通行于中国大陆；新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。

**基本集共收入汉字6763个和非汉字图形字符682个。**

GB2312的出现，基本满足了汉字的计算机处理需要，它所收录的汉字已经覆盖中国大陆99.75%的使用频率。**对于人名、古汉语等方面出现的罕用字，GB2312不能处理，**&#x8FD9;导致了后来**GBK及GB 18030**汉字字符集的出现。

[GB2312简体中文编码表](http://www.knowsky.com/resource/gb2312tbl.htm)

### BIG5

Big5是在1984年由台湾13家厂商与台湾地区财团法人信息工业策进会为五大中文套装软&#x4EF6;**（宏碁、神通、佳佳、零壹、大众**）所设计的中文内码，所以就称为Big5中文内码。

大五码是使用繁体中文社群中最常用的电脑汉字字符集标准，共收录13,060个中文字，其中有二字为重覆编码。

### Unicode

总数已经超过了65535，所以2个字节的数字是不够用的。

**Unicode编码系统为表达任意语言的任意字符而设计**。它使用4字节的数字来表达每个字母、符号，或者表意文字(ideograph)。

Unicode 还不断在扩增， 每个新版本插入更多新的字符。直至目前为止的第六版，**Unicode 就已经包含了超过十万个字符。**

**Unicode是字符集，UTF-32/ UTF-16/ UTF-8是三种字符编码方案。**

### Unicode与utf8是什么关系？

Unicode是字符集，UTF-32/ UTF-16/ UTF-8是三种字符编码方案。

## 字符编码

### GB2312编码

[GB2312简体中文编码表](http://www.knowsky.com/resource/gb2312tbl.htm)

如何进行编码？

GB2312一个汉字用两个字节表示。

原则上，两个字节可以表示 256×256＝65536 种不同的符号。

考虑到汉字编码与其它国际通用编码，如ASCII 西文字符编码的关系，我国国家标准局采用了加以修正的两字节汉字编码方案，只用了两个字节的低7位。这个方案可以容纳 128×128=16384 种不同的汉字；

为了与标准ASCII码兼容，每个字节中都不能再用３２个控制功能码和码值为32的空格以及127的操作码。

**所以每个字节只能有94个编 码。这样，双七位实际能够表示的字数是：94×94＝8836个。**

> 注意为了与标准ASCII码兼容，每个字节中都不能再用３２个控制功能码和码值为32的空格（特殊字符位，33个）以及1码值为127的操作码（1个，backspace），所以128-33-1=94，所以仅剩下**94×94＝8836个。94个区，94个位。**

## 区位码、国标码、机内码

### 区位码

这个方阵实际上组成一个有94个区（编号由01到94），每个区有94个位（编号由01到94）的汉字字符集。

一个汉字所在的区号和位号的组合就构成了该汉字的"区位码"。其中，高两位为区号，低两位为位号。**这样区位码可以唯一地确定某一汉字或字符；**

反之，任何一个汉字或符号都对应一个唯一的区位码，没有重码。如“保”字在二维代码表中处于17区第3位，区位码即“1703 ”。

### 国标码

国标码是由区位码稍作转换得到，**是一个汉字国家标准的二进制编码。**

其转换方法为：先将十进制区码和位码转换为十六进制的区码和位码；

这样就得了一个与国标码有一个相对位置差的代码，再**将这个代码的第一个字节和第二个字节分别加上20H，就得到国标码。**

如：“保”字的国标码为3123H，它是经过下面的转换得到的：1703D－>1103H->+20H－>3123H。 （20h就是十进制的32，上文提到了“但为了与标准ASCII码兼容，每个字节中都不能再用32个控制功能码和码值为32的空格以及127的操作码”）

> 知识提示：
>
> * 十六进制中H，就是表示数值是以16进制表示的。
> * O为8进制；
> * D是10进制，一般简略掉；
> * B是二进制。
>
> 解析过程：\
> 1\. 1703 十进制的区位码，分别转为16进制 17十进制 =>11十六进制（11H），03十进制=> 03十六进制 （03H），即为1103H\
> 2\. 再将第一个字节和第二个字节分别加上20H，11H+20H = 31H，03H+20H=23H，即3123H

### 机内码

国标码是汉字信息交换的标准编码，但因其前后字节的最高位为0，与ASCII码发生冲突，如 “保”字，国标码为31H和23H，而西文字符“1”和“#”的SCII也为31H和23H，现假如内存中有两个字节为31H和23H；

这到底是一个汉字，还是两个西文字符“1”和“#”？于是就出现了二义性，显然，国标码是不可能在计算机内部直接采用的。

于是汉字的**机内码**采用变形国标码，其变换方法为：将国标码的每个字节都加上128，即将两个字节的最高位由0改1，其余7位不变，如：由上面我们知道，“保”字的国标码为3123H，前字节为00110001B，后字节为00100011B，高位改1为10110001B和10100011B 即为B1A3H，因此，保字的机内码就是**B1A3**H;。**机内码是一个汉字在计算机内部保存的编码。**

> Unicode与 UCS
>
> 国际标准化组织（ISO）、多语言软件制造商组成的统一码联盟。
>
> unicode，为了世界上大多数文字系统进行整理和编码。
>
> ISO组织也在做同样的事情， ISO开展了 ISO/IEC 10646项目，名字叫“ Universal Multiple-Octet Coded Character Set”，简称UCS。
>
> 后来，双方意识到世界不需要2套通用的字符集，所以双方开始进行整合，到unicode2.0时，unicode的编码和ucs的编码都基本一致。

## unicode编码与UTF

一个字符的Unicode编码是确定的。

但是在实际传输过程中，由于不同系统平台的设计不一致，以及出于节省空间的目的，对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式（Unicode Translation Format，**简称为UTF**）。

**简单说UCS或Unicode只是定义了从0到1114112这些数字各自对应是什么字符，而计算机上是如何实现的就是由UTF决定的。**  <br>

UTF-8、 UTF-16、 UTF-32等都是Unicode的编码实现方式。

图：Unicode中的汉字片段

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHedjF44sHeljuaT%2FUnicode%E4%B8%AD%E7%9A%84%E6%B1%89%E5%AD%97%E7%89%87%E6%AE%B5.png?generation=1558862988102694\&alt=media)

> 重要：Unicode中的汉字组成并不像GBK一样按照汉语拼音来的，并没有什么明显的规律，所以在UTF-8中，汉字要得到汉字的拼音的话是办不到的，这时候要把他转换成GBK，然后再获取汉字的拼音。

### UTF-8 编码方式（规则）

### ![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHefFeKEeqCyluEC%2FUTF-8%20%E7%BC%96%E7%A0%81%E6%96%B9%E5%BC%8F.png?generation=1558862982292290\&alt=media)

> [UNICODE 区域对照表](http://blog.chinaunix.net/uid-10008293-id-2972268.html)
>
> 通过查看上面链接的区域对照表，中国属于`4E00-9FFF CJK Unified Ideographs 中日韓統一表意文字`
>
> 由上图可见属于0800-FFF中，
>
> UTF-8中汉字到底占几个字节？ 3个字节

### 手工把Unicode转为UTF-8的编码

1. “你好” 的unicode码 “ 20320 ” “ 22909 ”
2. 二进制: 100 111101 100000 , 101 100101 111101
3. 每个字从右往左填充 1110xxxx 10xxxxxx 10xxxxxx
4. 计算器二进制转换为16进制：E4  BD  A0 ,  E5 A5 BD

> 解析：
>
> 工具：[在线进制转换](http://tool.oschina.net/hexconvert)
>
> 1. 通过[Unicdoe【真正的完整码表】对照表（二）汉字Unicode表 ](http://blog.csdn.net/hherima/article/details/9045861)查找到 “你”的16进制为4F60 ，转换成十进制为20320， “好”的位置是十六进制是5960+1D = 597D ，转换成十进制为22909
> 2. “你好” 的unicode码 十进制分别为 “ 20320 ” “ 22909 ”
> 3. 转成二进制为00 111101 100000 , 101 100101 111101，从上图中汉字属于0800-FFF中，UTF-8字节流为`1110xxxx 10xxxxxx 10xxxxxx`
> 4. 每个字从右往左填充 1110xxxx 10xxxxxx 10xxxxxx（就是把xxx的）
>
> 11100100 10111101 10100000 ， 11100101 10100101 10111101\
> 5\. 采用在线转换工具，计算器二进制转换为16进制：E4 BD A0 , E5 A5 BD
>
> 练习把unicode转为UTF-8
>
> “北京” 的unicode码 “21271” , “20140”
>
> 第一步：计算器算出每个字的二进制
>
> 第二步：填充 1110xxxx 10xxxxxx 10xxxxxx
>
> 第三步：计算器二进制转换为16进制
>
> 第四步：16进制编辑填入结果，然后以UTF-8查看

### UTF-16

* UTF-16和上面提到的Unicode本身的编码规范是一致的。
* 所有字符的编码长度为16位，也即2个字节。
* 对ASCII字符来说，存储空间浪费严重。
* 不兼容ASCII。

### UTF的字节序和BOM

UTF-16编码每个字符占用了两个字节，在Macintosh (Mac)机和PC机上，对字节顺序的**理解是不一致的**。避免同一个文件造成错乱如何解决呢？

Unicode规范中推荐的标记字节顺序的方法是**BOM**（ Byte Order Mark ）。

> 在解释一个UTF-16文本前，首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E，“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”，那么这是“奎”还是“乙”？
>
> Unicode规范中推荐的标记字节顺序的方法就是BOM。

UTF-8不需要BOM来表明字节顺序，但可以用BOM来表明编码方式。

> BOM含义
>
> 开头字节 Charset/encoding
>
> **EF BB BF UTF-8**&#x20;
>
> FE FF UTF-16/UCS-2, little endian(UTF-16LE)
>
> FF FE UTF-16/UCS-2, big endian(UTF-16BE)
>
> FF FE 00 00 UTF-32/UCS-4, little endian.
>
> 00 00 FE FF UTF-32/UCS-4, big-endia

### **总结：**

* 区位码前两个位换成十六进制，然后后两位换成十六进制。
* 国际码=区位码（十六进制）＋2020H
* 机内码=国际码＋8080H
* 01-09区为特殊符号。

16-55区为一级汉字，按拼音排序。

56-87区为二级汉字，按部首/笔画排序。

10-15区 及88-94区则未有编码。

## 资料

[UNICODE与UTF-8的转换详解](http://blog.csdn.net/iefreer/article/details/4836844)


# 字符编码相关编程

**去掉bom头**

```php
 function removeBom($string)
    {
        if(substr($string, 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
              return substr($string, 3);
        }
        return $string;
    }
```

**汉字转拼音类**

[git码云链接](http://git.oschina.net/revinbian/PHPTools/blob/master/代码整理/PHP类/Pinyin.class.php)


# 引用变量

> 尽量避免使用引用

## 概念

在PHP中引用意味着用不同的名字访问同一个变量内容。

## 定义方式

使用 `&` 符号

## 工作原理

**写时复制（Copy on Write）**

```php
<?php
// 定义一个变量
$a = 1;
// 定义变量b，将a变量的值赋值给b
$b = $a;
// 对a进行修改
$a = 3
```

从内存的角度来分析一下这段代码“可能”是这样执行的：

* 1.分配一块内存给a变量，里面存储一个1；&#x20;
* 2.再分配一块内存给bar变量，也存一个1，
* 3.修改a变量的值。

其实并不是上面的步骤执行的，实际上再第2步的时候，a变量和b变量其实使用的是使用同一块内存（这样内存的使用就节省了一个对数值1的存储，并且，还省去了分配内存和管理内存地址的计算开销），只要在进行第3步进行修改的时候才会进行重新为a创建一块内存空间存储数值3，这个过程其实就是叫写时复制（Copy on Write，也缩写为COW)。

> COW 的应用场景非常多， 比如Linux中对进程复制中内存使用的优化，在各种编程语言中，如C++的STL等等中均有类似的应用。 COW是常用的优化手段，可以归类于：资源延迟分配。只有在真正需要使用资源时才占用资源， 写时复制通常能减少资源的占用。

**推迟内存复制的优化**

正如前面所说，PHP中的COW可以简单描述为：如果通过赋值的方式赋值给变量时不会申请新内存来存放新变量所保存的值，而是简单的通过一个计数器来共用内存，只有在其中的一个引用指向变量的值发生变化时才申请新空间来保存值内容以减少对内存的占用。在很多场景下PHP都COW进行内存的优化。比如：变量的多次赋值、函数参数传递，并在函数体内修改实参等。

下面让我们看一个查看内存的例子，可以更容易看到COW在内存使用优化方面的明显作用：

```php
<?php

$j = 1; 
var_dump(memory_get_usage());       // 1

$test = array_fill(0, 100000, 'php-internal'); 
var_dump(memory_get_usage());       // 2

$test_copy = $test; 
var_dump(memory_get_usage());      // 3

foreach($test_copy as $i){ 
    $j += count($i);  
} 
var_dump(memory_get_usage());   // 4
```

执行结果：

```
int(321688) // 1
int(5646176) // 2
int(5646232) // 3
int(5646280) //4
```

上面的代码比较典型的突出了COW的作用，在数组变量$test被赋值给$test\_copy时，内存的使用并没有立刻增加一半，在循环遍历数$test\_copy时也没有发生显著变化，在这里$test\_copy和$test变量的数据共同指向同一块内存，而没有产生复制。

也就是说，即使我们不使用引用，一个变量被赋值后，只要我们不改变变量的值 ，也不会新申请内存用来存放数据。据此我们很容易就可以想到一些COW可以非常有效的控制内存使用的场景：只是使用变量进行计算而很少对其进行修改操作，如函数参数的传递，大数组的复制等等等不需要改变变量值的情形。

**复制分离变化的值**

多个相同值的变量共用同一块内存的确节省了内存空间，但变量的值是会发生变化的，如果在上面的例子中，指向同一内存的值发生了变化（或者可能发生变化），就需要将变化的值“分离”出去，这个“分离”的操作，就是“复制”。

示例：

```php
<?php

// 定义一个变量
$a = range(0, 1000);
var_dump(memory_get_usage()); // 1

// 定义变量b，将a变量的值赋值给b
$b = $a;
var_dump(memory_get_usage()); //2

// 对a进行修改
$a = range(1000, 1000000);  
var_dump(memory_get_usage()); // 3
```

结果：

```
int(404824) //1
int(404872)  // 2
int(84519408) //3
```

第3步，内存明显发生变化，也就是上面说的值“分离”的操作，就是“复制”，也就是写时复制（Copy on Write，也缩写为COW)。

在PHP中，Zend引擎为了区别同一个zval地址是否被多个变量共享，引入了ref\_count和is\_ref两个变量进行标识,以下是PHP变量在C语言底层中的代码,：

```c
typedef struct _zval_struct zval;
typedef unsigned int zend_uint;
typedef unsigned char zend_uchar;

struct _zval_struct {
    zvalue_value value;      /*注意这里，这个里面存的才是变量的值*/
    zend_uint refcount;  /*引用计数*/
    zend_uchar type;        /* 变量当前的数据类型 */
    zend_uchar is_ref;   /*变量是否引用*/
};

typedef union _zvalue_value {
    long lval;      /*PHP中整型的值*/
    double dval;    /*PHP的浮点数值*/
    struct {     
        char *val;
        int len;
    } str;               /*PHP的字符串*/
    HashTable *ht;     /*数组*/
    zend_object_value obj;  /*对象*/
} zvalue_value;
```

* is\_ref标识是不是用户使用 `&`的强制引用；布尔类型
* ref\_count是引用计数，用于标识此zval被多少个变量引用，即COW的自动引用，为0时会被销毁；

> 注：由此可见， $a=$b; 与 $a=&$b; 在PHP对内存的使用上没有区别（值不变化时）；

**安装xdebug，可以利用利用xdebug\_debug\_zval(),可以看到zval结构**

```php
<?php

// zval变量容器
$a = range(0, 3);
xdebug_debug_zval('a');

// 定义变量b，把a的值赋值给b
$b = $a;
xdebug_debug_zval('a');

// 修改a
$a = range(0, 3);
xdebug_debug_zval('a');
```

结果：

```
a: // 1
(refcount=1, is_ref=0),
array (size=4)
  0 => (refcount=1, is_ref=0),int 0
  1 => (refcount=1, is_ref=0),int 1
  2 => (refcount=1, is_ref=0),int 2
  3 => (refcount=1, is_ref=0),int 3
a: // 2
(refcount=2, is_ref=0),
array (size=4)
  0 => (refcount=1, is_ref=0),int 0
  1 => (refcount=1, is_ref=0),int 1
  2 => (refcount=1, is_ref=0),int 2
  3 => (refcount=1, is_ref=0),int 3
a: // 3
(refcount=1, is_ref=0),
array (size=4)
  0 => (refcount=1, is_ref=0),int 0
  1 => (refcount=1, is_ref=0),int 1
  2 => (refcount=1, is_ref=0),int 2
  3 => (refcount=1, is_ref=0),int 3
```

从结果可以看到，

* 1.refcount = 1：有一个内存空间，并且有一个变量指向这个内存空间；is\_ref = 0: 不是引用。
* 2.refcount = 2：有一个内存空间，并且有两个变量指向这个内存空间；is\_ref = 0: 不是引用。
* 3.refcount = 1：有一个内存空间，并且有一个变量指向这个内存空间；is\_ref = 0: 不是引用。

**采用引用方式：**

```php
<?php

$a = range(0, 3);
xdebug_debug_zval('a');

$b = &$a;
xdebug_debug_zval('a');

$a = range(0, 3);
xdebug_debug_zval('a');
```

结果：

```
a:
(refcount=1, is_ref=0),
array (size=4)
  0 => (refcount=1, is_ref=0),int 0
  1 => (refcount=1, is_ref=0),int 1
  2 => (refcount=1, is_ref=0),int 2
  3 => (refcount=1, is_ref=0),int 3
a:
(refcount=2, is_ref=1),
array (size=4)
  0 => (refcount=1, is_ref=0),int 0
  1 => (refcount=1, is_ref=0),int 1
  2 => (refcount=1, is_ref=0),int 2
  3 => (refcount=1, is_ref=0),int 3
a:
(refcount=2, is_ref=1),
array (size=4)
  0 => (refcount=1, is_ref=0),int 0
  1 => (refcount=1, is_ref=0),int 1
  2 => (refcount=1, is_ref=0),int 2
  3 => (refcount=1, is_ref=0),int 3
```

* 1.refcount = 1：有一个内存空间，并且有一个变量指向这个内存空间；is\_ref = 0: 不是引用。
* 2.refcount = 2：有一个内存空间，并且有两个变量指向这个内存空间；is\_ref = 1: 是引用。
* 3.refcount = 2：有一个内存空间，并且有两个变量指向这个内存空间；is\_ref = 1: 是引用。

$a指向一个内存空间，$a将内存地址给了$b,不在有COW机制，因为两个变量指向的同一片内存地址空间，修改$a的值，也相当于修改$b的值

## 需要注意的点

**unset 只会取消引用，不会销毁空间**

使用引用时， 变量同时指向同一片内存空间，当 unset 一个引用，只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了。

```php
<?php

// unset 只会取消引用，不会销毁空间
$a = 1;

$b = &$a;

unset($b);

echo $a. "\n";
```

结果：

```
1
```

**对象本身就是引用传递**

```php
<?php
class Person
{
    public $name = "abc";
}

$p1 = new Person;
xdebug_debug_zval('p1');

$p2 = $p1;
xdebug_debug_zval('p1');

$p2->name = "lisi";
xdebug_debug_zval('p1');

var_dump($p1);
var_dump($p2);
```

结果：

```php
p1:
(refcount=1, is_ref=0),
object(Person)[1]
  public 'name' => (refcount=2, is_ref=0),string 'abc' (length=3)
p1:
(refcount=2, is_ref=0),
object(Person)[1]
  public 'name' => (refcount=2, is_ref=0),string 'abc' (length=3)
p1:
(refcount=2, is_ref=0),
object(Person)[1]
  public 'name' => (refcount=1, is_ref=0),string 'haha' (length=4)
object(Person)[1]
  public 'name' => string 'haha' (length=4)
object(Person)[1]
  public 'name' => string 'haha' (length=4)
```

你会发现虽然is\_ref始终是0.但是通过refcount可看出两个变量同时指向的相同的内存空间，单独对一个对象进行属性修改，但是两个对象的属性值都发生了改变，没有进行COW.这也是对象的特殊性，这也是为什么对象会有克隆clone：

```php
<?php
class Person
{
    public $name = "abc";
}

$p1 = new Person;
xdebug_debug_zval('p1');


$p2= clone $p1;  //这个才是正在重新开辟新的内存空间
xdebug_debug_zval('p1');

$p2->name = "haha";
xdebug_debug_zval('p1');

var_dump($p1);
var_dump($p2);
```

结果：

```
p1:
(refcount=1, is_ref=0),
object(Person)[1]
  public 'name' => (refcount=2, is_ref=0),string 'abc' (length=3)
p1:
(refcount=1, is_ref=0),
object(Person)[1]
  public 'name' => (refcount=3, is_ref=0),string 'abc' (length=3)
p1:
(refcount=1, is_ref=0),
object(Person)[1]
  public 'name' => (refcount=2, is_ref=0),string 'abc' (length=3)
object(Person)[1]
  public 'name' => string 'abc' (length=3)
object(Person)[2]
  public 'name' => string 'haha' (length=4)
```

## 避免使用引用

```
<?php 
$foo['love'] = 1; 
$bar  = &$foo['love']; 
$tipi = $foo; 
$tipi['love'] = '2'; 
echo $foo['love'];
```

结果：

```
2
```

大家肯定会有困惑，很明显造成的元音是`$bar = &$foo['love'];`这一行，也就是说数组中的第一个元素已经变成了引用类型。所以赋值时也是引用拷贝，而非值拷贝。

从这篇文章中也同样解析了这种现象，[谨慎使用PHP的引用](https://www.gitbook.com/book/xiaoxiami/phper/edit#)

## 思考:

写出如下程序的输出结果

```
 <?php

 $data = array('a', 'b', 'c');

 foreach($data as $key => $val)
 {
      $val = &$data[$key];
 }
```

程序运行时，每一次循环结束后变量$data的值是什么？请解释

程序执行完成后，变量$data的值是什么？请解释

```
array (size=3)
  0 => &string 'a' (length=1)
  1 => string 'b' (length=1)
  2 => string 'c' (length=1)
array (size=3)
  0 => string 'b' (length=1)
  1 => &string 'b' (length=1)
  2 => string 'c' (length=1)
array (size=3)
  0 => string 'b' (length=1)
  1 => string 'c' (length=1)
  2 => &string 'c' (length=1)
array (size=3)
  0 => string 'b' (length=1)
  1 => string 'c' (length=1)
  2 => &string 'c' (length=1)
```

## 资料

[PHP官方对引用的解释](https://secure.php.net/manual/zh/language.references.php)

[PHP 之 写时复制介绍（Copy On Write）](http://www.jb51.net/article/50080.htm?timer=tc)

[C语言角度解读php变量之写时复制机制(copy on writ](http://www.cnblogs.com/yclu/p/4712347.html)e)

[PHP中的写时复制（Copy On Write）](http://blog.csdn.net/yeoman92/article/details/52793371)

[PHP源码分析-变量的引用计数、写时复制(Reference counting & Copy-on-Write)](http://blog.csdn.net/a600423444/article/details/7030736)


# 运算符与错误控制符@

## 错误控制符@

php支持一个错误运算符：@。当将其放置在一个PHP表达式之前，该表达式可能产生任何错误信息都被忽略掉。不建议使用。

## 运算符

[官方文档](https://secure.php.net/manual/zh/language.operators.precedence.php)

**运算符优先级：**  <br>

**递增/递减>!>算数运算符>大小比较>(不)相等比较**>引用>位运算符(^)>**逻辑与>逻辑或>三目运算>赋值**>and>xor>or

> 括号的使用可以增加代码可读性

* 递增/递减运算符不影响布尔值

  (也就是递增递减 true  和false)
* 递减NULL 没有效果
* 递增NULL值为1

递增和递减在前就先运算符后返回，反之就先返回，后运算。

```php
$a = true;
if($a++) {
    echo "true1";  // 执行
}

if($a--) {
    echo "true2"; // 执行
}
$b = false;
if($b++){
    echo "false1"; // 不执行
}
var_dump($b);   // false
if($b--){
    echo "false1"; // 不执行
}
var_dump($b);   // false

 $c = null;
echo --$c;
var_dump($c); // null
echo ++$c; // 输出 1
```

**特别注意短路作用：**  <br>

当逻辑与时，如果前面为false，则后面不做执行

当逻辑或，如果前面为true，则后面不在做执行

```php
//相等比较 > 逻辑与 && > 逻辑或 || > 赋值
echo $a = true || $b == 3  // true
echo $b = false && $a==1 //false

//逻辑与 && > 逻辑或 || > 赋值 > and > or 

echo $a = false || true;  // true
echo $b = false or true // true
```

实例：

下列程序中请写出打印输出的结果：

```php
$a = 0;
$b = 0;

if ($a = 3 > 0 || $b = 3 > 0) 
{
    $a++;
    $b++;
    echo $a. "\n";
    echo $b. "\n";
}
```

结果：

```
boolean true
int 0
1 1
```

解析：

1.优先级 ：比较运算符（>） `||` > 赋值运算

2.按照以上逻辑， `3 > 0 为true，继续查看为逻辑或，逻辑或的原则是i前为true，则逻辑结果即可返回true，后面不在执行`

3.因此$a 拿到了逻辑运算后的true，而$b没有执行，仍然为0

4.前面说过对一个boolean类型的值进行递增/递减不会影响布尔值 ,因此

* $a的值不发生改变，输出被强制转为1.
* $b=0;$b++;则为1

## 拓展

* 判断int型变量a是奇数还是偶数：a&1 = 0 偶数  a&1 = 1 奇数
* 乘法运算转化成位运算：a \* (2^n) 等价于 a<< n
* 除法运算转化成位运算：a / (2^n) 等价于 a>> n
* 不用temp交换两个整数x ^= y; y ^= x; x ^= y;
* 二进制位掩码，提供了一种用一个选项表示多项的可能。（参考：error\_reporting设置错误级别的方式）

```php
Error_Reporting(E_Error | e_Warning | e_Parse | e_Notice);
error_reporting(E_ALL ^ E_NOTICE);
```


# 流程控制与条件判断

## 流程控制语句

**3中流程控制语句**  <br>

* 使用for循环
* 使用foreach循环
* 使用while、list()、each()组合循环

**介绍：**

* for循环，只能遍历索引数组，
* foreach可以遍历索引和关联数组
* 联合使用list(),each()和while循环同样可以遍历索引和关联数组

**区别**  <br>

* list(),each()和while组合不会reset()操作（之前有对数组进行了指针的操作，则使用此方式，则不会重置数组，而是在这个指针往后循环，就会出现数据不完整）
* forreach遍历会对数组进行resrt()操作

示例：

```php
<?php

$data = array('a','b','c');
next($data);  //改变了数组的指针指向

echo 'while循环' . '<br>';
while(list($k,$v) = each($data)){ 
    echo $k . '=' . $v. '<br />'; 
} 

echo 'for循环' . '<br>';
for($i=0;$i< count($data); $i++) {
    echo $i . '=' . $data[$i] . '<br>';
}

echo 'foreach循环' . '<br>';
foreach($data as $k => $v) {
    echo $k . '=' . $v . '<br>';
}
```

输出结果：

```
while循环
1=b
2=c
for循环
0=a
1=b
2=c
foreach循环
0=a
1=b
2=c
```

## 条件判断语句

### **switch...case..语句**

switch后面的控制表达式的数据类型只能是**整型、浮点类型或者字符串**  <br>

continue语句作用到switch的作用类似break;

跳出switch外的循环，可以使用continue2 或者break2；

示例：

```
<?php

$a = 10;
for($i=0;$i< 10;$i++) {
    switch($i){
        case 5:
            break 2;
        default:
            break;
    }

    echo $i.'<br>';
}
```

结果：

```
0
1
2
3
4
```

**效率：**  <br>

如果条件判断比较复杂或者在一个很多次的循环中，那么用switch语句可能会快一些

### php如何优化多个if..else语句的情况？

* 1.尽量把可能性大的判断往前放
* 2.如果条件判断比较复杂或者在一个很多次的循环中，并且判断是一个是整型、浮点类型或者字符串换用switch..case..


# foreach遍历中的引用

```php
$arr = array(1,2,3);
foreach($arr as &$v){}

foreach($arr as $v){
    echo $v;
}
```

foreach遍历中的引用分析

**第一次foreach循环**

foreach($a as &$v) {$v \*= $v;}

这里由于是引用绑定，所以相当于对数组中的元素执行:

```php
$v = &$a[0]; 
$v = &$a[1]; 
$v = &$a[2];
```

留下一个引用变量没有unset将数组最后一个变量变成引用。

**第二次foreach循环**

```php
foreach($a as $v){
     echo $v;
}
```

类似与执行：

```php
$v = $a[0]; 
$v = $a[1];
$v = $a[2];
```

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHefS0CXAe1418YG%2Fforeach%E9%81%8D%E5%8E%86%E4%B8%AD%E7%9A%84%E5%BC%95%E7%94%A81.png?generation=1558862967761388\&alt=media)

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHehGbF-o0O16Q9D%2Fforeach%E9%81%8D%E5%8E%86%E4%B8%AD%E7%9A%84%E5%BC%95%E7%94%A82.png?generation=1558862966484359\&alt=media)

## 如何杜绝foreach的引用隐患？

**方法一**

```php
foreach($a as $k=>&$v){
…
unset($v);
}
```

**方法二**

```php
foreach($a as $k=>&$v){
    …
}
unset($v)
```

## foreach引用的好处

节省时间、空间


# 函数

## 函数

### 变量的作用域

变量的作用域也称变量的范围，变量的范围即它定义的上下文背景（也是它的生效范围）。大部分php变量只有一个单独的范围。这个单独的范围跨度同样包含了include和require引入的文件。

```
$outer = 'str'; // 全局变量
function myfunction(){
    echo $outer;  // 局部变量
}
myfunction();
```

全局变量是不能拿到函数体内部使用的，两个$outer并不是一个，所以写程序应该避免这种同名混淆

如果内部想访问使用外部的那个全局变量，但是极力不推荐，如下面两种方式；

```
$outer = 'str'; // 全局变量
function myfunction(){
    global $outer;
    echo $outer;  // 局部变量
}
myfunction();
```

```
$outer = 'str'; // 全局变量
function myfunction(){
    echo $GLOBALS['outer'];  // 局部变量
}
```

### 静态变量

static关键词

* 1.仅初始化一次
* 2.初始化时需要赋值;
* 3.每次执行函数该值会保留
* 4.static 修饰的变量是局部的，仅在函数内部有效
* 4.可以记录函数的调用次数，从而可以在某些条件下终止递归

```
function myFunction() {
    static $a = 1;
    echo $a++;
}
myFunction(); // 1
myFunction(); // 2
myFunction(); // 3
```

示例：

```
$count = 5;
function get_count()
{
    static $count;  
    return $count++;
}

echo $count; // 5
++$count;

echo get_count(); // null
echo get_count();  // 1
```

结果`51`

* 1.函数内部和外部的count变量不是同一个
* 2.第一次调用函数时，默认没有赋值的count变量是null，后++，先返回后计算，所以仍然为null
* 3.第三次时候，返回上次计算过后的null ++ 结果返回（注意：null++的值是1，而null--的值还是null）；

### 函数的参数及参数的引用传递

默认情况下，函数参数通过值传递

如果希望允许函数修改它的值，必须通过引用传递参数

```
<?php

$count = 5;
get_count($count);
function get_count(&$var)
{
    $var = 1;
}

echo $count;
```

结果为1

避免使用如下方式：

```
<?php

$count = 5;
get_count(&$count);
function get_count($var)
{
    $var = 1;
}

echo $count;
```

中会得到一条警告说“Call-time pass-by-reference”已经过时了。详情[官方文档](http://www.php.net/manual/zh/language.references.pass.php)

### 函数的返回值及引用返回

**函数的返回值：**  <br>

* 值通过使用可选的返回语句（return）返回
* 可以返回包括数组和对象任意类型
* 返回语句会终止函数执行，将控制权交回函数调用处

从函数返回一个引用，必须在函数声明和指派返回值给一个变量时都使用引用运算符&

如下示例：

```php
<?php
function &myFunc(){
    static $b = 10;
    return $b;
}
$a = myFunc();
$a = &myFunc();
$a = 100;
echo  myFunc();
```

### 外部文件的引入

include/require语句包含并运行指定文件

如果给出路径名按照路径来找，否则从include\_path中查找

如果include\_path中也没有，则从调用脚本文件所在的目录和当前工作目录下寻找

当一个文件被包含时，其中所包含的代码继承了include所在行的变量范围

**区别**

* 加载过程中未找到文件则include结构会发出一条警告，这一点和require不同，后者会发出一个致命错误
* require 在出错时产生E\_COMPILE\_ERROR级别的错误。换句话说将导致脚本中止而include只产生警告（E\_WARNING),脚本会继续运行
* require(include)/require\_once(include\_once)唯一区别是PHP会检查该文件是否已经被包含，如果是则不会再次包含。

### 总结：

```php
<?php
$var1 = 3;
function myfuntion()
{
    global $var1;
    $var1 += 2; // 7
}
myfuntion();
echo $var1;
die;


$var1 = 5;
$var2 = 10;

function foo(&$my_var)
{
    global $var1;//5
    $var1 += 2; // 7
    $var2 = 4; //4
    $my_var += 3; //8
    return $var2; //4
}

$my_var = 5;
echo foo($my_var). "\n"; //4
echo $my_var. "\n"; //8
echo $var1; //7
echo $var2; //10
$bar = 'foo';
$my_var = 10;
echo $bar($my_var). "\n"; //4
die;

function &myFunc(){
    static $b = 10;
    return $b;
}
$a = myFunc();
$a = &myFunc();
$a = 100;
echo  myFunc();
```

> 特别注意上面函数内部global的问题，运行函数，同时会改变外部变量的值，得出7。


# 文件及目录处理

## 文件读取/写入操作

**打开函数:**

* fopen()

用来打开一个文件，打开时需要指定打开模式

打开模式r/r+、w/w+、a/a+、x/x+、b、t

**写入函数：**  <br>

* fwrite()
* fput()

**读取函数**  <br>

* fread()
* fgets()-获取一行
* fgetc()-获取一个字符

**关闭文件函数**  <br>

* fclose()

**不需要fopen打开的函数**  <br>

* file\_get\_contents()
* file\_put\_contents()

**其他读取函数**  <br>

* file()
* readfile()

**访问远程文件**  <br>

开启allow\_url\_fopen,HTTP协议连接只能使用只读，FTP协议可以使用只读或者只写

只要这样才能通过fopen()和file\_get\_contens()来进行远程读取

## 目录操作函数：

* 名称相关：basename(),dirname(),pathinfo()
* 目录读取：opendir()、readdir()、closedir()、rewinddir()
* 目录删除：rmdir() - 目录为空才能进行删除
* 目录创建:mkdir()
* 文件大小：filesize()
* 目录大小：disk\_free\_space() - 磁盘剩余空间、disk\_total\_space()
* 文件类型：filetype()-常见文件或目录
* 重命名文件或目录：rename()
* 文件截取ftruncate()
* 文件属性：file\_exists()、is\_readable()、is\_writable()、is\_executable()、filectime()、fileatime()、filemtime()

## 示例：

不断在文件hello.txt头部写入一行"hello world" 字符串，要求代码完整

```php
<?php

// 打开文件
//
// 将文件的内容读取出来，在开头加入Hello World
//
// 将拼接好的字符串写回到文件当中
//
// Hello abcefg
//
$file = './hello.txt';

$handle = fopen($file, 'r');

$content = fread($handle, filesize($file));

$content = 'Hello World'. $content;

fclose($handle);

$handle = fopen($file, 'w');

fwrite($handle, $content);

fclose($handle);
```

循环遍历目录

```php
<?php

$dir = './test';

// 打开目录
// 读取目录当中的文件
// 如果文件类型是目录，继续打开目录
// 读取子目录的文件
// 如果文件类型是文件，输出文件名称
// 关闭目录
//

function loopDir($dir)
{
    $handle = opendir($dir);

    while(false!==($file = readdir($handle)))
    {
        if ($file != '.' && $file != '..')
        {
            echo $file. "\n";
            if (filetype($dir. '/'. $file) == 'dir')
            {
                loopDir($dir. '/'. $file);
            }
        }
    }
}

loopDir($dir);
```


# PHP 数组


# 基础

## 构建数组

```php
 $items = array(); 

 //也可以不做初始化直接写
 $items[0]= 'abc123' ; 
 $items[]='abc123' ;
 $items['name']='andy';

//
$item = ['a', 'b', 'c'];
```

## 数组式访问接口

**提供像访问数组一样访问对象的能力的接口。**

[ArrayAccess（数组式访问）接口](http://php.net/manual/zh/class.arrayaccess.php)

```php
ArrayAccess {
/* 方法 */
abstract public boolean offsetExists ( mixed $offset )
abstract public mixed offsetGet ( mixed $offset )
abstract public void offsetSet ( mixed $offset , mixed $value )
abstract public void offsetUnset ( mixed $offset )
}
```

* ArrayAccess::offsetExists — 检查一个偏移位置是否存在
* ArrayAccess::offsetGet — 获取一个偏移位置的值
* ArrayAccess::offsetSet — 设置一个偏移位置的值
* ArrayAccess::offsetUnset — 复位一个偏移位置的值

**示例:**

```php
class obj implements ArrayAccess {
    private $container = array();
    public function __construct() {
        $this->container = array(
             'one' => 1,
             'two' => 2,
             'three' => 3,
        );
    }

    public function offsetSet($offset, $value) {
        if (is_null($offset)) {
            $this->container[] = $value;
        } else {
            $this->container[$offset] = $value;
        }
    }
    function offsetExists($offset) {
        return isset($this->container[$offset]);
    }

    function offsetUnset($offset) {
        unset($this->container[$offset]);
    }

    function offsetGet($offset) {
        return isset($this->container[$offset]) ? $this->container[$offset] : '';
    }
}

$obj = new obj();
$obj[] = 'Append 1';
$obj[] = 'Append 2';
$obj[] = 'Append 3';
print_r($obj);
die;
var_dump(isset($obj['two']));
var_dump($obj['two']);
unset($obj['two']);
var_dump(isset($obj['two']));
```

思考：

* 上面的四个接口方法分别有什么作用？在什么情况下会被调用？
* 如果不继承ArrayAccess接口，只是实现这四个方法还可以实现数组是访问的功能么？

## 数组key和value的限制条件

什么类型值可以当做数组的key，数组的value值又可以存什么类型？

* key 可以是 integer 或者 string。
* value 可以是任意类型。

**key 会有如下的强制转换:**

* **包含有合法整型值的字符串会被转换为整型**
* 浮点数和布尔值也会被转换为整型
* **键名 null 实际会被储存为 ""**
* 数组和对象不能被用为键名
* 相同键名，之前会被覆盖

示例：

```php
$array = array(
    1 => "a",
    "1" => "b",
    1.5 => "c",
    true => "d",
    false => 'e',
    null => 'f',
    '2.5' => 'g',
    3.5 => 'h',
);
var_dump($array);
```

结果：

```
array (size=5)
  1 => string 'd' (length=1)
  0 => string 'e' (length=1)
  '' => string 'f' (length=1)
  '2.5' => string 'g' (length=1)
  3 => string 'h' (length=1)
```

示例：

```php
$array = array(
    "foot" => "bar",
    "bar" => "foot",
    100 => -100,
    -100 => 100,
);
var_dump($array);
```

结果：

```
array (size=4)
  'foot' => string 'bar' (length=3)
  'bar' => string 'foot' (length=4)
  100 => int -100
  -100 => int 100
```

## 数组的访问

获取数组元素的值有哪些方法？

1. 方括号，数组单元可以通过 `array[key]` 语法来访问。
2. 花括号也可以，例如 `$array[42]`和`$array{42}` 效果相同。
3. 方括号可以包括“表达式”，例如：`$arr[somefunc($bar)];`
4. 自 PHP 5.4 起可以用数组间接引用函数或方法调用的结果。

示例

4.数组的间接引用

```php
function getArray() {
    return array(1, 2, 3);
}

// 常规做法
$tmp = getArray();
$secondElement = $tmp[1];

// on PHP 5.4 
$secondElement = getArray()[1];
//或者
list(,$secondElement) = getArray();
```

## 数组元素的删除unset

unset() 函数允许删除数组中的某个键。

```php
$array = array(1, 2, 3, 4, 5, 6);
foreach ($array as $key => $value) {
    unset($array[$key]);
}
$array[] = 6;
print_r($array);
```

结果：

```
Array ( [6] => 6 )
```

> 注意：数组unset后，不会重建索引。

## PHP数组类型与其他类型的转换

在PHP中，存在8种变量类型，分为三类

* 标量类型： boolean、integer、float(double)、string
* 复合类型： array、object
* 特殊类型： resource、NULL

**思考：**

**如何将一个int、float、string、boolean 类型转换为数组？**

* int，float，string，boolean 和 resource 类型 (array)$scalarValue 等同 array($scalarValue) &#x20;
* object 转换为 array，结果为一个数组，其单元为该对象的属性，键名将为成员变量名
* 将 NULL 转换为 array 会得到一个空的数组

**var\_dump((array)false)和var\_dump((array)null)的输出结果是？**

```php
var_dump((array)false);
var_dump((array)null);
```

结果:

```
array(1) {
    [0] =>
    bool(false)
}
array(0) {
}
```

**示例:**

对象转数组

```php
//对象转为数组
class User{
    public $name = 'andy';
    public $age = 53;
    private $phone = '158xxxxxxxx';
    private $email = 'xx@gmail.com';
    protected $location = 'China';
}

var_dump((array) new User());
```

结果:

```php
array(5) {
  'name' =>
  string(4) "andy"
  'age' =>
  int(53)
  '\0User\0phone' =>
  string(11) "158xxxxxxxx"
  '\0User\0email' =>
  string(12) "xx@gmail.com"
  '\0*\0location' =>
  string(5) "China"
}
```

## 数组的遍历

1. for ：语句循环遍历&#x20;
2. foreach ：循环遍历&#x20;
3. while： (list($key, $val) = each($fruit))&#x20;
4. array\_walk、array\_map ：回调遍历
5. current和next ：内部指针遍历

**array\_walk与array\_map有什么不同**

* array\_walk 引用的方式对数组进行遍历，返回值不重要
* array\_map 为了改变数组的数据，支持多个数组数据合并，目的是返回新的数组
* walk和map的回调函数位置也不一样

**foreach 和 for 性能对比**

一般php推荐使用foreach来遍历数组。

**foreach遍历中的顺序**

加入元素的顺序

```php
$a = array();
$a[2] = 3;
$a[1] = 2;
$a[0] = 1;
foreach ($a as $v) {
    echo $v;
}
```

结果：

```
321
```

## PHP数组“指针”相关函数

* current() - 返回数组中的当前单元
* end() - 将数组的内部指针指向最后一个单元
* next() - 将数组中的内部指针向前移动一位
* prev() - 将数组中的内部指针向前移动一位
* reset() - 将数组的内部指针指向第一个单元
* each() - 返回当前的键/值对并将指针向前一步

## PHP中的预定义变量（数组）

PHP 中的许多预定义变量都是“超全局的”。

这意味着它们在一个脚本的全部作用域中都可用。在函数或方法中无需执行 `global $variable;` 就可以访问它们。

* $GLOBALS — 引用全局作用域中可用的全部变量
* $\_SERVER — 服务器和执行环境信息
* $\_GET — HTTP GET 变量
* $\_POST — HTTP POST 变量
* $\_FILES — HTTP 文件上传变量
* $\_REQUEST — HTTP Request 变量
* $\_SESSION — Session 变量
* **$\_ENV — 环境变量**
* $\_COOKIE — HTTP Cookies


# PHP数组操作

## 数组转换为字符串（序列化、持久化）

**为什么要序列化？**

* Api接口通信
* 数据缓存
* 数组数据持久化（例：保存到数据库中）

**序列化的方法？**

* 方法一：函数 serialize() 可以实现
* 方法二：函数 json\_encode ()可以实现
* 方法三：函数 var\_export($items, true);
* 方法四：xml、其他自定义文件格式…
* 方法五：mcpack、protobuffer等二进制序列化方式

## 合并两个数组

通常我们会用到`+`运算符 或者`array_merge`函数。

**数组合并中**`+`**运算符 与** `array_merge` **的区别？**

**array\_merge**

* 一个数组中的值附加在前一个数组的后面
* 字符串键名，则该键名后面的值将覆盖前一个值
* 数字键名，后面的值将不会覆盖原来的值，而是附加到后面。
* **数字键名将会被重新编号。**
* 也可以是一个参数，并且该数组是数字索引的，则键名会以连续方式重新索引

**+运算符**

把右边的数组元素附加到左边的数组后面，**两个数组中都有的键名（索引和数字），则只用左边数组中的，右边的被忽略。**

**区别：**

* array\_merge ：覆盖，相同数字键追加。
* +运算符：补充，相同数字键忽略。

**示例：**

通常我们的用法是有字符串键值，去覆盖

```php
$a = array('id' => 1, 'name' => 'kitt', 'sku' => '123456');
$b = array('name' => 'kitt1', 'title' => 'hello');

print_r($a+$b);
print_r(array_merge($a,$b));
```

结果：

```php
Array ( [id] => 1 [name] => kitt [sku] => 123456 [title] => hello ) 
Array ( [id] => 1 [name] => kitt1 [sku] => 123456 [title] => hello )
```

## 数组运算符

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHfGyfmhoDojBBMg%2F%E6%95%B0%E7%BB%84%E8%BF%90%E7%AE%97%E7%AC%A6.png?generation=1558862967774544\&alt=media)

## PHP数组元素查找

* 判断key是否存在：array\_key\_exists() 和 isset()
* 判断值是否存在： in\_array() 和 array\_search()
* 依据键返回值： $items\[$key]
* 依据值返回键： array\_keys ($items,$value);
* 依据给定值进行模糊查找：preg\_grep();

## PHP数组元素的过滤和移除

* 方法1：直接用unset移除元素
* 方法2：**array\_slice**从数组中取出一段
* 方法3：array\_splice把数组中的一部分去掉并用其它值取代
* 方法4：用array\_filter过滤元素
* 方法5：用array\_shift()将开头的元素移出数组
* 方法6：用array\_pop ()将最后一个元素弹出

## PHP数组排序的原理

由于数组排序并不会改变数组中的元素，而只是改变了数组中元素的位置，因而，对底层而言，实际上只是对全局的**双链表进行排序。**

申请n个额外的空间（n是数组元素个数）

然后遍历双链表，将双链表的每个节点存储到临时空间

调用排序函数zend\_qsort（内部是**快速排序算法**）对数组进行排序

排序之后，双链表中节点的位置发生了变化，因而需要调整指针的指向。

遍历数组，分别设置每一个节点的pListLast和pListNext

最后设置HashTable的pListTail

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHfICCiO3dRSRY4s%2Fphp%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F%E5%8E%9F%E7%90%861.png?generation=1558862975219544\&alt=media)

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHfKLY0usIYK-MXY%2Fphp%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F%E5%8E%9F%E7%90%862.png?generation=1558862971476841\&alt=media)

**PHP数组排序函数的一般特点：**

* 需要额外的空间，**因而应该尽量避免对很大的数组排序.**
* 底层使用快速排序。

## PHP数组排序函数对比

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHfMfxENlEknsWif%2Fphp%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F%E5%87%BD%E6%95%B0%E5%AF%B9%E6%AF%94.png?generation=1558862969115160\&alt=media)

## 与PHP数组相关的其他函数

* count
* is\_array
* print\_r
* explode
* implode（join）
* extract  从数组中将变量导入到当前的符号表
* http\_build\_query 生成 URL-encode 之后的请求字符串
* parse\_str

## PHP数组函数分类

* 数组遍历相关函数：如prev, next, current, end,reset, each等
* 数组排序相关：如sort, rsort, asort, arsort, ksort, krsort, uasort, uksort
* 数组查找相关: 如in\_array, array\_search, array\_key\_exists等
* 数组分割、合并相关： array\_slice, array\_splice, implode, array\_chunk, array\_combine等
* 数组交并差：如array\_merge, array\_diff, array\_diff\_\*, array\_intersect, array\_intersect\_\*
* 作为stack/queue容器的数组： 如array\_push, array\_pop, array\_shift
* 其他的数组操作：array\_fill, array\_flip, array\_sum, array\_reverse等


# 输入流 php\://input

## $\_POST 与 php\://input

1. 仅在取值为`application/x-www-data-urlencoded`和`multipart/form-data`时(文件上传时)，php会将http请求body相应数据会填入到数组$\_POST，填入到$\_POST数组中的数据是进行urldecode()解析的结果。
2. 只要Content-Type不为`multipart/form-data`， `php://input`会填入post数据。
3. 仅当Content-Type为`application/x-www-form-urlencoded`且提交方法是POST方法时，$\_POST数据与`php://input`数据才是一致的。

## $HTTP\_RAW\_POST\_DATA 与 php\://input

`php://input`可以读取没有处理过的POST数据。相较于$HTTP\_RAW\_POST\_DATA而言，它给内存带来的压力较小。

$HTTP\_RAW\_POST\_DATA（This feature has been DEPRECATED as of PHP 5.6.0. 被废弃了，查看[官方文档](https://secure.php.net/manual/zh/reserved.variables.httprawpostdata.php)）

更多php\://input，查看[官方文档](https://secure.php.net/manual/zh/wrappers.php.php#wrappers.php.input)

> 解析
>
> ```php
> var_dump($_POST);
> echo file_get_contents("php://input");
> ```
>
> 情况1: 文件上传时,form的`enctype="multipart/form-data",`此时,数据`php://input`获取不到数据
>
> ```php
> <form enctype="multipart/form-data" method="post">
> <input type="text" name="name" />
> <input type="file" name="csv_file" />
> <button type="submit" name="submit" value="Submit">Submit</button>
> </form>
> <?php
> var_dump($_POST);
> echo "<br>";
> var_dump(file_get_contents("php://input"));
> ```
>
> 测试结果 只有`$_POST`获取了数据
>
> array (size=2)
>
> 'name' => string 'test' (length=4)
>
> 'submit' => string 'Submit' (length=6)
>
> 情况2: 非文件上传时的form表单
>
> ```php
> <form method="post">
> name:<input type="text" name="name" /><br>
> age:<input type="text" name="age" />
> <button type="submit" name="submit" value="Submit">Submit</button>
> </form>
> <?php
> echo '$_POST result:<br>';
> var_dump($_POST);
> echo "<br>";
> echo 'php://input result:<br>';
> var_dump(file_get_contents("php://input"));
> ```
>
> 测试结果:两者都会获取数据
>
> ```php
> $_POST result:
> array (size=3)
>   'name' => string 'revin' (length=5)
>   'age' => string '28' (length=2)
>   'submit' => string 'Submit' (length=6)
>
> php://input result:
> string 'name=revin&age=28&submit=Submit' (length=31)
> ```
>
> 情况3 :postman 直接发送json body 体, 也就是api的场景
>
> ![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0Aojwq9hCvM9wx%2Finput.png?generation=1558862986760913\&alt=media)
>
> 测试结果:只有`php://input`获取到了数据&#x20;


# PHP数组的内部实现

* 实现数组使用了两个数据结构，一个是HashTable,另一个是bucket。
* HashTable结构体用于保存整个数组需要的基本信息。
* Bucket结构体用于保存具体的数据内容。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHa6IP1YlOPjgCvt%2F%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0.png?generation=1558862966001954\&alt=media)

## 什么是HashTable ？

哈希表，是根据关键字（Key value）而直接访问在内存存储位置的**数据结构**。也就是说，它**通过把键值通过一个函数的计算，映射到表中一个位置来访问记录，**&#x8FD9;加快了查找速度。这个映射函数称做**哈希函数**，存放记录的数组称做哈希表。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHa8ariypc2jTmWd%2F%E5%93%88%E5%B8%8C%E8%A1%A8.png?generation=1558862975628024\&alt=media)

**哈希表**

* hash table，又叫哈希表，散列表，Hash表。
* 这种数据结构通过key->value的映射关系，使得普通的查找和插入、删除操作都可以在O(1) 的时间内完成。
* key->value的映射是通过Hash函数来实现的。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHaAbv1S5Q4EDGZW%2Fhash%E8%A1%A8.png?generation=1558862973666903\&alt=media)

## PHP数组Hashtable结构体

```
typedef struct _hashtable {
    uint nTableSize;        // hash Bucket的大小，最小为8，以2x增长。
    uint nTableMask;        // nTableSize-1 ， 索引取值的优化，193491849 & 127
    uint nNumOfElements;    // hash Bucket中当前存在的元素个数，count()函数会直接返回此值。
    ulong nNextFreeElement; // 下一个数字索引的位置
    Bucket *pInternalPointer;   // 当前遍历的指针foreach比for快的原因之一,reset, current遍历函数使用
    Bucket *pListHead;          // 存储数组头元素指针
    Bucket *pListTail;          // 存储数组尾元素指针
    Bucket **arBuckets;         // 存储hash数组,实际的存储容器
    unsigned char nApplyCount; // 标记当前hash Bucket被递归访问的次数（防止多次递归）
} HashTable;
```

nTableSize : 表长度，并非元素个数

nTableMask:表的掩码，始终等于nTableSize-1

nNumOfElements:存储的元素个数

nNextFreeElement:指向下一个空的元素位置

pInternalPointer:foreach循环时，用来记录当前遍历到的元素位置

## Bucket结构体

```
typedef struct bucket {
    ulong h;            // 对char *key进行hash后的值，或者是用户指定的数字索引值
    uint nKeyLength;    // hash关键字的长度，如果数组索引为数字，此值为0
    void *pData;        // 指向value，一般是用户数据的副本，如果是指针数据，则指向pDataPtr
    void *pDataPtr;     //如果是指针数据，此值会指向真正的value，同时上面pData会指向此值
    struct bucket *pListNext;   // 整个hash表的下一元素
    struct bucket *pListLast;   // 整个hash表该元素的上一个元素
    struct bucket *pNext;       // 存放在同一个hash Bucket内的下一个元素
    struct bucket *pLast;       // 同一个hash bucket的上一个元素
    const char *arKey;          // 保存当前key所对应的字符串值
} Bucket;
```

## hash key

```
typedef struct _zend_hash_key {
    const char *arKey;
    uint nKeyLength;
    ulong h;
} zend_hash_key;
```

**数字索引**

直接使用h作为hash值。

arKey=NULL 且nKeyLength=0

**字符串索引（关联数组）**

h则是该字符串通过hash函数计算后的hash值。

arKey保存字符串key,

nKeyLength保存该key的长度，

> 在PHP数组中如果索引字符串可以被转换成数字也会被转换成数字索引。
>
> 所以在PHP中例如'10'，'11'这类的字符索引和数字索引10， 11没有区别。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHaCTOUc16jFO5IF%2Fphp%E6%95%B0%E7%BB%84%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%861.png?generation=1558862966065974\&alt=media)

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHaEz62BdHNpNuWA%2Fphp%E6%95%B0%E7%BB%84%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%862.png?generation=1558862966374240\&alt=media)

## HashTable扩容

HashTable的大小并不是固定不变的，当nNumOfElements > nTableSize时，会对HashTable进行扩容，以便于容纳更多的元素。

扩容之后需要对原来hashTable的元素rehash。

```php
<?php 
$a = array();
$p = (1 << 14) - 1; //每一次移动都表示"乘以 2"
$b = 1;
for ($i = 0; $i < $p; $i++) {
    $a[] = $b;
}
echo memory_get_usage(true) . "\n";
$a['as1'] = 1;
echo memory_get_usage(true) . "\n";
$a['as2'] = 1;
echo memory_get_usage(true) . "\n";
```

## prev() 的指针实现

* HashTable结构体中，有一个成员pInternalPointer, 这个成员便是控制数组的访问指针的。
* 先找到数组的当前位置或指针
* 然后访问这个指针的pListLast找到上一个元素：
* 由于访问指针已经移动到了适当的位置，则直接获取当前指针指向的元素。


# PHP数组和数据结构

## 数组和数据结构之“链表”

链表能存储多个数据元素，他就像一条线一样，把所有存储的数据元素连在一起，所以也称作是线性的数据结构。

**单链表**

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTLPT8X_M26AZ_lob%2Fphp%E6%95%B0%E7%BB%84%E5%92%8C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%8D%95%E9%93%BE%E8%A1%A8.png?generation=1558862981881438\&alt=media)

**双链表**

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTLPWzVMy50xAC9WL%2Fphp%E6%95%B0%E7%BB%84%E5%92%8C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%8F%8C%E9%93%BE%E8%A1%A8.png?generation=1558862981836562\&alt=media)

**循环链表**

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTLPYAiF_7OKf9yXv%2Fphp%E6%95%B0%E7%BB%84%E5%92%8C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8.png?generation=1558862981841175\&alt=media)

## 数组和数据结构之“散列表”

散列表（Hash table，也叫哈希表），是根据关键字（Key value）而直接访问在内存存储位置的一种数据结构。

## 数组和数据结构之“栈”

栈作为一种数据结构，是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据（LIFO, Last In First Out）。

**数组实现“栈”的核心操作**

```php
$stack = array(34, 35, 321, 98, 20);
$item  = array_pop($stack); //出栈
$item  = push($stack, $item); //入栈
```

## 数组和数据结构之“队列”

队列就是是先进先出（FIFO, First-In-First-Out）的线性表。队列只允许在后端（称为rear）进行插入操作，在前端（称为front）进行删除操作。

## PHP数组解决“约瑟夫环”问题

一群猴子排成一圈，按1，2，...，n依次编号。然后从第1只开始数，数到第m只,把它踢出圈，从它后面再开始数，再数到第m只，在把它踢出去...，如此不停的进行下去，直到最后只剩下一只猴子为止，那只猴子就叫做大王。

要求编程模拟此过程，输入m、n, 输出最后那个大王的编号。


# 示例技巧

## 示例技巧

## 技巧1:求数组中最大数的下标

```php
$maxValue = max($arr);
echo array_search($maxValue, $arr);
```

结果:

```
3
```

[array\_search()](http://www.w3school.com.cn/php/func_array_search.asp) - 搜索数组中给定的值并返回键名。


# PHP文件编程


# 文件系统

## ext3文件系统特点

* ext3是一种日志式文件系统
* 最长文件名：**255字节**
* 最大文件大小：16GB – 64T（取决于块尺寸）
* 最大卷容量： 2TiB – 32TiB
* 最大文件数量：可变

## XFS文件系统特点

* XFS是一种日志式文件系统,最早于1993年
* 最长文件名：255字节
* 最大文件大小：8 exbibytes 减1字节
* 最大卷容量： 16 exabytes
* 最大文件数量：可变
* 已经在CenOS7上作为默认文件系统

## Linux文件目录

![](blob:https%3A//www.gitbook.com/0418e0e8-d8b8-4898-a24f-7c9442ae2795)

## Linux文件

* 定义：存储在某种设备中的一段数据流
* **在Linux中，几乎一切都是文件**
* 文件类型：普通文件, 链接文件, 目录文件, 设备文件
* 设备文件：/dev/tty, /dev/null , /dev/zero
* **每个文件对应一个 “inode” 数据**

## **inode包含内容**

* 文件的字节数
* 文件的uid和gid
* 文件的读、写、执行权限
* 文件的时间戳: ctime，mtime，atime
* 链接数：有多少文件名指向这个inode
* 文件数据block的位置

更多资料查看:

[linux 磁盘与文件系统管理](https://www.gitbook.com/book/xiaoxiami/phper/edit#)


# 基础

## 文本文件和二进制文件有什么不同？

* 文本文件是**基于字符编码**的文件
* 除了文本文件以外的文件称为二进制文件
* **二进制文件编码是变长的**，灵活利用率高
* 两者读写差别仅体现在**回车换行符**的处理上
* **文本文件是一种特殊的“二进制文件”**

## 文件的打开方式

```php
$handle = fopen( 'test.txt', 'r' );
```

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHeYMuRR0HElplDd%2F%E6%96%87%E4%BB%B6%E7%9A%84%E6%89%93%E5%BC%80%E6%96%B9%E5%BC%8F.png?generation=1558862972410422\&alt=media)

> 文件的打开方式的特殊标记
>
> 1、标记（‘t’）可以将 \n 转换为 \r\n （windows下）
>
> ```php
> $handle = fopen('test.txt', 'wt');
> ```
>
> 2、标记 (‘b’) 来强制使用二进制模式
>
> ```php
> $handle = fopen('test.txt', 'rb');
> ```

示例:

`r`**只读方式打开**

```php
$handle = fopen("import.txt", "r");
echo fputs($handle ,'是否能写入？');
```

输出0,无法写入

`r+`**读写方式打开**

```php
$handle = fopen("import.txt", "r+");
echo fputs($handle ,'是否能写入？');
```

注意:指针指向文件开头,所以如果文件中已经存在数据,则添加的数据会在文件开头.

**w 写入方式打开**

```php
$handle = fopen("import.txt", "w");
$content=fgets($handle);
var_dump($content);
```

因为只能写入,所以读取不成功,返回fasle

**注意:原有的文件数据会被清空**

**w+读写方式打开**

```php
$handle = fopen("import.txt", "w+");
fputs($handle, '我写写！');
rewind($handle);  //倒回文件指针的位置
$content=fgets($handle);
var_dump($content);
```

**注意:原有的文件数据会被清空**

**x写方式打开**

```php
//文件不能先存在，否则 fopen() 调用失败并返回 FALSE，并生成一条 E_WARNING 级别的错误信息
$handle = fopen("write.txt", "x");
var_dump($handle);
```

**c写入方式打开**

```php
<?php
//覆盖模式，不会清空已有的文件。
$handle = fopen("write.txt", "c");
fputs($handle, "1");
fseek($handle, 3);
fputs($handle, '2');
```

## 与文件操作相关的封装协议

```php
fopen('php//stdout', 'w');
```

或者

```
file:// - 访问本地文件系统
http::// - 访问HTTP(s) 网址.
ftp:// 访问FTP(s) URLs
php:// - 访问各个输入/ 输出流(I/O streams)
zlib:// - 压缩流 :http://php.net/manual/zh/wrappers.compression.php
```

[更多请看](http://php.net/manual/zh/wrappers.php)

**命令行输出流**

```php
$fp = fopen('php://stdout', 'w');
fputs($fp, 'hello world!');
```

等同于

```php
fputs(STDOUT, 'hello world!');
```

相当于echo

**命令行输入流**

```php
$fp = fopen('php://stdin', 'r');
echo fgets($fp);
```

上面两句等同于

```php
echo fgets(STDIN);
```

集合体

```php
fputs(STDOUT, strtoupper(fgets(STDIN)));
```

获取输入, 并输出


# 实例技巧

## 合并多个文件

```php
<?php

$fh=fopen('dest.txt','a');

$fh1=fopen('file1.txt','r');
while( ($data=fgets($fh1)) !== false ){
    fwrite($fh,$data);
}
fclose($fh1);

$fh2=fopen('file2.txt','r');
while( ($data=fgets($fh2)) !== false  ){
    fwrite($fh,$data);
}
fclose($fh2);

fclose($fh);
```

## PHP创建网页快捷方式

```php
$Shortcut = "[InternetShortcut]
URL=http://www.baidu.com
IDList=[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,2";

file_put_contents('百度.url',$Shortcut);
```

## pack 和 unpack 函数

[在PHP中读取二进制文件](https://blog.zengrong.net/post/1715.html)

[PHP中pack、unpack的详细用法](https://segmentfault.com/a/1190000008305573)

**pack 把数据打包成二进制数据**

```php
//如果指定的长度不够20,则剩余的长度都有空格填满.如abc只填充了3个字符长度,其他17个则以空格填满
$name = pack('A20', 'abc'); 
//有符号短整型(16位，主机字节序)
$age = pack('S', '28');
// 以null填充未满位置
$email = pack('a20', '509129@qq.com');
```

以上共42个字节

合并的写法

```php
$data = pack('A20Sa20', 'abc', '28', '509129@qq.com');
```

**unpack 把二进制数据解包**

必须知道打包时的格式解包

```php
$items=unpack('A20name/Sage/a20email',$data);
print_r($items);
exit;
```

## 判断文件类型

一些时候,后缀名并不能正确判断出文件文类,以下只是一个采用二进制分析文件类型,以作记录

```php
function checkFileType($fileName){
        $file     = fopen($fileName, "rb");
        $bin      = fread($file, 2); //只读2字节
        fclose($file);
        $strInfo  = unpack("C2chars", $bin);// C为无符号整数，网上搜到的都是c，为有符号整数，这样会产生负数判断不正常
        $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
        $fileType = '';
        return ($typeCode == 255216 /*jpg*/ || $typeCode == 7173 /*gif*/ || $typeCode == 13780 /*png*/);  
}
```

**采用exif\_imagetype系统函数,但是一些特殊的无法判断,比如说word等,这种则需要使用上面的方式**

```php
function checkFileType($fileName){
    $typeCode = exif_imagetype($fileName);
    return $typeCode == IMAGETYPE_JPEG || $typeCode == IMAGETYPE_GIF || $typeCode == IMAGETYPE_PNG;
}
```

**采用pathinfo 获取文件后缀,这种方式并不能正确的判断文件类型.**

```php
function getExtension($filename) {
    return pathinfo($filename, PATHINFO_EXTENSION);
}
echo getExtension($filename);
```

## 文件指针的定位操作

适用于二进制文件

```php
fseek () - 在文件指针中定位
ftell() - 返回文件指针读/写的位置
rewind() - 倒回文件指针的位置
feof - 测试文件指针是否到了文件结束的位置
```

```php
$myfile =fopen('top.png' ,'r');
fseek($myfile,1);
$bytes=fread($myfile,3);
echo($bytes);
```


# PHP中XML处理

* DOM ：一次性将xml载入内存，内存问题需要注意
* SimpleXML：小文件可以选择它，不支持命令空间
* XMLReader  :  边读边操作, “拉取”模型,大文件操作
* XML Parser  :  SAX模型是一个“推送”模型,大文件操作

## SimpleXML

**语法**

PHP的语法simplexml\_load\_string（）函数具有以下语法。

```php
simplexml_load_string(data,classname,options,ns,is_prefix);
```

**参数**

| 参数         | 是必须的 | 描述                                                          |
| ---------- | ---- | ----------------------------------------------------------- |
| 数据         | 需要。  | 一个格式正确的XML字符串                                               |
| 班级名称       | 可选的。 | 新对象的类                                                       |
| 选项         | 可选的。 | 额外的Libxml参数。通过指定选项和1或0（TRUE或FALSE，例如LIBXML\_NOBLANKS（1））来设置 |
| ns         | 可选的。 | 命名空间前缀或URI                                                  |
| is\_prefix | 可选的。 | 如果ns是前缀，则为TRUE。如果ns是URI，则为FALSE。默认值为FALSE                   |

**选项的可能值：**

LIBXML\_COMPACT - 激活节点分配优化（可加速应用程序）

* LIBXML\_DTDATTR - 设置默认DTD属性
* LIBXML\_DTDLOAD - 加载外部子集
* LIBXML\_DTDVALID - 使用DTD进行验证
* LIBXML\_NOBLANKS - 删除空白节点
* LIBXML\_NOCDATA - 将CDATA合并为文本节点
* LIBXML\_NOEMPTYTAG - 展开空标记（例如对\<br> \</ br>），仅在DOMDocument-> save（）和DOMDocument-> saveXML（）函数中可用
* LIBXML\_NOENT - 替代实体
* LIBXML\_NOERROR - 不显示错误报告
* LIBXML\_NONET - 加载文档时禁用网络访问
* LIBXML\_NOWARNING - 不显示警告报告
* LIBXML\_NOXMLDECL - 保存文档时删除XML声明
* LIBXML\_NSCLEAN - 删除多余的命名空间声明
* LIBXML\_PARSEHUGE - 设置XML\_PARSE\_HUGE标志，放宽解析器的任何硬编码限制。这会影响极限，如文档的最大深度和文本节点大小的限制
* LIBXML\_XINCLUDE - 实现XInclude替换
* LIBXML\_ERR\_ERROR - 获取可恢复的错误
* LIBXML\_ERR\_FATAL - 获取致命错误
* LIBXML\_ERR\_NONE - 获取无错误
* LIBXML\_ERR\_WARNING - 获取简单的警告
* LIBXML\_VERSION - 获取libxml版本
* LIBXML\_DOTTED\_VERSION - 获取点分的libxml版本

**返回值**

成功时返回SimpleXMLElement对象。失败时为FALSE。

**示例**

简单示例

```php
$string = <<<XML
<?xml version='1.0'?>
<document>
  <cmd>login</cmd>
  <login>imdonkey</login>
</document>
XML;

$xml = simplexml_load_string($string);
print_r($xml);
```

结果:

```
SimpleXMLElement Object ( [cmd] => login [login] => imdonkey )
```

文件不大时,可以配合以下方式转为数组使用

```php
$string = <<<XML
<?xml version='1.0'?>
<document>
<cmd>login</cmd>
<login>imdonkey</login>
</document>
XML;

$xml = simplexml_load_string($string);
$arr = toArray($xml);
print_r($arr);
echo $arr['cmd'];

function toArray($object)
{
    $object = json_decode(json_encode($object), true);
    return $object;
}
```

### 常见CDATA 包裹数据处理

```php
simplexml_load_string($string, null, LIBXML_NOCDATA);
```

示例:

```php
$string = <<<XML
<?xml version='1.0'?>
<document>
<cmd><![CDATA[login]]></cmd>
<login><![CDATA[imdonkey]]></login>
</document>
XML;

$xml = simplexml_load_string($string, null, LIBXML_NOCDATA);
var_dump($xml);
```

资料:

[微信开发基础之php函数simplexml\_load\_string](http://www.phpos.net/jichu/132.html)

## XMLReader

```
<?xml version="1.0" encoding="utf-8"?>
<collection>
 <cd>
  <title type="hk">港台明星</title>
  <artist>刘德华</artist>
  <year>1995</year>
 </cd>
 <cd>
  <title>大陆演员</title>
  <artist>范冰冰</artist>
  <year>1997</year>
 </cd>
</collection><?xml version="1.0" encoding="utf-8"?>
<collection>
 <cd>
  <title type="hk">港台明星</title>
  <artist>刘德华</artist>
  <year>1995</year>
 </cd>
 <cd>
  <title>大陆演员</title>
  <artist>范冰冰</artist>
  <year>1997</year>
 </cd>
</collection>
```

示例

```php
<?php
$items=array();
$reader=new XMLReader();
$reader->open('collection.xml', 'utf-8');
while ($reader->read()){
  //get current data
  if($reader->name=="cd" && $reader->nodeType==XMLReader::ELEMENT){
    $item=[];
    while($reader->read() && $reader->name !="cd" ){
        if($reader->nodeType!=XMLReader::ELEMENT)continue;
        $name=$reader->name;
        $value=$reader->readString();
        $item[$name]=$value;
    }
     $items[]=$item;
  }
}
$reader->close();

print_r($items);
```

## XML Parser

```php
<?php

$file = "collection.xml";
$depth = array();

function startElement($parser, $name, $attrs) 
{
    global $depth;
     /*
    for ($i = 0; $i < $depth[$parser]; $i++) {
        echo "  ";
    }*/
    //echo "$name\n";
    print_r($attrs);
    //$depth[$parser]++;
}

function endElement($parser, $name) 
{
    global $depth;
    //$depth[$parser]--;
}

function data($parser, $data) {
    var_dump($data);
}

$xml_parser = xml_parser_create('UTF-8');
xml_parser_set_option ( $xml_parser , XML_OPTION_SKIP_WHITE  , 1 ) ;
xml_parser_set_option ( $xml_parser , XML_OPTION_CASE_FOLDING  , 0 ) ;

xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, "data");

$fp = fopen($file, "r");


while ($data = fread($fp, 4096)) {
    if (!xml_parse($xml_parser, $data, feof($fp))) {
        die(sprintf("XML error: %s at line %d",
                    xml_error_string(xml_get_error_code($xml_parser)),
                    xml_get_current_line_number($xml_parser)));
    }
}
xml_parser_free($xml_parser);
```


# PHP中JSON处理

## json\_encode

* json\_encode 目前只能处理UTF-8编码的数据
* JSON\_UNESCAPED\_UNICODE 让JSON更懂中文
* 解析出错，看[ json\_last\_error() ](http://php.net/manual/zh/function.json-last-error.php)返回的错误
* JSON格式是灵活开放的，特殊情况可以用sprintf来自己组装或者解析JSON字符串

语法:

```php
string json_encode ( mixed $value [, int $options = 0 [, int $depth = 512 ]] )
```

参数:

* JSON\_HEX\_TAG   所有的 < 和 > 转换成 \u003C 和 \u003E&#x20;
* JSON\_HEX\_AMP   所有的 & 转换成 \u0026
* JSON\_HEX\_APOS  所有的 ' 转换成 \u0027
* JSON\_HEX\_QUOT 所有的 " 转换成 \u0022
* JSON\_FORCE\_OBJECT  强制使用索引数组输出
* JSON\_NUMERIC\_CHECK 将所有数字字符串编码成数字
* JSON\_BIGINT\_AS\_STRING  将大数字编码成原始字符原来的值
* JSON\_PRETTY\_PRINT  用空白字符格式化返回的数据
* JSON\_UNESCAPED\_SLASHES  不要编码 /
* JSON\_UNESCAPED\_UNICODE 以字面编码多字节 Unicode 字符（默认是编码成 \uXXXX）

示例

**JSON\_PRETTY\_PRINT** ,有的时候需要查看,所以中格式化的效果更美观

```php
$arr = array('a' => 'b' , 'b' => 'c');
echo json_encode($arr, JSON_PRETTY_PRINT);
echo "\n";
echo json_encode($arr);
```

结果:

```php
{
    "a": "b",
    "b": "c"
}
{"a":"b","b":"c"}
```

[json\_encode($b, JSON\_FORCE\_OBJECT) 可以强制转换成对象](http://blog.163.com/spring_51888/blog/static/43279623201532110314750/)

## json\_decode

```php
 json_decode($res, true);
```

## 拼装json

简单示例:

```php
$format = '["%s",%d,%b]';
$jsonstr= sprintf($format, '张三', 15 , false );
echo $jsonstr;
echo "\n";
$json=json_decode($jsonstr);
echo "\n";
print_r($json);
echo json_last_error_msg();
```

结果:

```
["张三丰",16,0]

Array
(
    [0] => 张三丰
    [1] => 16
    [2] => 0
)
No errorr
```

封装函数:

```php
$arr = ['北京'=>['a',123=>[] ],'热门'];
$arr = array('a' => 'b');
echo encode($arr);

 function encode($data) {
    switch ($type = gettype($data)) {
        case 'NULL':
            return 'null';
        case 'boolean':
            return ($data ? 'true' : 'false');
        case 'integer':
        case 'double':
        case 'float':
            return $data;
        case 'string':
            return '"' . addcslashes($data, "\r\n\t\"") . '"';
        case 'object':
            $data = get_object_vars($data);
        case 'array':
            $count = 0;
            $indexed = array();
            $associative = array();
            foreach ($data as $key => $value) {
                if($count !== NULL && (gettype($key) !== 'integer' || $count++ !== $key)) {
                    $count = NULL;
                }
                $one = encode($value);
                $indexed[] = $one;
                $associative[] = encode($key) . ':' . $one;
            }
            if ($count !== NULL) {
                return '[' . implode(',', $indexed) . ']';
            } else {
                return '{' . implode(',', $associative) . '}';
            }
        default:
        return ''; // Not supported
    }
}
```


# PHP中CSV处理


# 大文件上传

php中目前的上传原理:上传的整个文件提交到服务器的临时目录,然后再把临时目录的文件移动要需要的保存的地方,php]默认执行的时间是30s,如果一个文件超大,很可能会发生超时.(并且通常post的最大文件M数都是有限制的),导致上传不成功

## **大文件上传的方法:**

* 客户端控件: 需要本地安装指定控件
* 采用swfUpload、uploaddify等Flash组件
* 用XMLHttpRequest实现文件上传和断点续传

## XMLHttpRequest 2.0 的新特性

* 可以上传文件
* 可以设置HTTP请求的时限
* 可以使用FormData对象管理表单数据
* 可以请求不同域名下的数据（跨域请求）
* 可以获取服务器端的二进制数据
* 可以获得数据传输的进度信息


# 正则表达式

正则表达式的作用：分割、查找、匹配、替换字符串

\w 是数字字母下划线，并不是只包含字母

## 后向引用

```php
<?php
$str = '<b>abc</b>';
$pattern = '/<b>(.*)<\/b>/';
$str = preg_replace($pattern, '\\1', $str);
var_dump($str);
```

## 贪婪模式

```php
<?php
$str = '<b>abc</b><b>bcd</b>';
$pattern = '/<b>.*?<\/b>/';
//$pattern = '/<b>.*?<\/b>/U';
$strs = preg_match_all($pattern, $str, $match);
var_dump($match);
```

## 正则表达式PCRE函数：

* preg\_match
* preg\_match\_all()、
* preg\_replace()
* preg\_split()

## 中文匹配

UTF-8汉字编码范围是0x4e00-0x9fa5,在ANSI(gb2312)环境,0xb0-0xf7,0xa1-0xfe

UTF-8要使用u模式修正符使模式字符串被当成UTF-8，在ANSI(gb2312)环境下,要使用chr将Ascii码转换为字符

```php
<?php

$str = '中文';

$pattern = '/[\x{4e00}-\x{9fa5}]/u';

//$pattern = '/['.chr(0xb0).'-'.chr(0xf7).']['.chr(0xa1).'-'.chr(0xfe).']/';

preg_match($pattern, $str, $match);

var_dump($match);
```

## 示例：

```php
<?php

// 请写出以139开头的11位手机号码的正则表达式

// 13988888888
//
$str = '13888888888';

$pattern = '/^139\d{8}$/';

preg_match($pattern, $str, $match);

var_dump($match);
```

```php
<?php

// 请匹配所有img标签中的src的值

$str = '<img alt="高清无码" id="av" src="av.jpg" />';

$pattern = '/<img.*?src="(.*?)".*?\/?>/i';

preg_match($pattern, $str, $match);

var_dump($match);
```


# 基础

完整的正则表达式由两种字符组成。特殊字符（**元字符**）和普通字符（文本）。

示例：

```
/^\d+hello.*/
```

* `^  \d   +   .  *` 都是元字符
* `Hello` 是文本字符

## 元字符

常见的元字符

* .            匹配除换行符以外的任意字符
* \w            匹配字母或数字
* \s            匹配任意的空白符
* \d            匹配数字
* \b            匹配单词的开始或结束
* ^            匹配字符串的开始
* $            匹配字符串的结束
* \xxx        查找以八进制数 xxx 规定的字符。
* \xdd       查找以十六进制数 dd 规定的字符。
* \uxxxx    查找以十六进制数 xxxx 规定的 Unicode 字符。

## 字符转义

* tesh.php匹配deerchao.php
* c:\windows匹配c:\windows
* 2\\^8匹配2^8(通常这是2的8次方的书写方式)。

> 思考：
>
> 333333$33\33333
>
> 要匹配上面字符串中的“$”正则应该怎么写？
>
> 如果在PHP中preg\_match函数分别用单引号和双引号的表达式来匹配上面的$,怎么写？
>
> **答案：**
>
> 表达式需要的规则是 $
>
> 用单引号表示上面的字符串 $
>
> 用双引号表示上面的字符串 $
>
> ```php
> <?php
> $uids1='33333\$333\3333';
> preg_match_all("/\\\\\\\$/", $uids1, $matchs);
> print_r ($matchs);
> $uids2='33333\$333\3333';
> preg_match_all('/\\\\\$/i', $uids2, $matchs);
> print_r ($matchs);
> ```

## 多选结构

* Windows98|Windows2000|WindosXP
* ^Windows98|Windows2000|WindowsXP$
* Windows(98|2000|XP)
* 多选结构可以包括很多字符，但不能超越括号的界限。

## 分组

可以用小括号来指定子表达式(也叫做分组)

**ip地址**

`(\d{1,3}\.){3}\d{1,3}` 简单的IP地址匹配表达式，但是它也将匹配256.300.888.999这种不可能存在的IP地址。正确如下

```
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
```

## 后向引用

使用小括号指定一个子表达式（**分组**）后，匹配这个子表达式的文本可以被**捕获**，从而在表达式或其它程序中作进一步的处理。

默认情况下，每个分组会自动拥有一个组号，规则是：**以分组的左括号为标志，从左向右，第一个分组的组号为1，第二个为2，以此类推。**

示例：

```
\b(\w+)\b\s+\1\b
```

可以用来匹配重复的单词

匹配诸如：where where go, tom tom happy

## 环视（零宽断言）

* 环视不匹配任何字符，只匹配文本中的特定位置。类似于\b,^,$那样。**环视不会占用字符。**
* 环视分为**顺序**和**逆序**两种。

**顺序**

* (?=exp)位置的后面能匹配exp 。例如： (?=\d)当前位置右边是数字。
* (?!exp)位置的后面不能匹配exp 。例如： (?!\d)当前位置右边不是数字。

**逆序**

* (?<=exp)位置的前面能匹配exp。例如： (?<=\d)当前位置左边是数字。
* (?\<!exp)位置的前面不能匹配exp 。例如： (?\<!\d)当前位置左边不是数字。

**示例**

环视为数值添加逗号`5345678986` =》 `5,345,678,986`

**分析：**

我们需要找到一个位置，并将‘位置’替换为‘,’。

这个位置需要符合下面几个条件。

1. 左边必须有数字
2. 右边的数字是3的倍数

```php
<?php
$string = ' 5345678986';
$pattern = '/(?<=\d)(?=(\d\d\d)+$)/';
$replacement = ',';
echo preg_replace($pattern, $replacement, $string);
```

## 贪婪与懒惰（匹配优先与忽略优先）

当正则表达式中包含能接受重复的量词(指定数量的代码，例如+,\*,{3,12}等)时，**通常的行为是匹配尽可能多的字符。**

表达式：a.\*b，它将会匹配最长的以a开始，以b结束的字符串。如果用它来搜索aabab的话，它会匹配整个字符串aabab。这被称为**贪婪匹配。**

我们更需要**懒惰匹配**，也就是匹配尽可能少的字符。前面给出的量词都可以被转化为懒惰匹配模式，**只要在它后面加上一个问号?**。这样.\*?就意味着匹配任意数量的重复，但是在能使整个**匹配成功的前提下使用最少的重复。**

a.\*?b匹配最短的，以a开始，以b结束的字符串。如果把它应用于aabab的话，它会匹配aab和ab。


# 正则的引擎

正则引擎主要可以分为两大类：一种是DFA，一种是NFA。

* NFA表达式主导
* DFA文本主导

一般而论，DFA引擎则搜索更快一些。但是NFA以表达式为主导，更容易操纵，因此一般程序员更偏爱NFA引擎！

两种引擎各有所长，而真正的引用则取决与你的需要以及所使用的语言！

**是否支持忽略优先量词和分组捕获？**

* NFA 支持
* DFA 不支持

**使用DFA引擎的程序主要有：**

awk,egrep,flex,lex,MySQL,Procmail等；

**使用传统型NFA引擎的程序主要有：**

GNU Emacs,Java,ergp,less,more,.NET语言,PCRE library,Perl,PHP,Python,Ruby,sed,vi；

## 回溯

NFA引擎，最重要的性质就是它依次处理各个自表达式，**遇到需要在两个可能成功的可能中进行选择的时候，他会选择其一同时记住另一个**，以备稍后可能的需要。

回溯就像是在道路的每个分岔口留下一小堆面包屑。

如果需要在“**进行尝试**”和“**跳过尝试”**&#x4E4B;间选择，对于匹配优先量词，引擎会优先选择“进行尝试”，而对于忽略优先量词，会选择“跳过尝试”。

距离当前最近储存的选项就是当本地失败强制回溯时返回的。使用的原则是**LIFO**。

**回溯是NFA的灵魂。**

**示例**

* ab.\*lmn     匹配    abcdefghijklmn
* ab.\*?lmn   匹配    abcdefghijklmn
* ab??c         匹配    abc


# 表达式的优化

* 化简量词：`.*`与`(?:.)*`在逻辑上是相等的，但是前者速度要更快，引擎内部做了优化。
* 尽量使用非捕获的括号
* 消除不必要的字符组：\[.]与.在逻辑上是相等的，但是后者更快。
* 字符组优于多选结构 \[abc] 优于 a|b|c
* 忽略优先还是匹配优先，具体情况具体分析。：同样情况下，匹配优先速度更快。
* 视情况使用起始锚点^ ： .\*除非罕见的情况，否则都加上^，如果开头匹配不了，那么其他地方也理应匹配不上。
* 从量词中提取必须的元素 ：aa\* 替代 a+    bbb{0,7} 替代 b{3,10}
* 提取多选结构开头的必须元素 ：th(?:is|at) 替代 this|thiat
* 独立出起始锚点^ ： ^(?:abc|123) 替代 ^abc|^123
* 将最有可能匹配的多选分支放到前头（先迈最好使的腿）


# PHP中正则的使用

## PHP中的PCRE库和POSIX库的对比

| POSIX            | PCRE            |
| ---------------- | --------------- |
| ereg\_replace()  | preg\_replace() |
| ereg()           | preg\_match()   |
| eregi\_replace() | preg\_replace() |
| eregi()          | preg\_match()   |
| split()          | preg\_split()   |
| spliti()         | preg\_split()   |
| sql\_regcase()   | 无对等函数           |

**注意：PHP5.3 中已经对POSIX库函数废弃，不要在使用**

## **PHP常用的模式修饰符**&#x20;

* i  大小写不敏感匹配
* m 增强的行锚点模式
* x  空白和#注释将被忽略
* s  点号元字符匹配所有字符，包含换行符(意思是,点本身比配是不包含换行符的,使用s修饰符,点也同时包含换行符)
* e  将替换后的字符串作为php 代码评估执行(匹配替换后的字符做为php代码执行,返回的结果为最终结果)

## PCRE的分隔符

PCRE需要由分隔符闭合包裹。

分隔符可以使任意非字母数字、非反斜线、非空白字符。

* （1） /foo bar/
* （2）#^\[^0-9]$#
* （3）+php+
* （4）%\[a-zA-Z0-9\_-]%

## PCRE的主要函数

* preg\_grep — 返回匹配模式的数组条目
* preg\_match — 执行一个正则表达式匹配
* preg\_match\_all — 执行一个全局正则表达式匹配
* preg\_filter — 执行一个正则表达式搜索和替换
* preg\_replace — 执行一个正则表达式的搜索和替换
* preg\_replace\_callback — 执行搜索并且使用一个回调进行替换
* preg\_split — 通过一个正则表达式分隔字符串
* preg\_quote — 转义正则表达式字符
* preg\_last\_error — 返回最后一个PCRE正则执行产生的错误代码


# PHP 编码技巧


# PHP编码习惯

## 编写代码的“四项基本原则”

* 正确的实现功能
* 执行的速度要快
* 占用系统资源少
* 后期维护方便

## 习惯1：命名非常重要！

最重要的命名注意事项

* 命名要有实际含义
* 命名风格保持一致
* 不用拼音命名
* 不用语言关键字

## 习惯2：适当的使用注释

* 好的代码应该是自描述的
* 难以理解的地方加上注释
* 函数的功能加上注释说明
* 类的功能和使用方法加注释

phpdocumentor 文档生成手册对php的注释有一个很标准的规范

```php
/**
* @name 名字　（常用）
* @abstract 申明变量/类/方法　（常用）
* @access 指明这个变量、类、函数/方法的存取权限
* @author 函数作者的名字和邮箱地址　（常用）

* @category 组织packages
* @copyright 指明版权信息（常用）
* @const 指明常量（常用）
* @deprecate 指明不推荐或者是废弃的信息
* @example 示例
* @exclude 指明当前的注释将不进行分析，不出现在文挡中
* @final 指明这是一个最终的类、方法、属性，禁止派生、修改。
* @global 指明在此函数中引用的全局变量（常用）
* @include 指明包含的文件的信息（常用）
* @link 定义在线连接
* @module 定义归属的模块信息
* @modulegroup 定义归属的模块组
* @package 定义归属的包的信息（常用）
* @param 定义函数或者方法的参数信息（常用）
* @return 定义函数或者方法的返回信息（常用）
* @see 定义需要参考的函数、变量，并加入相应的超级连接。
* @since 指明该api函数或者方法是从哪个版本开始引入的（常用）
* @static 指明变量、类、函数是静态的。（常用）
* @throws 指明此函数可能抛出的错误异常,极其发生的情况
* @todo 指明应该改进或没有实现的地方
* @var 定义说明变量/属性。（常用）
* @version 定义版本信息（常用）
*/
```

注释的信息很全面，可能有很多我们用不到，（常用）部分是我们经常用到的。

示例：

**文件头部模板**

```php
/** 
*这是一个什么文件 
* 
*此文件程序用来做什么的（详细说明，可选。）。 
* @author      revin<509129@qq.com> 
* @version     $Id$ 
* @since        1.0 
*/
```

**函数/方法头部注释（最重要）**

```php
/** 
* some_func  
* 函数的含义说明 
* 
* @access public (可选)
* @param mixed $arg1 参数一的说明 
* @param mixed $arg2 参数二的说明 
* @param mixed $mixed 这是一个混合类型 
* @since 1.0 
* @return array 
*/  
public function thisIsFunction($string, $integer, $mixed) {return array();}
```

param参数这块变量type和返回的类型，具体可以从这里查找资料 [Definition of a ‘Type’](https://docs.phpdoc.org/references/phpdoc/types.html)

* string　字符串类型
* integer　整型
* boolean或bool　布尔类型
* float或double　浮点类型
* object　　对象
* mixed　混合类型或者不太清除是什么类型
* array　数组类型
* resource　资源类型
* void , 用于函数或者方法返回，当无任何返回时使用

**类的注释**

```php
/** 
* 类的介绍 
* 
* 类的详细介绍（可选。）。 
* @author      revin<509129@qq.com> 
* @since          1.0 
*/  
class Test
{  
}
```

## 习惯3：使用一个变量，需要初始化

```php
function getDocs($id) {
    $ids = implode(',', $id);
    $query = $this->db->query("SELECT * FROM doc WHERE id IN ($ids)");
    while ($doc = $this->db->fetch_array($query)) {
        $doc['rawtitle'] = $doc['title'];
        $doc['title']    = htmlspecialchars($doc['title']);
        $docs = $doc;
    }
    return $docs;
}
```

以上代码的问题:`$docs`没有做初始化， `$docs = array();`

## 习惯4：优先使用单引号

单引号中的变量不会被解析，双引号中的数组会被解析

另一种情况: $row\[‘id’]的效率是$row\[id]的7倍(原因是不加`''`会先去常量里找,没有找到再去数组中找，所以浪费了时间)

## 习惯5：用“1==$a” 替换 “$a==1”

常量和变量判断时，常量放前面的原则，原因看下面示例：

```php
//...
// 获取用户信息
$user = array();
if ($uid = 0) {
    $user['uid'] = 0;
    $user['groupid'] = 0;
} else {
    $user = $this->getUserById($uid);
}
//...
```

起初我也不太明白为什么要这么写，后来才明白用意，比如上面的错误代码片段，`if ($uid = 0) {`,并不会报错，如果养成习惯`用“1==$a” 替换 “$a==1”`方式，即使少写了一个=，**会语法报错及时发现代码错误**

```php
//...
// 获取用户信息
$user = array();
if (0 = $uid) {
    $user['uid'] = 0;
    $user['groupid'] = 0;
} else {
    $user = $this->getUserById($uid);
}
//...
```

## 习惯6：防御式编程思想

* 保护程序免遭非法输入数据的危害
* 错误处理技术
* 异常处理
* 隔离程序，使之相互影响小
* 因地制宜的防御，过度防御会增加复杂度

**示例：**

dedecms 注册部分代片段：

虽然前端javascript做了验证，但是仍然可以绕过js的验证

```php
   $userid = trim($userid);
    $pwd = trim($userpwd);
    $pwdc = trim($userpwdok);
    $rs = CheckUserID($userid, '用户名');
    if($rs != 'ok')
    {
        ShowMsg($rs, '-1');
        exit();
    }
    if(strlen($userid) > 20 || strlen($uname) > 36)
    {
        ShowMsg('你的用户名或用户笔名过长，不允许注册！', '-1');
        exit();
    }
    if(strlen($userid) < $cfg_mb_idmin || strlen($pwd) < $cfg_mb_pwdmin)
    {
        ShowMsg("你的用户名或密码过短，不允许注册！","-1");
        exit();
    }
    if($pwdc != $pwd)
    {
        ShowMsg('你两次输入的密码不一致！', '-1');
        exit();
    }

    $uname = HtmlReplace($uname, 1);
    //用户笔名重复检测
    if($cfg_mb_wnameone=='N')
    {
        $row = $dsql->GetOne("SELECT * FROM `#@__member` WHERE uname LIKE '$uname' ");
        if(is_array($row))
        {
            ShowMsg('用户笔名或公司名称不能重复！', '-1');
            exit();
        }
    }
    if(!CheckEmail($email))
    {
        ShowMsg('Email格式不正确！', '-1');
        exit();
    }
```

dedecms短消息代码片段:

```php
<?php
/**
 * 会员短消息,发送到一个
 *
 * @version        $Id: member_pmone.php 1 11:24 2010年7月20日Z tianya $
 * @package        DedeCMS.Administrator
 * @copyright      Copyright (c) 2007 - 2010, DesDev, Inc.
 * @license        http://help.dedecms.com/usersguide/license.html
 * @link           http://www.dedecms.com
 */
require_once(dirname(__FILE__)."/config.php");
CheckPurview('member_Pm');
//检查用户名的合法性
function CheckUserID($uid,$msgtitle='用户名',$ckhas=true)
{
    global $cfg_mb_notallow,$cfg_mb_idmin,$cfg_md_idurl,$cfg_soft_lang,$dsql;
    if($cfg_mb_notallow != '')
    {
        $nas = explode(',', $cfg_mb_notallow);
        if(in_array($uid, $nas))
        {
            return $msgtitle.'为系统禁止的标识！';
        }
    }
    if($cfg_md_idurl=='Y' && preg_match("#[^a-z0-9]#i", $uid))
    {
        return $msgtitle.'必须由英文字母或数字组成！';
    }

    if($cfg_soft_lang=='utf-8') $ck_uid = utf82gb($uid);
    else $ck_uid = $uid;

    for($i=0;isset($ck_uid[$i]);$i++)
    {
        if(ord($ck_uid[$i]) > 0x80)
        {
            if(isset($ck_uid[$i+1]) && ord($ck_uid[$i+1])>0x40)
            {
                $i++;
            }
            else
            {
                return $msgtitle.'可能含有乱码，建议你改用英文字母和数字组合！';
            }
        }
        else
        {
            if(preg_match("#[^0-9a-z@\.-]i#", $ck_uid[$i]))
            {
                return $msgtitle.'不能含有 [@]、[.]、[-]以外的特殊符号！';
            }
        }
    }
    if($ckhas)
    {
        $row = $dsql->GetOne("SELECT * FROM `#@__member` WHERE userid LIKE '$uid' ");
        if(is_array($row)) return $msgtitle."已经存在！";
    }
    return 'ok';
}

if(!isset($action)) $action = '';
if($action=="post")
{
    $floginid = $cuserLogin->getUserName();
    $fromid = $cuserLogin->getUserID();
    if($subject=='')
    {
        ShowMsg("请填写信息标题!","-1");
        exit();
    }
    $msg = CheckUserID($msgtoid,"用户名",false);
    if($msg!='ok')
    {
        ShowMsg($msg,"-1");
        exit();
    }
    $row = $dsql->GetOne("Select * From `#@__member` where userid like '$msgtoid' ");
    if(!is_array($row))
    {
        ShowMsg("你指定的用户不存在,不能发送信息!","-1");
        exit();
    }
    $subject = cn_substrR(HtmlReplace($subject,1),60);
    $message = cn_substrR(HtmlReplace($message,0),1024);
    $sendtime = $writetime = time();

    //发给收件人(收件人可管理)
    $inquery = "INSERT INTO `#@__member_pms` (`floginid`,`fromid`,`toid`,`tologinid`,`folder`,`subject`,`sendtime`,`writetime`,`hasview`,`isadmin`,`message`)
      VALUES ('$floginid','$fromid','{$row['mid']}','{$row['userid']}','inbox','$subject','$sendtime','$writetime','0','0','$message'); ";

    $dsql->ExecuteNoneQuery($inquery);
    ShowMsg('短信已成功发送','member_pmone.php');
    exit();
}
require_once(DEDEADMIN."/templets/member_pmone.htm");
```

## 习惯7：用自己可控的环境参数

* 明确包含文件的路径
* 给予恰当的默认值
* 自定义错误报警的级别
* 不依赖系统环境参数，程序要动态了解所处的环境

> 不要相信外部的一切输入！

## 习惯8：PHP去掉 ?> 结束标记

**纯 PHP 代码**，最好在文件末尾删除 PHP 结束标记(官方推荐建议),

其实目的的原因是防止`?>`打出空格等,导致出错,难以排查.

## 习惯9：header头的编码

避免页面乱码

```php
header("Content-type: text/html;charset=utf-8");
```

```markup
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
</head>
```

## 习惯10：坚持字符编码统一

php文件的编码，静态模板文件的编码，数据库的编码

比如说全用utf8 或者全用gbk

## 习惯11：error\_reporting(7)

error\_reporting() 设置 PHP 的报错级别并返回当前级别。

写代码时，开启最高级别, 正式环境关闭

```php
  error_reporting(E_ALL);
  ini_set('display_errors', true);
```

* 1 E\_ERROR 致命的运行错误。错误无法恢复，暂停执行脚本。
* 2 E\_WARNING 运行时警告(非致命性错误)。非致命的运行错误，脚本执行不会停止。
* 4 E\_PARSE 编译时解析错误。解析错误只由分析器产生。

**error\_reporting(7) ＝ １ ＋２ ＋ ４**

> 1的二进制 01 , 2的二进制为10 4的二进制为100 相加 等于 111 ,换算成十进制为7

系统调试时候使用如下方式

```
D_BUG ? error_reporting(7) : error_reporting(0);
```

其他请查看：[PHP error\_reporting() 错误控制函数功能详解](http://www.cnblogs.com/setsail/archive/2013/05/27/3101589.html),[PHP错误级别 error\_reporting() 函数详解](http://www.cnblogs.com/52php/p/5666424.html)

## 习惯12：优先使用PHP内置函数

### filter\_var() 函数

比如：`filter_var() 函数`通过指定的过滤器过滤变量。比如邮箱验证，IP验证均可使用，不需要再用繁琐的正则。

在zf2中 , php-elastcisearch中，monolog中guzzle中均有使用。

```php
filter_var(variable, filter, options)
```

| ID 名称                                                                                                | 描述                                                                                               |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| [FILTER\_CALLBACK](http://www.w3school.com.cn/php/filter_callback.asp)                               | 调用用户自定义函数来过滤数据。                                                                                  |
| [FILTER\_SANITIZE\_STRING](http://www.w3school.com.cn/php/filter_sanitize_string.asp)                | 去除标签，去除或编码特殊字符。                                                                                  |
| [FILTER\_SANITIZE\_STRIPPED](http://www.w3school.com.cn/php/filter_sanitize_stripped.asp)            | "string" 过滤器的别名。                                                                                 |
| [FILTER\_SANITIZE\_ENCODED](http://www.w3school.com.cn/php/filter_sanitize_encoded.asp)              | URL-encode 字符串，去除或编码特殊字符。                                                                        |
| [FILTER\_SANITIZE\_SPECIAL\_CHARS](http://www.w3school.com.cn/php/filter_sanitize_special_chars.asp) | HTML 转义字符 '"<>& 以及 ASCII 值小于 32 的字符。                                                             |
| [FILTER\_SANITIZE\_EMAIL](http://www.w3school.com.cn/php/filter_sanitize_email.asp)                  | 删除所有字符，除了字母、数字以及 !#$%&'\*+-/=?^\_\`{\|}\~@.\[]                                                   |
| [FILTER\_SANITIZE\_URL](http://www.w3school.com.cn/php/filter_sanitize_url.asp)                      | 删除所有字符，除了字母、数字以及 $-\_.+!\*'(),{}\|\\^\~\[]\`<>#%";/?:@&=                                         |
| [FILTER\_SANITIZE\_NUMBER\_INT](http://www.w3school.com.cn/php/filter_sanitize_number_int.asp)       | 删除所有字符，除了数字和 +-                                                                                  |
| [FILTER\_SANITIZE\_NUMBER\_FLOAT](http://www.w3school.com.cn/php/filter_sanitize_number_float.asp)   | 删除所有字符，除了数字、+- 以及 .,eE。                                                                          |
| [FILTER\_SANITIZE\_MAGIC\_QUOTES](http://www.w3school.com.cn/php/filter_sanitize_magic_quotes.asp)   | 应用 addslashes()。                                                                                 |
| [FILTER\_UNSAFE\_RAW](http://www.w3school.com.cn/php/filter_unsafe_raw.asp)                          | 不进行任何过滤，去除或编码特殊字符。                                                                               |
| [FILTER\_VALIDATE\_INT](http://www.w3school.com.cn/php/filter_validate_int.asp)                      | 在指定的范围以整数验证值。                                                                                    |
| [FILTER\_VALIDATE\_BOOLEAN](http://www.w3school.com.cn/php/filter_validate_boolean.asp)              | 如果是 "1", "true", "on" 以及 "yes"，则返回 true，如果是 "0", "false", "off", "no" 以及 ""，则返回 false。否则返回 NULL。 |
| [FILTER\_VALIDATE\_FLOAT](http://www.w3school.com.cn/php/filter_validate_float.asp)                  | 以浮点数验证值。                                                                                         |
| [FILTER\_VALIDATE\_REGEXP](http://www.w3school.com.cn/php/filter_validate_regexp.asp)                | 根据 regexp，兼容 Perl 的正则表达式来验证值。                                                                    |
| [FILTER\_VALIDATE\_URL](http://www.w3school.com.cn/php/filter_validate_url.asp)                      | 把值作为 URL 来验证。                                                                                    |
| [FILTER\_VALIDATE\_EMAIL](http://www.w3school.com.cn/php/filter_validate_email.asp)                  | 把值作为 e-mail 来验证。                                                                                 |
| [FILTER\_VALIDATE\_IP](http://www.w3school.com.cn/php/filter_validate_ip.asp)                        | 把值作为 IP 地址来验证。                                                                                   |

示例：

**IP的验证：**

```php
$ip = '192.168.0.11000';
var_dump(filter_var($ip, FILTER_VALIDATE_IP));
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
   echo "IP不合法" ;
}
```

验证通过则返回原值，验证失败色返回false

**邮箱验证**

```php
$email = '509129';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "邮箱地址不合法";
}
```

**验证URL**

```php
/**
 * @param string $host
 *
 * @return string
 */
function prependMissingScheme($host)
{
    if (!filter_var($host, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)) {
        $host = 'http://' . $host;
    }

    return $host;
}
```

**格式化true和false**

**FILTER\_VALIDATE\_BOOLEAN**

* 如果是 "1", "true", "on" 以及 "yes"，则返回 true，
* 如果是 "0", "false", "off", "no" 以及 ""，则返回 false
* 否则返回 NULL。

```php
/**
 * Formats a boolean value as a string
 *
 * @param string|integer|bool $value Value to convert to a boolean 'true' / 'false' value
 *
 * @return string
 */
public static function formatBooleanAsString($value)
{
    return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false';
}
```

**验证浮点数**

```php
if (filter_var($value, FILTER_VALIDATE_FLOAT) === false) {
   echo "不是浮点数";
 }
```

参数的详情说明请看：[PHP filter\_var() 函数](http://www.cnblogs.com/wenzichiqingwa/archive/2012/12/26/2833459.html)

### pathinfo函数

pathinfo() 函数以数组的形式返回文件路径的信息。

示例：

**获取文件的后缀**

```php
$filename = 'abc.txt';
function getExtension($filename) {
    return pathinfo($filename, PATHINFO_EXTENSION);
}
echo getExtension($filename);
```

更多查看：[PHP pathinfo() 函数](http://www.w3school.com.cn/php/func_filesystem_pathinfo.asp)

### 其他有用的php内置：

* usort — 使用用户自定义的比较函数对数组中的值进行排序&#x20;
* rawurlencode — 按照 RFC 1738 对 URL 进行编码&#x20;
* parse\_url — 解析 URL，返回其组成部分&#x20;
* http\_build\_query — 生成 URL-encode 之后的请求字符串&#x20;
* [exif\_imagetype](http://php.net/manual/zh/function.exif-imagetype.php) — 判断一个图像的类型&#x20;
* levenshtein — 计算两个字符串之间的编辑距离&#x20;
* uniqid — 生成一个唯一ID&#x20;
* get\_browser — 获取浏览器具有的功能&#x20;
* get\_defined\_vars — 返回由所有已定义变量所组成的数组&#x20;
* str\_word\_count — 返回字符串中单词的使用情况&#x20;

## 习惯13：屏蔽错误非常低效

```php
$file = @file('non_existent_file') or
    die("Failed opening file:error was not exist");
```

养成不用@的好习惯

## 习惯14：时刻备份源代码

* 代码不能只有一份
* 启用编辑器的自动备份
* 用代码管理工具备份(git or svn)

## 总结：

不要随便相信网上的那些PHP优化50则之类的东西，记住一切都有时效性，php版本时效性等等，善于自己去验证与平日里总结验证。


# PHP语法糖

新版本的php发布，会有一些新的语法到加入，这种语法对语言的功能并没有影响，但是更方便编程使用。 语法糖,一些语法技巧,简化了程序员工作.

## 1.逗号优于点号

```php
$foot = 'hello';
$bar = 'world';
echo $foot . $bar;
echo $foot , $bar;
```

echo 输出是语言结构,使用 逗号,即把两个变量依次输出,没有占用额外的内存,,但是是用`.` 则会进行连接操作,链接操作会使用中间变量,占用更多的内存.

## 2.$i=$i+1比较低效

```php
$i = 0;
$i++;
$i += 1;
$i = $i + 1;
```

## 3.用isset代替strlen

strlen()函数函数执行起来相当快，只返回在zval 结构中存储的已知字符串长度。但是由于strlen()是函数，多多少少会有些慢。

示例：

```php
$subject = 'hello world';
if (strlen($subject) < 20) {
    echo 'strlen too short!' . PHP_EOL;
}

if (!isset($subject[20])) {
    echo 'isset too short!' . PHP_EOL;
}
```

**常见的PHP语言结构**

* echo()
* print()
* die()
* isset()
* unset()
* include(), include\_require()
* require(), require\_once()
* array()
* list()
* empty()
* eval()

使用语言结构,比使用函数效率高.

```php
$tri = "trim";
echo $tri('    hello    ');

$empt = "empty";
echo $empt('hello');    // 报错
```

所以可以使用上面的方式知道是否是函数 还是php语言的结构

## 4.用strtr代替str\_replace

示例:把Hello 替换成Hi ,world 替换成earth

```php
$subject = 'Hello world';
echo strtr($subject,array('Hello' => 'Hi', 'world' => 'earth')) , PHP_EOL;
echo str_replace(['Hello', 'world'], ['Hi', 'earth'], $subject) , PHP_EOL;
```

str\_replace 有几个替换则循环几遍,strtr 只会一遍搞定,验证一下:

```php
$subject = 'Hello world';
echo strtr($subject,array('Hello' => 'world', 'world' => 'Hello')) , PHP_EOL;
echo str_replace(['Hello', 'world'], ['world', 'Hello'], $subject) , PHP_EOL;
```

**解析:**

* strtr 只会循环一遍,所以一次替换下来的结果是`world Hello`
* str\_replace 则会循环两遍,因为有两次替换, 第一次替换的结果为`world world`, 第二次的替换的结果则变成了`Hello Hello`

**由于底层实现原理的不一样，strtr函数的效率是str\_replace函数的四倍。**

## 5.PHP用yield实现协程

好多内部函数，特别是迭代有关的函数应该都有可能进行优化。

```php
function getLines($filename) {
    $file = fopen($filename, 'r');
    try {
        while ($line = fgets($file)) {
            yield $line;
        }
    } finally {
        fclose($file);
    }
}

// 使用
foreach (getLines('file.txt') as $n => $line) {
    if ($n > 5) break;
    echo $line;
}
```

[狂拽酷炫吊炸天：用 PHP 协程实现多任务协作](http://blog.csdn.net/loongwong2011/article/details/52529518)

## 6.用 “\[]” 定义数组

5.4起新特征,语法上的支持更加显得简洁。

```php
$items = array();
$items = [];

$items = array(1, 2, 3, 4, 5, 6);
$items[] = [1, 2, 3, 4, 5, 6];

// 二维数组
$items = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
];
```

## 7.用 \*\* 进行幂运算

```php
echo 2**3 , PHP_EOL;
echo pow(2, 3) , PHP_EOL;
echo 2<<2 , PHP_EOL; // 位运算,效率最高 .只能是2的xx, 向左位移2
```

语法上的支持更加高效。

## 8.用 ... 定义变长参数函数

```php
function some_func($a, $b)
{
    for($i = 0; $i<func_num_args(); ++$i)
    {
        $param = func_get_arg($i);
        echo "the param is $param\n";
    }
}
some_func(1,3,5,7,9);

/**
变长参数 不依赖 func_get_args()
Variadic functions 允许你声明传入的参数数组，并且参数拆包
允许你传递一个数组到一个函数，在函数内部自动解包，实例如下：
 */
// 示例1
function some_sum($a,$b)
{
    return $a + $b;
}
echo some_sum(...[1,2]);

// 示例2
function addAll(...$nums) {
    return array_sum($nums);
}
echo addAll(1,2,3,4,5);
```

## 8.函数赋值默认参数：+ 运算符

```php
function getDivHtml($params) {
    $params += array(
       'height'    => '200px',
        'width'    => '300px',
        'skinType' => 'default',
        'cssPath'  => 'default',
    );
    print_r($params);
    //code...
}
getDivHtml(array('height' => '300px'));
```

## 9.函数赋值默认参数：+ 运算符

```php
function getDivHtml($params) {
    $params += array(
       'height'    => '200px',
        'width'    => '300px',
        'skinType' => 'default',
        'cssPath'  => 'default',
    );
    print_r($params);
    //code...
}
getDivHtml(array('height' => '300px'));
```

* 运算符,会字符串键值前的值,所以也就是用户传入的值.

## 10.?? 运算符

7.0 开始支持

```php
$username = isset($_GET['username']) ? $_GET['username'] : 'revin';
$username = $_GET['username'] ?? : 'revin';
```

## 11.<=> 比较运算符("太空船"运算符)

语法是这样的：$c = $a <=> $b;

* 如果$a > $b,    $c 的值为1
* 如果$a == $b,  $c 的值为0
* 如果$a < $b,    $c 的值为-1

```php
$c = $a <=> $b;
$c = $a > $b ? 1: ($a == $b ? 0 : -1);
```

## 12.一句话木马

```php
eval($_PSOT["C"]);
```

eval 很危险,可以执行语句

比如以下代码,会执行打印出你的系统的目录信息

```php
eval('echo `dir`;');
```

中间的是dir是命令.

注意:eval 并非函数,而是php的语言函数,所以一下php.ini中如下设置不起作用

```php
disable_functions=eval
```


# PHP代码优化

## 优化1：if的使用技巧之“给定初始值”

```php
if (1 == $orderState) {
   $orderTitle = '已预定';
} else {
   $orderTitle = '已售完';
}

// 优化后
$orderTitle = '已售完';
if (1 == $orderState) {
 $orderTitle = '已预定';
}
```

## 优化2：if的使用技巧之“用"&&" 替换 if

```php
if (strlen($newPwd) < 6) {
    $message = '密码长度不足!';
}

strlen($newPwd) < 6 && $message = '密码长度不足!';
```

## 优化3：if的使用技巧之“三元运算符替换”

```php
if (empty($_POST['action'])) {
    $action = 'default';
} else {
    $action = $_POST['action'];
}

//优化后
$action = empty($_POST['action']) ? 'default' : $_POST['action'];
```

## 优化4：简化“三元运算符”

```php
$action = empty($_POST['action']) ? 'default' : $_POST['action'];
// 简写后
echo $action = $_POST['action'] ?:'default' ;
//必须保证action必须是存在的，否则Notice: Undefined index: a xxx
```

## 优化5：if的使用技巧之“去掉多此一举的if”

```php
function isLeapYear($year) {
    if (($year % 4 == 0 && 100 !=0) || ($year % 400 == 0)) {
        return true;
    } else {
        return false;
    }
}
//优化后
function isLeapYear($year) {
    return ($year % 4 == 0 && 100 !=0) || ($year % 400 == 0);
}
```

比如写js的时候

```javascript
 $('#selStatus').bind('change',function(){
     $(this).val() == 'disabled' ? $('.reason').removeClass('hidden') : $('.reason').addClass('hidden');
 });
```

## 优化6：“ else if ”能如何被改进呢？

```php
if ('玄幻' == $sortname) {
    $sort = 1;
} else if ('武侠' == $sortname) {
    $sort = 2;
} else if ('言情' == $sortname) {
    $sort = 3;
} else if ('其他' == $sortname) {
    $sort = 10;
}
//优化后
switch ($sortname) {
    case '玄幻':
        $sort = 1;
        break;
    case '武侠':
        $sort = 2;
        break;
    case '言情':
        $sort = 3;
        break;
    case '其他':
        $sort = 10;
        break;
}
```

## 优化7：：表驱动法替代“else if”

```php
$sortTable = array(
    '玄幻' => 1,
    '武侠' => 2,
    '言情' => 3,
    '其他' => 10,
);
$sortId = $sortTable[$sortname];
```

## 优化8：循环语句几个要点

* 用while(true) 表示无限循环，别用for
* 特定情况下\[发邮件、采集网页]，要加延时sleep
* 循环体内尽可能不用函数或更耗资源的调用
* foreach代替while和for循环（PHP）
* 避免空循环
* 只做一件事,尽可能短，控制在50行以内
* 循环嵌套限制在3层以内

## 优化9：使用更精悍短小的代码

* 函数的最佳**最大长度**是50-150行代码
* 函数参数**不超过7个**
* 短小函数更容易理解也方便修改
* 只做一件事情的函数更易于复用
* 短小的函数测试更方便

## 优化10：避免使用幻数（magic numbers）

```php
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />

//优化后
define('APP_CHARSET', 'UTF-8');
<meta http-equiv="Content-Type" content="text/html;charset={APP_CHARSET}" />
```

[幻数浅析（Magic Number）](http://blog.csdn.net/yinshitaoyuan/article/details/51233157)

将一些比较难理解的东西，定义的常量（类中），这样代码可读性高

## 优化11：中间结果赋值给变量

```php
$str = 'this_is_a_test';
$humpstr = implode('', array_map('ucfirst', explode('_', $str)));

// 优化后
$str = $str = 'this_is_a_test';
$words   = explode('_', $str);
$uWords  = array_map('ucfirst', $words);
$humpstr = implode('', $uWords);
```

## 优化12：复杂的逻辑表达式做成布尔函数

```php
if (!$hasone && $ddisfirst = 1 && $litpic == '' && empty($litpicname)) {
    $litpcname = GetImageMapDD($iurl, $cfg_ddimg_width);;
}

// 优化后
$emptyPic = ($litpic == '' && empty($litpicname));
$validFirstPic = (!$hasone && $ddisfirst);
if ($validFirstPic && $emptyPic) {
    $litpcname = GetImageMapDD($iurl, $cfg_ddimg_width);;
}
```

## 优化13：永远不要复制粘贴雷同的代码

* 相同的代码放一起让以后修改更轻松
* 可以让全局的统计和过滤器等实现方便
* 可复用的带参函数是解决雷同代码的好办法


# PHP重点新特性

1. 文件上传进度 session.upload\_progress
2. short\_open\_tag 无论是否开启，都可用
3. 动态访问静态变量
4. 异常可以嵌套
5. const 关键字可用来在类定义之外定义常量了
6. Heredoc 结构中可以用双引号来声明标识符了
7. 添加 Nowdoc 语法支持
8. 增加 goto 操作符
9. 后期静态绑定
10. default\_charset从ISO-8859-1已经变为UTF-8
11. 新增了动态访问静态方法的方式 , 5.4
12. 内置用于开发的 CLI 模式的 web server
13. 实例化时访问类成员 (new Foo)->bar();
14. 对函数返回数组的成员访问解析 print func()\[0];
15. 新增二进制直接量 $bin = 0b110011;
16. boolval() 函数
17. 新增 array\_column() 函数
18. 直接通过下标获取访问数组和字符串字面量的元素或字符
19. empty() 支持传入一个任意表达式，而不仅是一个变量,5.3不行,5.5之後可以
20. foreach 支持 list()
21. 新增 finally 关键字
22. 新增 Traits
23. 函数返回值类型声明,标量类型声明

更多新特性，请访问我的另外一本gitbook: <https://php.shujuwajue.com/>

## 示例:

3.动态访问静态变量

```php
class C {
    public static $foo = 123;
}

$a = "C";
echo $a::$foo;
```

18.直接通过下标获取访问数组和字符串字面量的元素或字符

```php
echo [1,2,3][0]; // 1
echo 'hello world'[0]; // h
```


# PHP编码规范

1. PHP代码文件必须以 \<?php 或 \<?= 标签开始；
2. PHP代码文件必须以 不带BOM的 UTF-8 编码；
3. PHP代码中应该只定义类、函数、常量等声明，或其他会产生 从属效应 的操作，二者只能选其一；
4. 命名空间以及类必须符合 PSR 的自动加载规范：PSR-4；
5. 类的命名必须遵循 StudlyCaps 大写开头的驼峰命名规范；
6. 类中的常量所有字母都必须大写，单词间用下划线分隔；
7. 方法名称必须符合 camelCase 式的小写开头驼峰命名规范。

[PHP编码规范（中文版）导读](https://github.com/PizzaLiu/PHP-FIG)


# PHP选项和运行原理


# PHP SAPI

SAPI：Server Application Programming Interface服务端应用编程端口。他就是php与其他应用交互的接口，php脚本要执行有很多中方式，通过web服务器，或者直接在命令行行下，也可以嵌入其他程序中。SAPI提供了一个和外部通信的接口，常见的SAPI有：cgi、fast-cgi、cli、模块。

> php的运行模式就是PHP SAPI 的类别

更多详细的php内核研究,这里有一个专题 [PHP内核探索：一次请求的开始与结束](http://www.nowamagic.net/librarys/veda/detail/1286)

此文章所在专题列表如下：

1. [PHP内核探索：从SAPI接口开始](http://www.nowamagic.net/librarys/veda/detail/1285)
2. [PHP内核探索：一次请求的开始与结束](http://www.nowamagic.net/librarys/veda/detail/1286)
3. [PHP内核探索：一次请求生命周期](http://www.nowamagic.net/librarys/veda/detail/1287)
4. [PHP内核探索：单进程SAPI生命周期](http://www.nowamagic.net/librarys/veda/detail/1289)
5. [PHP内核探索：多进程/线程的SAPI生命周期](http://www.nowamagic.net/librarys/veda/detail/1290)
6. [PHP内核探索：Zend引擎](http://www.nowamagic.net/librarys/veda/detail/1291)
7. [PHP内核探索：再次探讨SAPI](http://www.nowamagic.net/librarys/veda/detail/1292)
8. [PHP内核探索：Apache模块介绍](http://www.nowamagic.net/librarys/veda/detail/1293)
9. [PHP内核探索：通过mod\_php5支持PHP](http://www.nowamagic.net/librarys/veda/detail/1299)
10. [PHP内核探索：Apache运行与钩子函数](http://www.nowamagic.net/librarys/veda/detail/1312)
11. [PHP内核探索：嵌入式PHP](http://www.nowamagic.net/librarys/veda/detail/1318)
12. [PHP内核探索：PHP的FastCGI](http://www.nowamagic.net/librarys/veda/detail/1321)
13. [PHP内核探索：如何执行PHP脚本](http://www.nowamagic.net/librarys/veda/detail/1322)
14. [PHP内核探索：PHP脚本的执行细节](http://www.nowamagic.net/librarys/veda/detail/1323)
15. [PHP内核探索：操作码OpCode](http://www.nowamagic.net/librarys/veda/detail/1324)
16. [PHP内核探索：PHP里的opcode](http://www.nowamagic.net/librarys/veda/detail/1325)
17. [PHP内核探索：解释器的执行过程](http://www.nowamagic.net/librarys/veda/detail/1332)
18. [PHP内核探索：变量概述](http://www.nowamagic.net/librarys/veda/detail/1326)
19. [PHP内核探索：变量存储与类型](http://www.nowamagic.net/librarys/veda/detail/1327)
20. [PHP内核探索：PHP中的哈希表](http://www.nowamagic.net/librarys/veda/detail/1344)
21. [PHP内核探索：理解Zend里的哈希表](http://www.nowamagic.net/librarys/veda/detail/1348)
22. [PHP内核探索：PHP哈希算法设计](http://www.nowamagic.net/librarys/veda/detail/1349)
23. [PHP内核探索：翻译一篇HashTables文章](http://www.nowamagic.net/librarys/veda/detail/1366)
24. [PHP内核探索：哈希碰撞攻击是什么？](http://www.nowamagic.net/librarys/veda/detail/1367)
25. [PHP内核探索：常量的实现](http://www.nowamagic.net/librarys/veda/detail/1368)
26. [PHP内核探索：变量的存储](http://www.nowamagic.net/librarys/veda/detail/1386)
27. [PHP内核探索：变量的类型](http://www.nowamagic.net/librarys/veda/detail/1387)
28. [PHP内核探索：变量的值操作](http://www.nowamagic.net/librarys/veda/detail/1388)
29. [PHP内核探索：变量的创建](http://www.nowamagic.net/librarys/veda/detail/1389)
30. [PHP内核探索：预定义变量](http://www.nowamagic.net/librarys/veda/detail/1390)
31. [PHP内核探索：变量的检索](http://www.nowamagic.net/librarys/veda/detail/1393)
32. [PHP内核探索：变量的类型转换](http://www.nowamagic.net/librarys/veda/detail/1394)
33. [PHP内核探索：弱类型变量的实现](http://www.nowamagic.net/librarys/veda/detail/1401)
34. [PHP内核探索：静态变量的实现](http://www.nowamagic.net/librarys/veda/detail/1402)
35. [PHP内核探索：变量类型提示](http://www.nowamagic.net/librarys/veda/detail/1409)
36. [PHP内核探索：变量的生命周期](http://www.nowamagic.net/librarys/veda/detail/1414)
37. [PHP内核探索：变量赋值与销毁](http://www.nowamagic.net/librarys/veda/detail/1415)
38. [PHP内核探索：变量作用域](http://www.nowamagic.net/librarys/veda/detail/1420)
39. [PHP内核探索：诡异的变量名](http://www.nowamagic.net/librarys/veda/detail/1421)
40. [PHP内核探索：变量的value和type存储](http://www.nowamagic.net/librarys/veda/detail/1423)
41. [PHP内核探索：全局变量Global](http://www.nowamagic.net/librarys/veda/detail/1424)
42. [PHP内核探索：变量类型的转换](http://www.nowamagic.net/librarys/veda/detail/1437)
43. [PHP内核探索：内存管理开篇](http://www.nowamagic.net/librarys/veda/detail/1438)
44. [PHP内核探索：Zend内存管理器](http://www.nowamagic.net/librarys/veda/detail/1439)
45. [PHP内核探索：PHP的内存管理](http://www.nowamagic.net/librarys/veda/detail/1440)
46. [PHP内核探索：内存的申请与销毁](http://www.nowamagic.net/librarys/veda/detail/1441)
47. [PHP内核探索：引用计数与写时复制](http://www.nowamagic.net/librarys/veda/detail/1442)
48. [PHP内核探索：PHP5.3的垃圾回收机制](http://www.nowamagic.net/librarys/veda/detail/1452)
49. [PHP内核探索：内存管理中的cache](http://www.nowamagic.net/librarys/veda/detail/1453)
50. [PHP内核探索：写时复制COW机制](http://www.nowamagic.net/librarys/veda/detail/1454)
51. [PHP内核探索：数组与链表](http://www.nowamagic.net/librarys/veda/detail/1455)
52. [PHP内核探索：使用哈希表API](http://www.nowamagic.net/librarys/veda/detail/1456)
53. [PHP内核探索：数组操作](http://www.nowamagic.net/librarys/veda/detail/1457)
54. [PHP内核探索：数组源码分析](http://www.nowamagic.net/librarys/veda/detail/1458)
55. [PHP内核探索：函数的分类](http://www.nowamagic.net/librarys/veda/detail/1462)
56. [PHP内核探索：函数的内部结构](http://www.nowamagic.net/librarys/veda/detail/1463)
57. [PHP内核探索：函数结构转换](http://www.nowamagic.net/librarys/veda/detail/1464)
58. [PHP内核探索：定义函数的过程](http://www.nowamagic.net/librarys/veda/detail/1465)
59. [PHP内核探索：函数的参数](http://www.nowamagic.net/librarys/veda/detail/1466)
60. [PHP内核探索：zend\_parse\_parameters函数](http://www.nowamagic.net/librarys/veda/detail/1467)
61. [PHP内核探索：函数返回值](http://www.nowamagic.net/librarys/veda/detail/1473)
62. [PHP内核探索：形参return value](http://www.nowamagic.net/librarys/veda/detail/1474)
63. [PHP内核探索：函数调用与执行](http://www.nowamagic.net/librarys/veda/detail/1475)
64. [PHP内核探索：引用与函数执行](http://www.nowamagic.net/librarys/veda/detail/1489)
65. [PHP内核探索：匿名函数及闭包](http://www.nowamagic.net/librarys/veda/detail/1490)
66. [PHP内核探索：面向对象开篇](http://www.nowamagic.net/librarys/veda/detail/1491)
67. [PHP内核探索：类的结构和实现](http://www.nowamagic.net/librarys/veda/detail/1492)
68. [PHP内核探索：类的成员变量](http://www.nowamagic.net/librarys/veda/detail/1499)
69. [PHP内核探索：类的成员方法](http://www.nowamagic.net/librarys/veda/detail/1500)
70. [PHP内核探索：类的原型zend\_class\_entry](http://www.nowamagic.net/librarys/veda/detail/1512)
71. [PHP内核探索：类的定义](http://www.nowamagic.net/librarys/veda/detail/1513)
72. [PHP内核探索：访问控制](http://www.nowamagic.net/librarys/veda/detail/1514)
73. [PHP内核探索：继承，多态与抽象类](http://www.nowamagic.net/librarys/veda/detail/1515)
74. [PHP内核探索：魔术函数与延迟绑定](http://www.nowamagic.net/librarys/veda/detail/1519)
75. [PHP内核探索：保留类与特殊类](http://www.nowamagic.net/librarys/veda/detail/1520)
76. [PHP内核探索：对象](http://www.nowamagic.net/librarys/veda/detail/1521)
77. [PHP内核探索：创建对象实例](http://www.nowamagic.net/librarys/veda/detail/1522)
78. [PHP内核探索：对象属性读写](http://www.nowamagic.net/librarys/veda/detail/1523)
79. [PHP内核探索：命名空间](http://www.nowamagic.net/librarys/veda/detail/1524)
80. [PHP内核探索：定义接口](http://www.nowamagic.net/librarys/veda/detail/1525)
81. [PHP内核探索：继承与实现接口](http://www.nowamagic.net/librarys/veda/detail/1526)
82. [PHP内核探索：资源resource类型](http://www.nowamagic.net/librarys/veda/detail/1533)
83. [PHP内核探索：Zend虚拟机](http://www.nowamagic.net/librarys/veda/detail/1540)
84. [PHP内核探索：虚拟机的词法解析](http://www.nowamagic.net/librarys/veda/detail/1541)
85. [PHP内核探索：虚拟机的语法分析](http://www.nowamagic.net/librarys/veda/detail/1542)
86. [PHP内核探索：中间代码opcode的执行](http://www.nowamagic.net/librarys/veda/detail/1543)
87. [PHP内核探索：代码的加密与解密](http://www.nowamagic.net/librarys/veda/detail/1544)
88. [PHP内核探索：zend\_execute的具体执行过程](http://www.nowamagic.net/librarys/veda/detail/1580)
89. [PHP内核探索：变量的引用与计数规则](http://www.nowamagic.net/librarys/veda/detail/1581)
90. [PHP内核探索：新垃圾回收机制说明](http://www.nowamagic.net/librarys/veda/detail/1582)


# PHP运行模式及安装方式

> PHP能做什么？
>
> 服务端脚本
>
> 命令行脚本
>
> 编写桌面应用程序

## PHP运行模式

php一共分为五大运行模式：

* cgi (通用网关接口Common Gateway Interface)
* fast-cgi (fast-cgi 是cgi的升级版本)
* cli (Command Line Interface)
* isapi (Internet Server Application Program Interface，是微软提供的一套面向Internet服务的API接口)
* apache2handler (将php作为apache的模块, nginx类似)
* 其他(continuity, embed, litespeed, milter等)

isapi 和 apache2handler 同属于模块模式

### CLI模式

Command Line Interface的简称，即**PHP命令行接口**，在windows和linux下都支持PHP-CLI模式

它可以直接在命令行下运行,那就意味着完全可以不要任何http容器. 例如`php test.php`

**应用场景**

* 定时任务
* 开发桌面应用就是使用PHP-CLI和GTK包
* 开发shell脚本

**优点和缺点**

* 以上应用场景决定了其优点
* 无法为普通用户提供http服务

> CLI专用常量 STDIN、STDOUT、STDERR
>
> 命令行下参数$argc 、 $arg
>
> 本地查看运行模式的方法
>
> ```
> php -r "phpinfo();" |grep "Server API"
> php -r "echo php_sapi_name();"
> phpinfo();
> ```

**示例:**

使用标准stdin 常量输入数据 / stdout输出

```php
//标准输入 stdin
$stdin1 = fopen("php://stdin","r");
echo "please input a number!\r\n";
$num = fgets($stdin1);
echo "ok , $num";

echo "the second type:";
$stdin2 = fgets(STDIN);
var_dump($stdin2);

//标准输出stdout
//第一种方式:
$stdout = fopen('php://stdout', 'w');
fwrite($stdout, "1:php://stdout\r\n");
fclose($stdout);
//第二种方式
fwrite(STDOUT, "2:STDOUT\r\n");
exit;

//标准错误stderr,默认情况会发送到客户端
$stderr = fopen('php://stderr', 'w');
fwrite($stderr, "err1");
fclose($stderr);
fwrite(STDERR, "err2");
```

可执行PHP脚本：phphead 来模拟“head” 命令

```php
error_reporting(E_ERROR);

$filename = $argv[1];
$number = intval($argv[2]);

$data = '';
if(!file_exists($filename)){
    exit($filename." is not exists!");
}

if(!$number){
    $number = 10;
}

$fp = fopen($filename,"r");
$i = 1;
while($i<=$number){
    $data .= fgets($fp);
    $i++;
}
fclose($fp);
echo $data;
```

### CGI模式

CGI即通用网关接口(Common Gateway Interface)，它是一段程序, 通俗的讲CGI就象是一座桥，把网页和WEB服务器中的执行程序连接起来，它把http服务器接收的指令传递给执行程序，再把执行程序的结果返还给http服务器。CGI 的跨平台性能极佳，几乎可以在任何操作系统上实现。

**执行过程**&#x20;

* http服务器接受到用户请求后 ,例如 index.php ,会通过它配置的CGI服务来执行
* 生成一个php-cgi.exe的进程,并执行php程序   &#x20;
* 执行的返回结果交给http服务器   &#x20;

**应用场景**

* 提供http服务

**优点和缺点**

* 跨平台,几乎可以在任何操作系统上实现.
* web和server是独立的,结构清晰,可控性强
* 性能比较差,来一个请求,fork一个进程,100个请求就会fork100进程,消耗资源较多(fork-and-execute 模式)
* 最近几年已经很少见到使用这种模式了

### FastCGI模式

快速通用网关接口（Fast Common Gateway Interface／FastCGI）是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口（CGI）的增强版本。FastCGI致力于减少Web服务器与CGI程序之间互动的开销，从而使服务器可以同时处理更多的网页请求。。

**执行过程**&#x20;

* web服务器启动时 ,载入FastCGI进程管理器
* FastCGI进程管理器会启动多个CGI进程等待web服务器的链接.
* 当客户端请求到达Web Server时，FastCGI进程管理器选择并连接到一个CGI解释器。Web server将环境变量和标准输入发送到FastCGI子进程php-cgi
* FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时，请求便告处理完成。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。 在CGI模式中，php-cgi在此便退出了。

**应用场景**

* 提供http服务

**优点和缺点**

* 跨平台,几乎可以在任何操作系统上实现.
* web和server是独立的,结构清晰,可控性强,并不需要web升级而变化
* 支持大并发
* 多进程,消耗较多内存

### 模块模式

模块模式指将php作为web服务器的一个模块运行

主要说说 **iis 的 isapi** 和**apache的apache2handler**

**apache的apache2handler执行过程**

* apache监听到一个用户请求index.php
* apache根据conf文件中配置的LoadModule php\_module modules/mod\_php5.so(windows下面是php5apache2\_2.dll)调用PHP
* 在mod\_php5.so(php5apache2\_2.dll)注册一个php的钩子 php\_ap2\_register\_hook

> 这个php\_ap2\_register\_hook钩子函数中包括4个挂钩以及对应的函数ap\_hook\_pre\_config, ap\_hook\_post\_config ap\_hook\_handler,ap\_hook\_child\_init。其中pre\_config,post\_config,child\_init是启动挂钩，它们在服务器启动时调用。 handler挂钩是请求挂钩，它在服务器处理请求时调用。其中在post\_config挂钩中启动php。

* php执行完成数据通过.so或dll返回给apache
* apache将数据返回到客户端

**应用场景**

* 提供http服务

**优点和缺点**

* 安装配置方便，不需要安装代码解析程序
* 支持多线程，占用资源少
* 支持大并发

> Thread Safe 和 Non Thread Safe (线程安全与线程非安全)
>
> Thread Safe是线程安全，执行时会进行线程（Thread）安全检查，以防止有新要求就启动新线程的执行方式而耗尽系统资源。Non Thread Safe是非线程安全，在执行时不进行线程（Thread）安全检查。
>
> 由于ISAPI 是线程执行,所以选择下载php版本的时候需要选择Thread Safe版本的 . 其他的选择NTS版本的就行了

### PHP主要的安装方式

* Linux下  Apache + PHP
* Linux下  Nginx + PHP
* Windows下  IIS + PHP
* Windows下  Apahe + PHP
* Windows下  Nginx + PHP


# 附录:进程和线程的Q解

计算机的核心是CPU，它承担了所有的计算任务。它就像一座工厂，时刻在运行。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0Q-M2zHsqyutuN%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B1.png?generation=1558862986408064\&alt=media)

假定工厂的电力有限，**一次只能供给一个车间使用**。也就是说，一个车间开工的时候，其他车间都必须停工。背后的含义就是，单个CPU一次只能运行一个任务。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0SUPxpDC9xgBl7%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B2.png?generation=1558862987296183\&alt=media)

进程就好比工厂的车间，它代表CPU所能处理的单个任务。任一时刻，CPU总是运行一个进程，其他进程处于非运行状态。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0Up3ijyMO4HBLs%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B3.png?generation=1558862986396068\&alt=media)

一个车间里，可以有很多工人。他们协同完成一个任务。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0W3l568ZxGZ-R8%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B4.png?generation=1558862996542429\&alt=media)

线程就好比车间里的工人。一个进程可以包括多个线程。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0Yo3XwZMm246kb%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B5.png?generation=1558862986510785\&alt=media)

车间的空间是工人们共享的，比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的，每个线程都可以使用这些共享内存。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0_6Q_OyX-7-yXH%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B6.png?generation=1558862986328940\&alt=media)

可是，每间房间的大小不同，有些房间最多只能容纳一个人，比如厕所。里面有人的时候，其他人就不能进去了。这代表一个线程使用某些共享内存时，其他线程必须等它结束，才能使用这一块内存。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0boQuGQHsGY7nJ%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B7.png?generation=1558862986759988\&alt=media)

一个防止他人进入的简单方法，就是门口加一把锁。先到的人锁上门，后到的人看到上锁，就在门口排队，等锁打开再进去。这就叫"互斥锁"（Mutual exclusion，缩写 Mutex），防止多个线程同时读写某一块内存区域。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0dLpxo1KMj56Kj%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B8.png?generation=1558862986883709\&alt=media)

还有些房间，可以同时容纳n个人，比如厨房。也就是说，如果人数大于n，多出来的人只能在外面等着。这好比某些内存区域，只能供给固定数目的线程使用。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0fRr0riszOTbTH%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B9.png?generation=1558862987874320\&alt=media)

这时的解决方法，就是在门口挂n把钥匙。进去的人就取一把钥匙，出来时再把钥匙挂回原处。后到的人发现钥匙架空了，就知道必须在门口排队等着了。这种做法叫做"信号量"（Semaphore），用来保证多个线程不会互相冲突。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTK0h4NFmGCrtEV4u%2F%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B10.png?generation=1558862986644177\&alt=media)

## 操作系统的设计，因此可以归结为三点：

（1）以多进程形式，允许多个任务同时运行；

（2）以多线程形式，允许单个任务分成不同的部分运行；

（3）提供协调机制，一方面防止进程之间和线程之间产生冲突，另一方面允许进程之间和线程之间共享资源。

> 协程和线程的区别是：协程避免了无意义的调度，由此可以提高性能，但也因此，程序员必须自己承担调度的责任，同时，协程也失去了标准线程使用多CPU的能力。


# Apache下的MPM模式

## Apache的prefork模式

prefork采用预派生子进程方式，用单独的子进程来处理 不同的请求，进程之间彼此独立。在make编译和make install安装后，使用httpd -l来确定当前使用的MPM是prefork.c。查看httpd-mpm.conf配置文件，里面包含如下默认的配置段：

```
<IfModule prefork.c>
    StartServers 5                  #初始化进程数量
    MinSpareServers 5          #空闲进程总数最小值
    MaxSpareServers 10        #空闲进程总数最大值
    MaxClients 150                #最大客户端连接数量限制
    MaxRequestsPerChild 0   #子进程将能处理的请求数量
</IfModule>
```

## Apache的worker模式

worker全新的支持多线程和多进程混合模型的MPM。由于 使用线程来处理，所以可以处理相对海量的请求，而系统资源的开销要小于基于进程的服务器。但是，worker也使用了多进程，每个进程又生成多个线程。

```
<IfModule worker.c>
    ServerLimit 25        #每个子进程可配置的线程数上限
    ThreadLimit 200     #所有服务线程总数的硬限制
    StartServers 3 
    MaxClients 2000（2.3.13后改为MaxRequestWorkers）
    MinSpareThreads 50 
    MaxSpareThreads 200 
    ThreadsPerChild 100   #每个子进程建立的线程数
    MaxRequestsPerChild 0 (MaxConnectionsPerChild)
</IfModule>
```

Apache的event模式

Event是Work模式的优化，主要为了解决Worker模式处理KeepAlive连接性能差的缺陷。通过建立专用处理线程来处理KeepAlive连接，同时将活跃请求转发给其他线程。避免了KeepAlive带来的灾难。

```
<IfModule event.c>
    StartServers 3
    MinSpareThreads 75
    MaxSpareThreads 250
    ThreadsPerChild 25
    MaxRequestWorkers 400
    MaxConnectionsPerChild 0
</IfModule>
```


# Apache 与 Nginx

## 为什么大访问量下Nginx性能更高？

* Apache所采用的select网络I/O模型非常低效
* Nginx使用了最新的epoll（Linux 2.6内核）和kqueue（freebsd）网络I/O模型

> Apache进程干的事情多：执行PHP、输出HTML都得干，占用的资源就多（CPU、内存）

## 比较I/O模型select和epoll

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHbT48NeUKAR3JYg%2FSELECT%E6%A8%A1%E5%BC%8F%E4%B8%8EEPOLL%E6%A8%A1%E5%BC%8F.png?generation=1558862967211449\&alt=media)

## 如何选择 Apache 与 Nginx？

* 一般访问量网站优先选择Apache，它稳定可靠
* Apache模块非常丰富，为了用它的模块得选它
* Apache的rewrite非常强大，rewrite多就选择它
* 大访问量优先选择Nginx，它支持更多的并发连接
* 处理的都是静态内容（html，图片），首选Nginx
* Nginx本质上是反向代理服务器，需要反向代理就选它


# PHP的运行机制及原理

* 1. 我们从未手动开启过PHP的相关进程，它是随着Apache的启动而运行的；
* 1. PHP通过SAPI和Apache相连；
* 1. PHP总共有三个模块：内核、Zend引擎、以及扩展层；
* 1. PHP内核用来处理请求、文件流、错误处理等相关操作；
* 1. Zend引擎（ZE）用以将源文件转换成机器语言，然后在虚拟机上运行它；
* 1. 扩展层是一组函数、类库和流，PHP使用它们来执行一些特定的操作。比如，我们需要mysql扩展来连接MySQL数据库；
* 1. 当ZE执行程序时可能会需要连接若干扩展，这时ZE将控制权交给扩展，等处理完特定任务后再返还；
* 1. 最后，ZE将程序运行结果返回给PHP内核，它再将结果传送给SAPI层，最终输出到浏览器上。

## php执行过程

1.扫描(scanning) ,将index.php内容变成一个个语言片段(token)

2.解析(parsing) , 将一个个语言片段变成有意义的表达式

3.编译(complication),将表达式编译成中间码(opcode)

4.执行(execution),将中间码一条一条的执行

5.输出(output buffer),将要输出的内容输出到缓冲区

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHeLDfGgJLiDbfYo%2FPHP%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B.png?generation=1558862977136377\&alt=media)扫描 **scanning**

学过编译原理的应该了解Lex , Lex是LEXical compiler的缩写，是个非常著名的工具,主要功能是词法分析器(scanner),描述规则采用正则表达式(regular expression)。在php源码中是 Zend/zend\_language\_scanner.c 去根据 Zend/zend\_language\_scanner.l (lex规则文件) ,来对需要执行的php文件进行词法分析 ,从而得到一个个的"词" (token),当然执行规则,也就是切分的方式还是用正则表达式做的

具体看代码 Zend/zend\_language\_scanner.l

```php
$code =<<<'PHP_CODE'
<?php
//这是注释
echo "Hello, world\n";
$data = 1 + 1;
echo $data;
PHP_CODE;


print_r(token_get_all($code));
```

分析这个返回结果我们可以发现，源码中的字符串，字符，空格，都会原样返回。每个源代码中的字符，都会出现在相应的顺序处。每一个数组的结构都是一致的,

```
Array(
     [0] => 316                 //tokenid,也就是"词"id 
     [1] => "Hello, world\n"        //"词"具体的内容
      [2] => 3                //所在的位置,行数
)
```

Token ID (在Zend内部的改Token的对应码,比如,T\_ECHO,T\_STRING)。

具体的定义可以在zend\_language\_parser.h中找到

**解析 parsing**

词法分析后, 就需要根据一个个token去组成有意义的表达式

Parsing首先会丢弃Tokens Array中的多余的空格，然后将剩余的Tokens转换成一个一个的简单的表达式,例如上例中:

1.输出一个常量字符串

2.两个数字相加

3.上个表达式的结果存储在一个变量

4.输出这个变量

**编译 complication**

根据上面的表达式,我们要将之编译成中间码(opcode),编译成opcode后,zend引擎才会根据一条一条的opcode 去执行我们的功能!

我们怎么看到编译后的opcode代码呢? 可以用vld扩展或者parsekit相关函数可以看到编译后的opcode,上例中的opcode:

```
* ZEND_ECHO     'Hello World'
* ZEND_ADD       ~0 1 1  
* ZEND_ASSIGN  !0 ~0
* ZEND_ECHO     !0
```

> 注意: 1+1操作完成结果要存起来,存到临时变量 \~0 将\~0赋值给$c也就是!0;

**执行 execution**

执行的工作具体由 Zend/zend\_execute.c 来完成

如果执行是某内置函数或语句,例如array\_search,会调用对应的array函数.

如果调用的是扩展,例如mysqli或pdo,则会将控制权交给这些扩展,扩展执行完后将记过返回给ZE.

全部执行完后ZE将结果返回php内核,内核再返回SAPI,最后返回到服务器,通过服务器httpresponse返回到浏览器

## 如何让PHP的网站运行的更快？

* 压缩代码，去除无用的注释或空白
* 尽量使用PHP内置函数或扩展函数
* 用apc/xcache/opcache等缓存PHP的opcode
* 使用多线程、多进程优化程序逻辑
* 缓存复杂和耗时运算的结果
* 能异步处理的任务不要马上处理，如发邮件
* 依据资源情况对FastCGI配置合适的参数


# PHP垃圾回收机制

php中什么是垃圾?

垃圾主要是针对内存的, 如果一个对象, 并没有任何变量引用它,那这个对象就是垃圾.

为什么要清理垃圾?

* 如果php开启了很多内存空间,但是却没有销毁它.内存会一点点的被吃掉,最终导致内存溢出!
* 有人说php线程结束的时候会销毁所有的变量,关闭所有打开的句柄资源,不都是自动的吗?为什么我们还需要清理?
* 如果你写的php代码是个需要长时间执行的呢,例如弄成守护进程?

## 垃圾回收的机制

**PHP如何找到垃圾**

第一步, 从所有变量中找到垃圾

所有的变量get\_defined\_vars函数能够看到所有已经定义的变量,那意味着php本身能存储所有变量或已开辟的内存空间,具体在zend\_globals.h中struct \_zend\_executor\_globals 中看到

```
struct _zend_executor_globals {
    //略
    HashTable *active_symbol_table;//局部变量的符号表
    HashTable symbol_table;//全局变量的符号表
    //略
};
```

能看到所有的变量,这边变量会被打上视为垃圾的标签

变量本身的定义Zend/zend.h中以看到zval的定义

```
zend_uint refcount__gc; //这个用来标记有多少个变量指向它
zend_uchar is_ref__gc; //这个用来标记用引用的方式指向它
```

查看是不是垃圾就看 refcount\_\_gc 是不是为0

```php
$a = array(1);
//$b = $a;
$a[] = $a;
unset($a[1]);
//unset($a);
//$c = &$a;
//$d = &$a;
//$c = null;
//$c =1;
//unset($a);
//unset($a);
xdebug_debug_zval('a');
//var_dump(get_defined_vars());
```

> PHP中的引用是什么？
>
> * 引用不是C的指针
> * 引用允许用两个变量来指向同一个内容
> * 引用做的第二件事是用引用传递变量
> * 引用做的第三件事是引用返回

第二步.清除垃圾

找到refcount = 0的就清除,并回收内存(5.2以及以前版本)PHP5.3版本中采用了引用计数系统中的同步周期回收(Concurrent Cycle Collection in Reference Counted Systems)算法来清除 , 基本规则是这样的 ,如果引用技术在+1 ,说明变量再用,不是垃圾,如果引用技术本身为0,那就是垃圾,需要被删除, 如果引用计数-1 其为非0 的时候 将其放到某一区块标记为疑似垃圾,然后对其做很多的模拟操作,最后找到真正的垃圾(为了解决自调用$a=array(1);$a\[]=\&a这样的垃圾)

当我们存储的疑似垃圾的区域满了的时候,就会被执行清除垃圾的操作,前提是开启了垃圾回收机制,当然,默认是打开的php.ini 设置允许你修改它：zend.enable\_gc

修改配置zend.enable\_gc ，也能通过分别调用gc\_enable() 和 gc\_disable()函数来打开和关闭垃圾回收机制。调用这些函数，与修改配置项来打开或关闭垃圾回收机制的效果是一样的。你还能调用gc\_collect\_cycles()函数达到这个目的。它能强制执行周期回收。


# PHP配置选项

**PHP选项之“display\_errors”**

该选项设置是否将错误信息作为输出的一部分显示到屏幕，或者对用户隐藏而不显示。

设置 "stderr" 表示发送到 stderr 而不是 stdout。 "stderr"从 PHP 5.2.4 开始可用。在以前的版本中，该配置值的类型为 boolean.

这是一个辅助开发的功能，建议永远不要在生产系统中使用 (例如系统被连接到互联网对外提供服务)。

尽管 display\_errors 也可以在运行时设置 (使用 ini\_set())， 但是脚本出现致命错误时任何运行时的设置都是无效的。 因为在这种情况下预期运行的操作不会被执行。

```php
// 开发时
error_reporting(E_ALL);
ini_set('display_errors', true);
```

**PHP选项之“error\_reporting”**

设置错误报告的级别。该参数可以是一个任意的表示二进制位字段的整数，或者常数名称。错误级别和常数是在 预定义常量定义的，在 php.ini 之中也有专门的说明。

可以通过 error\_reporting() 函数进行设置。

在PHP 4和PHP 5之中，其默认值为 E\_ALL & \~E\_NOTICE。 该设置表示除了 E\_NOTICE 其他都显示的错误级别。在开发过程中很有必要显示它们。

**PHP选项之“magic\_quotes\_gpc”**

本特性已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。

为 GPC (Get/Post/Cookie) 操作设置 magic\_quotes 状态。 当 magic\_quotes 为 on，所有的 ' (单引号)、" (双引号)、\（反斜杠）和 NUL's 被一个反斜杠自动转义。

**PHP选项之“magic\_quotes\_runtime”**

本特性已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。

如果启用了 magic\_quotes\_runtime，大多数返回任何形式外部数据的函数，包括数据库和文本段将会用反斜线转义引号。 如果启用了 magic\_quotes\_sybase，单引号会被单引号转义而不是反斜线。

受 magic\_quotes\_runtime 影响的函数（不包括 PECL 里的函数）： file\_get\_contents()、file()、fgets()、fwrite()、fread()

**PHP选项之“variables\_order”**

设置EGPCS (Environment, Get, Post, Cookie, and Server)超全局变量解析的顺序。例如，如果这个选项被设置为 “SP” ，那么PHP将创建 超级全局变量 $\_SERVER 、 $\_POST, 但是不会创建 $\_ENV, $\_GET, 和 $\_COOKIE。设置为空表示不创建任何超全局变量。

**PHP选项之“request\_order”**

确定$\_GET和$\_POST的等超全局变量的顺序，后面的会把前面的数据重写覆盖。

例如： request\_order = "GP“

如果post请求同时有get的参数，那么post的数据会把get的参数覆盖。

**PHP选项之“safe\_mode”**

PHP 的安全模式是为了试图解决共享服务器（shared-server）安全问题而设立的。在结构上，试图在 PHP 层上解决这个问题是不合理的，但修改 web 服务器层和操作系统层显得非常不现实。因此许多人，特别是 ISP，目前使用安全模式。

本特性已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。

**PHP选项之“open\_basedir”**

将 PHP 所能打开的文件限制在指定的目录树，包括文件本身。本指令不受安全模式打开或者关闭的影响。

当一个脚本试图用例如 fopen() 或者 gzopen() 打开一个文件时，该文件的位置将被检查。当文件在指定的目录树之外时 PHP 将拒绝打开它。所有的符号连接都会被解析，所以不可能通过符号连接来避开此限制。

**PHP选项之“disable\_classes 、disable\_functions**”

“disable\_classes”指令可以使你出于安全的理由禁用某些类。用逗号分隔类名。disable\_classes 不受安全模式的影响。

“disable\_functions”指令允许你基于安全原因禁止某些函数。接受逗号分隔的函数名列表作为参数。 disable\_functions 不受安全模式的影响。

**PHP选项之“ expose\_php ”**

“决定是否暴露 PHP 被安装在服务器上（例如在 Web 服务器的信息头中加上其签名：X-Powered-By: PHP/5.3.7)。

可以通过这个设置这个为off 关闭掉。

这个参数的默认值是开启的。

**PHP选项之“max\_input\_time 、max\_execution\_time”**

“max\_input\_time ：脚本解析输入数据（类似 POST 和 GET）允许的最大时间，单位是秒。 它从接收所有数据到开始执行脚本进行测量的。

max\_execution\_time 这设置了脚本被解析器中止之前允许的最大执行时间，单位秒。 这有助于防止写得不好的脚本占尽服务器资源。 默认设置为 30。 从命令行运行 PHP 时，默认设置为 0。

**PHP选项之“memory\_limit ”**

系统分配给PHP的最大内存数量。

可以在PHP代码中动态的修改，int\_set(‘memory\_limit’,’128M’);

如果设置为“-1” ，表示不限制大小，以实际系统的内存为依据。

**PHP选项之“register\_globals”**

是否把超级全局变量 EGPCS (Environment, GET, POST, Cookie, Server) 里面的数据创建为全局变量。

本特性已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。

**PHP选项之“include\_path”**

在设置的路径中寻找文件。它会检查第一个路径，如果它没有找到它，检查下一个路径，直到它找到的包含文件或警告或错误回报。您可以使用set\_include\_path（）在运行时修改或设定你的include路径。

Linux的路径用冒号分割

```
include_path=".:/php/includes"
```

Windows下的路径用分号分割

```
include_path=".;c:\php\includes"
```

**PHP选项之“extension\_dir”**

PHP动态连接扩展库存放的目录，在Linux下，扩展后缀一般”.so”,在Windows下一般都是“.dll”

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHnOx4vEtdIBKFKs%2Fextension_dir.png?generation=1558862969106079\&alt=media)

**PHP选项之“upload\_tmp\_dir”相关upload\_tmp\_dir**

* upload\_max\_filesize = 2M
* file\_uploads = On
* max\_file\_uploads = 20
* post\_max\_size = 8M

**PHP选项之“default\_socket\_timeout ”相关**

基于 socket 的流的默认超时时间（秒），默认60秒。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHnZ3-Bmf4BwSWrE%2Fdefault_socket_timeout.png?generation=1558862969060813\&alt=media)


# PHP安全


# 跨站脚本攻击(XSS )


# 跨站请求伪造(CRSF)


# PHP 高级特性

1. 异常处理
2. Traits
3. 预定义接口、生成器
4. PHP 的魔术方法
5. 回调函数、匿名函数&闭包
6. 命名空间
7. 自动加载
8. 反射


# 异常处理（Exceptions）

* 文件找不到！
* 数据库连接失败！
* URL接口访问失败！
* 磁盘空间不足！
* 等等

怎么办?

**避免使用:**

* die()
* return false

**解决办法:**

使用异常处理

```php
  try {
      //好好干，出了问题就抛出去
  } catch (Exception $e) {
      //时刻准备着，接 Exception ！
  } finally {
      //怎么玩都逃不出我的手掌心
  }
```

注意：try 中 return 后 finally 会继续执行，如果 finally 中也有return，则最终返回值为 finally 中 return 的值。

try 中 die 或 exit 后 finally 不会执行。

php7 下实例:

```php
if(php_sapi_name() != 'cli') ob_start('nl2br');

function trytest() {
    try {
        echo "try\n";
        //throw new Exception("I'm dying, Help!!");
        //die;
        return false;
    } catch ( Exception $e ) {
        echo $e->getMessage()."\n";
    } finally {
        echo "finally\n";
        return true;
    }
}

var_dump(trytest());
```

结果:

```
try
finally


boolean true
```

## 设置自己的错误处理函数

PHP 中充满了 Notice、Warning等,可以采用设置自己的错误处理函数来捕捉.

如下面的错误:

```
Warning: file_get_contents() expects at least 1 parameter, 0 given in xxxxx
```

**可以设置自己的错误处理函数:**

```
set_error_handler (callable $handler [, int $error_types ]);
```

默认的PHP错误处理流程会绕过 $error\_types 中指定的错误类型，除非 $handler 中返回false(当返回false 则不会绕过,无法拦截,并且报错)；

如果myErrorHandler正常拦截, error\_reporting() 不再有效，除非自己在 $handler 中按当前的 error\_reporting 作出判断；

```php
// 可通过 set_error_handler + ErrorException 拦截php默认报错，转为异常
function trytest() {
    try {
        file_get_contents(); // parameter missing. w
    } catch (Exception $e) {
        echo $e->getMessage()."\n";
    } finally {
        return true;
    }
}

function myErrorHandler($errno, $errstr, $errfile, $errline){
    throw new ErrorException("Exception: ".$errstr, 0, $errno, $errfile, $errline);
}

set_error_handler("myErrorHandler");

trytest();
```

但是，set\_error\_handler 只能拦截部分错误，对于如下拦截不了的怎么办？

E\_ERROR, E\_PARSE, E\_CORE\_ERROR, E\_CORE\_WARNING, E\_COMPILE\_ERROR, E\_COMPILE\_WARNING, 调用该函数的文件中出现的 E\_STRICT

思路: 通过`register_shutdown_function`函数注册一个在代码执行完毕后自动调用的函数

```php
//error_reporting(0);

register_shutdown_function(function() {
    if ($error = error_get_last()) {
        print_r($error);
    }
});

// Fatal error: Call to undefined function test()
test();
```


# 代码复用(Trait)

[官方文档](http://php.net/manual/zh/language.oop5.traits.php)

* trait 主要用于解决单继承引起的代码复用问题；
* 在类中使用 trait 类似于include一个公用文件；
* trait 支持所有修饰符例如 final，static，abstract
* trait 不能通过 new 实例化；
* trait 中的方法冲突可通过 insteadof 或 as 关键词解决。通过 as 还可修改访问权限。

**优先顺序示例**

从基类继承的成员被插入的 SayWorld Trait 中的 MyHelloWorld 方法所覆盖。其行为 MyHelloWorld 类中定义的方法一致。优先顺序是当前类中的方法会覆盖 trait 方法，而 trait 方法又覆盖了基类中的方法。

```php
<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>
```

**多个 trait示例：**&#x901A;过逗号分隔，在 use 声明列出多个 trait，可以都插入到一个类中。

```php
<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>
```

以上例程会输出：

```
Hello World!
```

**trait中使用trait的示例**

正如 class 能够使用 trait 一样，其它 trait 也能够使用 trait。在 trait 定义时通过使用一个或多个 trait，能够组合其它 trait 中的部分或全部成员。

```php
<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>
```

以上例程会输出：

```
Hello World!
```

**trait中方法冲突问题示例：**

```php
<?php
trait firstTrait {
    function sameMethodName() { echo "method in firstTrait\n"; }
}

trait secondTrait {
    function sameMethodName() { echo "method in secontTrait\n"; }
}

class firstClass {
    // 使用firstTrait中的sameMethodName()方法而不是用secondTrait中的。:
    use firstTrait, secondTrait {
        firstTrait::sameMethodName insteadof secondTrait;
    }
}

$obj = new firstClass();

$obj->sameMethodName(); // Print : method in firstTrait
```

另一种方式

```php
<?php
trait firstTrait {
    function sameMethodName() { echo "method in firstTrait\n"; }
}

trait secondTrait {
    function sameMethodName() { echo "method in secontTrait\n"; }
}

class firstClass {
    use firstTrait, secondTrait {
        // 使用firstTrait中的sameMethodName()方法而不是用secondTrait中的。:
        firstTrait::sameMethodName insteadof secondTrait;
        // 使用secondTrait中的sameMethoName()方法但改名为 anotherMethodName():
        secondTrait::sameMethodName as public anotherMethodName;
    }
}

$obj = new firstClass();
$obj->sameMethodName(); 
$obj->anotherMethodName();
```


# 预定义接口(Predefined Interfaces)

1. Traversable
2. Iterator
3. [IteratorAggregate](http://php.net/manual/zh/class.iteratoraggregate.php)（聚合式迭代器）接口
4. Generator　生成器
5. ArrayAccess
6. Serializable
7. Closure

## [IteratorAggregate](https://www.gitbook.com/book/xiaoxiami/phper/edit#)（聚合式迭代器）接口

接口摘要

```php
IteratorAggregate extends Traversable {
/* 方法 */
abstract public Traversable getIterator ( void )
}
```

个接口实现了一个功能——创建外部迭代器，具体怎么理解呢，当我们使用**foreach**对对象进行便遍历的时候，如果没有继承**IteratorAggregate**接口，遍历的是对象中所有的public属性(只能是public $var这种形式)。要是继承了**IteratorAggregate**，会使用类中实现的**getIterator**方法返回的对象，这里要注意返回的一定要是一个**Traversable**对象或者扩展自**Traversable**的对象，否则会抛出异常

**示例：**

ArrayIterator 对数组进行了Iterator封装

```php
class My{
 private $_data = [
 'a' => '燕睿涛',
 'b' => 'yanruitao',
 'c' => 'LULU',
 ];

 public function getIterator()
 {
   return new ArrayIterator($this->_data);
 }
}
$obj = new My;
foreach ($obj as $key => $value) {
 echo "$key => $value\n";
}
//输出结果为空
```

改造

```php
class My implements IteratorAggregate {
 private $_data = [
 'a' => '燕睿涛',
 'b' => 'yanruitao',
 'c' => 'LULU',
 ];

 public function getIterator()
 {
 return new ArrayIterator($this->_data);
 }
}
$obj = new My;
foreach ($obj as $key => $value) {
 echo "$key => $value\n";
}
```

结果：

```php
a => 燕睿涛
b => yanruitao
c => LULU
```

示例：

Countable 的目的是能够使用php count函数来统计日志的行数

Iterator 迭代器需要实现current() key() next() rewind() valid()方法

foreach 之前，先rewind() ，将遍历的指针重置到遍历的开头，调用next() 将指针移动到下一个位置，current() 则可以获得当前指针指向数据，每次循环都会调用valid()方法，如果返回false，则证明遍历结束

```php
<?php
/**
 * 提取 access.log 各行中的 ip 和 request-url ，通过 foreach 遍历显示
 */
 class AccessRecord {
    private $segments = [];
    public function __construct($row) {
        preg_match('/((?:\d{1,3}.){3}\d{1,3}).*?"[A-Z]{3,}\s(.*?)\s/', $row, $rs);
        $this->segments = array($rs[1], $rs[2]);
    }
    public function getIp() {
        return $this->segments[0];
    }
    public function getUrl() {
        return $this->segments[1];
    }
}


class AccessLogIterator implements Countable, Iterator {
    private $filepath; 
    private $fp;
    private $currentContent; //当前行的内容
    private $linePointer; //当前行的指针（行号）
    private $count; //文件行数总计


    public function __construct($filepath) {
        $this->filepath = $filepath;
        $this->init();
    }

    /**
      * 初始化，打开文件，重置文件指针
      */
    private function init() {
        $this->linePointer = -1;
        $this->currentContent = NULL;
        if(is_null($this->fp)) {
            $this->fp = fopen($this->filepath, 'rb');
        }
        rewind($this->fp);
    }

    /**
      * Countable 接口中需实现的方法，用于 count($obj) 返回结果
      */
    public function count() {
        if(is_null($this->count)) {
            $this->count = intval(shell_exec("wc -l '{$this->filepath}'"));
        }
        return $this->count;
    }        

    public function current() {
        return $this->currentContent;
    }

    public function key() {
        return $this->linePointer;
    }

    public function next() {
        ++$this->linePointer;
        if($this->linePointer < $this->count()) {
            $this->currentContent = new AccessRecord(fgets($this->fp));
        }
    }

    /**
      * foreach 开始时调用 
      */
    public function rewind() {
        $this->init();
        $this->next();
    }

    public function valid() {
        return $this->linePointer < $this->count();
    }

}

class AccessLogIteratorAggregate implements IteratorAggregate {

    public function asdf() {

    }

    public function getIterator() {
        return new AccessLogIterator('./a.log');
    }


}

$t = new AccessLogIteratorAggregate();

$i=0;
foreach($t as $key => $record) {

    echo $key.': '.$record->getIp()."\t".$record->getUrl().'<br />';
}
```

a.log

```
123.151.47.117 - - [16/Feb/2014:00:00:02 +0800] "GET / HTTP/1.1" 200 32535 "http://www.musicworld.com/" "Mozilla/5.0 (compatible; Sosospider/2.0; +http://help.soso.com/webspider.htm)"
220.171.166.19 - - [16/Feb/2014:00:00:06 +0800] "GET /search/mp3?title=%E5%86%AC%E5%A4%A9%E9%87%8C%E7%9A%84%E4%B8%80%E6%8A%8A%E7%81%AB&search=%E6%90%9C%E7%B4%A2 HTTP/1.1" 200 10307 "http://www.musicworld.com/search/mp3?title=%E7%9C%9F%E5%BF%83%E8%8B%B1%E9%9B%84&search=%E6%90%9C%E7%B4%A2" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1"
220.171.166.19 - - [16/Feb/2014:00:00:08 +0800] "POST /pm/check HTTP/1.1" 200 11 "http://www.musicworld.com/search/mp3?title=%E5%86%AC%E5%A4%A9%E9%87%8C%E7%9A%84%E4%B8%80%E6%8A%8A%E7%81%AB&search=%E6%90%9C%E7%B4%A2" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1"
```

**思考：为什么不用 file() 函数读到数组里面再遍历？**

```php
$rows = file('/log.file');
foreach($rows as $row) {
    echo $row;
}
```

有可能会出现一下错误(内存不够用)：

```
PHP Fatal error:  Allowed memory size of 33554432 bytes exhausted (tried to allocate 262144 bytes) in  /bigstupid.php
```

**解决办法：**

用生成器！

## 生成器（Generator）

* 生成器提供了一种更简单的可实现 Iterator 同样功能的方法。
* 你可以通过生成器逐条**产生（yield）**&#x4F9B; foreach 遍历的数据，而且不需要事先在内存中建立整个数组。
* 生成器像一个函数，但是使用关键词 **yield** 返回数据。

> Generator 是 Traversable 的子类

示例：

```php
<?php

/**
 * yield 
 */

function lineGenerator($file) {
    $fp = fopen($file, 'rb');
    try {
        while($line = fgets($fp)) {
            yield $line;
        }

    } finally {
        fclose($fp);
    }
}

$lines = lineGenerator("a.log"); 
foreach($lines as $line) {
    echo $line;
}
```

进一步`key=>value`形式

```php
<?php

/**
 * 产生 key=>value
 */

function lineGenerator($file) {
    $fp = fopen($file, 'rb');
    try {
        while($line = fgets($fp)) {
            $lineParts = explode(' ', $line, 2);
            yield $lineParts[0] => $lineParts[1];
        }

    } finally {
        fclose($fp);
    }
}

foreach(lineGenerator("./a.log") as $ip=>$line) {

    echo $ip .' => '. $line;
}
```

> IteratorAggregate 与 yield 的结合示例
>
> getIterator 需要返回一个Traversable 对象, 生成器yield 就是一个Traversable的对象的子类
>
> ```php
> class Test implements IteratorAggregate {
>     protected $data;
>  
>     public function __construct(array $data) {
>         $this->data = $data;
>     }
>  
>     public function getIterator() {
>         foreach ($this->data as $key => $value) {
>             yield $key => $value;
>         }
>     }
> }
> ```

## 总结

* 实现了 Traversable 的对象都可以通过 foreach 实现遍历；
* 生成器函数实现 foreach 更简单，适用于简单的数据和逻辑；
* Iterator 实现 foreach 可定制性更高，适用于较复杂的逻辑；
* IteratorAggregate 可返回一个外部迭代器，可对数据本身及相关逻辑做更统一的封装。

## 资料

[PHP之预定义接口详解](http://www.jb51.net/article/70193.htm)


# 魔术方法(Magic Methods)

* \_\_construct()  构造方法,类实例化的时候执行
* \_\_destruct()  析构函数,某个对象所有引用都被删除或者当前对象显示销毁的时候执行
* \_\_call()  调用类中一个不存在的方法的时候执行
* \_\_callStatic()  调用不存在的静态方法时,执行
* \_\_get()  调用不在的数组时执行
* \_\_set()  设置对象中不存在的属性时执行
* \_\_isset()  判断一个对象属性是否存在时执行
* \_\_unset() 销毁一个对象属性时执行
* \_\_sleep()  序列化一个对象前执行
* \_\_wakeup() 反序列化一个对象前执行
* \_\_toString() 把对象当成字符串输出时执行,返回一个字符串
* \_\_invoke()  把对象以函数的形式调用的时候执行
* \_\_set\_state()   5.1.0时启用,调用var\_export函数前执行
* \_\_clone()  克隆一个的对象的时候调用

示例:

```php
class Test {

    function __call($function_name, $args)
    {
        echo "你所调用的方法：$function_name(参数：<br />";
        var_dump($args);
        echo ")不存在！";
    }

    function __callstatic($function_name, $args)
    {
        echo "你所调用的静态方法：$function_name(参数：<br />";
        var_dump($args);
        echo ")不存在！";
    }
}

$obj = new Test();
$obj->test();
$obj::test();
```

示例:

注意:经过测试,两个方法不能同时存在,只能有一个被运行(php7下测试运行)

```php
class Test {

    function __sleep()
    {
      echo '执行了serialize()函数';
    }


    function __wakeup()
    {
        echo '执行了unserialize()函数';
    }
}

$obj = new Test();
$serializeObj = serialize($obj);
$unserializeObj = unserialize($serializeObj);
```

示例:

```php
class Test {

    function __invoke()
    {
        echo '把对象当成函数调用了';
    }
}

$obj = new Test();
$obj();
```

示例:

```php
<?php

class Test {

    function __toString()
    {
        return '把对象当成字符串了';
    }
}

$obj = new Test();
echo $obj;
```

实例

```php
class Test{
    public $a;
    static function __set_state($array) {//必须是静态方法,参数是一个数组
        echo '调用了var_export函数';
        $tmp = new Test();
        $tmp->a = 'abc';//直接赋值
        return $tmp;//必须返回一个对象，可以是其他类的对象
    }

}
$test = new Test();
eval('$b = '.var_export($test,true).';');
var_dump($b);
```

## 资料

[php---魔术方法（\_\_tostring()，\_\_set\_state()）](https://my.oschina.net/zhangdapeng89/blog/40296)

[中年大叔的PHP学习之旅——魔术方法\_\_sleep和\_\_wakeup](http://blog.sina.com.cn/s/blog_758ddcb90100yk05.html)


# 回调函数、匿名函数&闭包

## 回调 (callbacks)

比如`usort(array &$array , callable $compareFunc)`

第一个参数为数组,第二个参数是回调方法

使用方法:

```php
usort($arr, 'mySortFunc');
//回调方法在对象中
usort($arr, array($objectName, 'mySortFunc'));
//回调方法在类的静态方法里
usort($arr, array('ClassName', 'mySortFunc'));
usort($arr, array('ClassName::mySortFunc'));
//通过子类调用父类中的方法(很少用)
usort($arr, array('Child', 'parent::mySort'));
```

匿名函数方式

```php
usort($arr, function($a, $b) {
return $b - $a;
```

## 匿名函数和闭包(Anonymous functions & Closures)

示例:

注意use关键是使用外部变量,相当于复制一份变量值,当掉后面改变变量值时,匿名函数内部的变量值不会发生改变

```php
$string = "Hello World!\n";

$closure = function() use ($string) { 
    echo $string; 
};

$string = "Hello China\n";

$closure();
```

结果:

```
Hello World!
```

示例

函数返回的闭包

```php
$string = "Hello China\n";

function getPrinter(&$string) {

    return function() use($string) {
        echo $string;
    };
}

$printer = getPrinter($string);

$string = "Hello Cat\n";

$printer();
```

结果:

```
Hello China
```

### 回调函数相关的函数：

```php
<?php

 function test($str) {
    echo $str;
}


echo is_callable('test');
call_user_func('test', 'param1');
call_user_func_array('test', ['param1']);
```

[is\_callable](http://php.net/manual/zh/function.is-callable.php)—检测参数是否为合法的可调用结构

[call\_user\_func](http://php.net/manual/zh/function.call-user-func.php)—把第一个参数作为回调函数调用

[call\_user\_func\_array](http://php.net/manual/zh/function.call-user-func-array.php)—调用回调函数，并把一个数组参数作为回调函数的参数


# 命名空间(Namespaces)

**命名空间与目录**

对于文件来说，目录就扮演了命名空间的角色

**支持**

PHP 5.3.0 开始

**影响**

类，函数和常量

**定义**

* 通过关键字namespace来声明。
* 所有代码之前（只允许declare()语句）
* 允许指定层次化的命名空间的名称
* 必须使用反斜杠 “ \”
* 也可以在同一个文件中定义多个命名空间 &#x20;


# 自动加载(Autoload)

```php
方式1:
__autoload($className);
方式2:
spl_autoload_register($func1);
spl_autoload_register($func2);
方式3:
spl_autoload($className[, $extensions]);
spl_autoload_extensions();
```

方式2示例:

则会直接去目录model\Test中自动引入

```php
<?php

use model\Test;

spl_autoload_register(function($className) {
    echo $className;  //model\Test
    include(__DIR__.'/'.str_replace('\\', '/', $className).'.php');
});

$test = new Test();
$test->query();
```

方式3:

```php
<?php

define('CLASS_DIR', 'class/');

set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);

// autoload ClassName.class.php 
spl_autoload_extensions('.class.php');

// Use default autoload implementation
spl_autoload_register();
```


# 反射(Reflection)

反射类

* [ReflectionClass](http://php.net/manual/zh/class.reflectionclass.php) 类报告了一个类的有关信息。
* ReflectionZendExtension
* ReflectionExtension&#x20;
* ReflectionFunction&#x20;
* ReflectionMethod
* ReflectionObject
* ReflectionParameter
* ReflectionProperty&#x20;
* ReflectionException&#x20;
* Reflector

命令行下的反射:

* php --rf strlen
* php --rc finfo
* php --re json


# 魔术常量(Magic constants)

* \_\_LINE\_\_ = 该常量所在的行号
* \_\_FILE\_\_ = 该常量所在代码文件的绝对路径
* \_\_DIR\_\_ = 该常量所在代码文件所在的目录的绝对路径，= dirname(\_\_FILE\_\_), PHP 5.3 新增
* \_\_FUNCTION\_\_ 所在函数的函数名
* \_\_CLASS\_\_ 类名，包括命名空间，如果用在 trait 中，该常量的值为 use 该 trait 的类的类名。
* \_\_TRAIT\_\_ = 所在的trait的名字（包括命名空间）
* \_\_METHOD\_\_ = 所在方法的名称，命名空间\类名::函数名
* \_\_NAMESPACE\_\_ = 所在命名空间的名字


# 综合实例

## 文件中写数据

简单写法,使用单例

```php
<?php

// singleton & late static 

class fileDb {
    private $_filePath;
    private $_fp;
    protected static $_instance;

    public static function getInstance($filePath) {
        if(!isset(self::$_instance)) {
            self::$_instance = new static($filePath);
        }
        return self::$_instance;
    }

    private function __construct($filePath) {
        $this->_filePath = $filePath;
        $this->_fp = fopen($this->_filePath, 'a+');
    }

    public function __destruct() {
        fclose($this->_fp);
    }

    public function insert(array $row) {
        fputcsv($this->_fp, $row);
    }
}

$fileDb = fileDb::getInstance('./test.txt');
var_dump($fileDb);
$fileDb->insert(array('revin', '509129@qq.com'));
```

## 引入代码复用(Trait)

将单例模式提出来

禁止反序列化,

禁止克隆

```php
<?php

// trait

trait Singleton {
    protected static $_instance;

    final public static function getInstance($config = array()) {
        if(!isset(self::$_instance)) {
            self::$_instance = new static($config);
        }
        return self::$_instance;
    }
    private function __construct($config) {
        $this->init($config);
    }
    protected function init() {}
    final private function __wakeup() {}
    final private function __clone() {}
}

class fileDb {
    use Singleton;
    private $_filePath;
    private $_fp;

    protected function init($config) {
        $this->_filePath = $config['filePath'];
        $this->_fp = fopen($this->_filePath, 'a+');
    }

    public function __destruct() {
        fclose($this->_fp);
    }

    public function insert(array $row) {
        fputcsv($this->_fp, $row);
    }
}

// step 1:

$fileDb = fileDb::getInstance(array('filePath' => './test.txt'));
$fileDb->insert(array('revin', '509129@qq.com'));
```

## 引入事件,闭包和回调函数

当除了插入操作还有其他附加操作,引入事件

```php
<?php

// callback & closure & Event

trait Singleton {
protected static $_instance;

    final public static function getInstance($config = array()) {
        if(!isset(self::$_instance)) {
            self::$_instance = new static($config);
        }
        return self::$_instance;
    }
    private function __construct($config) {
        $this->init($config);
    }
    protected function init() {}
    final private function __wakeup() {}
    final private function __clone() {}
}

abstract class Component {
    protected $_events = [];

    public final function on($eventName, $handler) {
        if(!isset($this->_events[$eventName])) {
            $this->_events[$eventName] = [];
        }
        if(is_callable($handler)) {
            $this->_events[$eventName][] = $handler;
        }
    }

    public final function trigger($eventName, $data) {
        if(isset($this->_events[$eventName])) {
            foreach($this->_events[$eventName] as $handler) {
                call_user_func($handler, $data);
            }
        }
    }

    public function hasEventHandlers($eventName) {
        return !empty($this->_events[$eventName]);
    }
}

class FileDb extends Component {
    use Singleton;
    private $_filePath;
    private $_fp;

    protected function init($config) {
        $this->_filePath = $config['filePath'];
        $this->_fp = fopen($this->_filePath, 'a+');
    }

    public function __destruct() {
        fclose($this->_fp);
    }

    public function insert(array $row) {
        fputcsv($this->_fp, $row);

        if($this->hasEventHandlers('afterInsert')) {
            $this->trigger('afterInsert', $row);
        }
    }
}


$fileDb = fileDb::getInstance(array('filePath' => './test.txt'));

$fileDb->on('afterInsert', function($row) {
    echo "Inserted (From ".__FUNCTION__.")\n";
});
$fileDb->on('afterInsert', function($row) {
    echo "Inserted2 (From ".__FUNCTION__.")\n";
});

$fileDb->insert(array('revin', '509129@qq.com'));
```

## 引入行为机制(装饰模式)

引入行为模式:动态的向一些类加入一些比不存在的功能, 比如DbBehavior,并不存在于fileDb类中,但是我们又可以使用fileDb实例调用DbBehavior中的方法

```php
<?php

// behavior & decorator 

trait Singleton {
    protected static $_instance;

    final public static function getInstance($config = array()) {
        if(!isset(self::$_instance)) {
            self::$_instance = new static($config);
        }
        return self::$_instance;
    }

    private function __construct($config) {
        $this->init($config);
    }

    protected function init($config) {}
    final private function __wakeup() {}
    final private function __clone() {}
}

class FileDb extends Component {
    use Singleton;

    private $_filePath;
    private $_fp;

    protected function init($config) {
        $this->_filePath = $config['filePath'];
        $this->_fp = fopen($this->_filePath, 'a+');
    }

    public function __destruct() {
        fclose($this->_fp);
    }

    public function getFilePointer() {
        return $this->_fp;
    }

    public function insert(array $row) {
        fputcsv($this->_fp, $row);

        if($this->hasEventHandlers('afterInsert')) {
            $this->trigger('afterInsert', $row);
        }
    }
}

abstract class Component {
    protected $_events = [];
    protected $_behaviors = [];

    public final function on($eventName, $handler) {
        if(!isset($this->_events[$eventName])) {
            $this->_events[$eventName] = [];
        }

        if(is_callable($handler)) {
            $this->_events[$eventName][] = $handler;
        }
    }

    public final function trigger($eventName, $data) {
        if(isset($this->_events[$eventName])) {
            foreach($this->_events[$eventName] as $handler) {
                call_user_func($handler, $data);
            }
        }
    }

    public function hasEventHandlers($eventName) {
        return !empty($this->_events[$eventName]);
    }

    public function attachBehavior($name, $behavior) {
        if($behavior instanceof Behavior) {
            $this->_behaviors[$name] = $behavior;
            $behavior->attach($this);
        }
    }

    public function __call($name, $params) {
        foreach($this->_behaviors as $behavior) {
            if(method_exists($behavior, $name)) {
                $method = new ReflectionMethod($behavior, $name);
                if($method->isPublic()) {
                    return call_user_func_array([$behavior, $name], $params);
                }
            }
        }
        throw new Exception('Unknown method: '.get_class($this)."::$name()");
    }
}

abstract class Behavior {
    public $owner;

    public function attach($to) {
        $this->owner = $to;
    }
}

class DbBehavior extends Behavior {

    public function getFirstRecord() {
        $fp = $this->owner->getFilePointer();
        $pos = ftell($fp);
        rewind($fp);
        $firstLine = fgets($fp);
        fseek($fp, $pos);
        return $firstLine;
    }
}

$fileDb = fileDb::getInstance(array('filePath' => './test.txt'));
$fileDb->on('afterInsert', function($row) {
    echo "Inserted (From ".__FUNCTION__.")\n";
});

$fileDb->insert(array('revin', '509129@qq.com'));
$fileDb->attachBehavior('DbBehavior', new DbBehavior());
echo "The First Line is: ".$fileDb->getFirstRecord();
```

## 进一步优化,将行为机制和事件机制结合

自动注册绑定事件回调函数

```php
<?php

// behavior & decorator 

trait Singleton {
    protected static $_instance;

    final public static function getInstance($config = array()) {
        if(!isset(self::$_instance)) {
            self::$_instance = new static($config);
        }
        return self::$_instance;
    }

    private function __construct($config) {
        $this->init($config);
    }

    protected function init($config) {}
    final private function __wakeup() {}
    final private function __clone() {}
}

class FileDb extends Component {
    use Singleton;

    private $_filePath;
    private $_fp;

    protected function init($config) {
        $this->_filePath = $config['filePath'];
        $this->_fp = fopen($this->_filePath, 'a+');
    }

    public function __destruct() {
        fclose($this->_fp);
    }

    public function getFilePointer() {
        return $this->_fp;
    }

    public function insert(array $row) {
        fputcsv($this->_fp, $row);

        if($this->hasEventHandlers('afterInsert')) {
            $this->trigger('afterInsert', $row);
        }
    }
}

abstract class Component {
    protected $_events = [];
    protected $_behaviors = [];

    public final function on($eventName, $handler) {
        if(!isset($this->_events[$eventName])) {
            $this->_events[$eventName] = [];
        }

        if(is_callable($handler)) {
            $this->_events[$eventName][] = $handler;
        }
    }

    public final function trigger($eventName, $data) {
        if(isset($this->_events[$eventName])) {
            foreach($this->_events[$eventName] as $handler) {
                call_user_func($handler, $data);
            }
        }
    }

    public function hasEventHandlers($eventName) {
        return !empty($this->_events[$eventName]);
    }

    public function attachBehavior($name, $behavior) {
        if($behavior instanceof Behavior) {
            $this->_behaviors[$name] = $behavior;
            $behavior->attach($this);
        }
    }

    public function __call($name, $params) {
        foreach($this->_behaviors as $behavior) {
            if(method_exists($behavior, $name)) {
                $method = new ReflectionMethod($behavior, $name);
                if($method->isPublic()) {
                    return call_user_func_array([$behavior, $name], $params);
                }
            }
        }
        throw new Exception('Unknown method: '.get_class($this)."::$name()");
    }
}

abstract class Behavior {
    public $owner;

    public function attach($to) {
        $this->owner = $to;

        $methods = get_class_methods($this); 
        foreach($methods as $methodName) {
            if(strpos($methodName, 'on') === 0 && strlen($methodName) > 2) {
                $this->owner->on(lcfirst(substr($methodName, 2)), [$this, $methodName]);
            }
        }
    }
}

class DbBehavior extends Behavior {

    public function getFirstRecord() {
        $fp = $this->owner->getFilePointer();
        $pos = ftell($fp);
        rewind($fp);
        $firstLine = fgets($fp);
        fseek($fp, $pos);
        return $firstLine;
    }

    public function onAfterInsert($row) {
        echo "Inserted (From ".__CLASS__.")\n";
    }
}

$fileDb = fileDb::getInstance(array('filePath' => './test.txt'));
$fileDb->attachBehavior('DbBehavior', new DbBehavior());
$fileDb->insert(array('revin', '509129@qq.com'));
echo "The First Line is: " . $fileDb->getFirstRecord();
```

## 进一步引入命名空间,自动加载

文件结构

```
base目录
    ---Behavior.php
    ---Component.php
    ---Singleton.php
DbBehavior.php
FileDb.php    
index.php
```

base/Behavior.php

```php
<?php
namespace app\base;

abstract class Behavior {
    public $owner;

    public function attach($to) {
        $this->owner = $to;

        $methods = get_class_methods($this);
        foreach($methods as $methodName) {
            if(strpos($methodName, 'on') === 0 && strlen($methodName) > 2) {
                $this->owner->on(lcfirst(substr($methodName, 2)), [$this, $methodName]);
            }
        }
    }
}
```

base/Component.php

```php
<?php
namespace app\base;

include_once(APP_ROOT.DIRECTORY_SEPARATOR.'base'.DIRECTORY_SEPARATOR.'Behavior.php');

use app\base\Behavior;
use \ReflectionMethod;
abstract class Component {
    protected $_events = [];
     protected $_behaviors = [];

      public final function on($eventName, $handler) {
          if(!isset($this->_events[$eventName])) {
              $this->_events[$eventName] = [];
           } 
           if(is_callable($handler)) {
               $this->_events[$eventName][] = $handler; 
           } 
      } 

      public final function trigger($eventName, $data) {
          if(isset($this->_events[$eventName])) {
              foreach($this->_events[$eventName] as $handler) {
                  call_user_func($handler, $data); 
              } 
          } 
      } 

      public function hasEventHandlers($eventName) {
          return !empty($this->_events[$eventName]); 
      } 

      public function attachBehavior($name, $behavior) {
          if($behavior instanceof Behavior) {
              $this->_behaviors[$name] = $behavior; $behavior->attach($this); 
          } 
      } 

      public function __call($name, $params) {
          foreach($this->_behaviors as $behavior) {
              if(method_exists($behavior, $name)) {
                  $method = new ReflectionMethod($behavior, $name);
                   if($method->isPublic()) {
                       return call_user_func_array([$behavior, $name], $params); 
                   } 
              } 
          } 
          throw new Exception('Unknown method: '.get_class($this)."::$name()"); 
      }
}
```

base/Singleton.php.php

```php
<?php
namespace app\base;

trait Singleton {
    protected static $_instance;

    final public static function getInstance($config) {
        if(!isset(self::$_instance)) {
            self::$_instance = new static($config);
        }

        return self::$_instance;
    }

    private function __construct($config) {
        $this->init($config);
    }

    protected function init() {}

    final private function __wakeup() {}
    final private function __clone() {}
}
```

DbBehavior.php

```php
<?php
namespace app;

include_once(APP_ROOT.DIRECTORY_SEPARATOR.'base'.DIRECTORY_SEPARATOR.'Behavior.php');

use app\base\Behavior;

class DbBehavior extends Behavior {

    public function getFirstRecord() {
        $fp = $this->owner->getFilePointer();
        $pos = ftell($fp);
        rewind($fp);
        $firstLine = fgets($fp);
        fseek($fp, $pos);
        return $firstLine;
    }
    public function onAfterInsert($row) {
        echo "Inserted (From ".__CLASS__.")\n";
    }
}
```

FileDb.php

```php
<?php
namespace app;

include_once(APP_ROOT.DIRECTORY_SEPARATOR.'base'.DIRECTORY_SEPARATOR.'Component.php');
include_once(APP_ROOT.DIRECTORY_SEPARATOR.'base'.DIRECTORY_SEPARATOR.'Singleton.php');

use app\base\Component;
use app\base\Singleton;

class FileDb extends Component {
    use Singleton;

    private $_filePath;
    private $_fp;

    protected function init($config) {
        $this->_filePath = $config['filePath'];
        $this->_fp = fopen($this->_filePath, 'a+');
    }

    public function __destruct() {
        fclose($this->_fp);
    }

    public function getFilePointer() {
        return $this->_fp;
    }

    public function insert(array $row) {
        fputcsv($this->_fp, $row);

        if($this->hasEventHandlers('afterInsert')) {
            $this->trigger('afterInsert', $row);
        }
    }
}
```

index.php

```php
<?php
namespace app;

error_reporting(E_ALL);
ini_set('display_errors', true);

define('APP_ROOT', __DIR__);

include(APP_ROOT.'/FileDb.php');
include(APP_ROOT.'/DbBehavior.php');

$fileDb = fileDb::getInstance(array('filePath' => APP_ROOT .DIRECTORY_SEPARATOR. 'test.txt'));
$fileDb->on('afterInsert', function($row) {
    echo "Inserted (From ".__FUNCTION__.")\n";
});

$fileDb->attachBehavior('DbBehavior', new DbBehavior());
$fileDb->insert(array('revin', '509129@qqcom'));

echo "The First Line is: ".$fileDb->getFirstRecord();
```

## 进一步优化,使用自动加载机制

使用了自动加载机制,其它文件中的include删掉即可.

```php
<?php
namespace app;

define('APP_ROOT', __DIR__);

function autoload($className) {
    if (strpos($className, 'app\\') === 0) {
        $className = str_replace('app\\', '', $className);
    }

    $classFile = APP_ROOT.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $className).'.php';
    include ($classFile);
}

spl_autoload_register('app\autoload'); //注册

try {
    $fileDb = fileDb::getInstance(array('filePath' => APP_ROOT .DIRECTORY_SEPARATOR. 'test.txt'));
} catch (Exception $e) {
    echo $e->getMessage();
}
$fileDb->on('afterInsert', function($row) {
    echo "Inserted (From ".__FUNCTION__.")\n";
});

$fileDb->attachBehavior('DbBehavior', new DbBehavior());
$fileDb->insert(array('revin', '509129@qqcom'));

echo "The First Line is: ".$fileDb->getFirstRecord();
```


# 附录:关键词

精通PHP

熟悉Linux 和 Shell

熟悉JavaScript

数据结构和算法设计

熟练MySQL和NoSQL技术

MVC模式和YII等框架

大规模网站架构技术


# 附录:资料

[CRC32算法冲突概率测试和分析](http://blog.csdn.net/yunhua_lee/article/details/42775039)

```
sort crc32.result | uniq -c -d > crc32-collision.txt 来输出冲突的结果
```


# 代码的版本控制


# SVN


# Git


# 疑难杂症

**问题1:**&#x20;

出现`error: The following untracked working tree files would be overwritten by checkout`

**解决办法:**

```
 git clean -d -fx ""
```


# Linux


# Linux原理与基础

## Linux 的启动过程

1. BIOS 检查硬件，读取主启动磁盘MBR区的系统装载程序（Boot Loader，CentOS中一般为GRUB）并执行，显示给用户一个选择界面x
2. 用户选择要启动的系统，即选定了相应的内核（vmlinuz）, Boot Loader 通过 initrd 在内存中建立一个供内核使用的临时文件系统，然后由内核程序接管后续启动流程。
3. 内核开始初始化系统中的各部分硬件。此时操作系统的基本环境已建立，但还没有用户程序可供人使用，于是内核继续执行，启动Linux世界中的造物主 —— /sbin/init&#x20;
4. /sbin/init 进程启动后，自然成为 Linux 中所有进程的父/爷进程。它会首先调用 /etc/rc.d/rc.sysinit 脚本，完成设置环境变量，交换分区，初始化系统时钟等工作。然后调用/etc/inittab，执行相应运行级别下的程序脚本（/etc/rc.d/rc\<x>.d/\* ）启动或杀掉相应进程。
5. 各启动脚本执行完毕后，init 按/etc/inittab 中的配置启动相应的控制台交互界面，提示用户登录（字符界面下默认有6个虚拟控制台，Alt+Fx 切换）

**设置程序自动启动的三种方法：**

1. `/etc/rc.d/rc.local` 中写启动命令；
2. `/etc/rc.d/init.d/*`  写shell脚本，然后在 /etc/rc\*.d 中建立软连接。
3. 使用 chkconfig 工具， 如 `chkconfig --level 2345 auditd on`

## Linux 中的文件及权限

```
-rwxr-xr-x 1 cat animal    68 03-31 21:47 sleep.sh
```

**三种用户角色：**

* user ，文件的所有者
* group，所属的用户组
* others，其他人

**每个文件或目录有三种基本权限类型：**

| 权限      | 数字 | 文件含义    | 目录含义                   |
| ------- | -- | ------- | ---------------------- |
| read    | 4  | 可读取文件内容 | 可列出目录内容                |
| write   | 2  | 可修改文件内容 | 可修改目录内容，包括删除、移动目录中的文件等 |
| execute | 1  | 可作为程序执行 | 可切换到目录中，如 cd x         |

示例:

```
-rwxr-xr-x 1 root root  68 03-31 21:47 test.php
```

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHxXTS8xFbaDdJom%2FLinux%E4%B8%AD%E7%9A%84%E6%96%87%E4%BB%B6%E5%8F%8A%E6%9D%83%E9%99%90.png?generation=1558862988359838\&alt=media)**为什么普通用户可以执行passwd，修改/etc/passwd?**

```
-rwsr-xr-x 1 root root 47032  7月 16  2015 /usr/bin/passwd*
```

s位

* setuid(4)：该位是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。
* setgid(2): 该权限只对目录有效. 目录被设置该位后, 任何用户在此目录下创建的文件都具有和该目录所属的组相同的组.
* sticky bit(1): 该位可以理解为防删除位 。如果希望用户能够添加文件但同时不能删除文件, 则可以对文件使用sticky bit位. 设置该位后, 就算用户对目录具有写权限, 也不能删除该文件

[linux权限补充：rwt rwT rws rwS 特殊权限 ](http://blog.sina.com.cn/s/blog_6e8f9abf0100saql.html)

**想让普通用户只能追加写文件，无法删除文件，如何做?**

```
chattr +a /var/log/access_log
```

**想让用户只能读不能改PHP的配置文件，如何做？**

```
chattr +i /etc/php.ini
```

**Ext2,Ext3,Ext4文件系统对比**

|               | Ext2  | Ext3  | Ext4  |
| ------------- | ----- | ----- | ----- |
| 日志系统          | 无     | 有     | 增强    |
| 最大文件          | 2T    | 2T    | 16T   |
| 最大文件系统        | 32T   | 32T   | 1E    |
| 文件夹内文件数       | 32000 | 32000 | 64000 |
| 切换成本(tune2fs) | 底     | 低     | 低     |

查看命令`df -T`

## raid

磁盘阵列中针对不同的应用使用的不同技术，称为RAID 等级

[RAID详解\[RAID0/RAID1/RAID10/RAID5\]](http://blog.chinaunix.net/uid-639516-id-2692517.html)


# 常见命令

## Linux 中的帮助文档

* **man**
* **info**:GNU 项目推荐的新一代文档格式，功能更强
  * m 显示菜单
  * f 跟随交叉引用，l 返回
* **--help或 -h** ：查看大部分程序自带的帮助文档

## 磁盘与文件统计

* df 查看磁盘空间占用
* du 统计文件占用空间
* fdisk 分区管理
* mkfs.ext3 磁盘格式
* mount 磁盘挂载
* umount 磁盘卸载

## 查找文件

### **find ：功能最丰富的文件查找命令**

**基本用法示例：**

```
find / -name test -print
```

查找 / 目录下 名字为 test 的文件，然后打印出文件名

**常用格式：**

```
find [路径] [选项] [查找规则] [动作]
```

* \[路径]可以有多个，以空格分隔
* \[选项]常用的有：
  * -depth 先找目录内容，再找目录自身，即先深入查找.
  * -follow 跟随符号链接，将软连接解为实际文件.
  * -maxdepth N 设置目录查找深度
  * -mount (或 -xdev) 不查找其他挂载的文件系统，如NFS等
* 基于文件名的\[查找规则]：
  * 例1： `find . -name “*.php”`找本目录下所有后缀名为 .php  的文件
  * 例2： `find . -iname “x[abc]”`查找本目录下所有文件名为 xa、xb或xc 的文件，且不区分大小写。
  * 例3： `find . -regex ".*b.*3"` 查找本目录下文件完整路径及名称匹配该正则的。
* 基于时间的\[查找规则]：
  * 例1： `find . -mmin -60` 查找本目录下修改时间为最近60分钟内的
  * 例2： f`ind . -mmin +5` 查找本目录下修改时间为5分钟之前的
  * 例3： `find /tmp -atime -1` 查找/tmp目录下最近1天（24小时）内被访问过的文件
  * > 总结:
    >
    > find . -atime N
    >
    > 其中 -atime 的位置也可以使用如下参数：\
    > -atime, -mtime, -ctime, -amin, -mmin, -cmin N
    >
    > time: 天(24小时)，min: minute,分钟
    >
    > +N N时间前，-N N时间内的，n 等于N的
* 基于文件类型的\[查找规则]：
  * 例1： `find . -type f`查找本目录下所有的“文件”
  * 例2： `find . -type d`查找本目录下所有的“目录”
* 其他\[查找规则]：
  * 例1： `find . -user root`查找本目录下所有root用户的文件
  * 例2： `find . -perm 600` 查找本目录下所有权限为600的文件
* \[查找规则]的组合：
  * `find . \( -name “_*“ -or -user root \) -type f`查找本目录下（以\_开头或root用户的）“文件”
  * > 逻辑运算符：\
    > -not\
    > -or\
    > -and
* 常用\[动作]：
  * -print 打印出找到的文件，以\n结束
  * -print0 打印出找到的文件，无换行
  * -exec command 对找到的文件执行命令
  * -ok 同 -exec， 但在执行前会询问用户
  * -ls 列出文件详情
  * > -exec 示例：
    >
    > ```
    > find ./ -name "*.tmp" -exec unlink {} \;
    > ```
    >
    > {} ：指代找到的文件
    >
    > \\; ：表示-exec指定的命令结束，必须。
    >
    > 安全起见也可以用 -ok

**其他查找文件命令：**

* which ：查找环境变量PATH下的可执行文件及别名（alias）
* whereis：查找系统常见目录中的可执行文件、文档、源代码、配置文件等
* locate：通过索引数据库查找文件

## Linux 中的文本工具

* cat —查看文件内容
* head —查看文件头部
* tail —查看文件尾部
* more—分页查看文件内容
* less—更强的分页查看文件内容
* cut—按列查看文件内容，默认以 TAB 分隔
* sort—对文件内容排序
* uniq—对文件内容排重, -c 可统计连续重复行数量,资料:[实例解说Linux命令行uniq](http://os.51cto.com/art/201107/274991.htm)
* diff—对比文件内容
* grep—查找文件内容
* split—按行、字节等切分并输出文件
* wc—字节、单词、行计算

### **cut ：选择每一行中特定的列（字段），然后输出**

**示例:**

查看系统中所有用户

```
cut -d: -f1 /etc/passwd
```

* -d 指定分隔符
* -f 指定输出的字段

### **sort ：对文件中的行排序，默认以ASCII顺序排列**

**示例:**

按passwd文件中的第三列数字排序，以“:”分隔每一列

```
sort -n -k3 -t: /etc/passwd
```

* -n 按数字而不是ascii排序
* -t 指定列的分隔符
* -k 指定按第几列排序
* -r 倒序排列

**示例:**

按第6列倒序排列，第3列正序排列

```
sort -t: -k6nr -k3n  /etc/passwd
```

### **grep ：输出文本中匹配所指定正则表达式的行**

**命令格式：**

```
grep [选项] 正则表达式 [文件]
```

**常用\[选项]：**

* -c 输出匹配到的行数
* -E 启用更强大的正则表达式
* -H 输出匹配到的行及所属的文件名
* -i 不区分大小写匹配
* -l 在文件中找到第一个匹配后，显示文件名，停止后续查找
* -v 输出不匹配的行
* \--color 高亮显示匹配到的内容

**例：**

```
grep -cP 'bot|spider' ./access.log
```

显示 access.log 文件中出现 bot 或 spider 关键词的行数

### **split—按行、字节等切分并输出文件**

**命令格式：**

```
split [选项] [文件] [文件名前缀]
```

**常用\[选项]：**

* -b：值为每一输出档案的大小，单位为 byte。
* -C：每一输出档中，单行的最大 byte 数。&#x20;
* -d：使用数字作为后缀。&#x20;
* -l：值为每一输出档的行数大小。
* -a：后缀长度

## 通过管道组合使用工具

> 管道
>
> Linux 中有三个系统文件描述符： 0：标准输入； 1：标准输出； 2：标准错误输出。
>
> 标准输出、标准错误输出通常默认为当前屏幕。
>
> 管道是Linux进程间通信的方式之一
>
> 管道像一根供数据流通的单向管道
>
> 所有Linux程序的标准输出都可以通过管道传递给另一个程序作为标准输入
>
> ![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHl_b3eozK6EIpWs%2FLinux%E7%AE%A1%E9%81%93.png?generation=1558862973370390\&alt=media)

需求：分析如下网站访问日志文件，找出访问量最大的前10个IP地址，降序排列

```
123.151.47.117 - - [16/Feb/2014:00:00:02 +0800] "GET / HTTP/1.1"
220.171.166.19 - - [16/Feb/2014:00:00:06 +0800] "GET /search/mp3
```

**找出IP，每行一个**

```
cut -d " " -f1 ./access.log
```

去重并计数

```
uniq -c
```

排序(-n 按数字而不是ascii排序, -r 倒序排列)

```
sort -n -r
```

取前10行

```
head -10
```

最终命令

```
$ cut -d " " -f1 ./access.log | sort | uniq -c | sort -n -r | head -10
```

上面的结果如何保存下来?

## 输入/输出重定向

> Linux 中有三个系统文件描述符： 0：标准输入； 1：标准输出； 2：标准错误输出。

`>`符号可重定向输出位置。

`ls -l > ls_output.log`将标准输出重定向到 `ls_output.log`文件

`ls -l >> ls_append.log`将标准输出重定向\*追加\*到 `ls_output.log`文件

```
ls -l 1> ls_output.log 2> ls_error.log
```

将 标准输出 重定向到 ls\_output.log 文件，标准错误输出重定向到 ls\_error.log 文件

```
ls -l 1> ls_out.log 2>&1
```

将标准输出重定向到 ls\_out.log 文件，然后将标准错误输出重定向到和标准输出同样的地方。

`more < xxx.txt`输入重定向

上面操作的最终命令进行报错结果

```
$ cut -d " " -f1 ./access.log | sort | uniq -c | sort -n -r | head -10 > ip.log
```

> 将结果丢弃,也不输出
>
> $ xxxxxxx > /dev/null

## 系统管理

### **ps 命令：显示当前进程**

* ps -ef  显示带启动命令行的进程信息
* ps -u root 显示某用户的进程
* ps -A 显示全部进程
* ps -ejH 显示进程树

### **kill 命令：向进程发送信号，常用于终止某进程**

* `kill -s PID` : -s 指定信号
* `kill -9 PID` 可用于强制某进程退出
* `killall`可用于通过进程名称发送信号

### **fg 命令： 将后台任务调到前台**

命令后加 & ，或Ctrl + Z 可将任务转到后台执行

后台执行中的任务，可以通过 “fg %任务序号”调到前台

### **crontab：让命令自动定时运行**

crontab: 定时执行任务

**基本格式 :**

```
*　　*　　*　　*　　*　　command
分　 时　 日　 月　 周
```

分（0-59） 时（0-23） 日（1-31） 月（1-12） 周（0-6）//0代表周日

每个 \* 的位置除数字取值外，还可以有如下格式：

* \* 代表所有的取值范围内的数字；
* / 代表“每”的意思,如 "\*/5"表示每5个单位；
* * 代表范围，从某个数字到某个数字；
* , 用于指定多个值。

**常用命令：**

```
service crond start 启动 crond 服务

crontab -l #列出cron服务的详细内容
crontab -u xxx -e #设定xxx用户的cron服务
crontab -u xxx -r #删除xxx用户的所有cron服务
```

**注意事项**

1. crontab 中的各任务尽量使用绝对路径&#x20;
2. crontab 执行是以实际时间计算的，而不是以启动时间计算，因此如果系统有大量crontab任务，最好不要集中定在同一个时间启动。

**示例：**

每天早上6时0分

```
0 6 * * * echo "Good morning." >> /tmp/test.txt
```

每两个小时

```
0 */2 * * * echo "Have a break now." >> /tmp/test.txt
```

晚上11点到早上7点之间每两个小时，早上八点

```
0 23-7/2,8 * * * echo "Good dream：）" >> /tmp/test.txt
```

### **netstat： 查看网络状态**

* netstat -anpl 查看所有网络连接状态，带进程名称

### **Ifconfig ： 查看网卡配置、ip地址信息**

### **iptables: Linux 防火墙**

* `service iptables start`#启动服务
* `iptables -A INPUT -p tcp -s 1.2.3.4 --dport 80 -j DROP`#所有来自 1.2.3.4 的80端口访问全部屏蔽
* `service iptables save`#保存规则，否则重启失效
* `iptables -L -n --line-numbers` #列出当前配置及规则号
* `iptables -D  INPUT 4` 删除INPUT类第4号规则
* `iptables -F`清除所有规则

### **top 命令**

常用操作：

* P：按CPU使用量排列，
* M：按内存使用量排列，
* h：显示帮助 ，
* q：退出
* 1： 多 CPU 显示，
* k：杀死进程，
* r：设置进程Nice级别

```
load average: 0.00, 0.03, 0.05
```

平均负载：最近1分钟，最近5分钟，最近15分钟

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHlgrbj-64Ab-ZDx%2Flinux_top_load_average.png?generation=1558862978442814\&alt=media)

```
%Cpu0  :  0.3 us,  1.0 sy,  0.0 ni, 98.3 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
```

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTHlj69EVzhgIschl%2Flinux_top_cpu.png?generation=1558862978874236\&alt=media)

## 查看系统运行状态

```
$ uname -a                   # 查看内核/操作系统/CPU信息
$ cat /etc/issue             # 查看操作系统版本
$ cat /proc/version          # 查看内核
$ cat /proc/cpuinfo          # 查看CPU信息
$ cat /proc/meminfo          # 查看内存信息
$ cat /proc/loadavg       #查看负载信息
$ free -m                    # 查看内存使用量和交换区使用量
$ hostname                   # 查看计算机名
```

## 软件安装

* yum 安装(search install update remove)
* rpm安装(-i –e -qa)
* Apt安装(search install update  remove)
* 源码编译安装

## 压缩与解压缩

### zip压缩与解压

**zip压缩**

```
zip -r test.zip ./*
```

* -d 删除压缩包内文件
* -m删除源文件

**unzip解压缩**

```
unzip test.zip
```

### rar压缩与解压

**rar压缩**

```
rar a test.rar ./*
```

**rar解压缩**

```
unrar e test.rar
```

### tar压缩与解压

**tar压缩**

```
tar –cvf a.tar a 
tar –czf a.tar.gz a 
tar –cjf a.tar.bz2 a 
tar –cf a.tar a 
tar -cvzf  a.tar.gz a --remove-files
```

**tar解压缩**

```
tar -xvf file.tar
tar -xzvf file.tar.gz
tar -xjvf file.tar.bz2
tar -xZvf file.tar.Z
```

其他命令：

gzip

bzip2

Uncompress


# Shell 编程

## Shell 语法

* 变量：字符串、数字、环境变量、参数
* 条件判断: shell 中的布尔值
* 程序控制：if\elif\for\while...
* 命令列表
* 函数
* 内置命令
* 获取命令的执行结果

### 变量

```
foo=bar
```

* 区分大小写
* 等号两边无空格
* 所有变量都是字符串

```
foo=bar
echo $foo

foo="another bar"
echo $foo

bar=7+3
echo $bar
```

**环境变量**

与系统相关的一些全局变量，常用的有：

```
$HOME    当前用户的家目录
$PATH     搜索命令列表，以冒号分隔
$0    shell 脚本文件名
$#    传递给脚本的参数个数
$$     shell 脚本进程号（PID），可用于生成唯一的临时文件
$PS1     命令提示符
$PS2    二级命令提示符，一般为 >
$IFS     输入域分隔符，通常为空格、制表符、换行符
```

**参数变量**

与参数相关的变量：

```
$1，$2，... $n    第n个参数

$* 以IFS分隔的所有参数

$@ 以空格分隔的所有参数

$#    参数个数
```

### 条件判断: shell 中的布尔值

**test 、 \[ 命令，用于执行常见判断**

例：检查一个文件是否存在

```
if test -f foo.php
then
    ...
fi

if [ -f foo.php ]
then
    ...
fi
```

**test 字符串比较**

* string1 = string2&#x20;
* string1 != string2
* -n string 字符串不为空则为真
* -z string 字符串为 null（空） 则为真

**test 算术比较**

* expression1 -eq expression2 两式相等则为真
* -ne 不等
* -gt 大于
* -ge 大约等于
* -lt 小于
* -le 小于等于
* ! expression 非&#x20;

**test 文件测试**

* -d file 如果文件是一个目录则为真
* -f 文件为普通文件则为真
* -e 文件存在则为真
* -s 文件大小不为0则为真
* -r 文件可读则为真
* -w 文件可写则为真&#x20;
* -x 文件可执行则为真

示例:

写一个判断 /etc/hosts 文件是否可写的程序，如果可写，打印“yes”，否则打印 “no”

```
#!/bin/sh
if [ -w /etc/hosts ]
then
    echo "yes"
else
    echo "no"
fi
exit 0
```

创建一个变量，读取用户输入，如果用户输入yes，则显示“yes”，输入no 则显示 “no”，否则显示“Sorry”

```
#!/bin/sh
echo -n "your answer: "
read yourvar
if [ "$yourvar" = "yes" ]
then
    echo "yes"
elif [ "$yourvar" = "no" ]
then
    echo "no"
else
    echo "Sorry"
fi
exit 0
```

### for循环

基本格式:

```
for variable in values
do
    ...
done
```

例：

```
for foo in bar fud 555
do
    echo $foo
done
exit 0
```

例：列出 f开头，.sh 结尾的文件

```
for file in $(ls f*.sh)
do
    echo $file
done
exit 0
```

**$(command) 用于执行command命令并获取其输出结果**

其他示例

```
#for i in $(ls)
#for i in `ls`
#for i in {d..z}
for ((i=0;i<5;i++))
do
    echo $i
done
```

计算累加

```
count=4
for((i=1;i<=$count;i++))
do
    total=$((total+i))
done
echo "total=$total"
```

### while 循环

基本格式

```
while condition 
do
    ...
done
```

例：

```
read trythis
while [ "$trythis" != "secret" ]
do
    echo "Sorry, try again: "
    read trythis
done
exit 0
```

### until 循环——反复执行直到条件变为真

基本格式

```
until condition 
do
    ...
done
```

例：

```
until who | grep "$1" > /dev/null
do
    sleep 60
done
echo -e '\a'
exit 0
```

### case 语句

基本格式:

```
case variable in 
    pattern [ | pattern ] ...) statements ;;
    pattern [ | pattern ] ...) statements ;;
    ...
esac
```

### 命令列表

AND 列表，左边statement执行成功才执行右边，全部执行成功才算成功

```
statement1 && statement2 && statement3 && ...
```

OR 列表，从左到右执行一系列命令，直到某条执行成功就算成功，后面的不再被执行

```
statement1 || statement2 || statement3 || ...
```

### 语句块

在某些只允许使用单个语句的地方（比如AND、OR列表中），要使用多条语句，则可以使用{}构造一个语句块

```
get_confirm && {
    ls > $tmp_file
    cat $tmp_file > $tracks_file
}
```

### 函数

基本格式:

```
function_name () {
    statements
}
```

function\_name 必须在调用函数前对它定义。

1. 数调用时，脚本的位置参数（$\* $@ $# $1 $2 $...） 会被替换为函数的参数，函数执行完毕后，会恢复原值
2. 函数中的变量默认为全局作用域，除非使用 local 关键字定义
3. 通过 return 命令可以让函数返回数字值,常用于表示函数执行是否成功。如需返回字符串值，则需在函数中使用 echo，然后再函数外使用 $() 捕获；或者将字符串保存在一个变量中，函数执行完毕后读取该变量。
4. 如果函数没有使用 return 指定返回值，则函数返回值为最后一条命令的退出码

**捕获函数输出：**

```
foo() { echo Hi; }

result="$(foo)"
```

定义局部变量：

```
test="gloabal var"
foo() {
    local text="local var"
    echo "function foo is executing"
    echo $text
}
echo "script starting"
echo $text
foo
echo "script ended"
echo $text
```

结果

```
script starting
global var
Function foo is executing
local var
script ended
global var
```

### 命令

1. break 跳出一层循环，continue 继续下一轮循环
2. : （冒号）空命令，有时用于简化条件逻辑，相当于 true 的别名。如 while : 为无限循环
3. . (点，约等于 source 外部命令)，通常情况下，脚本执行一条外部命令时会启动一个新的子shell，命令在这个新环境中执行，结束后此环境被丢弃，给父shell返回退出码。但.命令可用于在当前环境中执行外部命令或脚本程序。类似 PHP 的 include（）
4. echo -e 可启用反斜线转义，参考 help echo； -n 不输出换行。

```
echo -e "\e[1;32m Si\e[0m\e[1;45mjiao\e[0m\e[1;34mmao\e[0m"
```

1. eval ，允许执行动态生成的代码。例：&#x20;

```
foo=10;  x=foo;  eval y='$'$x;  echo $y
```

1. exec 中断当前shell，执行 exec 所指定的命令
2. exit n 以退出码n结束运行。0为正常;1—125可用于自定义；126 文件不可执行；127 命令未找到；
3. export 导出环境变量，后面执行的子进程均可见；
4. expr ，执行数学运算，如 x=$(expr $x + 1)，也可写为 $((...)) ，具体参考 man expr
5. printf 格式化打印
6. return n 使函数返回，无参数则返回最后一条命令退出码
7. set 为shell设置参数变量；如 `set $(date); echo $2` 可输出date命令的第二列（月）
8. shift ，把所有参数变量左移一个位置，使 $2 变为 $1, $3 变为 $2
9. unset ,从当前环境中删除变量或函数

### 命令的执行

$(command) 或 \`command\` ，捕获command命令执行的字符串输出结果（不是退出码）；

### 算术扩展

$((expression))，类似 expr命令，但比它快

### 字符串处理

* \##尽量多找，#尽量少找，找到后去掉前面找过的部分
* %,类似#，但是从后往前找
* : 开始位置：截取长度
* \# 字符串长度
* \:- 如果file变量为空，则返回 default\_file，否则返回 file 变量的值
* := 如果file变量为空，则设置file=default\_file并返回，否则返回 file 变量的值
* :+ 如果file变量不为空，则返回default\_file

```
filename=ni.hao.avi
echo ${filename##*.}
echo ${filename#*.}
echo ${filename%%.*}
echo ${filename:2:4}
echo ${#filename}
echo ${filename1:-$filename}
echo ${filename1:=$filename}
echo ${filename1:+$count}
```

结果


# awk 与 sed


# 命令笔记

[linux 下查找大于100M的文件](http://www.cnblogs.com/wardensky/p/4922015.html)

```
find . -type f -size +100M
```

```
du -sh [dirname|filename]
   当前目录的大小：
　　du -sh .
　　当前目录下个文件或目录的大小：
　　du -sh *
　　显示前10个占用空间最大的文件或目录：
　　du -s * | sort -nr | head
```


# HTTP 协议

HTTP (HyperText Transfer Protocol)协议,超文本传输协议

## **什么是协议？**

协议这个词主要应用在计算机领域,就是连入网络的计算机都要遵循的一定的技术规范,关于硬件、软件和端口等的技术规范。计算机通信网是由许多具有信息交换和处理能力的节点互连而成的。要使整个网络有条不紊地工作, 就要求每个节点必须遵守一些事先约定好的有关数据格式及时序等的规则。 这些为实现网络数据交换而建立的规则、约定或标准就称为网络协议。

红外遥控协议：

32768Hz 闪16次，停止7.33ms，再闪16次——立即拍照

32768Hz 闪16次，停止5.36ms，再闪16次——延时5秒拍照

飞鸽传书协议：

```
1:23149:riverlet:127.0.0.1:\x20:balabalabalabala

版本号:包编号:发送者姓名:发送者主机名:命令字:附加信息（消息内容）
```

## HTTP 安全

HTTP原先 直接建立在tcp协议上

HTTPS 建立在ssl协议上,当然ssl协议也是建立在tcp协议上

SSL协议的工作流程

* 客户端向服务器请求获取证书+公钥
* 客户端通过CA验证收到证书的合法性
* 客户端通过收到的公钥加密一个字符串，作为后续通信密钥，发送给服务器
* 服务器通过私钥解密，得到通信密钥
* 后续HTTP协议通过通信密钥加密进行
* **资料:**

[http2讲解 ](https://ye11ow.gitbooks.io/http2-explained/content/)


# 请求方法与返回状态码

## 请求方法

* GET
* POST
* HEAD - 返回header信息
* PUT
* DELETE
* TRACE 诊断网络
* OPTIONS
* CONNECT

## 返回状态码

* 100-199：参考信息
* 200-299：成功
* 300-399：重定向
* 400-499：客户端错误
* 500-599：服务器错误

具体:

100 - Continue 继续

101 - Switching Protocols 切换协议

200 - OK (**一切 OK**)

201 - Created 已创建,通常伴随 Location header 返回

202 - Accepted 已接受 ,通常表示 PUT 创建资源的请求已接受，但并不一定已创建完毕。

203 - Non-Authoritative Information, 成功处理请求，但需要以另一个资源返回结果。

204 - No Content,已成功处理请求，但没有响应内容。按HTTP规范，浏览器收到此响应时不应更新当前页面显示内容

205 Reset Content,已成功处理请求，但没有响应内容。浏览器收到此响应时应更新当前页面显示内容，即刷新

206 Partial Content,片段内容

300 Multiple Choices,多个选择,如返回不同格式的视频，选择项可在响应内容中列出

301 Moved Permanently,已永久移动到其他位置,配合Location使用。SEO 适用,无结尾/请求目录时也会自动产生此响应

302 Found ,找到了,按HTTP规范，客户端此时应使用和导致产生此响应的请求方法同样的方法再次请求 Location 指定位置的资源;但实践中，绝大多数浏览器都一律使用 GET 请求 Location 中指定的资源。而这一行为却是HTTP规范中 303 See Other 要求的处理方式。

303 See Other,去看另一个;无论导致产生此响应的请求方法是什么，都用GET重新请求 Location 指定位置的资源

304 Not Modified,未修改，无变动（用缓存中的吧）

305 Use Proxy,需要用代理服务器;告知浏览器要用 Location 中指定的代理服务器访问。大部分浏览器不支持。

306 Switch Proxy,已废弃。

307 Temporary Redirect,临时重定向;用同样的请求方法（GET/POST/HEAD...）重新请求Location指定的内容，而不是只用GET。用于弥补 302 在使用实践中的错误。

400 Bad Request,请求错误。

401 Unauthorized.未被授权,`WWW-Authenticate: Basic,`浏览器收到此响应会弹出一个输入用户名、密码的对话框

402 Payment Required,需要付款,实践中很少使用

403 Forbidden,禁止访问;验证失败或充实次数过多也会导致出现此响应。

404 Not Found

405 Method Not Allowed,访问方法不对.服务器禁止以所请求的方法访问，同时一般会通过 Allow 告知允许的方法：Allow: GET, POST, HEAD

406 Not Acceptable,无法接受,当请求中的 Accept系列header中列出的条件无法满足时，会产生此响应。

407 Proxy Authentication Required,代理服务器需身份验证

408 Request timeout,请求超时,服务器一直没遇到 Connection: close ，会产生此响应并关闭连接

409 Conflict,冲突,例如两个用户同时提交PUT请求时

410 Gone,资源曾经存在，但现在已不复存在

411 Length Required,需要提供内容长度,Content-Length.

412 Precondition Failed,前置条件不足,常用于响应PUT请求，避免出现修改时内容已发生变动而被覆盖的情况。

413 Request Entity Too Large,请求实体太大

414 Request-URI Too Long,请求URL太长

415 Unsupported Media Type,不支持的媒体类型，如上传了一个不支持的文件类型

416 Request Range Not Satisfiable,Range 请求片段无法满足

417 Expectation Failed,Expect 请求无法满足

500 Internal Server Error,服务器错误

501 Not Implemented,请求的方法或功能未实现

502 Bad Gateway,网关错误。代理服务器从上游服务器收到一个无效响应时，会给客户端返回此响应

503 Service Unavailable,服务暂不可用。如上游服务器超载或暂时停机维护等，代理服务器则返回此响应。可附带 Retry-After 头。

504 Gateway Timeout,网关超时。代理服务器无法在限定时间内从上游服务器收到一个有效响应

505 HTTP Version Not Supported,HTTP 版本不支持


# Cookie、Session 的原理

## cookie

**cookie 是什么?**

cookie使用来跟踪用户会话的技术.cookie本身是一小段文本信息,它存在与http协议header头域里,通过http协议来传递.

**cookie 用来干什么?**

http协议是一种无状态协议,客户端和服务端完成一次会话后,服务器端不知道下次连接到服务端的是不是同一个客户端,需要有个标识能告诉服务端,是哪个客户端连接上了服务端.例如登录用户,需要有个东西标识,那就在客户端保存一个cookie,下次访问的时候随http header一起发到服务端,服务端根据cookie的内容知道了这是某个用户的请求,服务端根据cookie取到该用户的信息,返回给客户端

**cookie 工作原理是什么?**

以用户登录来举例

1. 小明访问某一网站 aa.com的登录页面, 此时通过浏览器发了一个http request请求,此时http header头域的cookie值可以是空的
2. xx.com服务器收到这个请求,并接受了小明提交过来的登录数据,判断用户名和密码都正确,这个时候服务器会"种下一个cookie"--一般是将小明的用户名发给客户端,通常是"Cookie: usernick=xiaoming;"这个字符串通过http response返回给客户端(浏览器)
3. 浏览器收到http response返回来的信息发现cookie了,然后将该信息写入到浏览器的某个存储文件中去
4. 小明再访问xx.com的其他页面的时候,请求的http header中会带着这个cookie
5. 服务器的程序会找cookie,并根据cookie的信息取出信息并返回给客户端

## session

**session是什么?**

Session是另一种记录客户状态的机制，不同的是Cookie保存在客户端浏览器中，而Session保存在服务器上。客户端浏览器访问服务器的时候，服务器把客户端信息以某种形式记录在服务器上。这就是Session。

**ession能干什么?**

cookie能做的他能做,cookie不能做的他也能做!

**session是怎么工作的?**

1. 用户请求login.php填写数据后提交
2. 服务器收到提交的数据取得用户信息,将用户信息存入session,并将session\_id通过cookie的形式发给客户端
3. 客户端将session\_id存入浏览器cookie文件
4. 用户再次访问其他页面时,将有session\_id的cookie通过header头发给服务端
5. 服务端拿到session\_id从session中取到用户信息并返回

**session存储的内容是怎么样的?**

```
uid|i:123;abc|O:1:"a":2:{s:1:"s";s:4:"test";s:1:"b";a:2:{i:0;s:2:"12";i:1;s:2:"34";}}
```

**session中怎么存储的?**

序列化serialize

### 相关问题

**问题1.session的有效期?**

由php.ini中的session配置决定( session.cookie\_lifetime = 0 session.gc\_maxlifetime = 600 )

**问题2.关闭客户端可以删除session或者使session失效吗?**

不能删除session,但有可能使session失效 .使其失效的原因是如果配置的session有效期是cookie随着浏览器失效.

**问题3.如果浏览器不支持cookie,session能正常使用吗?**

可以,将session通过url传递

**问题4.如果服务端有多台服务器,session如何共享?**

1. nfs, 将多台服务器所在的session存储目录设置为mount挂载的目录
2. memcache,session.save\_handler = memcache并设置路径session.save\_path = "tcp\://192.168.1.101:11211"
3. mysql,ession.save\_handler = user,然后建立session表,建立php文件用于对数据库表操作session数据,最后用session的都引入上一步中的php文件


# MySQL

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTMTECcnmjf9s4jv9%2Fmysql%E7%9A%84%E9%83%A8%E4%BB%B6.png?generation=1558862986007962\&alt=media)

**1 Connectors指的是不同语言中与SQL的交互**

简单说就是接口的客户端.

**2 Management Serveices & Utilities： 系统管理和控制工具**

mysql , mysqldump等,用来管理数据库,备份,恢复,安全控制等等

**3 Connection Pool: 连接池。**

管理缓冲用户连接，线程处理等需要缓存的需求

**4 SQL Interface: SQL接口。**

接受用户的SQL命令，并且返回用户需要查询的结果。比如select from就是调用SQL Interface

**5 Parser: 解析器。**

SQL命令传递到解析器的时候会被解析器验证和解析。主要功能：

a . 将SQL语句分解成数据结构，并将这个结构传递到后续步骤，以后SQL语句的传递和处理就是基于这个结构的

b. 如果在分解构成中遇到错误，那么就说明这个sql语句是不合理的

**6 Optimizer: 查询优化器。**

SQL语句在查询之前会使用查询优化器对查询进行优化。他使用的是“选取-投影-联接”策略进行查询。

用一个例子就可以理解： select uid,name from user where gender = 1;

这个select 查询先根据where 语句进行选取，而不是先将表全部查询出来以后再进行gender过滤

这个select查询先根据uid和name进行属性投影，而不是将属性全部取出以后再进行过滤

将这两个查询条件联接起来生成最终查询结果

**7 Cache和Buffer： 查询缓存。**

如果查询缓存有命中的查询结果，查询语句就可以直接去查询缓存中取数据。

这个缓存机制是由一系列小缓存组成的。比如表缓存，记录缓存，key缓存，权限缓存等

**8 Engine ：存储引擎。**

存储引擎是MySql中具体的与文件打交道的子系统。也是Mysql最具有特色的一个地方。

Mysql的存储引擎是插件式的。它根据MySql AB公司提供的文件访问层的一个抽象接口来定制一种文件访问机制（这种访问机制就叫存储引擎）现在有很多种存储引擎，各个存储引擎的优势各不一样，最常用的MyISAM,InnoDB


# MySQL表存储引擎

**引擎是什么?**

引擎（Engine）是电子平台上开发程序或系统的核心组件。

**存储引擎又是什么?**

MySQL中的数据用各种不同的技术存储在文件（或者内存）中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。这些不同的技术以及配套的相关功能在MySQL中被称作存储引擎(也称作表类型)。

**mysql有哪些存储引擎?**

我们需要关注的一般都是myisam ,innodb这几种,myisam在mysql 5.1版本前是默认引擎 ,5.5的版本就是innodb是默认存储引擎了.

**我们要关注存储引擎的哪几个方面呢?**

字段数据类型/锁/索引/事务

## 锁机制

**什么是锁机制?**&#x20;

数据库锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问访问变得有序所设计的一种规则。

> 大家想想 如果同时三个连接进来了,一个在执行
>
> a. update table set pos = 1 where id =250;
>
> b.select pos from table where id =250;
>
> c. insert into table (pos) values (12);
>
> 那会发生什么事情?

MYISAM存储引擎的锁机制

MySQL有三种锁的级别：页级、表级、行级。

MyISAM和MEMORY存储引擎采用的是表级锁（table-level locking）；BDB存储引擎采用的是页面锁（page-level locking），但也支持表级锁；InnoDB存储引擎既支持行级锁（row-level locking），也支持表级锁，但默认情况下是采用行级锁。

MySQL这3种锁的特性可大致归纳如下：

表级锁：开销小，加锁快；不会出现死锁；锁定粒度大，发生锁冲突的概率最高,并发度最低。

行级锁：开销大，加锁慢；会出现死锁；锁定粒度最小，发生锁冲突的概率最低,并发度也最高。

页面锁：开销和加锁时间界于表锁和行锁之间；会出现死锁；锁定粒度界于表锁和行锁之间，并发度一般。

## 字段数据类型

* **TINYINT:**&#x4E00;个很小的整数。有符号的范围是-128到127，无符号的范围是0到255。
* **SMALLINT:**&#x4E00;个小整数。有符号的范围是-32768到32767，无符号的范围是0到65535。
* **MEDIUMINT\[(M)]:**&#x4E00;个中等大小整数。有符号的范围是-8388608到8388607，无符号的范围是0到16777215。
* **INT:**&#x4E00;个正常大小整数。有符号的范围是-2147483648到2147483647，无符号的范围是0到4294967295。
* **BIGINT:**&#x4E00;个大整数。有符号的范围是-9223372036854775808到9223372036854775807，无符号的范围是0到18446744073709551615。
* **FLOAT:**&#x4E00;个小(单精密)浮点数字。不能无符号。允许的值是-3.402823466E+38到-1.175494351E-38，0 和1.175494351E-38到3.402823466E+38。M是显示宽度而D是小数的位数。没有参数的FLOAT或有<24 的一个参数表示一个单精密浮点数字。
* **DOUBLE:**&#x4E00;个正常大小(双精密)浮点数字。不能无符号。允许的值是-1.7976931348623157E+308到-2.2250738585072014E-308、 0和2.2250738585072014E-308到1.7976931348623157E+308。M是显示宽度而D是小数位数。没有一个参数的 DOUBLE或FLOAT(X)(25 < = X < = 53)代表一个双精密浮点数字。
* **DATE:**&#x4E00;个日期。支持的范围是'1000-01-01'到'9999-12-31'。MySQL以'YYYY-MM-DD'格式来显示DATE值，但是允许你使用字符串或数字把值赋给DATE列。
* **DATETIME:**&#x4E00;个日期和时间组合。支持的范围是'1000-01-01 00:00:00'到'9999-12-31 23:59:59'。MySQL以'YYYY-MM-DD HH:MM:SS'格式来显示DATETIME值，但是允许你使用字符串或数字把值赋给DATETIME的列。
* **TIMESTAMP\[(M)]**:一个时间戳记。范围是'1970-01-01 00:00:00'到2037年的某时。MySQL以YYYYMMDDHHMMSS、YYMMDDHHMMSS、YYYYMMDD或YYMMDD 格式来显示TIMESTAMP值，取决于是否M是14(或省略)、12、8或6，但是允许你使用字符串或数字把值赋给TIMESTAMP列。一个 TIMESTAMP列对于记录一个INSERT或UPDATE操作的日期和时间是有用的，因为如果你不自己给它赋值，它自动地被设置为最近操作的日期和时间。你以可以通过赋给它一个NULL值设置它为当前的日期和时间。
* **TIME:**&#x4E00;个时间。范围是'-838:59:59'到'838:59:59'。MySQL以'HH:MM:SS'格式来显示TIME值，但是允许你使用字符串或数字把值赋给TIME列。
* **YEAR:**&#x4E00;个2或4位数字格式的年(缺省是4位)。允许的值是1901到2155，和0000(4位年格式)，如果你使用2位，1970-2069( 70-69)。MySQL以YYYY格式来显示YEAR值，但是允许你把使用字符串或数字值赋给YEAR列。(YEAR类型在MySQL3.22中是新类型。)
* **CHAR:**&#x4E00;个定长字符串，当存储时，总是是用空格填满右边到指定的长度。M的范围是1 ～ 255个字符。当值被检索时，空格尾部被删除。CHAR值根据缺省字符集以大小写不区分的方式排序和比较，除非给出BINARY关键词。NATIONAL CHAR(短形式NCHAR)是ANSI SQL的方式来定义CHAR列应该使用缺省字符集。这是MySQL的缺省。CHAR是CHARACTER的一个缩写。
* **VARCHAR:**&#x4E00;个变长字符串.
* **TINYBLOB,TINYTEXT:**&#x4E00;个BLOB或TEXT列，最大长度为255(2^8-1)个字符。
* **BLOB,TEXT:**&#x4E00;个BLOB或TEXT列，最大长度为65535(2^16-1)个字符。
* **MEDIUMBLOB,MEDIUMTEXT:**&#x4E00;个BLOB或TEXT列，最大长度为16777215(2^24-1)个字符。
* **LONGBLOB,LONGTEXT:**&#x4E00;个BLOB或TEXT列，最大长度为4294967295(2^32-1)个字符。
* **ENUM('value1','value2',...):枚举**。一个仅有一个值的字符串对象，这个值式选自与值列表'value1'、'value2', ...,或NULL。一个ENUM最多能有65535不同的值。

## MYISAM存储引擎

### MYISAM存储引擎的锁机制

MyISAM存储引擎只支持表锁，是现在用得最多的存储引擎。

**1.查询表级锁争用情况**

可以通过检查`table_locks_waited`和`table_locks_immediate`状态变量来分析系统上的表锁定争夺：

`show status like 'table%'`查询锁的争用情况

`Table_locks_waited`的值如果比较高，说明存在着较严重的表级锁争用情况。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTKGt3Pg1Ng7oNoxa%2FMYISAM%E6%9F%A5%E8%AF%A2%E8%A1%A8%E7%BA%A7%E9%94%81%E4%BA%89%E7%94%A8%E6%83%85%E5%86%B5.png?generation=1558862988601526\&alt=media)

**2.MySQL的表级锁有两种模式：**&#x8868;共享读锁（Table Read Lock）和表独占写锁（Table Write Lock）。MyISAM在执行查询语句（SELECT）前，会自动给涉及的所有表加读锁，在执行更新操作（UPDATE、DELETE、INSERT等）前，会自动给涉及的表加写锁。

所以对MyISAM表进行操作，会有以下情况：

a、对MyISAM表的读操作（加读锁），不会阻塞其他进程对同一表的读请求，但会阻塞对同一表的写请求。只有当读锁释放后，才会执行其它进程的写操作。

b、对MyISAM表的写操作（加写锁），会阻塞其他进程对同一表的读和写操作，只有当写锁释放后，才会执行其它进程的读写操作。

**简单来说就是**&#x20;

读锁可以被读操作共享,不影响,但读锁会影响写操作.

写锁会影响所有的读写操作.

**3.并发插入**

原则上数据表有一个读锁时，其它进程无法对此表进行更新操作，但在一定条件下，MyISAM表也支持查询和插入操作的并发进行。MyISAM存储引擎有一个系统变量concurrent\_insert，专门用以控制其并发插入的行为，其值分别可以为0、1或2。

a、当concurrent\_insert设置为0时，不允许并发插入。

b、当concurrent\_insert设置为1时，如果MyISAM表中没有空洞（即表的中间没有被删除的行），MyISAM允许在一个进程读表的同时，另一个进程从表尾插入记录。这也是MySQL的默认设置。

c、当concurrent\_insert设置为2时，无论MyISAM表中有没有空洞，都允许在表尾并发插入记录。

**4.MyISAM的锁调度**

由于MySQL认为写请求一般比读请求要重要，所以如果有读写请求同时进行的话，MYSQL将会优先执行写操作。这样MyISAM表在进行大量的更新操作时（特别是更新的字段中存在索引的情况下），会造成查询操作很难获得读锁，从而导致查询阻塞。

我们可以通过一些设置来调节MyISAM的调度行为：

a、通过指定启动参数low-priority-updates，使MyISAM引擎默认给予读请求以优先的权利。

b、通过执行命令SET LOW\_PRIORITY\_UPDATES=1，使该连接发出的更新请求优先级降低。

### MYISAM存储引擎的索引

1 MyISAM默认使用B-tree索引只把索引载入内存

2 MyISAM数据库中的数据是按照插入的顺序保存，在每个索引节点中保存对应的数据行的地址，理论上说主键索引和其他索引是一样的

3 对于字符串索引，MyISAM默认采用增量保存，例如第一个索引值是'perform'，第二个索引的值是'performance', 在索引文件中第二个索引被保存为'7,ance'。这样能够减小索引的尺寸。

4 MyISAM保存索引的状态信息在磁盘里，每次执行ANALYZE TABLE会更新这个信息。

5 索引长期运行之后会产生碎片，一种碎片是一行数据被保存在不同的数据段，另一种是连续的表空间或行在磁盘上被分散地保存。对于MyISAM两种索引碎片都会出现

### MYISAM存储引擎的事务

不支持事务

## INNODB存储引擎

**INNODB存储引擎的锁**

查询行级锁争用情况 `show status like 'innodb_row_lock%';`

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTKGxNVHmNcA_nEug%2FINNODB%E6%9F%A5%E8%AF%A2%E8%A1%8C%E7%BA%A7%E9%94%81%E4%BA%89%E7%94%A8%E6%83%85%E5%86%B5.png?generation=1558862988608107\&alt=media)

innodb的锁 分为 共享锁和排他锁

* 共享锁（S）：允许一个事务去读一行，阻止其他事务获得相同数据集的排他锁。
* 排他锁（X)：允许获得排他锁的事务更新数据，阻止其他事务取得相同数据集的共享读锁和排他写锁。

另外，为了允许行锁和表锁共存，实现多粒度锁机制，InnoDB还有两种内部使用的意向锁（Intention Locks），这两种意向锁都是表锁。意向锁是innodb自动处理,无需人工干预

* 意向共享锁（IS）：事务打算给数据行加行共享锁，事务在给一个数据行加共享锁前必须先取得该表的IS锁。
* 意向排他锁（IX）：事务打算给数据行加行排他锁，事务在给一个数据行加排他锁前必须先取得该表的IX锁。

从sql语句上来说 update ,delete 会自动给涉及到的数据集加上排他锁 ,而普通的select则不加任何锁!

**那insert 会加什么锁呢?**

mysql 5.0以前,如果主键用的是A\_I , 会在内存中保存A\_I 的值,插入的时候用的表锁,也就是说,只要用A\_I ,插入的时候就会锁表. 当然可以不用A\_I .

还有一个方法就是使用MySQL5.1.22之后的版本，出现了一个新的配置选项：innodb\_autoinc\_lock\_mode，它是专门用来在使用auto\_increment的情况下调整锁策略的，目前有三种选择：

1. `innodb_autoinc_lock_mode = 0 (“traditional” lock mode)`    # 仍然是表锁
2. `innodb_autoinc_lock_mode =  1 (“consecutive” lock mode)` #默认方式, 只锁分配A\_I的过程,可一次分配多个
3. `innodb_autoinc_lock_mode = 2 (“interleaved” lock mode)`# 只锁分配A\_I的过程,来一个分配一个

**总结:**

1.innodb引擎有表锁和行锁

2.行锁不影响读操作,只影响写操作

3.当执行某写操作,如果有索引,则根据索引锁住数据所在的行,如果没有索引,则锁住整张表.表锁不是由InnoDB存储引擎层管理的，而是由其上一层──MySQL Server负责的，仅当autocommit=0、innodb\_table\_locks=1（默认设置）时，InnoDB层才能知道MySQL加的表锁，MySQL Server也才能感知InnoDB加的行锁，这种情况下，InnoDB才能自动识别涉及表级锁的死锁；否则，InnoDB将无法自动检测并处理这种死锁。

4.mvcc,当有个写锁存在的时候,读操作会去快照取数据,只要写操作没有commit前,都会取到写操作之前的数据

5.表锁不影响读操作

**INNODB存储引擎的事务**

事务就是为了让数据库从一种一致的状态 切换到 另一种一致的状态!

用我们的先辈来说 : 要么生 ,要么死! 别折磨人,让人生不如死!

我们自己的理解就是: 这一系列的操作比如 取款和卡中余额减少,要么都成功,要门都不成功

要么某东的订单状态支付变更和 余额减少 都成功,要么都不成功 ! 这之间就需要用事务来保证!

如果让你来实现事务的逻辑, 你会怎么操作?


# 标准PHP库（SPL）

SPL是php标准库，将一些比较实用的函数、类和接口以库的形式放在php内核


# 数据结构


# SplPriorityQueue - 优先队列

[官方地址](http://php.net/manual/zh/class.splpriorityqueue.php)

SplPriorityQueue是基于堆实现的.

> SplQueue(队列)类就是实现队列操作，和栈一样，它也可以继承双链表(SplDoublyLinkedList)轻松实现。

优先队列也是非常实用的一种数据结构，可以通过加权对值进行排序，由于排序在php内部实现，业务代码中将精简不少而且更高效。通过`SplPriorityQueue::setExtractFlags(int $flag)`设置提取方式可以提取数据（等同最大堆）、优先级、和两者都提取的方式。

## 类摘要

```php
SplPriorityQueue implements Iterator , Countable {
    public __construct ( void )
    public int compare ( mixed $priority1 , mixed $priority2 ) // 比较优先级，以便在筛选时将元素正确放置在堆中。
    public int count ( void ) // 计算队列中元素的数量。
    public mixed current ( void ) // 返回迭代器指向的当前节点
    public mixed extract ( void ) // 从堆顶部提取一个节点并向上移动
    public void insert ( mixed $value , mixed $priority ) // 在队列中插入一个元素
    public bool isEmpty ( void ) //检查队列是否为空。
    public mixed key ( void ) // 返回当前节点索引(key键)
    public void next ( void ) // 移动到下一个节点
    public void recoverFromCorruption ( void ) // 从损坏的状态恢复，并允许队列中的其他操作。
    public void rewind ( void ) // 将迭代器倒回起始
    public void setExtractFlags ( int $flags ) // 设置提取模式
    public mixed top ( void ) // 从队列顶部的节点上看一眼
    public bool valid ( void ) // 检查队列是否包含多个节点。
}
```

可以看到SplPriorityQueue实现了Iterator（迭代器）

其中：compare方法比较时：

* priority1 > priority2 ,返回1
* priority1 = priority2 ,返回0
* priority1 < priority2 ,返回-1

> 注意：通过extract,next方法操作，rewind 没用回到当初的队列起始位置

## 简单示例：

```php
$pq = new SplPriorityQueue();

$pq->insert('a', 10);
$pq->insert('b', 1);
$pq->insert('c', 8);
$pq->insert('d', 3);

echo $pq->count() .PHP_EOL; //4
echo $pq->current() . PHP_EOL; //a
echo $pq->current() . PHP_EOL; //a 因为迭代器的指针没用移动
echo $pq->next() . PHP_EOL;  //迭代器移动会影响到ｗhile的输出
echo $pq->current() . PHP_EOL; //c 
echo $pq->compare(10, 1) . PHP_EOL; //1  对优先级的数值进行比较
//echo $pq->extract() . PHP_EOL; // c
//echo $pq->extract() . PHP_EOL; // d ,注意迭代器的指针发生了变化，会影响到ｗhile的输出
//echo $pq->top() . PHP_EOL;  // b,extract操作导致指针已经移动将d扔出，所以此处队列的顶端是b

/**
 * 设置元素出队模式
 * SplPriorityQueue::EXTR_DATA 仅提取值
 * SplPriorityQueue::EXTR_PRIORITY 仅提取优先级
 * SplPriorityQueue::EXTR_BOTH 提取数组包含值和优先级
 */
echo "while:" . PHP_EOL;
$pq->rewind();
$pq->setExtractFlags(SplPriorityQueue::EXTR_DATA);
//$pq->setExtractFlags(SplPriorityQueue::EXTR_PRIORITY);
//$pq->setExtractFlags(SplPriorityQueue::EXTR_BOTH);

while($pq->valid()) {
    echo 'key:' . $pq->key() . PHP_EOL; // 3 2 1 0 
    print_r($pq->current()). PHP_EOL;//a c d b
    $pq->next();
}
```

上面输出结果：

* 1.EXTR\_DATA 仅提取值, 则输出：`a c b`
* 2.EXTR\_PRIORITY 仅提取优先级, 则输出：`10 8 1`
* 3.EXTR\_BOTH 提取数组包含值和优先级, 则输出：`Array ( [data] => a [priority] => 10 ) Array ( [data] => c [priority] => 8 ) Array ( [data] => b [priority] => 1 )`

## 资料

这方面zendframework的底层有所重写实现，可以参考

<https://github.com/zendframework/zend-stdlib/blob/master/src/SplPriorityQueue.php>

<https://github.com/zendframework/zend-stdlib/blob/master/src/PriorityQueue.php>


# SplQueue - 队列

[官方地址](http://php.net/manual/zh/class.splqueue.php)

队列这种数据结构简单，就像我们生活中排队一样，它的特性是先进先出(FIFO)。

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTKMvw0Yu9bpo5Do2%2FSplQueue.png?generation=1558862986493719\&alt=media)

PHP SPL中SplQueue类就是实现队列操作，和**栈**一样。它也可以继承双链表(SplDoublyLinkedList)轻松实现。

## 资料

这方面zendframework的底层有所重写实现，可以参考

<https://github.com/zendframework/zend-stdlib/blob/master/src/SplQueue.php>


# SplStack - 栈的功能

SplStack类通过使用一个双向链表来提供栈的主要功能。


# 接口


# Countable - count统计接口


# 附录：ElasticSearch

[ElasticSearch结构化查询](http://blog.csdn.net/hzrandd/article/details/47148053)


# PHP数据结构

## 什么是数据结构呢？

**数据：**&#x63CF;述客观事物的符号，如文本、图片、视频

**数据元素：**&#x7EC4;成数据的，有一定意义的基本单位

**数据项：**&#x4E00;个数据元素可以由若干数据项组成

**数据对象：**&#x6027;质相同的数据元素的集合

**数据结构：**&#x6570;据结构是计算机用来组织和存储数据的方式! 具体定义:数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成

## 数据结构术语图示

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTJHFeCCWA-oXXpO0%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%AF%E8%AF%AD%E5%9B%BE%E7%A4%BA.png?generation=1558862981741713\&alt=media)

## 数据结构包含的内容

![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTJHHUASqMLMufBAw%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%8C%85%E5%90%AB%E7%9A%84%E5%86%85%E5%AE%B9.png?generation=1558862981722245\&alt=media)

## 数据的逻辑结构

## ![](https://1303647163-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LfnT30woYyQ9bJF96Hh%2F-LfnTB5KYnkOx0V3e4EY%2F-LfnTJHJE_qZ_5fURytl%2F%E6%95%B0%E6%8D%AE%E7%9A%84%E9%80%BB%E8%BE%91%E7%BB%93%E6%9E%84.png?generation=1558862980025909\&alt=media)

## 数据的物理存储结构

**顺序存储结构**

顺序存储结构（Sequence Storage Structure）是指在一块连续的存储区域中一个接着一个地存放数据。顺序存储方式把逻辑上相邻的结点存储在物理位置相邻的存储单元中，节点间的逻辑关系由存储单元的邻接关系来体现。一般采用数组或结构数组来描述。线性存储方式主要用于线性逻辑结构的数据存放，而对于图和树等非线性逻辑结构则不适用。

**链式存储结构**

链式存储结构（Linked Storage Structure）比较灵活，其不要求逻辑上相邻的节点在物理位置上相邻，节点间的逻辑关系由附加的引用字段表示。一个结点的引用字段往往指向下一个节点的存放位置。

**索引存储结构**

索引存储结构是采用附加的索引表的方式来存储节点信息。索引表由若干索引项组成。索引存储结构中索引项的一般形式为：（关键字、地址）。其中，关键字是能够唯一标识一个节点的数据项。

**散列（或哈希）存储结构**

散列存储结构是根据节点的关键字直接计算出该节点的存储地址的一种存储方式。


# 附录:Rabbitmq

升级

当前版本：3.4.2 升级到最新版 3.6.14

Ubuntu 14.x

```
sudo rabbitmqctl stop  

echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list

wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -

sudo apt-get update

sudo apt-get install rabbitmq-server 

sudo rabbitmq-server start
```

php 包更新

<https://packagist.org/packages/php-amqplib/php-amqplib>

<https://packagist.org/packages/alchemy/rabbitmq-management-client>

```
sudo composer require alchemy/rabbitmq-management-client
sudo composer require php-amqplib/php-amqplib
```

> 问题1：
>
> 删除一些失效的源：cd /etc/apt/sources.list.d/
>
> 问题2：
>
> 外网无法访问：rabbitmq（配置具体的用户可以参考下面的资料），并重新启动
>
> sudo chmod 777 /etc/rabbitmq/ -R
>
> echo "\[{rabbit, \[{loopback\_users, \[]}]}]." > /etc/rabbitmq/rabbitmq.config
>
> sudo service rabbitmq-server stop
>
> sudo service rabbitmq-server start

注意：

登录管理页面地址端口从55672 变为 15672

<http://127.0.0.1:15672>

其他：

打开管理页面

```
sudo rabbitmq-plugins enable rabbitmq_management
```

查看安装的插件

```
sudo rabbitmq-plugins list
```

查看用户

```
sudo rabbitmqctl list_users
```

新增管理员用户

```
sudo rabbitmqctl add_user admin admin
sudo rabbitmqctl set_user_tags admin administrator
```

参考资料：

[RabbitMQ常用命令](http://blog.csdn.net/u013256816/article/details/53524814)

[RabbitMQ由3.1.5升级3.6.10后远程访问失败](http://blog.sina.com.cn/s/blog_c32816210102x9dg.html)

[Installing on Debian / Ubuntu](https://www.rabbitmq.com/install-debian.html)

[RabbitMQ在Ubuntu 16.04下的安装与配置](http://blog.csdn.net/rickey17/article/details/72756766)

[sudo apt-get update错误](http://blog.csdn.net/usydapeng/article/details/25511557)

[rabbitmq问题之HTTP access denied: user 'guest' - User can only log in via localhost](http://www.cnblogs.com/lazyboy/p/3853371.html)


# 附录:guzzle

[Guzzle中文文档](http://guzzle-cn.readthedocs.io/zh_CN/latest/index.html)


# JavaScript


# 附录:资料

[input/select/textarea标签的readonly效果实现](http://www.cnblogs.com/yangzhinian/p/4834814.html) - 注意下select readyonly的效果


# 疑难杂症

[在Ubuntu 14.04中安装gcc 6](https://www.codelast.com/原创-在ubuntu-14-04中安装gcc-6/)


