2008-09-28

Head First Pattern之代理模式


去年买的Head First Pattern英文版,看了一点点,看起来还是比较吃力。。今年开始一点点的看,慢慢的看进去了,真是好书啊,一点点的从实际例子入手,一步步的、循序渐进的说明每一个设计模式,真是足够的深入浅出!以前也看过阎宏的《Java与模式》,结合中国的传统道家文化、儒家思想,甚至西游记、红楼梦、女娲造人都用上了,说的是也算够透彻了的,但是总感觉还是有些东西理解的不太深。
下面总结下这些天看的代理模式。。

一句话概括代理模式,就是用代理对象对真实对象的访问控制,代理对象和真实对象都实现同一个Subject接口。
类图表示如下:
图截自http://refcardz.dzone.com/里的免费书:Design Patterns
个人理解,代理模式在现实例子里,可以有非常多的变种,关键在于代理对象如何实现对真实对象的访问控制。变化在于访问控制的方式。着重说明下书中的3个例子,就是3种代理模式的使用场合。。

远程代理

远程代理的例子是java中的RMI。真是足够深入浅出的,让我以前对RMI非常模糊的印象也渐渐清晰起来。咱们一步步细细道来。。

第一步:定义远程接口

1.继承java.rmi.Remote接口
定义服务接口,服务接口必须继承自Remote接口。Remote接口是一个标记接口,就是这个接口,没有任何要实现的方法,仅仅是用来标识其实现类具有某种功能(个人理解),就像Serializable接口,仅仅表示实现这个接口的类能被序列化。
public interface MyRemote extends Remote {

2.服务接口中所有方法抛出RemoteException异常
RMI客户端的方法调用其实是调用实现Remote接口的Stub(桩),桩的实现是基于网络和IO的(底层就是socket),客户端在调用方法过程中,任何错误都有可能发生,所以必须让客户端知道所发生的异常,并能捕捉。
import java.rmi.*;
public interface MyRemote extends Remote {
    public String sayHello() throws RemoteException;
}

3.保证返回值和参数必须是可序列化的
远程方法的参数要通过网络传输,因此必须是可序列化的,返回值也是同样。如果用原生类型(int、float等)、String、集合等,就没问题,如果用自己的类型,必须实现Serializable接口(和Remote接口一样,都是标记接口)。

第二步:实现远程服务

1.实现远程接口
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
    public String sayHello(){
        return "Hello, I'm server.";
    }
}

2.继承UnicastRemoteObject 
要想成为一个远程服务对象,需要有远程的功能。最简单的方法就是实现UnicastRemoteObject方法了。

3.声明一个无参数的构造函数,且抛出RemoteException
public MyRemoteImpl() throws RemoteException{}

4.用RMI registry注册服务
实现远程服务后,要布远程服务供客户端使用。要实例化一个远程服务,放入RMI注册表中。注册了服务实现对象后,RMI会把Stub(桩)放入注册表,让客户端使用。
try{
    MyRemote service = new MyRemoteImpl();
    Naming.rebind("RemoteHello",service);
}catch(Exception e){
    // ...
}

第三步:生成Stub和Skeletons(桩和骨架)

1.在远程实现类上运行rmic(不是远程接口)
rmic MyRemoteImpl(类名,不带.class)
会生成桩和骨架代码:MyRemoteImpl_Stub.class、MyRemoteImpl_Skel.class
rmic是jdk bin目录下的工具

第四步:运行rmiregistry

1.rmiregistry
必须让rmiregistry能访问到你的服务相关类,要么把类放入classpath,要么在classes目录下直接运行rmiregistry

第五步:启动服务

1.另一个dos窗口里启动服务类
java MyRemoteImpl


客户端调用方法:
MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/RemoteHello");
String msg = service.sayHello();// 调用桩的方法

通过RMI registry查找服务后,返回桩,客户端必须用MyRemoteImpl_Stub.class和MyRemote.class。桩MyRemoteImpl_Stub.class、骨架MyRemoteImpl_Skel.class、MyRemote.class、MyRemoteImpl.class必须在服务端。


这么多乱七八糟的跟代理模式有什么关系?
其实客户端返回的MyRemote,其实是MyRemote_Stub,就是代理对象了,服务端的MyRemoteImpl及时实际对象,通过RMI,来获得远程对象的代理,再通过代理,来访问实际对象(远程服务实现类MyRemoteImpl)所实现的远程服务方法(MyRemote定义)。对应类图,每个类在代理模式中的角色分别是:
Subject:MyRemote接口
RealObject:MyRemoteImpl服务实现类
Proxy:MyRemote_Stub桩

在RMI中,找到服务后,拿到的MyRemote service其实是一个代理对象(MyRemote_Stub),对代理对象的方法调用,实际是通过RMI来访问远程服务实现对象的方法。也就是说代理对象MyRemote service(实际是MyRemote_Stub)通过RMI机制对远程服务对象来做访问控制,也就实现了代理模式。

虚拟代理

虚拟代理举的是一个Swing的例子。

我是这么理解的:一个对象的创建非常耗时,通过代理对象去调用,在真实对象创建前,返回一个假的调用,等真实对象创建好了,这时候返回给客户端的就是一个真实对象的相应方法调用。


也就是延迟加载的问题,Swing例子中,要显示一个Icon,但是要通过网络加载一个图片,在图片通过网络加载成功前,先显示一个“加载中,请稍候...”(如果是真实对象的调用,应该显示一个图片),在代理对象中通过后台线程去加载图片,加载完了后,再偷偷的把“加载中,请稍候...”的字样偷偷换成加载成功后的图片。


没想到这也算代理模式的一种应用场景。以前有这么在Swing中用过,需要从数据库中查找数据,但是比较耗时,就先显示“加载数据中,请稍候...”,等加载完了,再在JTable中显示出来。如果用代理模式的方式来思考,好像比较的好吧。。


同样在jsp页面里,通过ajax来加载数据好像也是这样的道理,数据没加载之前就是“加载中...”,加载完了再通过innerHTML来改变显示,也是同样的延迟加载问题。

如果用代理模式的方式来考虑,可以定义一个JavaScript类(这个类其实是个代理),这个类有个方法要显示一些从Server取出的数据,但是调用显示方法时,后台数据还没有加载,就先显示加载中请稍候之类的文本,这时候通过ajax从Server取数据(创建真实对象),取出来之后在回调函数中更新显示HTML元素的innerHTML。跟那个Swing的例子一模一样吧。不过好像JavaScript中好像没有谁会定义接口、实现、代理对象吧,但是思路其实是一样的。

不知道这样理解代理模式,算不算曲解。。。


JDK动态代理

jdk里的动态代理支持,主要是通过java.lang.reflect包中Proxy、InvocationHandler等几个类来实现的。具体如何实现可参考JDK中文文档。

使用场合:

好像在在一本Hibernate的书上,对数据库Connection的close方法调用,用动态代理的方式来拦截,并不真正关闭连接,而是返回到数据库连接池中。

在Spring中的拦截貌似有些是用动态代理实现的?不过动态代理使用时要基于接口,但是Spring是使用动态生成字节码的方式?对Spring内部实现机制不熟。。不敢妄自猜测。。等有时间好好研究再来说明。。

动态代理,我觉得最好的使用场合是给方法调用增加预处理和后处理,更加灵活了,可以做一些额外的事,同时也做到无侵入的解耦合,因为代理对象和实际对象的接口是一样的,唯一需要注意的地方是,客户端调用者是拿的接口,接口到底是使用代理对象还是实际对象,调用者并不知道,这就需要对代理对象的创建用类似工厂的方式来封装创建。比如一下代码:
PersonBean getOwnerProxy(PersonBean person){
    return (PersonBean)Proxy.newProxyInstance(
             person.getClass().getClassLoader(),
             person.getClass().getInterfaces(),
             new OwnerInvocationHandler(person));
}

PersonBean为Subject接口,OwnerInvocationHandler实现InvocationHandler接口。

和Decorator的比较

Decorator模式在jdk的java.io包中使用非常广泛。主要用来为一个类添加新的行为。
而Proxy模式中,代理对象并不对实际对象添加新的行为,只是对实际对象做访问控制。

多线程环境下的单例模式实现

----Head First Pattern之单例模式



单例模式我想大家都比较熟悉,就是在JVM运行期间一个类只有一个实例,任何时候都是取得同一个实例,也就是一个全局变量了。
单例模式分懒汉式和饿汉式,但是懒汉式的单例在多线程环境下会有同步的问题,下面详细介绍了用3中方法来解决此问题。
单例模式具有以下几个特点:
1.JVM运行期间有且只有一个实例
2.构造函数是私有的
3.通过一个静态工厂方法来获得唯一的实例
4.累内部有一个私有静态实例,通过静态工厂方法创建后,每次再调用静态工厂方法,返回的都是同一个实例

饿汉式:
public class Singleton{
    private static Singleton uniqueInstance = new Singleton();
    // 其他实例变量
    private Singleton(){}
    public static Singleton getInstance(){
        return uniqueInstance;
    }
    
    // 其他方法
}

懒汉式:
public class Singleton{

    private static Singleton uniqueInstance;

    // 其他实例变量

    private Singleton(){}

    public static Singleton getInstance(){

        if(uniqueInstance == null){

            uniqueInstance = new Signleton();

        }

        return uniqueInstance;

    }

    

    // 其他方法

}

多线程环境下的单例模式:
上面的代码就是最基本的单例模式示例代码。但是懒汉式单例有一个问题,因为要保证有且仅有一个实例,如果在多线程环境下调用Singleton.getInstance(),就可能会有多个实例!为了解决多线程访问的问题,有3种解决方法供选择:

1.静态工厂方法加同步关键字,这种方法是在对性能要求不高的情况下采用。
public class Singleton{


    private static Singleton uniqueInstance;


    // 其他实例变量


    private Singleton(){}


    public static synchronised Singleton getInstance(){


        if(uniqueInstance == null){


            uniqueInstance = new Signleton();


        }


        return uniqueInstance;


    }


    


    // 其他方法


}

2.始终用饿汉式单例
public class Singleton{

    private static Singleton uniqueInstance = new Singleton();

    // 其他实例变量

    private Singleton(){}

    public static Singleton getInstance(){

        return uniqueInstance;

    }

    

    // 其他方法

}
饿汉式的方法,会依赖于JVM在加载类的时候,就创建唯一的实例。在每个线程访问getInstance方法前,唯一实例已经被创建。

3.用双检查锁来减少懒汉式中静态方法getInstance的同步开销
对public static synchronised Singleton getInstance()的每次调用,都需要同步,而双检查锁的方式只是在第一次创建实例时同步,其他时候并不需要同步。
public class Singleton{
    private volatile static Singleton uniqueInstance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(uniqueInstance == null){
            synchronised(Singleton.class){
                if(uniqueInstance == null){
                       uniqueInstance = new Singleton();
                }
            }

        }
        return uniqueInstance;
    }
}

如果调用时实例为null,则进入同步区块,此时再进行判断,如果还为null,就创建唯一的实例。有可能在一个线程在 if(uniqueInstance == null) 后进入同步区块前,另一个线程恰好已经创建成功并从同步区块中出来,这就需要进入同步区块后,再做uniqueInstance是否为null的判断。
同时uniqueInstance需要加volatile关键字,保证在创建单例实例时,多个线程能正确处理uniqueInstance变量。

注意:
双检查锁的方式在Java1.4及1.4以前版本不能工作!!因此双检查锁只能在Java 5及以上版本才可以使用。
记得Effictive Java中也提到过双检查锁,也说不能在Java1.4中使用。
原因是Java 1.4及以前的JVM中对volatile关键字的实现允许对双检查锁不合适的同步。(谁能帮我再深入解释下?)原文是:
Unfortunately, in Java version 1.4 and earlier, many JVMs contain implementations of the volatile keyword that allow improper synchronization for double-checked locking. If you must use a JVM other than Java 5, consider other methods of implementing your Singleton.



移动硬盘与适配器模式



----Head
First Pattern
之适配器模式


很多人都有移动硬盘,它比U盘容量大的多,体积也小,携带方便,用来拷资料、备份都很不错。并且硬盘价格越来越便宜,120G的移动硬盘现在才300多。





其实移动硬盘里面就是一块普通的笔记本硬盘,还有一个IDE口(或SATA口,看硬盘的类型了,现在一般都是SATA口即串口,IDE口就是并口)和USB口的转换卡,用USB线和电脑上的USB口连接起来就行了。





我手头有一个40GIDE口笔记本硬盘,想在PC电脑上用,但又不想把电脑拆开插主板的IDE线上。看到电脑上还有很多空闲的USB口,就想能不能让笔记本硬盘的IDE口连到电脑的USB口?直接连肯定不行,接口不匹配啊。就去电子市场逛了一个下午,发现有一种转接卡,一头可以插IDE口,一头是USB口,能连USB线,USB线就可以连电脑的USB口了。我买了转接卡回到家,把转接卡一头插在笔记本硬盘的IDE口上,另一头连上USB线,再把USB线连到电脑的USB口上。大功告成,现在我的笔记本硬盘就变成移动硬盘了。转接卡就是连接IDE口和USB口的适配器。这就是适配器模式。


下面看适配器的UML类图:



这个类图有点问题,其实Adaptee应该是一个接口,还有个实现这个接口的ConcreteAdaptee,而Adapter是目标接口,ConcreteAdapter必须实现Adapter,持有一个Adaptee。移动硬盘和适配器模式对应关系如下:


Adapter
USB接口


Adaptee:笔记本硬盘的IDE接口


ConcreteAdapter:转接卡





总而言之,适配器模式就是把一个接口Adaptee,适配成目标接口Adapter





某些时候适配器和装饰模式有点相同之处,但是两者本质是不一样的。


Adapter是把所包装的接口,转换为另一个接口。而Decorator是给所包装的接口,添加新的行为或方法。













Servlet与模板方法模式

----Head First Pattern之模板方法模式


这年头大家都用struts,或者其他MVC框架,很少有人直接用Servlet了吧,但是相信大部分人都应该知道怎么写Servlet的吧,继承HttpServlet类,覆盖里面的doGet、doPost方法即可,大部分情况下,我们都是对GET和POST一样处理,一般也就这么写了:
public MyServlet extends HttpServlet{
    public void doGet(HttpServletRequest request, HttpServletResponse response){
        // 处理
    }
    public void doPost()HttpServletRequest request, HttpServletResponse response){
        // 不关心get、post请求,因此post处理直接调用get处理

        doGet(request, response);

    }
}
Servlet只要在web.xml中部署好之后,就可以处理浏览器的请求了。上面代码可以看出来,doGet方法处理浏览器的GET请求,doPost处理POST请求。
Servlet是由Tomcat之类的servlet容器来调用处理浏览器请求的,并需要集成基类HttpServlet,如果大家查看HttpServlet源码的时候,就会发现,其实里面有一个
protected void service(HttpServletRequest req, HttpServletResponse resp)
方法,servlet容器实际调用的是service方法,service方法的实现就是根据HTTP请求的类型(GET、POST,还是其他),将处理委派给doGet、doPost等方法,由这些子类的方法来最终处理浏览器的请求。

由此可以看出,HttpServlet定义了一个处理的框架或者说模板,实现Servlet只需继承HttpServlet并实现doGet、doPost等方法即可。

是引出模板方法模式定义的时候了,模板方法模式:在一个方法中定义一个算法的骨架,将某些步骤推迟到子类中实现。模板方法允许子类重新定义算法的某些步骤,而不改变算法的结构。
简单UML类图如下:
简单实现一个抽象类:
public abstract Template{
    public void final templateMethod(){
        step1();
        step2();
        hook();
    }
    public abstract void step1();
    public abstract void step2();
    public void hook(){}
}

这个抽象类,定义了一个算法的骨架,需要step1、step2,都是抽象方法,需要子类来实现。而templateMethod是final的,即不允许子类覆盖。其中定义了方法的步骤,step1、step2。
如下为具体实现:
public ConcreteTemplate extends Template{
    public void step1(){System.out.println("step1");}
    public void step2(){System.out.println("step2");}
    public void hook(){System.out.println("hook");}
    public static void main(String[] args){
        Template temp = new ConcreteTemplate();
        temp.templateMethod();
    }
}

可以看到其中加入了一个hook方法,即钩子方法。hook方法在抽象类中的实现为空,是留给子类做一些可选的操作。如果某个子类需要一些特殊额外的操作,则可以实现hook方法,当然也可以完全不用理会,因为hook在抽象类中只是空方法而已。

其他扩展:
1.可以定义多个hook方法
2.hook方法可以定义一个返回为boolean的方法,有子类来决定是否调用hook方法。
eg:抽象类的templateMethod可以这样实现:
    public void final templateMethod(){

        step1();

        step2();
        if(allowHook())

            hook();

    }
    public boolean allowHook(){return true;}
    public void hook(){}
子类中可以覆盖allowHook,以决定是否调用hook方法。
3.抽象类定义的步骤,可以有默认实现,而非全是abstract方法。HttpServlet中已经有doGet、doPost等方法的默认实现,大家可以参考其源码实现。

更好的一个模板方法的例子是Applet,实现自己的Applet,必须继承自Applet,里面实现init、start、stop、destroy等方法即可。这些方法都是有默认实现的,如果看源码,可以发现默认实现其实都是空。

了解模板方法模式之后,大家可能意识到Servlet并且完全按照模板方法定义的那样,而是有一些区别,比如提供默认doGet、doPost的实现等。这都说明,在实际编程中,并非生搬硬套设计模式,而是根据实际,会做些变动或变形,但其本质不变。
不能为了模式而模式。那究竟什么时候需要设计模式呢?个人理解,当你发现你的类有问题的时候,比如有重复代码了,感觉有些不对劲了,感觉不适应以后的扩充了,这时候不妨考虑下,是不是该设计模式出场了。。
纯粹个人理解。。有不当之处,请及时拍砖。。





JavaScript正则表达式总结

一、RegExp对象和字面量
     RegExp对象构造方式:
     var re = new RegExp("cat");
     var re = new RegExp("cat","gim");
     g:global,表示找到所有的匹配,如果不指定g选项,只匹配第一个
     i:不区分大小写
     m:匹配多行
     字面量方式:
     var re = /cat/;
     var re = /cat/gi;
     两者区别:
     非字面量来表达正则表达式时,对元字符转义必须用双重转义,而字面量不用,eg:
     var re = /?/; // ?为元字符必须转义
     var re = new RegExp('\?'); // 因为RegExp是用字符串来构造,而必须也转义,故得写成 '\?'
二、简单模式
     1、元字符
          (、[、{、、^、$、|、)、?、*、+、.
     2、使用特殊字符
          用ascii码和unicode表示字符
          t   制表符
          n   换行符
          r   回车符
          f   换页符
          a   alert字符
          e   escape字符
          cX   与X相对应的控制字符
          b   回退字符
          v   垂直制表符
          0   空字符
          以上特殊字符用非字面量方式构造表达式时,要双重转义
     3、字符类
         简单类:[]来表示单个字符有或的关系,如/[bc]/,匹配b或者c
         负向类:^来表示不匹配后面跟着的字符,如/[^bc],/不匹配b或c
         范围类:-来表示一个范围,如/[a-z]/,匹配a-z的字母,可以和负向类结合/^0-9/
         组合类:以上几种类组合而成的字符类,如/a-z0-9n/,匹配a-z的字母或者0-9的数字或者换行符
         类不能嵌套,就是不支持联合类和交叉类,/a-m[p-z]/和/a-m[^b-e]/在JavaScript中是非法的
         预定义类:
         .    =    [^nr]   除了换行和回车之外的任何字符
         d  =    [0-9]      数字
         D  =    [^0-9]   非数字
         s  =     [ tnx0Bfr]   空白字符
         S  =    [^ tnx0Bfr]   非空白字符
         w  =    [a-zA-Z_0-9]     单词字符(字母、数字、下划线)
         w  =    [^a-zA-Z_0-9]     非单词字符
     4、量词:某个模式出现的次数
         简单量词:
         ?   出现0或1次
         *   出现0或多次
         +   出现1或多次(至少1次)
         {n}   =n次
         {n,m}   >=n,<=m
         {n,}     >=n
         贪婪量词:先匹配整个字符串,如果不匹配则去掉最后一个字符再匹配,直到没有任何字符。所有简单量词都是贪婪的。
         惰性量词:和贪婪量词相反,即先匹配第一个字符,不匹配则匹配第一第二个字符,直到最后整个字符串。所有简单量词后面加?就是惰性的了。
         支配量词:只匹配整个字符串一次,不匹配就结束。所有简单量词后面加+就是支配的了。
三、复杂模式
     1、分组:用括号来包括一些字符、字符类、量词来使用的。个人理解为可以将字符、字符类、量词等用括号来组成一个单元,可以对这个单元使用量词,同时这个单元是被存储起来的。分组可以嵌套。
     2、反向引用:每个分组都是被存储起来的,存储在分组中的匹配值称为反向引用。
         使用正则表达式的test、match、search方法之后,可以通过正则表达式的实例变量来访问到反向引用。RegExp.$1,RegExp.$2...来访问反向引用。
         可以直接在分组表达式中使用分组。eg:var re = /dog1/,转义1就引用了第一个反向引用
         可以在String的replace方法中通过特殊的序列$1、$2来访问反向引用。eg:var sToChange = '1234 5678';var reMatch = /(d{4}) (d4)/;var sNew = sToChange.replace(reMatch,'$2 $1');很轻松的实现单词的顺序转换。
     3、候选:用|来表示模式的或关系。
     4、非捕获性分组:创建反向引用的分组是捕获性分组,不创建反向引用的分组就是非捕获性分组。eg:(?:bad)
     5、前瞻:某个特定的字符分组出现在另一个字符串之前时才捕获,正向前瞻要放在(?=和)之间(不是分组),负向前瞻放在(?!和)之间。eg:/(bed(?=room))/匹配跟在room前面的bed;/(bed(?!room))/匹配不跟在room前的bed。
     6、边界:^行开头,$行结尾,b单词边界,B非单词边界。
     7、多行模式:m选项打开,能影响^和$,使^匹配开头和换行符后面的位置,$匹配真正的结尾和换行符。
        
    
    
    
    

2008-09-17




JavaScript正则表达式总结

一、RegExp对象和字面量
     RegExp对象构造方式:
     var re = new RegExp("cat");
     var re = new RegExp("cat","gim");
     g:global,表示找到所有的匹配,如果不指定g选项,只匹配第一个
     i:不区分大小写
     m:匹配多行
     字面量方式:
     var re = /cat/;
     var re = /cat/gi;
     两者区别:
     非字面量来表达正则表达式时,对元字符转义必须用双重转义,而字面量不用,eg:
     var re = /?/; // ?为元字符必须转义
     var re = new RegExp('\?'); // 因为RegExp是用字符串来构造,而必须也转义,故得写成 '\?'
二、简单模式
     1、元字符
          (、[、{、、^、$、|、)、?、*、+、.
     2、使用特殊字符
          用ascii码和unicode表示字符
          t   制表符
          n   换行符
          r   回车符
          f   换页符
          a   alert字符
          e   escape字符
          cX   与X相对应的控制字符
          b   回退字符
          v   垂直制表符
          0   空字符
          以上特殊字符用非字面量方式构造表达式时,要双重转义
     3、字符类
         简单类:[]来表示单个字符有或的关系,如/[bc]/,匹配b或者c
         负向类:^来表示不匹配后面跟着的字符,如/[^bc],/不匹配b或c
         范围类:-来表示一个范围,如/[a-z]/,匹配a-z的字母,可以和负向类结合/^0-9/
         组合类:以上几种类组合而成的字符类,如/a-z0-9n/,匹配a-z的字母或者0-9的数字或者换行符
         类不能嵌套,就是不支持联合类和交叉类,/a-m[p-z]/和/a-m[^b-e]/在JavaScript中是非法的
         预定义类:
         .    =    [^nr]   除了换行和回车之外的任何字符
         d  =    [0-9]      数字
         D  =    [^0-9]   非数字
         s  =     [ tnx0Bfr]   空白字符
         S  =    [^ tnx0Bfr]   非空白字符
         w  =    [a-zA-Z_0-9]     单词字符(字母、数字、下划线)
         w  =    [^a-zA-Z_0-9]     非单词字符
     4、量词:某个模式出现的次数
         简单量词:
         ?   出现0或1次
         *   出现0或多次
         +   出现1或多次(至少1次)
         {n}   =n次
         {n,m}   >=n,<=m
         {n,}     >=n
         贪婪量词:先匹配整个字符串,如果不匹配则去掉最后一个字符再匹配,直到没有任何字符。所有简单量词都是贪婪的。
         惰性量词:和贪婪量词相反,即先匹配第一个字符,不匹配则匹配第一第二个字符,直到最后整个字符串。所有简单量词后面加?就是惰性的了。
         支配量词:只匹配整个字符串一次,不匹配就结束。所有简单量词后面加+就是支配的了。
三、复杂模式
     1、分组:用括号来包括一些字符、字符类、量词来使用的。个人理解为可以将字符、字符类、量词等用括号来组成一个单元,可以对这个单元使用量词,同时这个单元是被存储起来的。分组可以嵌套。
     2、反向引用:每个分组都是被存储起来的,存储在分组中的匹配值称为反向引用。
         使用正则表达式的test、match、search方法之后,可以通过正则表达式的实例变量来访问到反向引用。RegExp.$1,RegExp.$2...来访问反向引用。
         可以直接在分组表达式中使用分组。eg:var re = /dog1/,转义1就引用了第一个反向引用
         可以在String的replace方法中通过特殊的序列$1、$2来访问反向引用。eg:var sToChange = '1234 5678';var reMatch = /(d{4}) (d4)/;var sNew = sToChange.replace(reMatch,'$2 $1');很轻松的实现单词的顺序转换。
     3、候选:用|来表示模式的或关系。
     4、非捕获性分组:创建反向引用的分组是捕获性分组,不创建反向引用的分组就是非捕获性分组。eg:(?:bad)
     5、前瞻:某个特定的字符分组出现在另一个字符串之前时才捕获,正向前瞻要放在(?=和)之间(不是分组),负向前瞻放在(?!和)之间。eg:/(bed(?=room))/匹配跟在room前面的bed;/(bed(?!room))/匹配不跟在room前的bed。
     6、边界:^行开头,$行结尾,b单词边界,B非单词边界。
     7、多行模式:m选项打开,能影响^和$,使^匹配开头和换行符后面的位置,$匹配真正的结尾和换行符。
        
    
    
    
    

2008-09-13

Chrome印象


自从Google Chrome发布以来,各大媒体争相报道,各个牛人出来发表一番评论。。有人批评有人褒扬,据说下载人数达到几百万了。。市场占用率统计也超过Opera了,试用人数比使用IE8的要多。。具体统计数据我不清楚。。只是站在一个普通程序员的角度说下个人的看法。

Chrome是基于webkit核心的。据说webkit非常小巧且功能强大,比firefox的核心引擎Gecko要小巧的多。据说Gecko非常复杂,代码非常多,尽管功能非常强大,占用内存也非常多,Mozilla也一直在改进,并且Mozilla也一直在发展一个javascript的引擎,据说比Chrome的v8要快27%。但是我觉得好像Chrome占用内存比Firefox3.0要多不少呢。

第一印象:

Chrome的第一印象是非常简洁,没有工具栏和菜单,秉承了Google的一贯风格,比如google的首页等。只有标签页和地址栏,操作起来也非常顺手,也挺快,看上去很清爽,还算比较喜欢。

内存占用和速度:

今天用Google Docs在写东西,开了一个一个窗口,其中一个是Google Docs的首页,第二个窗口有3个标签页,占用内存还真不少。这些都是Ajax页面,也都是相当占资源。一共169M多。并且有时响应比较慢。突然不动了,过一小会才会好。
我的Firefox3.0.1开了6个标签页,其中一个是GMail。也占了大概160多M。
Firefox3.0以后,内存占用是比以前少的了,2.*时,开几个标签,放一个上午,有时内存占用最多能达到近500M,3.0还没发现占用这么多的时候。并且3.0以后,速度比2.*也是快了不少。
感觉内存占用上,Chrome比较多,比Firefox还要多。速度上,几乎差不太明显。至于Chrome的多进程方式,也不错。

扩展:

目前Chrome还是Beta,没有提供插件机制,我想用Firefox最大的好处就是插件多吧,其次才是速度快、安全、更符合标准、bug修复速度快等等优点。不过Chrome内置了Google Gears。运行Google Docs和Google Reader可以直接启用offline功能。这点都是由预谋的。Google Gears提供的离线存储功能,对以后Google基于Chrome的所谓云计算,应该会提供非常多的支持。并且现在若干个Google服务都已经支持基于Google Gears的离线功能了。


总结:


总的来说,对Google而言,推出自己的浏览器,就是为以后Google的未来搭建一个平台,所有基于Google的服务都很容易就能在Chrome在运行,相当于一个客户端了。试想全球那么多用户使用Chrome,Google想在浏览器上做点什么手脚,集成点自己的服务,比如Google Toolbar(Chrome竟然没有集成Toolbar,这点我也比较惊讶,Google Gears已经有了)、或其他的服务,简直太容易了吧。至于在服务里投放广告之类,更容易了。不过像微软已经开始以什么明目起诉Google了。老实说,很明显的,google在Chrome集成google的搜索服务,就像IE8里集成live搜索一样(google也投诉了吧,嘿嘿),这应该是必然的事,不过google也得做好被微软之类起诉垄断或者应对其他攻击方式的准备吧。

Google的目标应该会比较长远。。


2008-09-06

在线状态下编辑,现在断开网络。


现在是在断开网络的情况下继续编辑编辑。。。


在Chrome中,内置对Google Gears的支持,就测试下Google Docs的离线操作。在启用Gears之后,在桌面创建图标,然后同步。同步后,断开网络,看到Docs检测到在离线状态下编辑。现在编辑完之后,连上网络。。

在用笔记本电脑的情况下,离线状态下写文档,上线之后自动同步,还是非常有用的。。。
可惜,我不习惯用本本,也没有本本。。嘿嘿。。
唉,不知道在blgger写博客,会不会有人看。。