博客服务器换 Linode 啦

之前使用的服务器因为一些原因用不了,前天购买了一台阿里云的最低配置 512M 的 VPS,绑定了域名,还没等我把数据库导入,昨天访问域名的时候就开始提示我需要备案,放弃之。今天购买了 Linode 最低配置款(2G 内存,20 美元一月),没多犹豫,选择了位于东京的机房,之前申请的招行的 VISA 信用卡这次终于发挥了作用。

Linode 感觉很不错,用阿里云的时候可选择系统基本比较少,并且系统比较老(比如,阿里云的只有 Ubuntu 12.04),而 Linode 可以安装各个几乎你能听说过的所有 Linux 发行版,并且还都是最新的版本,我装了 Ubuntu 14.04 LTS 版。几条命令安装好 nginx fpm mysql 环境,导入数据库,上传博客文件,博客很快就恢复了。

使用 Linode 的感觉就是快,apt-get 安装包的时候几乎没有等待下载时间,速度简直是飞快。

这台 VPS 是 2G 内存,目前只跑了我的这一个博客,性能大大的剩余啊,如果有朋友有独立博客需要托管的,并且信任本人,可以将博客放在我这台 VPS 上。要求很简单,希望你的博客站点是一个健康的站点,不会对 VPS 照成太大压力,本人可以提供普通权限的 ssh 账号,可以通过 sftp 管理你的代码,提供一个数据库及数据库连接账号,如果需要安装什么软件,或者有重启服务的需求可以跟我说明。本人以人格担保(也没有别的什么能担保的了)不会在未经你同意的情况查看你的任何代码。至于数据安全,我只能保证,如果我的博客数据是安全的,那你的数据也是安全的。有需要请来邮件 i#upliu.net,邮件里面请注明你要绑定的域名,你的站点需要的一些特殊配置(比如需要什么额外扩展),我会为你开通,并邮件回复通知你。

tar 解压常用压缩文件格式命令大全

常用压缩文件格式就那么几种,解压命令总结在此:

tar xzf filename.tar.gz
tar xjf filename.tar.bz2
tar xzf filename.tgz
tar xf filename.tar
xz -d filename.tar.xz; tar xf filename.tar # 这里解压一个 tar.xz 格式文件分成了两步,xz -d 会解压得到 filename.tar 文件,然后 tar 命令进行解包
unrar filename.rar
unzip filename.zip
7z x filename.7z

对 tar 命令参数说明:

  1. x 是解压,对应的 c 是压缩
  2. f 表示要解压文件(解压一个文件时 xf 这两个参数必带)
  3. v 输出解压过程(这个参数可选,没有则不输出)
  4. z 解压被 gzip 压缩过的文件,也就是 tar.gz 或者 tgz 格式的文件
  5. j 解压被 bzip2 压缩过的文件,也就是 tar.bz2 格式的文件

注意,有很多人搞不清楚打包和压缩的区别,tar 本身是一个打包命令,一般来说,以 tar 格式结尾的文件是由 tar 打包生成的文件,tar.gz(tar.bz2) 是打包后用 gzip(bzip2) 算法压缩过,实际上解压一个 tar.gz(tar.bz2) 文件是分两步进行的,先用 gzip(bzip2) 解压缩,然后用 tar 解开包。

configure: error: Cannot locate header file libintl.h 错误的解决方法

MAC OS 上编译 PHP 时,在 configure 配置阶段出现如题所示错误。找不到 libintl.h 头文件。

解决方法如下:

1. 安装 gettext:

brew install gettext

2. 编辑 configure 文件:

将:

for i in $PHP_GETTEXT /usr/local /usr ; do

更改为:

for i in $PHP_GETTEXT /usr/local /usr /usr/local/opt/gettext; do

3. 重新运行 ./configure 即可

深入理解 PHP 之 count 函数

曾有一次面试时面试官问我

count('abc')

会返回多少。当时我一下子懵了,不带这样玩的啊,count 函数不是用来计算数组元素个数的么,你传个字符串进去是几个意思啊。然后我满脸疑问并不自信的回答,是 3 吗,因为我觉得 PHP 的字符串也有时候可以表现得像数组,比如你可以用下标来取到字符串中对应位置的字符。面试官说是 1,然后我说没这样用过,一般都是用 count 来计算数组元素个数,面试官说,这就说明你代码量不够了。

写了简单文件,我们来看看给 count 函数传入非数组的参数时会返回什么:

<?php

var_dump(count('abc'));
var_dump(count(''));
var_dump(count(0));
var_dump(count(false));
var_dump(count(null));

$books = array(
	array(
		'name' => 'Pairs',
		'price' => 30,
	),
	array(
		'name' => 'Apple',
		'price' => 20,
	),
);

var_dump(count($books));
var_dump(count($books, COUNT_RECURSIVE));

上面代码输出如下:

int(1)
int(1)
int(1)
int(1)
int(0)
int(2)
int(6)

可以看到 count 在作用于非数组的变量时,除了 null,其它都返回 1,为什么会这样呢,我们来看看 PHP 有关于 count 函数的 C 源代码:

/* {{{ proto int count(mixed var [, int mode])
   Count the number of elements in a variable (usually an array) */
PHP_FUNCTION(count)
{
	zval *array;
	long mode = COUNT_NORMAL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE) {
		return;
	}

	switch (Z_TYPE_P(array)) {
		case IS_NULL:
			RETURN_LONG(0);
			break;
		case IS_ARRAY:
			RETURN_LONG (php_count_recursive (array, mode TSRMLS_CC));
			break;
		case IS_OBJECT: {
#ifdef HAVE_SPL
			zval *retval;
#endif
			/* first, we check if the handler is defined */
			if (Z_OBJ_HT_P(array)->count_elements) {
				RETVAL_LONG(1);
				if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value) TSRMLS_CC)) {
					return;
				}
			}
#ifdef HAVE_SPL
			/* if not and the object implements Countable we call its count() method */
			if (Z_OBJ_HT_P(array)->get_class_entry && instanceof_function(Z_OBJCE_P(array), spl_ce_Countable TSRMLS_CC)) {
				zend_call_method_with_0_params(&array, NULL, NULL, "count", &retval);
				if (retval) {
					convert_to_long_ex(&retval);
					RETVAL_LONG(Z_LVAL_P(retval));
					zval_ptr_dtor(&retval);
				}
				return;
			}
#endif
		}
		default:
			RETURN_LONG(1);
			break;
	}
}
/* }}} */

源码很明显,如果传入的参数为 null,则返回 0,如果传入的数组,则计算数组元素个数,如果是对象,则按对象进行相应处理(这个过程我不是太明白),重点在于最后的 default,也就是说其它任何类型都返回 1.

完。

各种语言对负数取模运算结果的对比

上次学 python 的时候发现负数的取模运算结果和 PHP 的不一样,于是今天就试了一下,对比下各编程语言的差异。代码如下(这里只给出 PHP 代码,完整的其它语言的代码请点这里):

#! /usr/bin/env php
<?php

printf("%d %d %d %d\n", 9 % 4, -9 % 4, 9 % -4, -9 % -4);
printf("%d %d %d %d\n", 8 % 4, -8 % 4, 8 % -4, -8 % -4);

上面的代码很简单,第一行是不能除尽的情况,第二行是可以除尽的情况。各种语言的输出结果如下:

c -----------------------------
1, -1, 1, -1
0, 0, 0, 0

java --------------------------
1 -1 1 -1
0 0 0 0

js ----------------------------
1 -1 1 -1
0 0 0 0

php ---------------------------
1 -1 1 -1
0 0 0 0

lua ---------------------------
1	3	-3	-1
0	0	0	0

perl --------------------------
1 3 -3 -1
0 0 0 0

python ------------------------
1 3 -3 -1
0 0 0 0

rb ----------------------------
1
3
-3
-1
0
0
0
0

sh ----------------------------
1 -1 -1 1
0 0 0 0

根据输出我们可以得到下面两个结论:

  1. 能被整除的情况下,各语言都是返回零。
  2. 不能整除的情况下,C style 风格的几种语言(c,java,js,php)输出的结果一致,另外几种语言(lua,perl,python,ruby)输出的结果一样,但与 C style 的结果不一样,shell 最特殊。

PHP 手册上面明确说明:取模运算符 % 的结果和被除数的符号(正负号)相同。即 $a % $b 的结果和 $a 的符号相同。计算规则是先按被除数和除数的绝对值进行计算,然后按规定相应地符号。

Python 取模的结果符号与除数一致。Python 取模运算两个操作数一正一负的情况下比 PHP 要复杂,具体怎么算的请看这里。Python2 的负数除法也有点奇怪啊,-26 / 20 的结果不是 -1 而是 -2,完全不符合直觉啊。Python3 里面 -26  / 20 的值是 -1.3。

PHP 中 array_replace 和 array_merge 区别

PHP 5.3.0 新增了一个函数 array_replace,和 array_merge 作用很相似。

先看下面代码示例:

<?php

$base = array(
	'name' => 'Lily',
	'age' => 20,
	'No index value',
);

$replace = array(
	'name' => 'Lucy',
	'addres' => 'Hubei',
	'No index value in array $replace',
);

print_r(array_merge($base, $replace));
print_r(array_replace($base, $replace));

上面代码输出为:

Array
(
    [name] => Lucy
    [age] => 20
    [0] => No index value
    [addres] => Hubei
    [1] => No index value in array $replace
)
Array
(
    [name] => Lucy
    [age] => 20
    [0] => No index value in array $replace
    [addres] => Hubei
)

很明显,array_replace 和 array_merge 的区别表现在处理数字索引的数据时,array_merge 会认为是不同的索引,不会进行覆盖,而 array_replace 则进行了覆盖。实际上 array_replace 和数组的 + 法运算的处理比较类似,但是参数的顺序得调换一下位置,下面的代码输出一致:

<?php

/* .....  数组定义同上 */

print_r(array_replace($base, $replace));
print_r($replace + $base);

 

没注意运算符优先级(漏掉括号)导致的一个 BUG

昨天新上线的一个项目,换了 $_SESSION 的处理方式。代码中有这样一条 SQL

$expiration = 1440 // <---- 这个在别的地方定义的
$sql = 'DELETE FROM session WHERE last_activity < ' . time() - $expiration;

线上代码运行过程不停的 warning ,说 SQL 语句执行出错,出错语句为 “-1440”,找了很长时间都没找到这个 BUG,后来终于找到那个 SQL  语句:

// 运行这一行语句,你会得到 -1400,而不是我们想要的 SQL 语句
echo 'DELETE FROM session WHERE last_activity < ' . time() - 1440;

为什么会是这样呢,因为字符串链接符 . 的优先级比运算符 – 要高,前面那一串先和 time() 返回的数字连成一个字符串,然后再与 1440 做减法运算,前面的一串字符串强制转换为数字 0,然后减 1440,就得到了 -1400,然后错误就出现啦。找到问题就好解决了,加上括号便 OK。

$expiration = 1440 // <---- 这个在别的地方定义的
$sql = 'DELETE FROM session WHERE last_activity < ' . (time() - $expiration);

话说这类错误很不好发现啊,项目里面那么多地方运行 SQL 查询,哪知道是哪里出错的呢。

看来有必要在 Log 里面里面记录函数调用栈,这样才能知道是哪里出错,能快速定位 BUG。

PHP CLI 模式输出彩色字符串

最近在写公司后台脚本程序代码。想到可以将不同的提示信息标注为不同的颜色,比如错误信息为红色,这样在监控输出地时候就可以将注意力集中在红色文字上。参考了别人的代码,做了一点小改动。如果有需要,请自取:(请注意,理论上该代码只能在 *nix 终端内能呈现彩色,Windows 不能用。)

ColorEcho: https://github.com/upliu/ColorEcho

MAC 上编译 PHP intl 扩展出现 Unable to detect ICU prefix or no failed 错误解决方法

进入扩展源码目录:

cd php-5.5.8/ext/intl

依次运行:

phpize
./configure

configure 这步会出错:

configure: error: Unable to detect ICU prefix or no failed. Please verify ICU install prefix and make sure icu-config works.

解决方法如下:

首先安装 icu4c:

brew install icu4c

然后:

./configure --with-icu-dir=/usr/local/opt/icu4c

接下来:

make && make install

大功告成!

Javascript 保留两位小数 保留多位小数

网上搜一番,发现很多手动算的文章,然后想起有 toFixed 方法就可以完成这个事情。我的疑问来了,不是有 toFixed 方法吗?干嘛要手动算,难道 IE6 不支持?我测试了一下,IE6 是支持的,直接 toFixed 就得了,还写函数干嘛?

var num = 123.456789;
alert(num.toFixed(2)); // 输出 123.46
alert(num.toFixed(3)); // 输出 123.457

alert(3.1415926.toFixed(2)); // 输出 3.14

// 下面是网上搜索到的函数
function formatFloat(src, pos)
{
    return Math.round(src*Math.pow(10, pos))/Math.pow(10, pos);
}

alert(formatFloat("1212.2323", 2));

我又想起 PHP 里面有需求是要获取微秒级别的时间戳,我们知道 time 是秒级的,PHP 里还有个函数是 microtime,这个函数默认返回字符串形式,要得到数字形式的怎么弄呢,网上还依然有很多博客(甚至时间为2012 2013年发布的文章,PHP5都出来10年了啊喂)在介绍下面这种老旧的方法:

function microtime_float(){ 
	list($usec, $sec) = explode(" ", microtime()); 
	return ((float)$usec + (float)$sec); 
}

但其实自 PHP 5.0 起 microtime 函数可以接受一个参数,如果为 true,则返回一个浮点数。

我相信,现在几乎没有不支持 PHP5 的环境了吧,microtime_float() 这类函数还有什么存在意义?