存档

‘程序员’ 分类的存档

php中set names与mysql_set_charset

2010年4月13日 闫鹏 没有评论

今天看到大家在讨论,发现这是个很严重而又容易疏忽的问题,我以前也一直是用set names,遂记录下来,也提醒自己一把。

1.set names与mysql_set_charset有什么区别?

一般情况下, 使用”SET NAMES”就足够了, 也是可以保证正确的. 那么为什么手册又要说推荐使用 mysqli_set_charset(PHP>=5.0.5)呢。手册里面也没有明确说明。我们可以看下php扩展的源代码:

//php-5.2.11-SRC/ext/mysqli/mysqli_nonapi.c line 342
PHP_FUNCTION(mysqli_set_charset)
{
    MY_MYSQL            *mysql;
    zval                *mysql_link;
    char                *cs_name = NULL;
    unsigned int        len;
    if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis()
                , "Os", &mysql_link, mysqli_link_class_entry, &cs_name, &len) == FAILURE) {
        return;
    }
    MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL*, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID);
    if (mysql_set_character_set(mysql->mysql, cs_name)) {
                //** 调用libmysql的对应函数
        RETURN_FALSE;
    }
    RETURN_TRUE;
}

可以看到php的mysql扩展是直接调用了mysql的mysql_set_character_set函数,接下来看看mysql的代码

//mysql-5.1.30-SRC/libmysql/client.c, line 3166:
int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name)
{
  struct charset_info_st *cs;
  const char *save_csdir= charsets_dir;
  if (mysql->options.charset_dir)
    charsets_dir= mysql->options.charset_dir;
  if (strlen(cs_name) < MY_CS_NAME_SIZE &&
     (cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(0))))
  {
    char buff[MY_CS_NAME_SIZE + 10];
    charsets_dir= save_csdir;
    /* Skip execution of "SET NAMES" for pre-4.1 servers */
    if (mysql_get_server_version(mysql) < 40100)       return 0;     sprintf(buff, "SET NAMES %s", cs_name);     if (!mysql_real_query(mysql, buff, strlen(buff)))     {       mysql->charset= cs;
    }
  }
  //以下省略

可以看到,除了调用real_query设置set names,还设置了mysql的charset变量。

2.这样有什么影响?

mysql_real_escape_string会受到影响,它与mysql_escape_string的区别就 是,  它会考虑”当前”字符集。如果仅仅使用set names,mysql_real_escape_string可能会失效。

例子:

$mysqli = new mysqli("localhost", "user", "pass", "test", 3306);

/* check connection */
if (mysqli_connect_errno()) {
    printf("Connect failed: %s\n", mysqli_connect_error());
    exit();
}

$mysqli->query('SET NAMES gbk'); //使用set names设置字符集
$city = chr(0xbf).chr(0x5c); //0xbf5c是个有效的gbk字符,模拟用户输入
$city = $mysqli->real_escape_string ($city);//使用real_escape进行过滤

/* this query will fail, cause we didn't escape $city */
 if (!$mysqli->query("INSERT into myCity(name) VALUES ('$city')")) {
    print "INSERT into myCity (name) VALUES ('$city')\n";
    printf("Error: %s\n", $mysqli->error);
}

var_dump($city);

var_dump($mysqli->client_encoding());

$mysqli->close();

3.解决方案

mysqli_set_charset函数对PHP和Mysql有版本要求,必须当mysql版本大于5,PHP版本大于5.0.5时,此函数才有效。至于另一个mysql_set_charset函数,则更要求PHP版本大于5.2.3时才能有效。对于mysql4.1以上版本,使用”SET character_set_client=binary;”
推荐使用mysql_set_charset设置字符集的方案,只有在环境不允许的情况下,我们才推荐使用第二种binary编码的方案。但是无论在什么情况下,都禁止使用”SET NAMES”来作为设置字符集的操作。

分类: 程序员 标签: ,

文件grep-选出两个文件中相同/不同的内容

2010年2月2日 闫鹏 没有评论

升级于2010-6-10,发现了一个bug,当时用notmatch模式时,如果文件2先eof将导致文件1中的内容没有输出

这个是最近做数据统计用的比较多的一个脚本,目的是根据key值,输出文件中相同或者不同的行。例如文件A的样子:

10000007^H_O
10000036^Hzerui
10000037^Hyanpeng_haha

文件B的样子:

10000037^Hyanpeng_haha

注意文件中的^H是一个字符,在vim中使用输入ctrl+v再输入ctrl+h就可以看到了,这里用它主要是起到文件不同列之间的分隔符作用

使用命令

php mygrep.php A B

可以得到“10000037^Hyanpeng_haha”这一行数据,使用命令

php mygrep.php A B “notmatch”

可以得到另外两行数据。

脚本默认使用第零列作为筛选依据,默认使用^H作为分隔符,在脚本中自己配置一下可以更改。脚本工作时,要求筛选依据是已排序的。对于排序,可以使用sort命令操作文件,sort通过-t参数指定分隔符,-f指定按照那一列进行排序。

mygrep.php的代码:

$base_file_name    = $argv[1];
$check_file_name   = $argv[2];
$mode              = isset($argv[3])?$argv[3]:false;

$field_index = 0;
$delimiter = ',';

//////////////////////////////////////////////////////////////
$base_file = @fopen($base_file_name, 'r');
$check_file = @fopen($check_file_name, 'r');
$read_base_file = true;
$compare_result = 0;
$readed_base_file_arr = array();

//以要检查的文件作为外层循环
while(!feof($check_file)){
    //读取要检查的文件
    $readed_check_arr = explode($delimiter, trim(fgets($check_file)));
    do{
        //读取基准文件
        if($read_base_file === true){
            $readed_base_arr = explode($delimiter, trim(fgets($base_file)));
        }   

        //作比较
        $compare_result = strcmp($readed_check_arr[$field_index], $readed_base_arr[$field_index]);
        $grep_flag = ($compare_result == 0);
        //如果是要取出不相等的数据
        //且检查key小于或等于基准key时,说明需要将检查key下移一行,即此比较key已经比较完成
        //此时如果比较结果相等则不输出,
        //如果比较结果不等,并且检查key大于基准key则输出,如果检查结果小于基准key,说明检查key要下移继续检查
        if($mode !== false && (strcmp($mode, "notmatch") == 0)
            && $compare_result >= 0){
            $grep_flag = !$grep_flag;
        }

        //print("comparing check=".$readed_check_arr[$field_index]." base=".$readed_base_arr[$field_index]." result=$compare_result
grep_flag=$grep_flag\n");

        if($grep_flag){
            print implode($delimiter, $readed_base_arr)."\n";
        }

        //总是设置为要读取,如果跳出循环,则由外面设置
        $read_base_file = true;

    //如果检查的key是大于基准的key,并且检查文件未到尾,则重新读取一行基准文件进行检查
    }while($compare_result > 0 && !feof($base_file));

    if(feof($base_file)){
        break;
    }

    //当检查key小于基准key时,不读取下个基准key,等于则是因为已经比较过,两个都需要读取
    if($compare_result < 0){
        $read_base_file = false;
    }
}

//当check_file结束了但是base_file未结束
do{
    print implode($delimiter, $readed_base_arr)."\n";
    $readed_base_arr = explode($delimiter, trim(fgets($base_file)));
}while(!feof($base_file));

fclose($check_file);
fclose($base_file);
分类: 程序员, 软件 标签:

java中String.split导致的内存泄露

2010年1月29日 闫鹏 4 条评论

有的时候,由于我们队Java api理解上面的错误,可能会导致出现一些意外的问题,比如内存泄露。认为虚拟机会为你创建一个新的对象,引用了不同的内存区域,结果却和想象中的不一样,导致服务宕机。

今天在查网关的内存溢出问题时,发现问题出现在String.split函数上面。大家都认为Java中的String是只读的,new新对象的时候一定是分配了新的内存,但虚拟机内部却不一定如此。sun的hotspot1.5版本在实现java的类时做了优化,为了减少内存拷贝,一个String对象可能会引用另外一个String对象的内存。查看jdk的源代码可知,String内部是用一个名为value的char数组存储内容的,但同时另外也有两个int类型的变量:offset与count。

查看String.split()函数的源代码,随后调用的是Pattern.split(),随后调用String.subSequence(),随后调用String.substring()。String的substring代码如下

public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}

如果看到最后return的是new String,以为就是一个新的String就真的囧了,这不是一个向外开放的构造函数,它创建了一个新的对象,却是用的原来String对象中的内存

// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}

本来这样进行优化无可厚非,但在网关的应用情况下就出现问题了:
一次性查询得到一个String,里面记载了1000条数据结果,将这1000条数据结果拆分,封装成1000个对象,存在缓存里面。缓存是用LinkedHashMap,当Entry达到一定数量时就进行淘汰。这样就导致这1000个对象都淘汰了,才会释放存储了1000个数据的String,这个String可是相当的大啊。
又因为查询的时间段是有重叠的,导致每次查询的String都记载了很多重复的内容,虽然缓存里面只对String不同的地方保持了一次引用,但是却无意间存储了大量的无用数据。这就导致了jvm的内存溢出。

好多年没搞java了,结合以前上学时候查java内存泄露的一点经验,说明了:

1.出现内存泄露,通常都是有容器保持了大量的引用,查问题的时候,可以从容器优先开始

2.查看jdk的源代码,是很有必要的。

分类: 程序员 标签:

linux的shell下文件批量改名

2010年1月27日 闫鹏 没有评论

以前写的一个小脚本,用在linux的shell批量修改大量文件名的情况下。昨天不小心删除了大量文件,找回的时候,又一次用到了它,在遇到大批量文件的时候,还是挺有效果的,共享一下。

用法:

1.将你要改名的文件都放在同一个文件夹下,运行”ls -l > oldnames”,然后“vi oldnames”,这时候看到的大概是这个样子:

total 1224
drwxrwxr-x   3 yanpeng yanpeng    4096 Sep 17 16:13 app
drwxrwxr-x   2 yanpeng yanpeng    4096 May 19  2009 bin
drwxrwxr-x   2 yanpeng yanpeng    8192 Jan 26 16:40 docpay

2.输入”ctrl+v”,移动光标,选中要删除的那一堆内容,其实就是文件名前面的那些列,输入“d”,得到的文件大概为

app
bin
docpay

3.删除掉第一行的空行,保存退出vi,然后”cp oldnames newnames”。

4.修改newnames文件中想要改名的文件为新文件名

5.将change_name.sh脚本放在你的那个放置文件的夹下,运行./change_name.sh就可以了(注意有执行权限,加执行权限的方法chmod a+x change_name.sh)

change_name.sh脚本内容,拷贝下,存好就能用了


#!/bin/sh

OLDNAMES='oldnames'
NEWNAMES='newnames'

fileline=`sed -n '$=' $OLDNAMES`
echo $fileline

index=1
while [ $index -le $fileline ]
do
oldname=`sed -n "${index}p" $OLDNAMES`
newname=`sed -n "${index}p" $NEWNAMES`
mv $oldname $newname
index=`expr $index + 1`
done
分类: 程序员, 软件 标签: ,

dreamhost上面无法使用memcached

2010年1月26日 闫鹏 1 条评论

昨天在dreamhost上面倒腾编译memcached,先编译了libevent才编好memcached,结果memcached需要libevent的库位于/usr/lib里面才行。没法启动(其实就算启动了也不一定行,ssh的那台机器根本就不是为网站提供服务的机器)。发邮件后,dreamhost的回复是这样的:

Unfortunately we don't support this on our shared hosting servers. If
you'd like to run this, you'll need to upgrade to a Private Server, which
you can do in your control panel here:
https://panel.dreamhost.com/?tree=vserver.provision
You would have to run this on a Private Server only, I'm sorry to say.
Please don't hesitate to write back if you need help with anything else.

看来dreamhost推销它的ps真实不遗余力啊,可是对比服务质量与价格,都没有linode好,要不是为了便宜,我为啥来你这里啊。

打算山寨个文件缓存,虽然通用性不强,但用在这里也够了。

分类: 生活, 程序员 标签:

用好你的vim

2010年1月20日 闫鹏 2 条评论

以前上学的时候,主要用java,Eclipse真是个好东东。上班后,开始用c,php,python等,越来越发现vim是一个好东东,如果不是写java的话,推荐大家投入vim的怀抱。
vim的根本性好处就是:写代码的时候,你的手腕不需要动。不需要动鼠标,不需要移动右手去按那个“上下左右”(当然是在你用hjkl的情况下)
基本的那些用vimtutor看看就好了,高级的应用里面,我接触到的一个是正则,另外一个是列编辑。建议大家写代码的时候,都列对齐,使用ctrl+v进行列编辑,简直太爽了。

关于vim的插件,都是放在在~/.vim文件夹中的,在这个文件夹下面有一些子文件夹

1.plugin。启动Vim时, 它自动载入一些全局的plugin,就是从这个目录里面读取的

2.ftplugin。这种插件是通过“:filetype plugin on”打开的,默认这个命令可以配置在~/.vimrc文件中,插件的作用比如是帮助控制缩进等,对python语言来说尤其重要

3.doc。这个可以放置一些plugin的帮助文件

上面的这些定义比较宽泛,是在vim手册里面描述的。另外,python推荐使用indent/python.vim,可以按照规范的控制python的缩进。

————问题的分割线————
问题:为啥不是写java就推荐vim呢?
回答:Eclipse可以根据java代码的编译情况,自动添加import选项,并且重构功能也很强大,是一个很好的选择。

分类: 程序员, 软件 标签: