之前的工作涉及到SIM卡的UCS2编码,需要将字符用81格式编码,在网上找了许多资料,结合自己的研究,现和大家一起分享。
本文主要介绍如何获得字符的80,81,82格式编码,代码在andoid平台上测试,成功存储联系人信息并成功解码。由于有些东西是自己研究所得,难免会有错误,如有不实的地方,欢迎批评指正。
解码文件及方法: frameworks/base/telephony/java/com/android/internal/telephony/uicc/IccUtil.java的adnStringFieldToString()方法。
什么是UCS2编码?
UCS2(Unicode Character Set)是一种字符编码方式,2代表一个字符编码后为2个字节。
我们在SIM卡上存储联系人时就是使用UCS2编码。
SIM卡上的UCS2编码主要有三种格式:80,81,82
下面说明每种格式的编码方式:
‘80’格式:
1.第一个字节为0x80
2.之后的字节为UCS字符,每个字符占用2个字节。(在android平台上用UTF-16BE编码)。
例:东方不败
编码后的字符为:4e1c 65b9 4e0d 8d25
用80格式表示是:80 4e1c 65b9 4e0d 8d25
‘81’格式:
1. 第一个字节为0x81
2.第二个字节为字符串长度
3.第三个字节为基址,为一个UCS码的第15位到第8位(0xxx xxxx x000 000)
4.第四个字节以后为81格式的UCS码
- 如果第8位是0(xxxx xxxx 0xxx xxxx),则此字符的81格式编码就用第7到1位表示(0000 0000 0xxx xxxx),第8位补0;
- 如果第8位是1(xxxx xxxx 1xxx xxxx),则此字符的81格式编码为UCS编码减去基址左移7位,第8位补1。
例:5Жик
编码后的字符为:0035 0416 0438 043a
用81格式表示是:81 04 08 35 96 b8 ba
下面我们以这个例子来看一下基址以及字符编码的获得:
取得第一个字节不是’00’的字符编码:
0416 = 0000 0100 0001 0110
0438 = 0000 0100 0011 1000
043a = 0000 0100 0011 1010
所以基址为0000 1000,也就是08
然后用基址算出这4个字符的81编码:
0035 = 0000 0000 0011 0101,第8位为0,所以它的81编码为0011 0101,也就是53
0416 = 0000 0100 0001 0110,基址左移7位为0000 0100 0011 1010 = 0400,减去基址以后为16,也就是0001 0110,然后最高位补1,为1001 0110,即96
后面的以此类推,为b8,ba
从这里可以看出,81格式只能在第15位到第8位相同的情况下才能进行。也就是说编码的范围只由后7位决定,而且是连续的,也就是2的7次方,即128个字符。所以中文并不适合用81格式来编码,因为两个汉字之间的UCS编码很可能大于128。
‘82’格式:
1.第一个字节为0x82
2.第二个字节为字符串长度
3.第三和第四个字节为基址,基址可以为UCS码中第一个字节不为0的最小值。
4.第四个字节以后为82格式的UCS码
- 如果第8位是0(xxxx xxxx 0xxx xxxx),则此字符的82格式编码就用第7到1位表示(0000 0000 0xxx xxxx),第8位补0;
- 如果第8位是1(xxxx xxxx 1xxx xxxx),则此字符的82格式编码为UCS编码减去基址,第8位补1。
例:a不乎了
编码后的字符为:0061 4e0d 4e4e 4e86
用82格式表示是:82 04 4e0d 61 80 c1 f9
下面我们以这个例子来看一下基址以及字符编码的获得:
选取UCS码第一个字节不为0的最小值,此处的四个字符第一个字节都不为0,所以选择这四个字符的最小值作为基址,也就是4e0d
然后用基址算出这4个字符的82编码:
0061 = 0000 0000 0110 0001,第8位为0,所以它的82编码为61
4e0d = 0100 1110 0000 1101,减去基址4e0d等于0,最高位补1为1000 0000,即80
4e4e = 0100 1110 0100 1110,减去基址等于41,最高位补1为1100 0001,即c1
剩下的4e86以此类推。
82格式和81有同样的限制,同样也只能表示128个字符,而且也要求连续。
那我们在什么情况下选择80,81还是82格式呢?
1.如果可能的话,尽量选择81格式,因为同样大小的字节用81格式可以表示最多的字符。如果有14个字节可以存储,那么使用81格式可以存储11个字符(3+N)。但是81格式最多只能表示128个字符。
2.选择82格式,14个字节可以存储10个字符(4+N),同样最多只能表示连续的128个字符。
3.选择80格式,14个字节可以存储6个字符(1+2N),但是80格式可以表示的范围广,可以从0000到FFFF。所以在大多数情况下汉字只能用80格式。
下面是一个用java写的例子以及测试程序:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /** 2 * 函数会优先使用81和82格式编码,如果不满足编码条件,选择80格式编码 3 * 4 * @param src 源UCS2字节数组 5 * @param srcOff 源字节数组起始位置 6 * @param srcLen 源字节数组长度 7 * @param destOff 目标数组的起始位置 8 * @param dest 目标数组 9 * @return 目标数组 10 */ 11 public static byte[] ucs2ToAlphaField(byte[] src, int srcOff, int srcLen, 12 int destOff, byte dest[]) { 13 int i; 14 int min = 0x7FFF; 15 int max = 0; 16 int temp; 17 int outOff; 18 // 当源数组的长度大于2时才能使用81或者82格式 19 if (srcLen > 2) { 20 // 取得源数组的最小值与最大值 21 for (i = 0; i < srcLen; i += 2) { 22 //每个字符的第1个字节不为0 23 if (src[srcOff + i] != 0) { 24 temp = (int) (((src[srcOff + i] << 8) & 0xFF00) | (src[srcOff 25 + i + 1] & 0xFF)); 26 // 因为在81格式中基址左移7位后最高位要补0,所以8000到FFFF不能使用81 27 if (temp < 0) { 28 max = min + 130; 29 break; 30 } 31 if (min > temp) { 32 min = temp; 33 } 34 if (max < temp) { 35 max = temp; 36 } 37 } 38 } 39 } 40 // 如果所有字符的UCS码都在连续的128的范围内,那么第1个字节都相同 41 if ((max - min) < 129) { 42 // 如果第15位到第8位相同,就可以使用81格式编码 43 if ((byte) (min & 0x80) == (byte) (max & 0x80)) { 44 // 算出81格式的长度,初始化目标数组 45 dest = new byte[srcLen / 2 + 3]; 46 // 设置目标数组的第2个字节为长度 47 dest[destOff + 1] = (byte) (srcLen / 2); 48 // 设置第1个字节为81 49 dest[destOff] = (byte) 0x81; 50 // 基址为第15位到第8位, 51 min = (int) (min & 0x7F80); 52 dest[destOff + 2] = (byte) ((min >> 7) & 0xFF); 53 outOff = destOff + 3; 54 } 55 // 第8个字节不一样(一个为0,一个为1),那么就使用82格式 56 else { 57 // 算出82格式的长度,初始化目标数组 58 dest = new byte[srcLen / 2 + 4]; 59 // 设置目标数组的第2个字节为长度 60 dest[destOff + 1] = (byte) (srcLen / 2); 61 // 设置第1个字节为82 62 dest[destOff] = (byte) 0x82; 63 // 基址为源数组最小值(第1个字节为0的除外) 64 dest[destOff + 2] = (byte) ((min >> 8) & 0xFF); 65 dest[destOff + 3] = (byte) (min & 0xFF); 66 outOff = destOff + 4; 67 } 68 69 for (i = 0; i < srcLen; i += 2) { 70 // 如果第1个字节为0,则取7到1位,最高位补0 71 if (src[srcOff + i] == 0) { 72 dest[outOff] = (byte) (src[srcOff + i + 1] & 0x7F); 73 } 74 // 如果第1个字节不为0,则用UCS码减去基址,最高位补1 75 else { 76 temp = (int) ((((src[srcOff + i] << 8) & 0xFF00) | (src[srcOff 77 + i + 1] & 0xFF)) - min); 78 dest[outOff] = (byte) (temp | 0x80); 79 } 80 outOff++; 81 } 82 // 返回目标数组 83 return dest; 84 } 85 86 // 不满足81和82的格式要求,使用80格式编码 87 dest = new byte[srcLen + 1]; 88 dest[destOff] = (byte) 0x80; 89 System.arraycopy(src, 0, dest, 1, srcLen); 90 // 返回80格式目标数组 91 return dest; 92 }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 public static void main(String args[]){ 2 String src = "5Жик"; 3 try { 4 byte[] dest = null; 5 byte[] srcByte = src.getBytes("UTF-16BE"); 6 dest = ucs2ToAlphaField(srcByte, 0, srcByte.length, 0, dest); 7 for (int i = 0; i < srcByte.length; i++) { 8 System.out.print(Integer.toHexString(srcByte[i] & 0xFF) + " "); 9 } 10 System.out.println(); 11 for (int i = 0; i < dest.length; i++) { 12 System.out.print(Integer.toHexString(dest[i] & 0xFF) + " "); 13 } 14 } catch (UnsupportedEncodingException e) { 15 e.printStackTrace(); 16 } 17 }