字符串编码问题
对自己在程序中所遇到的字符串编码问题的一个总结,首先,我们先看看我们经常听说的几种编码格式:ASCII,Unicode,UTF-8 当然还有我想拿出来单独来说的GB2312,GBK等等。
首先说,这种字符编码是美国制定的,所以它只对英文字符与二进制位之间做了一个对应,区分大小写的英文字符共52个,再加上例如空格之类的控制字符,ASCII共规定了128个字符编码。此时,一个字节的最高位(置为0)是没有使用的。
针对 码无法用于识别其他国语言的问题,欧洲国家决定,将上述的最高位使用起来,于是,就得到了一个扩展的ASCII码,这个编码理论上可以表示256个字母,我们也常称作ISO-8859-1(Latin-1).当然,这个仍然不解决问题,欧洲国家的字母远大于这个数量。除此之外,对于亚洲国家,这个问题就更严重了。故一个字节是肯定无法解决问题的。
再说Unicode,为解决上述问题,就出现了另外一种编码方式。Unicode,这种编码可以说将世界上所有的符号都纳入其中,但是需要注意的是,Unicode是一个符号集,但是却没有规定一个符号的二进制代码应该如何去存储。试想,要囊括世界上所有的符号,肯定需要一个较大的空间去存储表示这个符号的二进制(在后面的验证部分将可以看到)。这时候如果出现一种能变长存储Unicode的方式可能会更好。UTF-8就是实现变长存储的方法。
UTF-8的编码规则如下:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表表示了UTF-8的编码规则:
下面,我们单独说下GB2312,GBK这两种编码,在早期只有 码的时候,为了处理汉字,就设计了GB2312这种编码,该编码使用两个字节,这样的话,算上原来的ASCII码,原则上这种编码方式可以存储256*256个字符。当然,初期一共收集了7445个字符,汉字区的内码范围高字节从B0-F7,低字节从A1-FE(注意:这样的好处是什么呢?就是保证了这两个字节的最高位都为1,这样就好处理编码是否为中文了啊)。由于前期的GB2312编码所代表的汉字太少。后来的GBK编码收录了两万七千多个汉字,包括少数民族文字等。这时候,如果保证两个字节的最高位都是1的话,只能存储128*128=16384个字符,这时候GBK就只保证了高字节最高位为1(因为我们解析字符的时候是从高位到低的,故此种编码方式同样不影响我们解析中文字符)。
以上的理论扯了不少,我们说下实际的实验测试,测试工具使用了vs2008与myeclipse.首先,在c++中,我们想知道默认的string的编码是哪种,
我们挑选”汉”字做测试。“汉”字的Unicode编码是6C49(0110,1100,0100,1001) ,而GB码是BABA(1011,1010,1011,1010)。此时我们测试代码如下:
#include<iostream>
#include<string>
#include<bitset>
using namespace std;
int main()
{
string a="汉";
const char * test=a.c_str();
cout<<test<<endl;
for(int i=0;(*test)!='\0';i++,test++)
{
bitset<8> test_bitset(*test);
cout<<test_bitset.to_string()<<endl;
}
for(int i=0;i<a.size();i++)
{
cout<<int(a[i])<<endl;
}
}
输出结果为
由以上结果可知:其实vs2008中string字符串的默认编码为GB.-70即为10111010(负数在机器码中以补码表示)此时,而且高字节与低字节的最高位都是1,我们通过这个便可判断是否为中文了。我曾经遇到的问题主要是做一个网页爬虫的时候所遇到的编码转换问题,当时使用了一个api的一个函数做了转换,具体代码如下:
//字符编码转换函数
string crawl::convert( char* str,int source_code,int target_code)
{
//int len=str.length();
int unicode_len=MultiByteToWideChar(source_code,0,str,-1,NULL,0);
wchar_t * unicode;
unicode=new wchar_t[unicode_len+1];
//memset(unicode,0,(unicode+1)*2);
MultiByteToWideChar(source_code,0,str,-1,(LPWSTR)unicode,unicode_len);
unicode[unicode_len]='\0';
unsigned char * target_data;
int target_len=WideCharToMultiByte(target_code,0,(LPWSTR)unicode,-1,NULL,0,NULL,NULL);
target_data=new unsigned char[target_len+1];
memset(target_data,0,target_len+1);
WideCharToMultiByte(target_code,0,(LPCWSTR)unicode,-1,(char*)target_data,target_len,NULL,NULL);
target_data[target_len]='\0';
string temp((char*)target_data);
delete []unicode;
delete []target_data;
return temp;
}
大致的思路为首先将多字符转换为宽字符,然后将宽字符转回多字符。
下面说java测试的结果。测试代码如下:
package Main;
import java.io.UnsupportedEncodingException;
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException
{
String a=new String("汉");
System.out.println("==============default====================");
byte[] bytes=a.getBytes();
for(int i=0;i<bytes.length;i++)
{
System.out.println(bytes[i]);
}
System.out.println("===============GB2312===================");
bytes=a.getBytes("GB2312");
for(int i=0;i<bytes.length;i++)
{
System.out.println(bytes[i]);
}
System.out.println("===============unicode===================");
bytes=a.getBytes("Unicode");
System.out.println(new String(bytes,"Unicode"));
for(int i=0;i<bytes.length;i++)
{
System.out.println(bytes[i]);
}
System.out.println("===============US-ASCII===================");
bytes=a.getBytes("US-ASCII");
for(int i=0;i<bytes.length;i++)
{
System.out.println(bytes[i]);
}
System.out.println("===============ISO-8859-1===================");
bytes=a.getBytes("ISO-8859-1");
System.out.println(new String(bytes,"ISO-8859-1"));
for(int i=0;i<bytes.length;i++)
{
System.out.println(bytes[i]);
}
System.out.println("===============UTF-8===================");
bytes=a.getBytes("UTF-8");
System.out.println(new String(bytes,"UTF-8"));
for(int i=0;i<bytes.length;i++)
{
System.out.println(bytes[i]);
}
}
}
测试结果如下:
对测试结果分析可知:
1、 java中,string默认编码为GB,其实可以理解为系统的默认编码,可以被称作ANSI.(当然台湾可能是big5)
2、 Unicode编码使用了四个字节,可以发现,”汉”字的Unicode码其实为6C49(0110,1100,0100,1001)。使用两个字节即可完成,但是使用四个字节是为了保证可以表示所有unicode码,不够灵活。UTF-8解决了这个问题。
3、 原始编码为多字节编码时,请勿向下转换(上例中GB转换为ISO-8850-1后变为乱码)。
注:在java中应注意,string默认编码为GB,故使用java抓取UTF-8时容易出现乱码问题。此时一般获取数据流后会讲string更改。,另,写入文件时应注意:如果原文件没有指定文件写入编码格式,例如:
String utf_string=new String(document.toString().getBytes("UTF-8"),"UTF-8");
BufferedWriter bw=new BufferedWriter(new FileWriter(new File(fileName)));
更改为
File temp_f=new File(fileName);
BufferedWriter bw_utf=new BufferedWriter
(new OutputStreamWriter(new FileOutputStream(temp_f),"UTF-8"));
即可。
本文参考链接:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://www.chi2ko.com/tool/CJK.htm