新的博客

实在是受不了 Wordpress 的垃圾评论了,于是谋划了半年之后,终于有闲心把一些博客内容迁移到 hexo 来。
好在之前就是用 wp-markdown 插件来写博客,迁移的过程只是需要添加一些 title 和修改代码的标记而已。

所以,2015年了,希望能有更新的面貌!

proxychains不能解析dns时的解决办法

最近在摆弄cubieboard,装了proxychains却不能科学上网。总是提示:

1
2
3
/usr/lib/proxychains3/proxyresolv: 16: /usr/lib/proxychains3/proxyresolv: dig: not found
|DNS-response|: * is not exist
curl: (6) Couldn't resolve host '*'

后来一搜,发现原因是libproxychains3这个库需要dnsutils这个依赖,但是忘了写在包管理器了。

1
apt-get install dnsutils

安装之后完破,然后就可以科学上网了:P

使用Python和pyexiv2批量修改EXIF信息

虽然说windows的文件管理器也可以批量改日期,但是对拍摄时间束手无策…强迫症的我不容许这样的事情发生,于是在sutar的怂恿下开始了批量改EXIF的探索。

首先是工具的选择,采用了狂霸酷炫的Python和栈溢出推荐的pyexiv2

pyexiv还提供了windows的安装包…很狂霸酷炫…

之后就照着pyexiv官方教程开始探索,一开始发现修改Exif.Image.DateTime并不能影响windows里的拍摄日期,后来发现是影响拍摄日期的项目是Exif.Photo.DateTimeOriginal,修改成功后一本满足。

之后就是用python遍历修改的文件夹,然后就可以批量修改啦,因为我用了吧.py文件丢在工作目录下自动全部改的淳朴.bat思路,于是还得注意筛选。其实放在上一级目录,打开下一集目录就能不用筛选了。

挺简单的程序,也没有什么算法,就直接贴代码啦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pyexiv2
from datetime import timedelta
from os import listdir
import os

def add(path,delta):
meta = pyexiv2.ImageMetadata(path)
meta.read()
meta['Exif.Photo.DateTimeOriginal'].value += delta
meta.write()

delta = timedelta(hours=6)

pwd = os.getcwd()
for item in listdir(pwd):
if item[-3:]=='JPG':
path = pwd + '\\' + item
print path
add(path,delta)

新浪PHP开发工程师笔/面试总结八·PHP实现快速排序和归并排序

不出意外应该是最后一篇了…接下来的日子要安心弄一门语言了

用PHP实现快排和归并排序花了我一个晚上加一个白天的时间…总结这篇估计也要2个小时…

1、快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function qsort($seq) {
$count = count($seq);
if( $count > 1 ) {
$pivot = $seq[0];
$less = array();
$greater = array();
for ($i = 1; $i < $count; $i++) {
if ($seq[$i]<=$pivot) {
$less[] = $seq[$i];
} else {
$greater[] = $seq[$i];
}
}
$less = qsort( $less );
$greater = qsort( $greater );
return array_merge($less,array($pivot),$greater);
} else {
return $seq;
}
}

这个是快速排序的简单版本,和维基百科上的完全一样,不过上述程序一度无法运行,一直提示内存耗尽…

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 40 bytes)

后来检查了代码,发现错在对基准数pivot的处理上,一开始我为了想标新立异,想弄个和维基不一样的解法。

原来的想法大概是:pivot放在less那边,然后merge的时候就只需要merge(less,greater)就可以了…

后来发现这样做的话,less子数组就永远≥2了,所以分片操作会一直进行直到内存耗尽…所以和维基上的相比也就没有改进了…

之后是快排的in-place版:

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
function quicksort(&$seq,$left,$right){
if($right>$left){
$pivot_index = $left;
$new_pivot_index = partition($seq,$left,$right,$pivot_index);
quicksort($seq,$left,$new_pivot_index-1);
quicksort($seq,$new_pivot_index+1,$right);
}
}
function partition(&$seq,$left,$right,$pivot_index){
swap($seq[$pivot_index],$seq[$right]);
$store_index = $left;
for($i=$left;$i<$right;$i++){
if($seq[$i]<=$seq[$right]){
swap($seq[$store_index],$seq[$i]);
$store_index++;
}
}
swap($seq[$store_index],$seq[$right]);
return $store_index;
}
function swap(&$one,&$two){
$temp = $two;
$two = $one;
$one = $temp;
}
function entry(&$seq){
quicksort($seq,0,count($seq)-1);
}

原地分区(in-place)版的好处是不需要额外的存储空间,但是理解起来还是花了一些时间。

以上代码由三个主要函数组成,quicksort()函数负责控制迭代过程;partition()函数负责分区,在分区的过程中实现排序;最后是swap()函数负责交换元素的次序。

需要理解的地方就是分区函数partition()的实现,store_index这个“指针”是分区的依据,通过store_index指针,把比基准数小的数全部换到前面(“左边”区域),然后把基准数交换到左边区域的右边一个元素。以这种方式让左区都小于基准,右区都大于基准。在实现in-place版时,我就是对着伪代码写的,所以一次就成功了。

维基百科上还说,随机选择基准数的索引index可以让运算更有效率…感觉不是很难,就没实现…

2、归并排序

归并排序在维基上的词条里没有伪代码和php实现,最接近的C++实现也好长…只好自己闷头“理解了一会”。最后得出的结论大概也是不停地分子数组,直到每个数组的元素都只有一个,然后进入归并的进程,代码实现如下:

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
function merge_sort($seq){
$size = count($seq);
if($size>1){
$mid_count = floor($size/2);
$sub_left = merge_sort(array_slice($seq,0,$mid_count));
$sub_right = merge_sort(array_slice($seq,$mid_count));
return merge($sub_left,$sub_right);
} else {
return $seq;
}
}

function merge($sub_left,$sub_right){
$result = array();
while(!empty($sub_left)&&!empty($sub_right)){
if($sub_left[0]<$sub_right[0]){
array_push($result,array_shift($sub_left));
} else {
array_push($result,array_shift($sub_right));
}
}
while(!empty($sub_left))
array_push($result,array_shift($sub_left));
while(!empty($sub_right))
array_push($result,array_shift($sub_right));
return $result;
}

为了实现上述算法,我还看了一下array_push()array_shift()array_slice()三元运算符的用法…因为感觉上面的代码太长了,想用三元运算符压缩一下:

array_push($arr_c,array_shift($arr_one[0]<$arr_two[0]?$arr_one:$arr_two));

其实最后执行起来和用if应该没什么速度上的差别…不过就是看起来比较爽,不料竟然跑不动…会提示以下内容:

Fatal error: Only variables can be passed by reference in /var/www/shift.php on line 5

在array_shift函数的文档下有人发了如下解释:

I haven’t really read into it, but if you’re complaining about a change in PHP 5.0.5 that made it so you couldn’t do: $val = array_shift(preg_split()); or $val = array_shit(function_that_returns_array); Then you’re not using this function correctly. This function’s argument is supposed to be a pointer to a variable. It then modifies that variable and returns a value. When you specify a function, php CAN NOT modify the return value of that function. It should be common sense but apparently its not.

大意是array_shift函数应该传入一个数组的指针,因为这个函数不能对另一个函数返回的数组(函数的返回值)进行操作。

后来就用if实现了…不过通过调试这个bug,我也知道了三元选择符的用法,还专门验证了分片函数是否可用,也算积累了一点经验吧!

3、写在最后的话:

这个面经完成历时一个月…中间穿插了各种毕业活动以及搬行李回家等等过程,能够写完真是可喜可贺。通过这次面试,我最切身的体会就是想要学好一门语言,务必要把这些基本的内容都自己敲一遍跑一跑,最好自己想改进一下,然后弄出点bug,然后对很多功能就可以理解得更深了。

所以,我还没有上路呢…接下来的时间要好好努力了!

新浪PHP开发工程师笔/面试总结七·关于SQL

SQL题目考查了建立索引,limit的用法,面试的时候还问了知道的数据库引擎的类型有哪些…虽然自己考过四级数据库…不过俨然已经忘光光了…(更何况还没过!)

1、limit的用法

limit的用法就太简单了…

1
select * from table limit index,offset

也就是说,要6~10元素,就是

1
limit 5,5

比较容易弄错的就是index啦…从0开始数什么的。

2、索引的用法

感觉索引的语法很简单,但是用起来讲究很多…发现现在已经没有精力探讨后者了,就简单讲讲语法吧…参考了官方文档的说法:

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name 
ON table_name (col_name[length][ASC | DESC],...)

这篇讲索引的文章挺长的,以后考虑看看:P

3、数据库引擎

擦…写着写着,服务器竟然挂掉了…官网也上不去了,只能静待恢复了…

关于MySQL的数据库引擎,维基里是这样说的:

Independent storage engines (MyISAM for read speed, InnoDB for transactions and referential integrity, MySQL Archive for storing historical data in little space)

具体就不翻译了…大致上就有MyISAMInnoDBMySQL Archive三个比较常用于MySQL的存储引擎,其中:MySQL Archive是专门用来存储一些归档的历史数据的,这很新鲜。InnoDB和MyISAM的最大区别是是否支持事务(transaction),据说InnoDB就是这样“慢慢取代MyISAM”的?不过“MyISAM在5.5版之前的MySQL里是默认引擎”,具有读取性能比较好的特点。

以上这些拿来回答面试应该够了吧!

困死了…睡觉!

新浪PHP开发工程师笔/面试总结六·零碎的

这个系列要加快完结了…大概还有SQL limit的用法和PHP实现几种排序两篇,今天就把一些零碎的功能说一下。awk和sed以后有机会再看好了…正则也是…

1、error reporting的用法

error_reporting() 函数能够在运行时设置 error_reporting 指令。 PHP 有诸多错误级别,使用该函数可以设置在脚本运行时的级别。 如果没有设置可选参数 level, error_reporting() 仅会返回当前的错误报告级别。

感觉直接贴样例已经足够直观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 关闭所有PHP错误报告
error_reporting(0);

// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// 报告 E_NOTICE也挺好 (报告未初始化的变量
// 或者捕获变量名的错误拼写)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// 除了 E_NOTICE,报告其他所有错误
// 这是在 php.ini 里的默认设置
error_reporting(E_ALL ^ E_NOTICE);

// 报告所有 PHP 错误 (参见 changelog)
error_reporting(E_ALL);

// 报告所有 PHP 错误
error_reporting(-1);

// 和 error_reporting(E_ALL); 一样
ini_set('error_reporting', E_ALL);

2、include和require的区别

includerequire的区别,手册里有官方说明。

require 和 include 几乎完全一样,除了处理失败的方式不同之外。 require 在出错时产生 E_COMPILE_ERROR 级别的错误。换句话说将导致脚本中止而 include 只产生警告(E_WARNING),脚本会继续运行。

这大概就是为什么引用html多用include,引用php多用require的原因吧。当时我只猜到了结局,没猜到原因。

此外include竟然是可以返回值的,比如

1
$foo = include('bar.php')

此时bar.php里面如果有:

1
2
return "success"
`

那么

1
$foo = "success"

如果没有返回语句并且引用成功的话,则返回1。

3、PHP遍历文件夹

PHP 5 引入了一个新函数scandir,输入一个目录,可以返回一个包含目录下所有文件的数组,形式如下:

1
array scandir ( string $directory [, int $sorting_order [, resource $context ]] )

其中,$sorting_order如果是1,那么输出的文件就是按字母降序的,不填时默认按字母顺序升序。$context感觉好复杂…就先不看了。

要想遍历,就需要迭代,控制是否打开下一级目录可以借助is_dir()函数来判断给定文件名是否为目录。使用方法如下:

1
bool is_dir ( string $filename )

了解了上述两个函数之后,遍历文件夹就没问题了。

吐槽一下…要不是朋友提醒我wordpress不能换主题,我还不知道scandir是个危险的函数呢…默认被lnmp一键安装包禁用了…

具体的就是些拼接的工作…有空再写好了!

4、查看客户端IP和服务端IP

查看客户端和服务端ip,有个超全局变量$_SERVER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$_SERVER['REMOTE_ADDR']//客户端
$_SERVER['SERVER_ADDR']//服务器
```

真是太容易了…

5、==和===的区别

就是===要求类型也一样,比如
```php
$a = 3;
$b = '3';

echo $a == $b;//1
echo $a === $b;//没有输出
echo $a === 3;//1

不知道为啥第二行没有输出…今天困了,就睡了吧…

新浪PHP开发工程师笔/面试总结五·时间函数的用法

你竟然没有用过时间函数?

面试官如是说…

这是考察PHP实际运用能力的第一道题,当时看到这道题的时候也确实有点自惭形秽了…无论学什么语言,获取并显示时间都是基础得不能再基础的内容了吧。

官方手册里对date()函数的描述是:

1
string date ( string $format [, int $timestamp ] )

其中,$format字段是用来描述输出的date的形势,后面的$timestamp默认是time(),也就是当前时间。看到手册里的“Unix纪元”几个字的时候有点吓尿的感觉。

返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数。

当时也不是没打算猜一下该怎么写,不过这个$format字段到底要怎么写确实是毫无印象了…掌握好format,显示时间就轻松愉快了。如:

想输出 Jun 12th, 2013 00:42:11, 则是

echo date("M jS, Y H:i:s");

常用的$format字段:

  • 年份:

Y 四位数年份;y 两位数年份;

L 是否为闰年,bool

  • 月份:

数字月份:m 有前导零;n 无前导零

文字月份:F 完整January; M 三字缩写Jan

t 返回当前月份天数

  • 日:

星期中的第几日:N 数字1~7; w 数字0~6;

星期几: D 文本Mon到Sun; l L的小写,文本Sunday到Saturday

月份中的第几日: d 有前导零; j 无前导零; S 月份英文后缀th等

年份中的第几日: z

  • 周:

年份中的第几周:W

  • 时间:

上午/下午:a am/pm; A AM/PM

时:g/G 12/24小时制,无前导零;h/H 12/24小时制,有前导零;

分:i 00~59分,有前导零

秒:s 00~59秒,有前导零

此外还有时区的,貌似用不着,先不总结了。

新浪PHP开发工程师笔/面试总结四·佳佳和return的问题

面试的时候,面试官问我:

你觉得return $a++;和$ret = $a++;return $ret;不一样么?

当时头脑已经有点意识模糊了,就觉得return好像比较屌一点,会等整个函数运行完再返回值。

现在实际测试了之后发现我真的意识模糊了,

1
2
3
4
5
6
7
8
$ret = 1;
function add(){
global $ret;
return $ret++;
}
echo add();//1
echo "<br/>$ret"//2
`

说明程序是可以在函数return之后继续运行的,也就是说返回$ret当前的值之后,++的操作并没有因为return而中断,$ret变量还是自增了。

++放前面和放后面的方法学C的时候已经学得挺清楚了,就是调用前自增和调用后自增的问题,现在怎么想不明白了呢?真是越学越傻啊…

新浪PHP开发工程师笔/面试总结三·静态变量和其他变量范围问题

接上回说到,变量范围里还有一个static关键字,即静态变量。PHP手册里对静态变量的说明是:

静态变量仅在局部函数域中存在,但当程序执行离开此作用域时,其值并不丢失。

静态变量也提供了一种处理递归函数的方法。递归函数是一种调用自己的函数。写递归函数时要小心,因为可能会无穷递归下去。必须确保有充分的方法来中止递归。


1
2
3
4
5
6
7
8
function test(){
static $count = 0;
echo $count ."\n";
$count++;
}
test();//输出0
test();//输出1
test();//输出2

还有递归调用时:

1
2
3
4
5
6
7
8
function test(){
static $count = 0;
echo $count ."\n";
$count++;
while($count<10)
test();
}
test();

则输出0到9。

此外,static关键字不能用表达式赋值,比如

1
static $a = 1+2;

是不可以的。

此外,static还有一个容易混淆的部分,就是声明类的时候如果使用了static变量,那么这个变量会对这个类的所有对象共享(changing value of a static in one instance changes value in all instances)。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class static_class{
public function func_static(){
static $count = 0;
$count++;
echo $count;
}
}

$a = new static_class();
$b = new static_class();

$a->func_static();//输出1
$b->func_static();//输出2
$a->func_static();//输出3

调试上述程序的时候还出现了个有趣的现象,感觉变量作用域的问题还是需要非常小心的。比如说如果希望每个类都有单独的变量的话,如果不慎把声明和使用写成如下形式:

1
2
3
4
5
private $count = 0;
public function func_static(){
$count++;
echo $count;
}

那么上述测试代码就会输出三个1。一开始的时候还怀疑过是不是用private声明有问题,后来才发现类里面的函数如果要调用类里面的对象,需要加入$this->才行。因为把前面的声明改成$count = 2还是会输出三个1。看来测试的时候要慎重选取初始值呀,等于0这样的表达式虽然写得很顺手,但是有可能会掩盖掉真正出错的原因。在上述例子里面出错的原因就是func_static函数里没有出现过$count变量,所以PHP就新建了一个局部的$count=0,所以才会输出三个1啦。

写成这样就可以了:

1
2
3
4
5
private $count = 0;
public function func_static(){
$this->count++;
echo $this->count;
}

本着现学现用的思想,在func_static里面声明引用也可以很好的解决上述问题,而且不用打这么多个$this,即

1
$count =& $this->count;

总结:

  • static关键字声明的变量在程序运行时值会一直保持,不会因为多次调用时被重复声明覆盖。
  • static关键字声明的变量在同一个类的不同实例里值会共享。

新浪PHP开发工程师笔/面试总结二·全局变量

PHP的官方文档里有这样一篇文章,很详细地说明了global关键字、$GLOBALS数组和static关键字的问题。其中关于全局变量的讨论:

总结如下:

  • global关键字建立了局部变量和全局变量的引用,从而可以在局部函数里操作全局变量。
  • GLOBALS数组是一个超全局变量,是“在全部作用域中始终可用的内置变量”。在全局声明的所有函数都会自动出现在GLOBALS数组中。
  • global关键字的效果会被新的新建引用语句”=&”覆盖。
  • 特别地,global的应用范围是“顶层”而不是更高一层。(全局变量里的note里提到的)

吐槽一下wp-markdown好难用…分隔线和前面的序号老是混在一起…

那么下面就来具体讨论一下PHP中的全局变量的用法:

在PHP中,用户自定义函数中会产生一个局部变量区域,因此函数内的变量缺省只在函数内生效。global关键字和$GLOBALS数组的出现大概就是为了解决全局变量在局部变量区域内的引用问题而出现的。

例如

1
2
3
4
5
$a=1;
function Test(){
echo $a;//这里的$a引用了一个 原来不存在的 后来函数自己声明的 局部$a
}
Test();

不会有任何输出。


1
2
3
4
5
6
function Test(){
global $a;
echo $a;
echo $GLOBALS['a'];
}
Test();

则都会有输出。

而且还有一种有趣的现象,即函数内部新声明的全局变量也可以在函数外部调用。不知道怎么用比较严谨的说法表示了,就直接举例吧:

1
2
3
4
5
6
function declare(){
global $not;
$not =5;
}
declare();
echo $not;//此处输出5

即使在函数外部从来没有声明过$not变量,但是只要在函数内添加了global关键字,就可以在函数外调用了。

关于global和$GLOBALS的区别,在官方文档的“变量范围”里讲得不是很清楚,但是在“引用做什么”里的一句话感觉做了很好的总结:

把global $var;当成是$var=&GLOBALS[‘var’];的简写,从而将其他引用赋给$var只改变了本地变量的引用。

可以写一个函数验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function vartest(){
global $var1,$var2;
$var1 = $var2 = 1;
$var1 =& $GLOBALS['var2'];
$var1 = 2;
echo "local \$var1 = $var1\n";//此处输出2
echo "local \$var2 = $var2\n";//此处输出2
}
vartest();
echo "global \$var1 = $var1\n";//此处输出1
echo "global \$var2 = $var2\n";//此处输出2

function globaltest(){
$var3 =& $GLOBALS['var3'];
$var3 = 5;
}
echo "global \$var3 = $var3\n";//此处没有输出

实际操作时发现:

  • global关键字改变是局部变量的引用;
  • 这个引用可以通过建立新的引用覆盖;
  • global关键字和新建引用语句并不完全相同,未经global关键字声明的函数不能被函数外调用。