HTTP状态码

HTTP状态码(HTTP Status Code)是用来表示网页服务器HTTP 响应状态的3位数字代码。它由RFC 2616规范定义的,并得到RFC 2518、RFC 2817、RFC 2295、RFC 2774、RFC 4918等规范扩展。所有状态码的第一个数字代表了响应的五种状态之一

HTTP/1.1定义的状态码值和对应的原因短语(Reason-Phrase)的例子。
1XX表示:消息
这一类型的状态码,代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束。由于HTTP/1.0 协议中没有定义任何1xx状态码,所以除非在某些试验条件下,服务器禁止向此类客户端发送1xx响应。 这些状态码代表的响应都是信息性的,标示客户应该采取的其他行动。
100 : Continue 客户端应当继续发送请求。这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分.
101 : witching Protocols 服务器已经理解了客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到 在Upgrade消息头中定义的那些协议。: 只有在切换新的协议更有好处的时候才应该采取类似措施.
102: Processing 由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。
2XX表示:成功
这一类型的状态码,代表请求已成功被服务器接收、理解、并接受。
200 : OK
201 : Created 已创建
202 : Accepted 接收
203 : Non-Authoritative Information 非认证信息
204 : No Content 无内容
205 : Reset Content 重置内容
206 : Partial Content 服务器已经成功处理了部分GET请求。类似于FlashGet或者迅雷这类的HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
207: Multi-Status 由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码.
3XX表示: 重定向
这类状态码代表需要客户端采取进一步的操作才能完成请求。通常,这些状态码用来重定向,后续的请求地址(重定向目标)在本次响应的Location域中指明。
300 : Multiple Choices 多路选择
301 : Moved Permanently 永久转移
302 : Found 暂时转移
303 : See Other 参见其它
304 : Not Modified 未修改
305 : Use Proxy 使用代理
306: Switch Proxy 在最新版的规范中,306状态码已经不再被使用。
307 : Temporary Redirect 请求的资源现在临时从不同的URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或 Expires中进行了指定的情况下,这个响应才是可缓存的。
4XX表示: 客户端错误
这类的状态码代表了客户端看起来可能发生了错误,妨碍了服务器的处理。
400 : Bad Request 错误请求
401 : Unauthorized 未认证
402 : Payment Required 需要付费
403 : Forbidden 禁止
404 : Not Found 请求失败,请求所希望得到的资源未被在服务器上发现
405 : Method Not Allowed 方法不允许
406 : Not Acceptable 不接受
407 : Proxy Authentication Required 需要代理认证
408 : Request Time-out 请求超时
409 : Conflict 冲突
410 : Gone 失败
411 : Length Required 需要长度
412 : Precondition Failed 条件失败
413 : Request Entity Too Large 请求实体太大
414 : Request-URI Too Large 请求URI太长
415 : Unsupported Media Type 不支持媒体类型
416 : Requested range not satisfiable 如果请求中包含了Range请求头,并且Range中指定的任何数据范围都与当前资源的可用范围不重合,同时请求中又没有定义If-Range请求头,那 么服务器就应当返回416状态码。
417 : Expectation Failed 在请求头Expect中指定的预期内容无法被服务器满足
421 :There are too many connections from your internet address 从当前客户端所在的IP地址到服务器的连接数超过了服务器许可的最大范围。通常,这里的IP地址指的是从服务器上看到的客户端地址.
422: Unprocessable Entity 请求格式正确,但是由于含有语义错误,无法响应。(RFC 4918 WebDAV)
423: Locked 当前资源被锁定。(RFC 4918 WebDAV)
424: Failed Dependency 由于之前的某个请求发生的错误,导致当前请求失败,例如PROPPATCH。(RFC 4918 WebDAV)
425: Unordered Collection 在WebDav Advanced Collections草案中定义,但是未出现在《WebDAV顺序集协议》(RFC 3658)中。
426:Upgrade Required 客户端应当切换到TLS/1.0。(RFC 2817)
449: Retry With 由微软扩展,代表请求应当在执行完适当的操作后进行重试。
5XX表示: 服务器错误
这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生
500 : Internal Server Error 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现
501 : Not Implemented 未实现
502 : Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
503 : Service Unavailable 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。
504 : Gateway Time-out 网关超时
505 : HTTP Version not supported 服务器不支持,或者拒绝支持在请求中使用的HTTP版本。这暗示着服务器不能或不愿使用与客户端相同的版本。响应中应当包含一个描述了为何版本不被支持以及服务器支持哪些协议的实体。
506 : Variant Also Negotiates 由《透明内容协商协议》(RFC 2295)扩展,代表服务器存在内部配置错误:被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点。
507 : Insufficient Storage 服务器无法存储完成请求所必须的内容。这个状况被认为是临时的。WebDAV(RFC 4918)
509 : Bandwidth Limit Exceeded 服务器达到带宽限制。这不是一个官方的状态码,但是仍被广泛使用。
510 : Not Extended 获取资源所需要的策略并没有没满足。

MySQL修改root密码的几种方法

方法1: 用SET PASSWORD命令

首先登录MySQL。

格式:mysql> set password for 用户名@localhost = password(‘新密码’);

例子:mysql> set password for root@localhost = password(‘admin100’);

上面例子将用户root的密码更改为admin100

方法2:用mysqladmin

格式:mysqladmin -u用户名 -p旧密码 password 新密码

例子:mysqladmin -uroot -p123456 password admin100

上面例子将用户root原来的密码123456改为新密码admin100

方法3:用UPDATE直接编辑user表

首先登录MySQL。

mysql> use mysql;

mysql> update user set password=password(‘admin100’) where user=’root’ and host=’localhost’;

mysql> flush privileges;

方法4:在忘记root密码的时候,可以这样。

以windows为例:

  1. 关闭正在运行的MySQL服务。

  2. 打开DOS窗口,转到mysql\bin目录。

  3. 输入mysqld –skip-grant-tables 回车。–skip-grant-tables 的意思是启动MySQL服务的时候跳过权限表认证。

  4. 再开一个DOS窗口(因为刚才那个DOS窗口已经不能动了),转到mysql\bin目录。

  5. 输入mysql回车,如果成功,将出现MySQL提示符 >。

  6. 连接权限数据库: use mysql; 。

  7. 改密码:update user set password=password(“admin100”) where user=”root”;(别忘了最后加分号) 。

  8. 刷新权限(必须步骤):flush privileges;。

  9. 退出 quit。

  10. 注销系统,再进入,使用用户名root和刚才设置的新密码admin100登录。

css去除webkit内核的默认样式

##前言
做移动端的朋友应该知道,iphone的默认按钮是个很恶心的圆角,select下拉框也有默认样式无法修改。

###解决办法
这时候可以用到
在按钮和select样式里加入下面css
–webkit–appearance:none //去除默认样

###示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
input{
background:#FF9933;
border–radius:5px;
color:#fff;
padding:3px 20px;
–webkit–appearance:none;
}
select{
background:url(images/img1.jpg) no–repeat;
background–size:20px;
background–position:95% center;
padding:3px 10px;
width:30%;
–webkit–appearance:none;
}

jquery图片上传插件,支持进度条,非flash,v1.2版

插件代码

需要先引入jquery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* v1.1
* 田丰硕 tianfs.com
*/
(function ($) {
$.extend($.fn, {
upload: function (settings) {

var options = $.extend({
fileType: "gif|jpg|jpeg|png|bmp", //允许的文件格式
uploadUrl: "", //上传URL地址
inputName: "upfile",
uploadData: {}, //上传时需要附加的参数 可以是 function
beforeSubmitFn: "", //上传前执行的方法 原型 beforeSubmit(xhr, $this);
successFn: "", //上传成功后执行的方法 uploadSuccess(response, statusText, xhr, $this)
errorFn: "", //上传失败后执行的方法
onProgress:"" //返回上传进度信息
}, settings);

//上传准备函数
var methods = {
//验证文件格式
checkFile: function (filename) {
var pos = filename.lastIndexOf(".");
var str = filename.substring(pos, filename.length);
var str1 = str.toLowerCase();
if (typeof options.fileType !== 'string') { options.fileType = "gif|jpg|jpeg|png|bmp"; }
var re = new RegExp("\.(" + options.fileType + ")$");
return re.test(str1);
},
createInputFile: function(){
/*var new_fileType = options.fileType.replace(/\|/g,",");
new_fileType = "."+new_fileType;
Prompt.topShow(new_fileType)
var $input = $("<input type='file' name='"+options.inputName+"'> accept='"+new_fileType+"'");*/
var $input = $("<input type='file' class='upload_tmp_input' style='display:none' name='"+options.inputName+"'> ");
return $input;
},
onload: function () {

}
};
//上传主函数
this.each(function () {
var $this = $(this);
$this.bind("click", function () {
$(".upload_tmp_input").remove()
//创建input file
var $input = methods.createInputFile();
$this.before($input);
$input.change(function(){
if ($input.val() === "") {
alert("请选择要上传的文件!");
return false;
}
//验证图片
if (!methods.checkFile($input.val())) {
alert("文件格式不正确,只能上传格式为:" + options.fileType + "的文件。");
return false;
}
var pic = $input.get(0).files[0];
var formData = new FormData();
formData.append(options.inputName , pic);
if (typeof options.uploadData == 'function') {
var data = options.uploadData($this);
if (data) {
for (var x in data) {
formData.append(x , data[x]);
}
}
} else {
if (options.uploadData) {
for (var x in options.uploadData) {
formData.append(x , options.uploadData[x]);
}
}
}
/**
* 必须false才会避开jQuery对 formdata 的默认处理
* XMLHttpRequest会对 formdata 进行正确的处理
*/
$.ajax({
type: "POST",
url: options.uploadUrl,
data: formData ,
processData : false,
//必须false才会自动加上正确的Content-Type
contentType : false ,
dataType : "JSON",
xhr: function(){
var xhr = $.ajaxSettings.xhr();
xhr.upload.addEventListener("progress" , function (evt){
console.log(evt);
var loaded = evt.loaded; //已经上传大小情况
var tot = evt.total; //附件总大小
var per = Math.floor(100*loaded/tot); //已经上传的百分比
var onProgress;
try { onProgress = eval(options.onProgress) } catch (err) { };
if (onProgress) {
onProgress(per, $this);
}
}, false);
return xhr;
},
beforeSend:function (xhr) {
$this.attr("disabled", true);
var beforeSubmitFn;
try { beforeSubmitFn = eval(options.beforeSubmitFn) } catch (err) { };
if (beforeSubmitFn) {
var $result = beforeSubmitFn(xhr, $this);
if (typeof ($result) == "boolean")
return $result;
}
},
error : function (response, statusText, xhr) {
var errorFn;
try { errorFn = eval(options.errorFn) } catch (err) { };
if (errorFn) {
errorFn(response, statusText, xhr, $this);
}
$this.attr("disabled", false);
$input.remove();
},
//上传成功
success : function (response, statusText, xhr) {
//response = eval("(" + response + ")");
var successFn;
try { successFn = eval(options.successFn) } catch (err) { };
if (successFn) {
$.ajaxSetup({ cache: false });//这个不能少,否则ie下会提示下载
successFn(response, statusText, xhr, $this);
}
$this.attr("disabled", false);
$input.remove();
}

});
})
$input.click()
});

});
}
});
})(jQuery)

使用代码

1
2
3
4
5
6
7
8
9
$(“.upload-quan”).upload({
uploadData: {},
uploadUrl : “”,
inputName: “upfile”,
fileType: “bmp|png|jpeg|jpg|gif”,
successFn: function (response, statusText, xhr, $this) {
console.log(response);
}
});

用css3 media实现响应式布局

##前言
响应式布局,说直白点就是一个网站能够兼容多个终端,可以按不同的分辨率显示不同的状态。而实现这个就要用到css3的Media Queries(媒介查询)。这个功能非常的强大,但是有优点的同时,缺点也是会存在的。那就是兼容各种设备工作量大,效率低下,加载时间长等。但是学起来很容易,看完下面的代码你就会了。

###示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.page{
width:960px;
height:1000px;
margin:0 auto;
background:#CCC;
}
/* 设备最大宽度960px */
@media screen and (max–width: 960px) {
.page{
width:100%;
background:#69F;
}
}
/* 宽度大于480px且小于768px */
@media screen and (min–width: 480px) and (max–width:768px) {
.page{
width:100%;
background:#F00;
}
}
/* 设备最大宽度480px */
@media screen and (max–width:480px){
.page{
width:100%;
background:#00FF00;
}
}

这样就可以在不同的分辨率下采取不同的样式了。

另外还有一点,如果是移动端开发,一定要在头部加上以下代码。

怎么样,很容易吧。当然这只是响应式布局的一部分,其他的可以网上搜索相关资料。以上,只是个人对于响应式布局的一些理解,技术更新的速度很快,所以我们也要与时俱进。

用一个数值保存多选的值

前言

在开发过程中,对于网页中的多选,我们有很多种存储方式,常见的如逗号分隔。下文介绍一种通用设计方式:用一个整数来存储复选框的值。

准备知识 —— 位与运算

位与运算:二进制运算,相同位的两个数字都为1,则为1;若有一个不为1,则为0,如:

1
2
3
4
5
 下方是运算
00101
& 11100
------------
00100

设计

将多项的选项值分别设置为 2 的 n 次方,n 从 0 开始,每多一项,n + 1。即 1,2,4,8…
多选的存储值为各项值之和,如选中了第 1、3 项,则值为:1 + 4 = 5

1
$ hexo server

回显

假设存储的值为 5 ,要使相应的项被勾选,则是循环多项的值,每项与存储值 5 进行 位与运算,如果值与选项本身的值相等,则选中该项;相反地,如果运算值为 0 ,则设置为不选中:

1
2
3
4
5
下方回显
1 & 5 = 1
2 & 5 = 0
4 & 5 = 4
8 & 5 = 0

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html> 
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Checkbox Test</title>
</head>
<body>
<form>
<input type="checkbox" name="test" value="1"> 1
<input type="checkbox" name="test" value="2"> 2
<input type="checkbox" name="test" value="4"> 4
<input type="checkbox" name="test" value="8"> 8
</form>
<input type="text" id="result" placeholder="设置要回显的值">
<button id="show">回显</button>
<script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
<script>
$(function () {
$("[name='test']").on("change", function () {
var result = 0;
$("[name='test']:checkbox:checked").each(function(){
result += parseInt($(this).val());
});
$("#result").val(result); });
$("#show").on("click", function () {
var result = parseInt($("#result").val());
$("[name='test']:checkbox").each(function(){
var value = parseInt($(this).val());
if ((result & value) == value) {
$(this).prop("checked", true);
} else {
$(this).prop("checked", false);
}
});
});
});
</script>
</body>
</html>

php垃圾回收机制

写时复制

首先,php的变量复制用的是写时复制方式,举个例子.

1
2
3
4
5
6
7
8
9
10
$a='蛋蛋'.time();
$b=$a;
$c=$a;
//这个时候内存占用相同,$b,$c都将指向$a的内存,无需额外占用

$b='蛋蛋1号';
//这个时候$b的数据已经改变了,无法再引用$a的内存,所以需要额外给$b开拓内存空间

$a='蛋蛋2号';
//$a的数据发生了变化,同样的,$c也无法引用$a了,需要给$a额外开拓内存空间

引用计数

既然变量会引用内存,那么删除变量的时候,就会出现一个问题了:

1
2
3
4
5
6
7
8
9
10
$a='蛋蛋';
$b=$a;
$c=$a;
//这个时候内存占用相同,$b,$c都将指向$a的内存,无需额外占用

$b='蛋蛋1号';
//这个时候$b的数据已经改变了,无法再引用$a的内存,所以需要额外给$b开拓内存空间

unset($c);
//这个时候,删除$c,由于$c的数据是引用$a的数据,那么直接删除$a?

很明显,当$c引用$a的时候,删除$c,不能把$a的数据直接给删除,那么该怎么做呢?
这个时候,php底层就使用到了引用计数这个概念
引用计数,给变量引用的次数进行计算,当计数不等于0时,说明这个变量已经被引用,不能直接被回收,否则可以直接回收,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$a = '蛋蛋'.time();
$b = $a;
$c = $a;

xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');

$b='蛋蛋2号';
xdebug_debug_zval('a');
xdebug_debug_zval('b');

echo "脚本结束\n";


//输出

a: (refcount=3, is_ref=0)='蛋蛋1578154814'
b: (refcount=3, is_ref=0)='蛋蛋1578154814'
c: (refcount=3, is_ref=0)='蛋蛋1578154814'
a: (refcount=2, is_ref=0)='蛋蛋1578154814'
b: (refcount=1, is_ref=0)='蛋蛋2号'
脚本结束

注意,xdebug_debug_zval函数是xdebug扩展的,使用前必须安装xdebug扩展

引用计数特殊情况

当变量值为整型,浮点型时,在赋值变量时,php7底层将会直接把值存储(php7的结构体将会直接存储简单数据类型),refcount将为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$a = 1111;
$b = $a;
$c = 22.222;
$d = $c;

xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
xdebug_debug_zval('d');
echo "脚本结束\n";


//输出

a: (refcount=0, is_ref=0)=1111
b: (refcount=0, is_ref=0)=1111
c: (refcount=0, is_ref=0)=22.222
d: (refcount=0, is_ref=0)=22.222
脚本结束

当变量值为interned string字符串型(变量名,函数名,静态字符串,类名等)时,变量值存储在静态区,内存回收被系统全局接管,引用计数将一直为1(php7.3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$str = '蛋蛋';    // 静态字符串
$str = '蛋蛋' . time();//普通字符串


$a = 'aa';
$b = $a;
$c = $b;

$d = 'aa'.time();
$e = $d;
$f = $d;

xdebug_debug_zval('a');
xdebug_debug_zval('d');
echo "脚本结束\n";

//输出

a: (refcount=1, is_ref=0)='aa'
d: (refcount=3, is_ref=0)='aa1578156506'
脚本结束

当变量值为以上几种时,复制变量将会直接拷贝变量值,所以将不存在多次引用的情况

###引用时引用计数变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$a = 'aa';
$b = &$a;
$c = $b;

xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
echo "脚本结束\n";

//输出

a: (refcount=2, is_ref=1)='aa'
b: (refcount=2, is_ref=1)='aa'
c: (refcount=1, is_ref=0)='aa'
脚本结束

当引用时,被引用变量的value以及类型将会更改为引用类型,并将引用值指向原来的值内存地址中.

之后引用变量的类型也会更改为引用类型,并将值指向原来的值内存地址,这个时候,值内存地址被引用了2次,所以refcount=2.

而$c并非是引用变量,所以将值复制给了$c,$c引用还是为1

详细引用计数知识,底层原理可查看:https://www.cnblogs.com/sohuhome/p/9800977.html

php生命周期

php将每个运行域作为一次生命周期,每次执行完一个域,将回收域内所有相关变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
/**
* Created by PhpStorm.
* User: Tioncico
* Date: 2020/1/6 0006
* Time: 14:22
*/

echo "php文件的全局开始\n";

class A{
protected $a;
function __construct($a)
{
$this->a = $a;
echo "类A{$this->a}生命周期开始\n";
}
function test(){
echo "类test方法域开始\n";
echo "类test方法域结束\n";
}
//通过类析构函数的特性,当类初始化或回收时,会调用相应的方法
function __destruct()
{
echo "类A{$this->a}生命周期结束\n";
// TODO: Implement __destruct() method.
}
}

function a1(){
echo "a1函数域开始\n";
$a = new A(1);
echo "a1函数域结束\n";
//函数结束,将回收所有在函数a1的变量$a
}
a1();

$a = new A(2);

echo "php文件的全局结束\n";
//全局结束后,会回收全局的变量$a

可看出,每个方法/函数都作为一个作用域,当运行完该作用域时,将会回收这里面的所有变量.

再看看这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
echo "php文件的全局开始\n";

class A
{
protected $a;

function __construct($a)
{
$this->a = $a;
echo "类{$this->a}生命周期开始\n";
}

function test()
{
echo "类test方法域开始\n";
echo "类test方法域结束\n";
}

//通过类析构函数的特性,当类初始化或回收时,会调用相应的方法
function __destruct()
{
echo "类{$this->a}生命周期结束\n";
// TODO: Implement __destruct() method.
}
}

$arr = [];
$i = 0;
while (1) {
$arr[] = new A('arr_' . $i);
$obj = new A('obj_' . $i);
$i++;
echo "数组大小:". count($arr).'\n';
sleep(1);
//$arr 会随着循环,慢慢的变大,直到内存溢出

}

echo "php文件的全局结束\n";
//全局结束后,会回收全局的变量$a

全局变量只有在脚本结束后才会回收,而在这份代码中,脚本永远不会被结束,也就说明变量永远不会回收,$arr还在不断的增加变量,直到内存溢出.

内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function a(){
class A {
public $ref;
public $name;

public function __construct($name) {
$this->name = $name;
echo($this->name.'->__construct();'.PHP_EOL);
}

public function __destruct() {
echo($this->name.'->__destruct();'.PHP_EOL);
}
}

$a1 = new A('$a1');
$a2 = new A('$a2');
$a3 = new A('$3');

$a1->ref = $a2;
$a2->ref = $a1;

unset($a1);
unset($a2);

echo('exit(1);'.PHP_EOL);
}
a();
echo('exit(2);'.PHP_EOL);

当$a1和$a2的属性互相引用时,unset($a1,$a2) 只能删除变量的引用,却没有真正的删除类的变量,这是为什么呢?

首先,类的实例化变量分为2个步骤,1:开辟类存储空间,用于存储类数据,2:实例化一个变量,类型为class,值指向类存储空间.

当给变量赋值成功后,类的引用计数为1,同时,a1->ref指向了a2,导致a2类引用计数增加1,同时a1类被a2->ref引用,a1引用计数增加1

当unset时,只会删除类的变量引用,也就是-1,但是该类其实还存在了一次引用(类的互相引用),

这将造成这2个类内存永远无法释放,直到被gc机制循环查找回收,或脚本终止回收(域结束无法回收).

手动回收机制

在上面,我们知道了脚本回收,域结束回收2种php回收方式,那么可以手动回收吗?答案是可以的.

手动回收有以下几种方式:

unset,赋值为null,变量赋值覆盖,gc_collect_cycles函数回收

unset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A
{
public $ref;
public $name;

public function __construct($name)
{
$this->name = $name;
echo($this->name . '->__construct();' . PHP_EOL);
}

public function __destruct()
{
echo($this->name . '->__destruct();' . PHP_EOL);
}
}

$a = new A('$a');
$b = new A('$b');
unset($a);
//a将会先回收
echo('exit(1);' . PHP_EOL);
//b需要脚本结束才会回收

//输出
$a->__construct();
$b->__construct();
$a->__destruct();
exit(1);
$b->__destruct();

unset的回收原理其实就是引用计数-1,当引用计数-1之后为0时,将会直接回收该变量,否则不做操作(这就是上面内存泄漏的原因,引用计数-1并没有等于0)

=null回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A
{
public $ref;
public $name;

public function __construct($name)
{
$this->name = $name;
echo($this->name . '->__construct();' . PHP_EOL);
}

public function __destruct()
{
echo($this->name . '->__destruct();' . PHP_EOL);
}
}

$a = new A('$a');
$b = new A('$b');
$c = new A('$c');
unset($a);
$c=null;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');

echo('exit(1);' . PHP_EOL);

=null和unset($a),作用其实都为一致,null将变量值赋值为null,原先的变量值引用计数-1,而unset是将变量名从php底层变量表中清理,并将变量值引用计数-1,唯一的区别在于,=null,变量名还存在,而unset之后,该变量就没了:

1
2
3
4
5
6
7
8
9
10
$a->__construct();
$b->__construct();
$c->__construct();
$a->__destruct();
$c->__destruct();
a: no such symbol //$a已经不在符号表
b: (refcount=1, is_ref=0)=class A { public $ref = (refcount=0, is_ref=0)=NULL; public $name = (refcount=1, is_ref=0)='$b' }
c: (refcount=0, is_ref=0)=NULL //c还存在,只是值为null
exit(1);
$b->__destruct();

变量覆盖回收

通过给变量赋值其他值(例如null)进行回收:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class A
{
public $ref;
public $name;

public function __construct($name)
{
$this->name = $name;
echo($this->name . '->__construct();' . PHP_EOL);
}

public function __destruct()
{
echo($this->name . '->__destruct();' . PHP_EOL);
}
}

$a = new A('$a');
$b = new A('$b');
$c = new A('$c');
$a=null;
$c= '练习时长两年半的个人练习生';
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');

echo('exit(1);' . PHP_EOL);

//输出

$a->__construct();
$b->__construct();
$c->__construct();
$a->__destruct();
$c->__destruct();
a: (refcount=0, is_ref=0)=NULL
b: (refcount=1, is_ref=0)=class A { public $ref = (refcount=0, is_ref=0)=NULL; public $name = (refcount=1, is_ref=0)='$b' }
c: (refcount=1, is_ref=0)='练习时长两年半的个人练习生'
exit(1);
$b->__destruct();

可以看出,c由于覆盖赋值,将原先A类实例的引用计数-1,导致了$c的回收,但是从程序的内存占用来说,覆盖变量并不是意义上的内存回收,只是将变量的内存修改为了其他值.内存不会直接清空.

gc_collect_cycles

回到之前的内存泄漏章节,当写程序不小心造成了内存泄漏,内存越来越大,可是php默认只能脚本结束后回收,那该怎么办呢?我们可以使用gc_collect_cycles 函数,进行手动回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function a(){
class A {
public $ref;
public $name;

public function __construct($name) {
$this->name = $name;
echo($this->name.'->__construct();'.PHP_EOL);
}

public function __destruct() {
echo($this->name.'->__destruct();'.PHP_EOL);
}
}

$a1 = new A('$a1');
$a2 = new A('$a2');

$a1->ref = $a2;
$a2->ref = $a1;

$b = new A('$b');
$b->ref = $a1;

echo('$a1 = $a2 = $b = NULL;'.PHP_EOL);
$a1 = $a2 = $b = NULL;
echo('gc_collect_cycles();'.PHP_EOL);
echo('// removed cycles: '.gc_collect_cycles().PHP_EOL);
//这个时候,a1,a2已经被gc_collect_cycles手动回收了
echo('exit(1);'.PHP_EOL);

}
a();
echo('exit(2);'.PHP_EOL);

//输出

$a1->__construct();
$a2->__construct();
$b->__construct();
$a1 = $a2 = $b = NULL;
$b->__destruct();
gc_collect_cycles();
$a1->__destruct();
$a2->__destruct();
// removed cycles: 4
exit(1);
exit(2);

注意,gc_colect_cycles 函数会从php的符号表,遍历所有变量,去实现引用计数的计算并清理内存,将消耗大量的cpu资源,不建议频繁使用

另外,除去这些方法,php内存到达一定临界值时,会自动调用内存清理(我猜的),每次调用都会消耗大量的资源,可通过gc_disable 函数,去关闭php的自动gc

通过PHP生成PHP

背景介绍

百度收录的页面比较慢,最近新的三方流以及方案页面发布出来,想着每天多一点抓取。现在整体的方案是:

  1. 页面有点击的时候, js 自动推送
  2. 通过更新 sitemap.xml 自动让蜘蛛来抓取
  3. 通过脚本主动推送新页面到引擎

今天主要是上述的第三点,主动推送新页面到百度引擎中去。尝试的方法是通过 php 代码生成 php 代码。

注意事项

  1. EOF 的结尾空格问题
  2. EOF 中的 $ 符号要转义
  3. 纯属手痒尝试,模仿请自行负责

代码留存备份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
include('head.php');
$sql = "select * from flow where published='y'";

$f_values = '';
$f_values .=<<<EOF
<?php
\$urls = [
EOF;
$flows = get_table_data($sql,$mysqli_space);
if(false == empty($flows))
{
foreach($flows as $flow)
{
$f_values.=<<<EOF
'https://www.12306.com/flow-{$flow['d_id']}.html',
EOF;
$f_values.=PHP_EOL;
}
}
//案例详情
$sql_bcase = "select * from beautycase where published='y'";

$cases = get_table_data($sql_bcase,$mysqli_space);
if(false == empty($cases))
{
foreach($cases as $case)
{
$f_values.=<<<EOF
'https://www.12306.com/beautycase-{$case['d_id']}.html',
EOF;
$f_values.=PHP_EOL;
}
}


$f_values.=<<<EOF
];
\$api = 'http://data.zz.baidu.com/urls?site=www.12306.com&token=abcdefg';
\$ch = curl_init();
\$options = array(
CURLOPT_URL => \$api,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => implode("\n", \$urls),
CURLOPT_HTTPHEADER => array('Content-Type: text/plain'),
);
curl_setopt_array(\$ch, \$options);
\$result = curl_exec(\$ch);
echo \$result;
EOF;
file_put_contents('/www/wwwroot/TransDataToPub/putUrls.php',$f_values);