001-Character类的实现机制与源码解读

目录

一、实现机制

、设计模式

一、实现机制

Character类用于对字符类型的数据进行包装和处理,具体实现主要依赖于CharacterData类及CharacterDataLatin1、CharacterData00、CharacterData01、CharacterData02等子类。

其中CharacterDataLatin1类用于对ISO-8859-1字符集(即Latin1。0~127:基本ASCII字符集,128~255:扩展ASCII字符集)中的字符进行处理,CharacterData00等主要用于对Unicode字符集中的字符进行处理。

处理方法如下(以CharacterDataLatin1为例):

1、首先对字符属性进行编码,并用32bit的空间存储:

31

30

29

28

27

26

25

24

23

22

21

20

19

18

17

16

15

14

13

12

11

10

09

08

07

06

05

04

03

02

01

00

说明:

Bit 31: 1位,存镜像属性。镜像属性:指有开始有结束的、成对出现的特性。如: ()、{}、[]等。

Bit 27~30: 4位,方向性特性。

Bit 18~26: 9位,存转换大小写的有符号偏移量。大小写互换时用。

Bit 17: 1位,若值为1,则加上有符号偏移量将字符转换为小写。此时字符为大写。

Bit 16: 1位,如果为1,减去有符号偏移量将字符转换为大写。此时字符为小写。

Bit 15: 1位,如果为1,则此字符有一个与其大写字符等价的字符(可能是其本身)

Bit 12~14:  3位,指明字符是否可以作为标识符的一部分。

        =0:不能是标识符的一部分   

        =1:可忽视的控制;可以用作Unicode标识符或Java标识符首字符意外的部分

        =2:可以用作Java标识符首字符以外的部分,但不能作用Unicode标识符首字符以外的部分

        =3:可以用作Unicode标识符或Java标识符首字符以外的部分

        =4:是Java空白字符

        =5:可以用作Java标识符(首字符和后续部分),可以用作Unicode标识符的后续部分,但不能用作首字符(下划线)

        =6:可以Java标识符(首字符和后续部分),但不能用作Unicode标识符(首字符和后续部分)($)

        =7:可以用作Unicode标识符或Java标识符

从上述可以看出:若12~14位等于5、6、7时可以用作Java标识符的首字符,等于1、2、3、5、6、7时可以用作Java标识符除首字符外的后续部分。

Bit 10~11:

        =0:此字符没有数字属性

        =1:数字偏移量 + 字符编码,然后用0x1F掩蔽将产生所需的数值

        =2:此字符有一个“奇怪”的数值

        =3:Java超十进制数字(a~z):数字偏移量 + 字符编码,然后使用0x1F掩蔽,然后添加10将生成所需的数值

Bit 05~09: 数字偏移量:

        用于数字字符与其数值之间的转换。数字字符有:0~9a~z(大小写一样),与之对应的十进制数值是:0~35。

        数字字符0~9的偏移量:0x10, 大小写字母A~Za~z的偏移量是:0x1f

Bit 00~04: 字符类型:

        =1: 大写字符

        =2: 小写字符  

        =9: 数字

        =12: 空

        =20~30: 标点

2、操作:

由上述可知,32bit中存储着字符的不同属性,我们可以通过屏蔽法(位与运算)获取对应位的值来获取相应的属性。

1)通过如下代码可以获取ASCII值为0~127的字符的类型

void printType() {

              out.println("ASCII值为:1~127的后5位:");

              for(int i = 0; i <= 127; i++) {

                     if(i%10 == 0 && i !=0)

                           out.println("");

                     out.printf("%3d:%x\t", i, (CharacterDataLatin.A[i] & 0x1f));

              }

}

        这里的重点是"CharacterDataLatin.A[i] & 0x1f",A数组中存的是字符的32位特征码,0x1F转换成二进制是00011111,二者与运算后,即可获得标识字符类型的最后5位。

        其他特征的获取方法类似。

2)方法实现:

int getNumericValue(int ch) {
        int val = getProperties(ch);
        int retval = -1;

        switch (val & 0xC00) { //取第10~11位,
            case (0x00000000):         // 非数字
                retval = -1;
                break;
            case (0x00000400):              // 即10~11位的值是1,ch是0~9对应的ASCII码
                retval = ch + ((val & 0x3E0) >> 5) & 0x1F;  //((val & 0x3E0) >> 5)是取偏移量,即(Ascii码 + 偏移量) & 0x1f = 数字。
                break;
            case (0x00000800)      :       // "strange" numeric
                 retval = -2; 
                 break;
            case (0x00000C00):           // Java supradecimal
                retval = (ch + ((val & 0x3E0) >> 5) & 0x1F) + 10;  //((Ascii码 + 偏移量) & 0x1F) + 10 = 扩展数字a=10, b=11,…)。
                break;
        }
        return retval;
    }

int toLowerCase(int ch) {
        int mapChar = ch;
        int val = getProperties(ch);

        if (((val & 0x00020000) != 0) &&    //取第17位,不等于0说明可以转小写
                ((val & 0x07FC0000) != 0x07FC0000)) {       
            int offset = val << 5 >> (5+18);         //取偏移量:18~26位
            mapChar = ch + offset;               //字符编码 + 偏移量 = 对应小写字符的编码
        }
        return mapChar;

}

int toUpperCase(int ch) {
        int mapChar = ch;
        int val = getProperties(ch);

        if ((val & 0x00010000) != 0) {        //取第16位,不等于0说明可以转大写
            if ((val & 0x07FC0000) != 0x07FC0000) {        //如果18~26位不全是1
                int offset = val  << 5 >> (5+18);        //取偏移量:18~26位
                mapChar =  ch - offset;        //字符编码 - 偏移量 = 对应大写字符的编码
            } else if (ch == 0x00B5) {        //0x00B5是扩展ASCII字符集中的一个字符
                mapChar = 0x039C;
            }
        }
        return mapChar;
}

以上方法如果理解了,其他方法就很容易懂了

三、设计模式

在Character的实现中,主要用到了如下设计模式:

1、简单工厂模式、享元模式:

private static class CharacterCache {
        private CharacterCache(){}

        static final Character[] cache;
        static Character[] archivedCache;

        static {
            int size = 127 + 1;

            // Load and use the archived cache if it exists
            CDS.initializeFromArchive(CharacterCache.class);
            if (archivedCache == null || archivedCache.length != size) {
                Character[] c = new Character[size];
                for (int i = 0; i < size; i++) {        //将ASCII值位0~127的字符缓存起来
                    c[i] = new Character((char) i);
                }
                archivedCache = c;
            }
            cache = archivedCache;
        }
 }

public static Character valueOf(char c) {
        if (c <= 127) { // 基本ASCII字符从缓存获取
            return CharacterCache.cache[(int)c];
        }
        return new Character(c);        //其他新建
}

在以上代码中,只要ASCII值>127时,valueOf就会新构建一个Character对象,在此可以看成是简单工厂;当ASCII值<=127时,valueOf就从CharacterCache.cache缓存中获取,用到了享元模式。

说明:

1)若要构建的产品不多,就可用简单工厂模式来实现。通常是用一个静态方法来集中构建某一产品。

2)若要用到大量的相同或相似的细粒度对象,为节省资源,通常将这些对象放于缓存中,需要时直接从缓存获取,这就是享元模式。享元模式的核心是“共享”实例。

2、单例模式:

 static final CharacterDataLatin1 instance = new CharacterDataLatin1();
 private CharacterDataLatin1() {};

        如上代码,将CharacterDataLatin1类的构造方式设置成私有的,外部类只能通过instance属性获得其实例。

        通常,在一个应用中,某类只需要一个实例时,就可以用单例模式,如:数据库连接。

        设计模式的具体内容,在此不做详述,但要注意的是:享元模式和单例模式都存在线程安全问题,使用时要注意这一点。

以上内容若存在问题,请指正,谢谢!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值