ASCII字符集
上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码。
即(American Standard Code for Information Interchange, 美国信息交换标准码)。
我们一般都是用字节(byte,即8个01串 byte = 8bits)来作为基本单位。ASCII码使用7个bits来表示一个字符,总共表示128个字符。即第一位为0,
剩下的7个字节来表示实际内容.后来IBM公司在此基础上进行了扩展,用8bit来表示一个字符,总共可以表示256个字符.也就是当第一个bit是0时,
仍表示之前那些常用的字符.当为1时就表示其他补充的字符。
ASCII码主要是为了表示英文字符而设计的,英语用256个符号编码就够了,但是用来表示其他语言,256个符号是不够的。 比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。
于是就出现了Unicode和ISO这样的组织来统一制定一个标准,任何一个字符只对应一个确定的数字.ISO取的名字叫UCS(Universal Character Set),SO组织在ASCII码基础上又制定了一系列标准用来扩展ASCII编码,它们是ISO-8859-1~ ISO-8859-15,其中ISO-8859-1覆盖了大多数西欧语言字符,所以应用得最广泛。Unicode取的名字就叫unicode了.
Unicode详细介绍
Unicode的第一个版本是用两个字节(16bit)来表示所有字符。我们可以这样理解,Unicode是用0至65535之间的数字来表示所有字符.
其中0至127这128个数字表示的字符仍然跟ASCII完全一样.
UTF
Unicode Transformation Format的缩写,表示了Unicode的存储转换形式。Unicode只表示了字符的代码点。如何存储则是通过UTF规定来实现的。
最常见的就是UTF-16和UTF-8。
UTF-16
UTF-16中任何字符对应的数字都用两个字节来保存.它是完全对应于UCS-2的,即把UCS-2规定的代码点通过Big Endian或Little Endian方式直接保存下来。
所以UTF-16采用2个字节来存储Unicode。UTF-16也可以表示UCS-4的部分字符,所以UTF-16也采用4个字节来存储Unicode。
UTF-8
UTF-8采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以由1-6个字节组成。
UTF-8的编码规则是:
(1)如果一个字节,最高位(第8位)为0,表示这是一个ASCII字符(00-7F)。可见,所有的ASCII编码已经是UTF-8了。
(2)如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数,例如:110xxxxx代表它是双字节UTF-8字符的首字节。
(3)如果一个字节,以10开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节。
UTF-8 与UTF-16的区别
如果全部英文或英文与其他文字混合,但英文占绝大部分,用UTF-8就比UTF-16节省了很多空间.而如果全部是中文这样类似的字符或者混合字符中中文占绝大多数.
UTF-16就占优势了,可以节省很多空间.
用UTF-16表示"汉"
假如用UTF-16表示的话就是01101100 01001001(共16 bit,两个字节).程序解析的时候知道是UTF-16就把两个字节当成一个单元来解析.这个很简单.
用UTF-8表示"汉"
用UTF-8就有复杂点.因为此时程序是把一个字节一个字节的来读取,然后再根据字节中开头的bit标志来识别是该把1个还是两个或三个字节做为一个单元来处理.
0xxxxxxx,如果是这样的01串,也就是以0开头后面是啥就不用管了XX代表任意bit.就表示把一个字节做为一个单元.就跟ASCII完全一样.
110xxxxx 10xxxxxx.如果是这样的格式,则把两个字节当一个单元
1110xxxx 10xxxxxx 10xxxxxx 如果是这种格式则是三个字节当一个单元.
这是约定的规则.你用UTF-8来表示时必须遵守这样的规则.我们知道UTF-16不需要用啥字符来做标志,所以两字节也就是2的16次能表示65536个字符.
而UTF-8由于里面有额外的标志信息,所有一个字节只能表示2的7次方128个字符,两个字节只能表示2的11次方2048个字符.而三个字节能表示2的16次方,65536个字符.
由于"汉"的编码27721大于2048了所有两个字节还不够,只能用三个字节来表示.
所有要用1110xxxx 10xxxxxx 10xxxxxx这种格式.把27721对应的二进制从左到右填充XXX符号(实际上不一定从左到右,也可以从右到左,这是涉及到另外一个问题.等会说.
刚说到填充方式可以不一样,于是就出现了Big-Endian,Little-Endian的术语.Big-Endian就是从左到右,Little-Endian是从右到左.
由上面我们可以看出UTF-8需要判断每个字节中的开头标志信息,所以如果一当某个字节在传送过程中出错了,就会导致后面的字节也会解析出错.而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力强.
Unicode版本2
前面说的都是unicode的第一个版本.但65536显然不算太多的数字,用它来表示常用的字符是没一点问题.足够了,但如果加上很多特殊的就也不够了.于是从1996年开始又来了第二个版本.用四个字节表示所有字符.这样就出现了UTF-8,UTF16,UTF-32.原理和之前肯定是完全一样的,UTF-32就是把所有的字符都用32bit也就是4个字节来表示.然后UTF-8,UTF-16就视情况而定了.UTF-8可以选择1至8个字节中的任一个来表示.而UTF-16只能是选两字节或四字节..由于unicode版本2的原理完全是一样的,就不多说了.
前面说了要知道具体是哪种编码方式,需要判断文本开头的标志,下面是所有编码对应的开头标志
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian
FF FE UTF-16/UCS-2, big endian
FF FE 00 00 UTF-32/UCS-4, little endian.
00 00 FE FF UTF-32/UCS-4, big-endian.
其中的UCS就是前面说的ISO制定的标准,和Unicode是完全一样的,只不过名字不一样.ucs-2对应utf-16,ucs-4对应UTF-32.UTF-8是没有对应的UCS
ISO-8859-1 编码
ISO-8859-1仍然是单字节编码,它总共能表示256个字符。ISO-8859-1向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。所以,Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。
因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。
官方描述以及演进历史:
ISO/IEC 8859-1,又称Latin-1或“西欧语言”,是国际标准化组织内ISO/IEC 8859的第一个8位字符集。它以 ASCII为基础,在空置的0xA0-0xFF的范围内,
加入192个字母及符号,藉以供使用变音符号的拉丁字母语言使用。
其他ISO-8859编码
ISO/IEC 8859-2 Latin-2或“中欧语言”,是国际标准化组织内ISO/IEC 8859的其中一个8位字符集。
ISO/IEC 8859-3 南欧语言字符集
ISO/IEC 8859-4 北欧语言字符集
ISO/IEC 8859-5 是国际标准化组织内ISO/IEC 8859的其中一个8位字符集。此字集收录西里尔字母,供俄语、白俄罗斯语、保加利亚语、马其顿语、塞尔维亚语、乌克兰语使用。(哈萨克语及蒙古语尽管不是斯拉夫文字,但也使用西里尔字母书写)
ISO/IEC 8859-6 供现代阿拉伯语使用
ISO/IEC 8859-7 供现代希腊语使用
ISO/IEC 8859-8 供希伯来语使用
ISO/IEC 8859-9 是国际标准化组织内ISO/IEC 8859的其中一个8位字符集。它主要用以表示土耳其语及库尔德语文字。这个字符集由ISO/IEC 8859-1演化而来,它把原属冰岛语的 Ð、ð、Ý、ý、Þ、þ 字母移走,换上 Ğ、ğ、İ、ı、Ş、ş 六个土耳其语字母。
对于我们而言,知道其是ascii的超集就可以了,8859是8位,而ascii是7位,其就是把ascii编码高位扩展了。
把8859-1转化为utf8的简单代码
private static byte[] code8859toUTFNew(byte[] src){
byte [] dest = new byte[src.length*2 +1];
int i = 0, j = 0;
int value;
for(i =0; i < src.length;i++){
value = src[i]& 0xFF;
//System.out.println("value="+value);
if (value <= 0x7F)
{
dest[j++] = src[i];
}else if(value >0x7F && value <= 0xFF)//110xxxxx 10xxxxxx
{
Log.d(TAG, "charset value="+value);
dest[j++] = (byte)((value>>6)& 0x1F|0xC0);
dest[j++] = (byte)(value&0x3F|0x80);
}
}
//System.out.println(j);
byte [] end = new byte[j];
System.arraycopy(dest, 0, end, 0, j);
Log.d(TAG, "charset number i="+i+",j="+j);
return end;
}