转乱码UTF8和UTF-8网页编码
http://www.lovelucy.info/utf8-vs-utf-8.html#more-794
一、遇到的問題
曾經被字符集間復雜的轉換搞怕了,正好新項目要求國際化,需要能夠顯示多種語言,于是一開始就規定統統使用 UTF-8 編碼。
- 所有代碼文件使用 UTF-8 編碼存盤
- MySQL數據庫所有表,所有字段設置 Collation (中文翻譯為“整理”?)屬性為 “utf8_general_ci”
- 所有頁面輸出
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
即便是這樣,PHP 從數據庫中讀取內容,顯示到網頁上,還是出現了亂碼,英文沒問題,中文統統都是?問號。這樣也行?艱苦卓絕的 debug 開始了……
二、調查原因
MySQL 的字符集以繁多而著名,而其默認又是 latin1 的瑞典語編碼,數據導入導出的時候一不留神就亂碼了。
參考以上鏈接的官方文檔,總結之:MySQL 對于字符集的支持細化到四個層次:
- server 服務器級
- database 數據庫級
- table 表級
- connection 連接級
確保每一個級別都是使用的 UTF-8 編碼。檢查了一下,貌似我沒有設置 connection 連接級。前 三種字符集級別只是規定了數據存儲在 MySQL 中的編碼格式,客戶端讀出數據后完全可以按照自己的意愿來解讀數據。最后的 connection 連接級就是規定了客戶端以什么編碼來解析讀取到的數據。也就是說,不論是 php 代碼還是 DB 管理軟件,在從 MySQL 讀取數據之前都需要設定自己作為客戶端的編碼格式。
好吧,那么,在任何查詢執行之前,先執行一句 set names utf-8。(使用框架進行開發的話,大多數框架應該會自動完成這一步,程序員一般只需要改配置文件)
$conn = mysql_connect($db_host, $db_user, $db_password);
if(!$conn)
die("Could not connect to mysql.");
mysql_select_db($db_name);
mysql_query("set names 'utf-8'"); 刷新頁面,仍舊亂碼。
三、解決
只好再繼續調查。¥#$…&%=^*&+%-#!@_@ 苦逼地一天就這樣過去了……
咦,等等,官方文檔里寫的是 set names utf8 哦,和 set names utf-8 有啥區別么?趕緊試一下。
刷新頁面,我擦,正常了。
搜了一下,發現被坑的人還真不少。UTF-8應該是標準的寫法,在大多數場合都是有中間那個橫杠的,只是MySQL這里偏偏就非主流去掉了橫杠使用UTF8。
遇到同樣問題,而本文未能幫你解決的,這篇亂碼總結可能會幫到你。
?
//以下是討論內容
今天看到大家在討論,發現這是個很嚴重而又容易疏忽的問題,我以前也一直是用set names,遂記錄下來,也提醒自己一把。
應該使用 mysql_ set_ charset(); 不要使用sql query來設置,有風險。
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) 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”來作為設置字符集的操作。
轉載于:https://www.cnblogs.com/xiaohong/archive/2012/03/13/2393327.html
總結
以上是生活随笔為你收集整理的转乱码UTF8和UTF-8网页编码的全部內容,希望文章能夠幫你解決所遇到的問題。