海峰's profile随便写写了BlogListsNetwork Tools Help

Blog


    December, 2008

    Software Process Improvement Tips and Tricks

    信息安全(Information Security)简介

    续 3 –信息安全风险管理

    这一节我们继续介绍一下信息资产的威胁评估。

    Outlook(12-06-09-26-34)

    d ) 威胁评估

    威胁是一种对组织及其资产构成潜在破坏的可能性因素,是客观存在的。威胁可以通过威胁主体、资源、动机、途径等多种属性来描述。造成威胁的因素可分为人为因素和环境因素。根据威胁的动机,人为因素又可分为恶意和非恶意两种。环境因素包括自然界不可抗的因素和其它物理因素。威胁作用形式可以是对信息系统直接或间接的攻击,在机密性、完整性或可用性等方面造成损害;也可能是偶发的、或蓄意的事件。

    在对威胁进行分类前,应考虑威胁的来源。表1提供了一种威胁来源的分类方法。

    表1  威胁来源列表

    image

    对威胁进行分类的方式有多种,针对上表的威胁来源,可以根据其表现形式将威胁分为以下几类。表2提供了一种基于表现形式的威胁分类方法。

    表2 一种基于表现形式的威胁分类表

    image

    如果你对我们的过程小贴士有任何的疑问或建议,不妨给我们邮件:process@achievo.com

    欢迎您的参与和支持 !

              下一期会继续介绍资产的脆弱性评估。

    Software Process Improvement Tips and Tricks

    信息安全(Information Security)简介

    续2 –信息安全风险管理

    从上一节的介绍中我们知道了,在风险评估的过程中要依据企业的服务分类和业务过程进行信息资产分类和识别。这一节我们就来介绍一下信息资产的分类方法及其重要性评估。

    Outlook

    a) 信息资产的分类

    资产是具有价值的信息或资源,它能够以多种形式存在,有无形的、有形的,有硬件、软件,有文档、代码,也有服务、形象等。机密性、完整性和可用性是评价资产的三个安全属性。风险评估中资产的价值不仅仅以资产的经济价值来衡量,而是由资产在这三个安全属性上的达成程度或者其安全属性未达成时所造成的影响程度来决定的。安全属性达成程度的不同将使资产具有不同的价值,而资产面临的威胁、存在的脆弱性、以及已采用的安全措施都将对资产安全属性的达成程度产生影响。为此,有必要对组织中的资产进行识别。

    在一个组织中,资产有多种表现形式;同样的两个资产也因属于不同的信息系统而重要性不同,而且对于提供多种业务的组织,其支持业务持续运行的系统数量可能更多。这时首先需要将信息系统及相关的资产进行恰当的分类,以此为基础进行下一步的风险评估。在实际工作中,具体的资产分类方法可以根据具体的评估对象和要求,由评估者灵活把握。根据资产的表现形式,可将资产分为数据、软件、硬件、文档、服务、人员等类型,如下面表格所示。

    分类

    示例

    电子资产

    保存在信息媒介上的各种数据资料,包括源代码、数据库数据、系统文档、运行管理规程、计划、报告、用户手册等

    软件资产

    系统软件:操作系统、语言包、工具软件、各种库等

    应用软件:外部购买的应用软件,外包开发的应用软件等

    源程序:各种共享源代码、自行或合作开发的各种代码等

    硬件资产

    网络设备:路由器、网关、交换机等

    计算机设备:大型机、小型机、服务器、工作站、台式计算机、移动计算机等

    存储设备:磁带机、磁盘阵列、磁带、光盘、软盘、移动硬盘等

    传输线路:光纤、双绞线等

    保障设备:动力保障设备(UPS、变电设备等)、空调、保险柜、文件柜、门禁、消防设施等

    安全保障设备:防火墙、入侵检测系统、身份验证等

    其他:打印机、复印机、扫描仪、传真机等

    服务资产

    办公服务:为提高效率而开发的管理信息系统(MIS),包括各种内部配置管理、文件流转管理等服务

    网络服务:各种网络设备、设施提供的网络连接服务

    信息服务:对外依赖该系统开展的各类服务

    纸质资产

    纸质的各种文件,如传真、电报、财务报告、发展计划等

    人员资产

    掌握重要信息和核心业务的人员,如主机维护主管、网络维护主管及应用项目经理等

    其它

    企业形象,客户关系等

    b) 资产安全属性

    机密性、完整性和可用性是评价资产的三个安全属性。

    机密性confidentiality指信息与信息系统不被非授权者所获取或利用的特性,包括数据保密和访问控制等方面。

    完整性integrity指信息与信息系统真实、准确和完备,不被冒充、伪造和篡改的特性,包括身份真实、数据完整和系统完整等方面。

    · 数据完整性 data integrity 数据所具有的特性,即无论数据形式作何变化,数据的准确性和一致性均保持不变。

    · 系统完整性 system integrity 在防止非授权用户修改或使用资源和防止授权用户不正确地修改或使用资源的情况下,信息系统能履行其操作目的的品质。

    可用性availability指信息与信息系统可被授权者在需要的时候访问和使用的特性 。

    c) 资产价值

    资产的重要程度或敏感程度的表征。资产价值是资产的属性,也是进行资产识别的主要内容。

    资产价值应依据资产在机密性、完整性和可用性上的赋值等级,经过综合评定得出。综合评定方法可以根据自身的特点,选择对资产机密性、完整性和可用性最为重要的一个属性的赋值等级作为资产的最终赋值结果;也可以根据资产机密性、完整性和可用性的不同等级对其赋值进行加权计算得到资产的最终赋值结果。加权方法可根据组织的业务特点确定。最终确定资产的重要性等级。

    如果你对我们的过程小贴士有任何的疑问或建议,不妨给我们邮件:process@achievo.com

    欢迎您的参与和支持 !

    October, 2008

    SSH框架中根据不同的用户切换到对应的数据库

    特别是SAAS应用系统,一般的我们会为每个公司客户创建一个对应的数据库,然而我们肯定不会为每个客户部署一台服务器,这样应用系统就存在如何在多个数据库中自动切换的问题;

    首先创建我们自己的数据源实现类,如下:

    package whf.framework.jdbc;

    import java.io.PrintWriter;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.Map;

    import javax.sql.DataSource;

    import org.springframework.jdbc.datasource.DriverManagerDataSource;

    import whf.framework.config.Configuration;
    import whf.framework.config.PropertiesConfiguration;
    import whf.framework.ext.entity.Database;
    import whf.framework.ext.service.DatabaseServiceImp;
    import whf.framework.log.Log;
    import whf.framework.log.LogFactory;
    import whf.framework.meta.Meta;
    import whf.framework.security.UserContext;
    import whf.framework.service.SpringService;
    import whf.framework.util.StringUtils;
    import whf.framework.util.ThreadContext;
    import whf.framework.util.Utils;

    /**
    * 可以根据上下文,动态分派数据库联接的数据源<br>
    * 搜索数据源的顺序:<br>
    * 1.线程当前数据源名称(手工输入的,优先级最高)
    * 2.从对象定义中获取;
    * 3.获取用户当前所在的数据源分支,从UserContext中获取;
    * 4.使用缺省
    * @author King
    *
    */
    public class SwitchableDataSource extends SpringService implements DataSource {
        private static Log log = LogFactory.getLog(SwitchableDataSource.class);
        private Map<String, DataSource> registeredDataSources = Utils.newHashMap();
        private static String defaultDataSourceName;
        public void setRegisteredDataSources(Map<String, DataSource> registeredDataSource) {
            this.registeredDataSources = registeredDataSource;
        }

        public void setDefaultDataSourceName(String defaultDataSourceName) {
            SwitchableDataSource.defaultDataSourceName = defaultDataSourceName;
        }
        //
        private Configuration jdbcConfiguration;
        private synchronized void registerDataSource(String dataSourceName) {
            if(this.registeredDataSources.containsKey(dataSourceName)) return;
            if(this.jdbcConfiguration == null) {
                try{
                    this.jdbcConfiguration = new PropertiesConfiguration(SwitchableDataSource.class.getResource("/conf/jdbc.properties"));;
                }catch(Exception e){
                    throw new RuntimeException(e);
                }
            }
            String driverClassName = this.jdbcConfiguration.getString("jdbc." + dataSourceName + ".driverClassName");
            if(StringUtils.isEmpty(driverClassName)) driverClassName = this.jdbcConfiguration.getString("jdbc.driverClassName");
            String username = null;
            String password = null;
            String url = this.jdbcConfiguration.getString("jdbc." + dataSourceName + ".url");
            if(!StringUtils.isEmpty(url)) {
                username = this.jdbcConfiguration.getString("jdbc." + dataSourceName + ".username");
                password = this.jdbcConfiguration.getString("jdbc." + dataSourceName + ".password");
            } else {
                try{
                    Database db = DatabaseServiceImp.getDatabaseService().findByCode(dataSourceName);
                    url = db.getUrl();
                    username = db.getUsername();
                    password = db.getPassword();
                }catch(Exception e){
                    throw new RuntimeException(e);
                }
            }
            this.registeredDataSources.put(dataSourceName, new DriverManagerDataSource(driverClassName, url, username, password));
        }
        public final DataSource getDataSource(String dataSourceName) {
            if(!this.registeredDataSources.containsKey(dataSourceName)) {
                this.registerDataSource(dataSourceName);
            }
            return this.registeredDataSources.get(dataSourceName);
        }

        public final DataSource getDefaultDataSource() {
            return this.getDataSource(defaultDataSourceName);
        }
        private DataSource getCurrentDataSource() {
            return this.getDataSource(getCurrentDataSourceName());
        }
        /**
         * @return 获取当前线程的数据源
         */
        public final static String getCurrentDataSourceName() {
            String dsn = ThreadContext.getCurrentDataSourceName();
            Meta meta = ThreadContext.getCurrentMeta();
            if(meta != null && StringUtils.isEmpty(dsn)) {
                dsn = meta.getBranch();
            }
            if(StringUtils.isEmpty(dsn)) {
                UserContext uc = ThreadContext.getUserContext();
                if(uc != null)
                    dsn = uc.getBranch();
            }
            if(StringUtils.isEmpty(dsn))
                dsn = defaultDataSourceName;
            log.debug("DataSourceName: "+dsn + "\n");
            return dsn;
        }

        @Override
        public int getLoginTimeout() throws SQLException {
            return this.getCurrentDataSource().getLoginTimeout();
        }

        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return this.getCurrentDataSource().getLogWriter();
        }

        @Override
        public void setLoginTimeout(int seconds) throws SQLException {
            this.getCurrentDataSource().setLoginTimeout(seconds);
        }

        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {
            this.getCurrentDataSource().setLogWriter(out);
        }

        @Override
        public Connection getConnection() throws SQLException {
            return this.getCurrentDataSource().getConnection();
        }

        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            return this.getCurrentDataSource().getConnection(username, password);
        }

        @Override
        public boolean isWrapperFor(Class<?> iface) throws SQLException {
            return this.getCurrentDataSource().isWrapperFor(iface);
        }

        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException {
            return this.getCurrentDataSource().unwrap(iface);
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            if(StringUtils.isEmpty(defaultDataSourceName))
                throw new Exception("Default datasource name can not be empty!");
            if(this.registeredDataSources.get(defaultDataSourceName) == null)
                throw new Exception("Can not found default datasource for default dataSourceName:" + defaultDataSourceName);
            super.afterPropertiesSet();
        }
        public static void main(String[] args) throws Exception {
            System.out.println(SwitchableDataSource.class.getResource("/conf/jdbc.properties"));
        }
    }

    这个数据源切换期可以按照三种方式切换:

    1 在线程上下文环境中设置,参考whf.framework.util.ThreadContext.setCurrentDataSourceName, 在具体应用中这个也具有最高优先级,主要满足一些特殊的需要,限定某些操作只能在某个数据库中处理;

    2 根据对象配置,如果当前操作的对象必须在某个数据库中,例如一些配置数据,可以限定为从某个自定的数据库中存取;

    3 根据当前操作用户;

    第二步 部署数据源到Spring

    <bean id="dataSource" class="whf.framework.jdbc.SwitchableDataSource">
        <property name="defaultDataSourceName">
            <value>framework</value>
        </property>
        <property name="registeredDataSources">
            <map>
                <entry key="framework">
                    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
                        <property name="driverClassName">
                            <value>${jdbc.framework.driverClassName}</value>
                        </property>
                        <property name="url">
                            <value>${jdbc.framework.url}</value>
                        </property>
                        <property name="username">
                            <value>${jdbc.framework.username}</value>
                        </property>
                        <property name="password">
                            <value>${jdbc.framework.password}</value>
                        </property>
                    </bean>
                </entry>
                <entry key="monica">
                    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
                        <property name="driverClassName">
                            <value>${jdbc.monica.driverClassName}</value>
                        </property>
                        <property name="url">
                            <value>${jdbc.monica.url}</value>
                        </property>
                        <property name="username">
                            <value>${jdbc.monica.username}</value>
                        </property>
                        <property name="password">
                            <value>${jdbc.monica.password}</value>
                        </property>
                    </bean>
                </entry>
            </map>
        </property>
    </bean>

     

    其中参数使用Spring的properties设置,如

    <bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location"
            value="classpath:conf/jdbc.properties" />
    </bean>

    最后就是调用

    首先用户的数据库是需要可识别的,因此用户必须有一个(用户所属组织)参数,用以识别对应的数据库;在用户登录时,需要三个参数:所属组织,用户名,密码

    ok,上述已经完成;

    完整代码参考:http://whfframework.googlecode.com/svn/trunk/

    Spring中实现简单的性能监控

    以下介绍在Spring中如何实现简单的性能监控,监控每一个Spring管理的方法调用过程中花费的时间,并把相关信息记录到日志数据库中;
    根据以上的问题,我们首先想到的应该就是Spring提供的AOP了,确实是用AOP可以很容易的解决这个问题;
    参考如下步骤:
    1 创建MethodBeforeAdvice,记录方法执行的开始时间,如下
    package whf.framework.aop;
    import java.lang.reflect.Method;
    import whf.framework.service.Service;
    public class MethodBeforeAdvice implements org.springframework.aop.MethodBeforeAdvice {
        public static ThreadLocal<Long> threadLocal = new ThreadLocal<Long>();
        public void before(Method method, Object[] args, Object target)
                throws Throwable {
            if(target instanceof Service){
                threadLocal.set(System.currentTimeMillis());
            }
        }
    }
    2 创建MethodAfterAdvice,将监控的信息记录到数据库
    package whf.framework.aop;
    import java.lang.reflect.Method;
    import org.springframework.aop.AfterReturningAdvice;
    import whf.framework.config.ApplicationConfig;
    import whf.framework.log.Log;
    import whf.framework.log.LogFactory;
    import whf.framework.log.entity.Logable;
    import whf.framework.log.util.LoggerUtils;
    import whf.framework.meta.Metaable;
    import whf.framework.security.entity.User;
    import whf.framework.service.Service;
    import whf.framework.util.StringUtils;
    import whf.framework.util.ThreadContext;
    public class MethodAfterAdvice implements AfterReturningAdvice {
        private static Log log = LogFactory.getLog(MethodAfterAdvice.class);
        public void afterReturning(Object object, Method method, Object[] args,
                Object target) throws Throwable {
            if(target instanceof Service){
                long consumeTime = System.currentTimeMillis() - MethodBeforeAdvice.threadLocal.get();
                User user = ThreadContext.getUserInUserContext();
                String sessionId = ThreadContext.getSessionId();
                String department = user ==null? null:user.getDept() == null?null: user.getDept().getCode();
                String operator = user == null?"anonymous":user.getName()+"("+user.getUsername()+")";
                String ip = ThreadContext.getUserContext() != null? ThreadContext.getUserContext().getRemoteIpAddress(): null;
                if(ApplicationConfig.getInstance().isServiceMonitoring() &&
                        !(target instanceof Logable) && !(target instanceof Metaable)) {
                    String params = StringUtils.toString(args);
                    whf.framework.log.entity.Log logEntity = new whf.framework.log.entity.Log(department, operator,
                            target.getClass().getSuperclass().getName(), "SERVICE INVOKE", method.getName(), params,
                            sessionId, ip, consumeTime, null);
                    LoggerUtils.log(logEntity);
                }
                if(object != null)
                    log.debug("Method:" + consumeTime + " -\t" + operator + " - \t" + target.getClass().getName() +":"+object.getClass().getName()+"->"+method.getName());
                else
                    log.debug("Method:" + consumeTime + " -\t" + operator + " - \t" + target.getClass().getName()+"->"+method.getName());
            }
        }
    }
    3 增加配置,将监控配置到Spring中
    <bean id="methodBeforeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice">
            <bean  class="whf.framework.aop.MethodBeforeAdvice" />
        </property>
        <property name="mappedName">
            <value>*</value>
        </property>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    </bean>
    <bean id="methodAfterAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice">
            <bean class="whf.framework.aop.MethodAfterAdvice" />
        </property>
        <property name="mappedName">
            <value>*</value>
        </property>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    </bean>   
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="proxyTargetClass">
            <value>true</value>
        </property>
        <property name="beanNames">
            <value>*Service</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>transactionInterceptor</value>
                <value>methodBeforeAdvisor</value>
                <value>methodAfterAdvisor</value>
            </list>
        </property>
    </bean>
    完成了,现在Spring会自动的将所有的监控相关信息记录到数据库;
    其中需要说明的是,如果你监视的结果存储到数据库的话,那么你的日志的表一定要在这里面过滤掉,不然会出现死循环(插入日志 -〉 监控到日志查询信息 -〉 插入日志 。。。。。。)
    详细源代码参考:http://whfframework.googlecode.com/svn/trunk/

    June, 2008

    数据流图

    数据流图(Data Flow Diagram,简称DFD) 是一种最常用的结构化分析工具,它从数据传递和加工角度,以图形的方式刻画系统内的数据运动情况。

    数据流表示数据的流动情况;加工表示对数据的加工处理过程,它的名字应能简明扼要地表明所完成的是什么加工;数据存贮在数据流图中起着保存数据的作用,指向数据存贮的数据流可以理解为写数据,从数据存贮引出的数据流可以理解为读数据,双向数据流可以理解为修改数据;数据源点或终点,表示图中出现数据的始发点或终止点,它在图中的出现仅仅是一种符号,并不需要以软件的形式进行设计和实现。

    在数据流图中,如果有两个以上数据流指向一个加工或从一个加工中引出,则这些数据流之间往往存在一定的关系。

     

    在画数据流图时,如下几个问题值得注意:

    1)是画数据流图而不是画程序框图

    对于很多人来说,通过学习计算机语言已经比较熟悉程序框图了,在画数据流图时很容易将它们搞混。程序框图是从对数据进行加工的角度描述系统的,其箭头是控制流,表示的是对数据进行加工的次序,它用于描述怎样解决问题;数据流图则是从数据的角度来描述系统的,其箭头是数据流,表示的是数据的流动方向,它用于描述是什么问题。

    2)数据流及加工的命名

    通常是先为数据流命名,然后再为加工命名。在给数据流命名时,应避免使用象“数据”、“输入”之类的缺乏具体含义的名字;在给加工命名时,理想的做法是取由一个具体的及物动词加一个具体的宾语构成的名字,如果必须用两个动词,则可以考虑将这个加工再分解成两个加工。在命名时,所取的名字应适合整个数据流或加工,而不是仅仅反映它的某些成分。如果发现某个数据流或加工难以命名,那么很可能是数据流图分解不当造成的,此时应该考虑重新分解数据流图。

    取自"http://codex.wordpress.org.cn/%E6%95%B0%E6%8D%AE%E6%B5%81%E5%9B%BE"

    April, 2008

    一位软件工程师的6年总结

    “又是一年毕业时”,看到一批批学子离开人生的象牙塔,走上各自的工作岗位;想想自己也曾经意气风发、踌躇满志,不觉感叹万千……本文是自己工作6年的经历沉淀或者经验提炼,希望对所有的软件工程师们有所帮助,早日实现自己的人生目标。本文主要是关于软件开发人员如何提高自己的软件专业技术方面的具体建议,前面几点旨在确定大的方向,算是废话吧。

    谨以此文献给那个自己为你奉献3年青春与激情的开发团队。还有团队成员:PPL、YT、YK 、TYF、LGL、CHL、CDY、CB、DPD。

    1、分享第一条经验:“学历代表过去、能力代表现在、学习力代表未来。”其实这是一个来自国外教育领域的一个研究结果。相信工作过几年、十几年的朋友对这个道理有些体会吧。但我相信这一点也很重要:“重要的道理明白太晚将抱憾终生!”所以放在每一条,让刚刚毕业的朋友们早点看到哈!

    2、一定要确定自己的发展方向,并为此目的制定可行的计划。不要说什么,“我刚毕业,还不知道将来可能做什么?”,“跟着感觉走,先做做看”。因为,这样的观点会通过你的潜意识去暗示你的行为无所事事、碌碌无为。一直做技术,将来成为专家级人物?向管理方向走,成为职业经理人?先熟悉行业和领域,将来自立门户?还是先在行业里面混混,过几年转行做点别的?这很重要,它将决定你近几年、十年内“做什么事情才是在做正确的事情!”。

    3、软件开发团队中,技术不是万能的,但没有技术是万万不能的!在技术型团队中,技术与人品同等重要,当然长相也比较重要哈,尤其在MM比较多的团队中。在软件项目团队中,技术水平是受人重视和尊重的重要砝码。无论你是做管理、系统分析、设计、编码,还是产品管理、测试、文档、实施、维护,多少你都要有技术基础。算我孤陋寡闻,我还真没有亲眼看到过一个外行带领一个软件开发团队成功地完成过软件开发项目,哪怕就一个,也没有看到。倒是曾经看到过一个“高学历的牛人”(非技术型)带一堆人做完过一个项目,项目交付的第二天,项目组成员扔下一句“再也受不了啦!”四分五裂、各奔东西。那个项目的“成功度”大家可想而知了。

    4、详细制定自己软件开发专业知识学习计划,并注意及时修正和调整(软件开发技术变化实在太快)。请牢记:“如果一个软件开发人员在1、2年内都没有更新过自己的知识,那么,其实他已经不再属于这个行业了。”不要告诉自己没有时间。来自时间管理领域的著名的“三八原则”告诫我们:另外的那8小时如何使用将决定你的人生成败!本人自毕业以来,平均每天实际学习时间超过2小时。

    5、书籍是人类进步的阶梯,对软件开发人员尤其如此。书籍是学习知识的最有效途径,不要过多地指望在工作中能遇到“世外高人”,并不厌其烦地教你。对于花钱买书,我个人经验是:千万别买国内那帮人出的书!我买的那些家伙出的书,!00%全部后悔了,无一本例外。更气愤的是,这些书在二手市场的地摊上都很难卖掉。“拥有书籍并不表示拥有知识;拥有知识并不表示拥有技能;拥有技能并不表示拥有文化;拥有文化并不表示拥有智慧。”只有将书本变成的自己智慧,才算是真正拥有了它。

    6、不要仅局限于对某项技术的表面使用上,哪怕你只是偶尔用一、二次。“对任何事物不究就里”是任何行业的工程师所不应该具备的素质。开发Windows应用程序,看看Windows程序的设计、加载、执行原理,分析一下PE文件格式,试试用SDK开发从头开发一个Windows应用程序;用VC++、 Delphi、Java、.Net开发应用程序,花时间去研究一下MFC、VCL、J2EE、.Net它们框架设计或者源码;除了会用J2EE、 JBoss、Spring、Hibernate等等优秀的开源产品或者框架,抽空看看大师们是如何抽象、分析、设计和实现那些类似问题的通用解决方案的。试着这样做做,你以后的工作将会少遇到一些让你不明就里、一头雾水的问题,因为,很多东西你“知其然且知其所以然”!
    7、在一种语言上编程,但别为其束缚了思想。“代码大全”中说:“深入一门语言编程,不要浮于表面”。深入一门语言开发还远远不足,任何编程语言的存在都有其自身的理由,所以也没有哪门语言是“包治百病”的“灵丹妙药”。编程语言对开发人员解决具体问题的思路和方式的影响与束缚的例子俯拾皆是。我的经验是:用面对对象工具开发某些关键模块时,为什么不可以借鉴C、C51、汇编的模块化封装方式?用传统的桌面开发工具(目前主要有VC++、Delphi)进行系统体统结构设计时,为什么不可以参考来自Java社区的IoC、AOP设计思想,甚至借鉴像Spring、Hibernate、JBoss等等优秀的开源框架?在进行类似于实时通信、数据采集等功能的设计、实现时,为什么不可以引用来自实时系统、嵌入式系统的优秀的体系框架与模式?为什么一切都必须以个人、团队在当然开发语言上的传统或者经验来解决问题???“他山之石、可以攻玉”。

    8、养成总结与反思的习惯,并有意识地提炼日常工作成果,形成自己的个人源码库、解决某类问题的通用系统体系结构、甚至进化为框架。众所周知,对软件开发人员而言,有、无经验的一个显著区别是:无经验者完成任何任务时都从头开始,而有经验者往往通过重组自己的可复用模块、类库来解决问题(其实这个结论不应该被局限在软件开发领域、可以延伸到很多方面)。这并不是说,所有可复用的东西都必须自己实现,别人成熟的通过测试的成果也可以收集、整理、集成到自己的知识库中。但是,最好还是自己实现,这样没有知识产权、版权等问题,关键是自己实现后能真正掌握这个知识点,拥有这个技能。

    9、理论与实践并重,内外双修。工程师的内涵是:以工程师的眼光观察、分析事物和世界。一个合格的软件工程师,是真正理解了软件产品的本质及软件产品研发的思想精髓的人(个人观点、欢迎探讨)。掌握软件开发语言、应用语言工具解决工作中的具体问题、完成目标任务是软件工程师的主要工作,但从软件工程师这个角度来看,这只是外在的东西,并非重要的、本质的工作。学习、掌握软件产品开发理论知识、软件开发方法论,并在实践中理解、应用软件产品的分析、设计、实现思想来解决具体的软件产品研发问题,才是真正的软件工程师的工作。站在成熟理论与可靠方法论的高度思考、分析、解决问题,并在具体实践中验证和修正这些思想与方式,最终形成自己的理论体系和实用方法论。

    10、心态有多开放,视野就有多开阔。不要抱着自己的技术和成果,等到它们都已经过时变成垃圾了,才拿出来丢人现眼。请及时发布自己的研究成果:开发的产品、有创意的设计或代码,公布出来让大家交流或者使用,你的成果才有进化和升华的机会。想想自己2000年间开发的那些Windows系统工具,5、6年之后的今天,还是那个样子,今天流行的好多Windows系统工具都比自己的晚,但进化得很好,且有那么多用户在使用。并且,不要保守自己的技术和思想,尽可能地与人交流与分享,或者传授给开发团队的成员。“与人交换苹果之后,每个人还是只有一个苹果;但交换思想之后,每个人都拥有两种思想”,道理大家都懂,但有多少人真正能做到呢?

    11、尽量参加开源项目的开发、或者与朋友共同研制一些自己的产品,千万不要因为没有钱赚而不做。网络早已不再只是“虚拟世界”,网上有很多的开源项目、合作开发项目、外包项目,这都是涉猎工作以外的知识的绝好机会,并且能够结识更广的人缘。不要因为工作是做ERP,就不去学习和了解嵌入式、实时、通信、网络等方面的技术,反过来也是一样。如果当他别人拿着合同找你合作,你却这也不会,那也不熟时,你将后悔莫及。

    12、书到用时方恨少,不要将自己的知识面仅仅局限于技术方面。诺贝尔经济学奖得主西蒙教授的研究结果表明: “对于一个有一定基础的人来说,他只要真正肯下功夫,在6个月内就可以掌握任何一门学问。”教育心理学界为感谢西蒙教授的研究成果,故命名为西蒙学习法。可见,掌握一门陌生的学问远远没有想想的那么高难、深奥。多方吸取、广泛涉猎。极力夯实自己的影响圈、尽量扩大自己的关注圈。财务、经济、税务、管理等等知识,有空花时间看看,韬光养晦、未雨绸缪。

    13、本文的总结与反思:

    A:不要去做技术上的高手,除非你的目标如此。虽然本文是关于提高软件开发知识的建议,做技术的高手是我一向都不赞同的。你可以提高自己的专业知识,但能胜任工作即止。

    B:提高软件知识和技术只是问题的表面,本质是要提高自己认识问题、分析问题、解决问题的思想高度。软件专业知识的很多方法和原理,可以很容易地延伸、应用到生活的其它方面。

    C:在能胜任工作的基础上,立即去涉猎其它领域的专业知识,丰富自己的知识体系、提高自己的综合素质,尤其是那些目标不在技术方面的朋友。

    March, 2008

    JBPM简介

    JBoss jBPM为设计及开发工作流和业务流程管理系统提供了一个先进的平台。由API、特定领域的语言和图形建模工具组成的框架让开发人员和业务分析人员能够使用通用平台进行沟通及操作。

    工作流管理和业务流程管理(BPM)正在迅速成为企业获得软件敏捷性和适应性的重要方法。JBoss jBPM是一个面向流程的工作流/BPM框架和工具集,它使业务分析人员能够与软件组件进行交互、有助于获得有效的业务解决方案。

    许多企业在积极寻求一种结构化方法,以便设计业务动作/事务,并且优先使用自动化流程加以执行。业务流程管理(BPM)和工作流管理使用动作、任务和流程等概念,提供了解决这个问题的办法。

    业务流程管理一词通常是指企业通过一系列活动,以能够适应动态变化的环境的方式,自动管理及优化流程。这些活动通常寻求来自软件工程和工具的帮助。因而,BPM一词往往直接用来指软件工程技术和工具。

    BPM体现为三个不同的实践

    1.流程设计:指设计现有及新的流程这一任务;

    2.流程执行:执行自动化序列的相关事件,这些事件涉及软件流程以及/或者人为活动;

    3.流程监控:观察及审查单个流程的状态,以便这些流程的统计数字和性能可以加以记录、报告及优化。

    BPM力求让软件工程师们能够与业务分析人员共享同样的概念和框架,因而,软件开发商试图创建这样的工具,让企业可以通过使用图形建模工具、特定领域的语言和专有应用软件,获取、设计及优化业务流程。

    JBoss jBPM 3.0提供了这样的功能:使用业务流程执行语言(BPEL)、灵活而且可插入的应用编程接口(API)、本地流程定义语言以及图形建模工具,利用基于行业标准的编制机制开发新的自动化业务流程和工作流。

    JBoss jBPM是采用开放源代码(LGPL许可证)的框架,包括了Java API、工具和定义语言,可以充当Web应用或者独立的Java应用。JBoss jBPM相当于业务分析人员和开发人员之间的中介,为他们提供了名为jPDL的通用流程定义语言。

    JBoss jBPM架构综述

    JBoss jBPM定义了使用JBoss流程定义语言编写的文件里面的流程定义。jPDL是一种面向图形编程(GOP)的语言,它基于节点、转换和动作组成的模型。在这种模型里面,节点是在流程定义过程中彼此相遇时执行的命令。转换负责指导流程定义的执行过程,而动作在节点或者转换事件发生时执行特定逻辑。

    在jBPM中,流程定义被封装成流程档案(process archives)。流程档案被传送到jPDL流程引擎加以执行。jPDL流程引擎负责遍历流程图、执行定义的动作、维持流程状态,并且记录所有流程事件。

    JBoss jBPM在以下组件里面进行封装:

    流程引擎: 该组件通过下列委托组件(delegate component)来执行定义的流程动作、维持流程状态,并记录所有流程事件:请求处理程序、状态管理程序、日志管理程序、定义加载程序、执行服务。

    流程监管器: 该模块跟踪、审查及报告流程在执行时的状态。

    流程语言: 流程定义语言(jPDL)基于GOP。

    交互服务: 这些服务把遗留应用提供成流程执行时所用的功能或者数据。

    图1表明了这些组件之间的关系。

    20060828093249349

    如图1所示,含有动作处理程序的jBPM流程定义由jBPM流程引擎加以加载及执行。 如果流程引擎在流程定义过程中遇到拥有相关动作的节点,所有相关的动作处理程序就会被调用。动作处理程序是Java代码的实例,在执行时能够与外部系统进行交互。

    下面就是简单的动作处理程序的示例:

    import org.jbpm.graph.def.*;

    import org.jbpm.graph.exe.*;

    public class MyActionHandler

    implements ActionHandler

    {

    public void execute(ExecutionContext executionContext)

    {

    System.out.println("MyActionHandler has executed: " + executionContext);

    }

    }

    流程档案里面的PDL文件名为process-definition.xml。该文件含有诸流程的正式描述。以下示子表明了process-definition.xml文件的例子:

    < ?xml version="1.0" encoding="UTF-8"?>

    < !DOCTYPE process-definition PUBLIC "-//jbpm/jBPM Mapping DTD 2.0//EN" "http://jBPM.org/dtd/processdefinition-2.0.dtd">

    < process-definition name="purchase process">

    < !--...-->

    < !-- START-STATE -->

    < start-state name="request a purchase">

    < transition to="evaluating"/>

    < /start-state>

    < !-- NODES -->

    < state name="evaluating">

    < !--...-->

    < transition name="approve" to="purchase approved"/>

    < transition name="disapprove" to="done"/>

    < /state>

    < fork name="purchase

    approved">
    < transition to="decrement inventory" />

    < transition to="increment revenue" />

    < /fork>

    < state name="decrement inventory">

    < !--...-->

    < transition to="join" />

    < /state>

    < state name="increment revenue">

    < !--...-->

    < transition to="join" />

    < /state>

    < join name="join">

    < transition to="done" />

    < /join>

    < !-- END-STATE -->

    < end-state name="done" />

    < /process-definition>

    流程定义基于定向图(directed graph)。有向图由节点、转换、一个起始状态以及一个终止状态组成。每个节点的类型定义了该节点的运行时行为。流程定义在执行时,以下实体就会起到作用:

    ● 流程实例: 流程实例是流程定义的一次执行。

    ● 标记: 标记是一条执行路径。标记是运行时概念,它含有指向定向图中节点的指针。一旦创建了流程实例,就会创建主要执行路径的标记。该标记名为流程实例的根标记,它位于流程定义的起始状态。

    ● 信号: 信号指示标记继续由转换实现的图像执行。

    ● 节点: 节点负责图像执行的继续进行。如果标记进入节点,节点就会执行。不会传播执行的节点被认为是状态节点。

    ● 动作:动作是流程执行过程中出现事件时执行的Java代码的实例。主要的事件类型有:“进入节点”、“离开节点”和“进行转换”。

    20060828093345794

    图2 jBPM图形建模设计器

    使用jBPM图形建模设计器,就很容易创建流程定义。设计器目前作为Eclipse插件而安装。图2表明了图形建模设计器的示例屏幕。

    图形设计器可以用来创建流程定义、把动作处理程序与事件连接起来、编辑定义来源、创建流程档案、测试流程定义,等等。

    部署JBoss jBPM

    JBoss jBPM把流程定义存储在数据库中。因而,把流程部署到JBoss jBPM里面需要解析process-definition.xml,并且把它存储在JBoss jBPM数据库中。可通过以下方法实现这项工作:

    ● 使用由JBoss jBPM提供的par Ant任务来创建流程档案。

    ● 使用deploypar实用程序。该实用程序还可以创建流程档案,并且把流程档案部署到jBPM数据库上。Deploypar实用程序把jBPM.properties文件作为一个属性。该文件指定了配置选项,其中包括流程档案所要部署到的那个数据库。

    ● 通过编程解析process-definition.xml,并把它存储到数据存储区中。

    让JBoss jBPM完成一些简单步骤

    JBoss jBPM充当编制引擎,它位于企业应用的中间,能够实现不同应用之间的集成和协调。

    本文使用随同jBPM交付的示例部署,讨论如何利用jBPM和jPDL来创建及修改具有Web功能的简单的订单处理系统。

    1.下载JBoss jBPM

    JBoss jBPM入门套件(http://www.jboss.com/products/jBPM/downloads)包括了执行JBoss jBPM所需的一切,只是没有Java开发者工具包(JDK)。JBoss jBPM入门套件里面的JBoss应用服务器需要J2SE 1.4或者更新版本。

    下载入门套件后,把它解压缩到选择的目录下面。一旦解压缩了该入门套件,就有了类似如下的目录结构:

    ● Jbpm-starters-kit-3.1。

    ● Jbpm:含有JBoss jBPM产品的源代码。

    ● jbpm-bpel:含有JBoss jBPM的BPEL扩展件方面的信息。

    ● jbpm-db:含有把JBoss jBPM连接到其他数据库的示例配置。

    ● jbpm-designer:含有用于JBoss jBPM可视化流程设计器的Eclipse插件。

    ● jbpm-server:含有JBoss应用服务器和JBoss jBPM引擎以及示例流程。

    2.执行JBoss jBPM引擎

    为了启动部署了jBPM的JBoss应用服务器,进入jbpm-server目录,执行里面的启动脚本。命令窗口会随同jBPM控制台窗口一起出现,类似图3。

    20060828093426193

    现在,打开浏览器窗口,输入http://localhost:8080/jbpm。就会看到JBoss jBPM的示例Web应用的登录页面。

    以cookie monster用户的身份登录,选择“创建新的Web销售订单”链接。这会创建预制的“Web销售”流程的新实例,如图4所示。

    20060828093515234

    实际的定义文件:processdefinition.xml位于websale.par里面,如以下列表所示:

    < ?xml version="1.0"?>

    < process-definition name="websale"

    xmlns="urn:jbpm.org:jpdl-3.1">

    < !-- SWIMLANES (= process roles) -->

    < swimlane name="buyer" />

    < swimlane name="salesman">

    < assignment expression="user(ernie)" />

    < /swimlane>

    < swimlane name="accountant">

    < assignment expression="user(bert)" />

    < /swimlane>

    < swimlane name="shipper">

    < assignment expression="user(grover)" />

    < /swimlane>

    < !-- NODES -->

    < start-state name="create new web sale order">

    < task swimlane="buyer">

    < controller>

    < variable name="item"/>

    < variable name="quantity"/>

    < variable name="address"/>

    < /controller>

    < /task>

    < transition to="evaluate web order" />

    < /start-state>

    < task-node name="evaluate web order">

    < task swimlane="salesman">

    < timer duedate="20 seconds" repeat="10 seconds">

    < action class="org.jbpm.websale.RemindActor">

    < swimlaneName>salesman< /swimlaneName>

    < /action>

    < /timer>

    < controller>

    < variable name="item" access="read"/>

    < variable name="quantity" access="read"/>

    < variable name="address" access="read"/>

    < variable name="comment"/>

    < /controller>

    < /task>

    < transition name="ok" to="salefork" />

    < transition name="more info needed" to="fix web order data" />

    < /task-node>

    < task-node name="fix web order data">

    < task swimlane="buyer">

    < controller>

    < variable name="comment" access="read"/>

    < variable name="item" />

    < variable name="quantity" />

    < variable name="address" />

    < /controller>

    < /task>

    < transition to="evaluate web order" />

    < /task-node>

    < fork name="salefork">

    < transition name="payment" to="wait for money" />

    < transition name="shipping" to="ship item" />

    < /fork>

    < task-node name="wait for money">

    < task swimlane="accountant">

    < controller>

    < variable name="item" access="read" />

    < variable name="quantity" access="read" />

    < variable name="address" access="read" />

    < variable name="money received" />

    < /controller>

    < /task>

    < transition to="update books" />

    < /task-node>

    < node name="update books">

    < action class="org.jbpm.websale.UpdateBooks">

    < msg>accountancy application is now informed of the payment< /msg>

    < /action>

    < transition to="salejoin" />

    < /node>

    < node name="ship item">

    < action class="org.jbpm.websale.ShipItem">

    < swimlaneName>shipper< /swimlaneName>

    < msg>${shipper} now ships ${item} to ${address}< /msg>

    < /action>

    < transition to="salejoin" />

    < /node>

    < join name="salejoin">

    < transition to="end" />

    < /join>

    < end-state name="end" />

    < /process-definition>

    一旦“创建新的Web销售订单”页面加载完毕,填写“物品”和“数量”表格字段,其中cookie作为物品,1作为数量。然后选择“保存”和“结束任务”,即可完成“创建新的Web销售订单”任务。Web应用会向jBPM发出信号,要求把Web销售流程标记转移到“评估Web订单”任务,然后使用输入数据作为流程变量。这时候,登录页面会再次显示。你会注意到这样一则消息:“新的任务已分配给'ernie’。”

    现在,选择“以另一个用户登录”链接、以ernie的身份登录。你会注意到:“评估Web订单”任务呈高亮显示。把所需地址输入到注释字段,然后选择“需要更多信息”按钮。这样就可以把流程标记转移到“修复Web订单数据”任务,并且让浏览器回到登录页面。

    这时候,可以以cookie monster用户的身份登录,选择“修复Web订单数据”链接,就可以查看高亮显示的任务如何变化、体现流程标记在执行流程中的位置。

    重复流程定义在浏览器窗口中显示的步骤,出现提示时以相应用户的身份登录,完成填写每项新任务的表格。你最终会进入到流程末端,这时就会出现屏幕,告诉你流程已完成。

    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1793775

    March, 2008

    IReport编码问题解决(转)

    一、在ireport直接生成中的解决方法

    1、mysql数据库的字符编码不变,采用默认编码;

    2、ireport的数据库设置中用以下方式:

    jdbc:mysql://localhost:3306/database?user=&password=&useUnicode=true&characterEncoding=GB2312

    3、 text   field   expresssion 中写入以下字符转换代码:

    new   String($F{name}.getBytes("iso-8859-1"),"gb2312")

    注:如果是在web中应用,就不需要字符集转换,直接写$F{name}就可以了,但是在web应用中的数据库连接写法中应照2的写法

    4、i18n中的设置可以是UTF-8或者gb2312

    二、移植到web(jsp)的应注意的事项

    (1)找不到报表文件
    看一下你的物理路径中是否能找到那个test.jasper 文件,没有的话建立正确的目录把设
    JasperReport 与iReport 的配置与使用
    计编译好的test.jasper 文件放进去就行了。
    (2)运行后没有报错,什么反映也没有
    安装一下Adobe acrobat reader 等PDF 阅读工具软件。
    (3)可以运行,但出不了PDF 文件,只是在IE 中有一堆乱码
    没有报错就说明程序没有问题,在你自己的客户端找找问题所在,可以试着换个Adobe
    acrobat reader 版本,同时如果是以Localhost 等域名的方法访问的话,换成IP 方式试一下。
    (4)能运行,但出不来中文
    请确认您的web-inflib 目录中是否有iTextAsian.jar 文件,并且您已经在报表设计器中将
    字段的font 调成汉字类型了。

    三、生成PDF注意事项

    以下引用:http://blog.csdn.net/CloneIQ/archive/2007/01/05/1474938.aspx

    1 运行环境
        1.1 JasperReport 1.2.8
        JasperReports是iReport的核心内容。它是一个强有力的开源报表产生工具,可以将内容输出到屏幕上、打印机或生成PDF, HTML, XLS, CSV和XML等文件。它完全用Java编写,并可在各种Java应用(包括J2EE或WEB应用)中用来产生动态报表内容。JasperReports组织根据在一个XML文件中定义的报表设计通过JDBC访问关系数据库中的数据.填充报表数据之前必须先将报表设计被编译成一个jasper文件。
    下载地址: http://jasperreports.sourceforge.net/
       1.2 iReport 1.2 8
        iReport是一个制作Jasperreport XML文件的优秀可视化开发工具,通过它可以方便的设计报表模板。目前最高版本为iReport 1.3.0
    下载地址: http://ireport.sourceforge.net
       1.3 iText 1.3.1
       iText是一个开放源码的Java类库,是用来生成PDF文件的。
    下载地址: http://itext.sourceforge.net
       1.4 iTextAsian.jar
       如果要在生成的pdf文件显示中文等亚洲字符,还必须下载itext的亚洲字符包。
    下载地址: http://itextdocs.lowagie.com/downloads/iTextAsian.jar
    以上四个包必须加到应用程序的构建路径中或加到CLASSPATH,若是Web应用放入相应的WEB-INFlib中。
    2 设置中文支持
        为了在报表中能够显示中文,加入以上四个包还不够(因为以上四个包仅对报表生成及中文显示提供了支持),必须设置报表上各显示对象的相关属性,各属性设置说明如下:
    Font        name:    宋体 (中文字体)
        PDF font name:   STSong-Light
        PDF  Encoding:  UniGB-UCS2-H(Chinese Siplified)
        PDF   Embeded: √
        如下图所示(显示对象的属性设置):
    字体设置
    注意:此三项的设置必须确保iTextAsian.jar亚洲语言包已经包含在项目中,否则出现编译错误.
        若读者觉得对每一个对象设置比较麻烦,可通过iReport的”Format””Fonts”菜单进行统一设置,并设成报表的默认字体。如下图所示:
    3可能出现的问题
       经过以上的设置,报表中文的显示应该没什么问题了,笔者在报表的开发过程中遇到几个问题,现总结如下:
    3.1 问题描述
       在WinXp中运行iReport1.2.8,设计报表时将数据字段的字体设置成中文字体,调好格式后编译并运行,并使用JRViewer进行预览一切正常(中文显示正常),但使用PDF、Excel预览出现如下错误:
    Could not load the following font :
       pdfFontName   : STSong-Light
       pdfEncoding   : UniGB-UCS2-H
       isPdfEmbedded : true
    产生原因:成成PDF的亚洲语言包没有包含到项目中
    解决办法:将亚洲语言包iTextAsian.jar包含到项中,可能过iReport的”Options””Classpath”将iTextAsian.jar添加Classpath中或者直接将iTextAsian.jar复制到iReport安装目录下的lib中.若是Web项目则应该将iTextAsian.jar复制到此项目的WEB-INFlib中.
    3.2 问题描述
        在WinXp中运行iReport1.2.8,设计报表时将数据字段的字体设置成中文字体,调好格式后编译并运行,并使用JRViewer进行预览一切正常(中文显示正常),使用PDF、Excel预览时无任何错误,但中文无法显示(只显示空白).
    产生原因:报表显示对象的PDF字体属性设置不正确
    解决办法: 报表对象有关字体属性的设置如下:
    Font        name:         宋体 (中文字体)
        PDF font name:  STSong-Light
        PDF Encoding:   UniGB-UCS2-H(Chinese Siplified)
        PDF Embeded:  √
       即如下图所法
       3.3问题描述
        生成PDF、Excel文件时,中文显示一切正常,但生成Html文件时显示乱码
    产生原因:主要是网页编码问题
    解决方法
     在JSP页中生成Html时,设置 <%@ page contentType="text/html;charset=GB2312" %> 
     在Servlet中生成Html时,response.setContentType("text/html;charset=GB2312");
      若用户对项目进行了统一编码设定及过滤,则需要另作考虑,但主要还是集中在编码问题上.例如:笔者最近做的项目是其于Struts的,对编码进行了统一和过滤处理,生成Html页面时无需指编码就可正确生成Html页面,并不会出乱码,若设置成charset=GB2312反而出现了乱码.总之,对编码的统一与处理还是比较嘛烦的事.
        另外, 我们通过查看iReport生成的jrxml(<?xml version="1.0" encoding="UTF-8" ?>)文件可以发现, iReport保存的汉字都是UTF-8编码。了解它的编码可以有助于我们统一编码方式。

    February, 2008

    如何使用Oracle 的imp 和 exp 导入,导出数据

    exp的参数供你参考:

    userid 执行导出的帐户的用户名/口令,如果这是exp命令后的第一个参数,则关键字userid就不必指定

    buffer 用于获取数据行的缓冲区尺寸,缺省值随系统而定,通常设为一个高值(>64000)

    file 导出转储文件的名字

    filesize 一个导出转储文件的最大尺寸。如果file条目中列出了多个文件,将根据filesize设置值导出这些文件

    compress 一个Y/N标志,用于指定导出是否应把碎片段压缩成单个盘区。这个标志影响将存储到导出文件中的storage子句

    grants 一个Y/N标志,用于指定数据库对象的权限是否导出

    indexes 一个Y/N标志,用于指示表上的索引是否导出

    rows 一个Y/N标志,用于指示行是否导出。如果设置为N,在导出文件中将只创建数据库对象的DDL

    constraints 一个Y/N标志,用于指示表上的约束条件是否导出。

    full 若设为Y,执行Full数据库导出

    owner 导出数据库帐户的清单;可以执行这些账户的User导出

    tables 导出表的清单,可以执行这些表的Table导出

    recordlength 导出转储文件记录的长度,以字节为单位。除非是在不同的操作系统间转换导出文件,否则就使用缺省值

    inctype 要执行的导出类型(允许值为COMPLETE(缺省)、CUMULATIVE和INCREMENTAL),导出类型将在下几节描述

    direct 一个Y/N标志,用于指示是否执行Direct导出。Direct导出在导出期间绕过缓冲区,从而大大提高导出处理的效率

    record 用于Incremental导出,这个Y/N标志指示一个记录是否存储在记录导出的数据字典表中

    parfile 传递给Export的一个参数文件名。这个文件可以包含这里列出的全部参数条目

    statistics 这个参数指示导出对象的analyze命令是否应写到导出转储文件上。其有效值是

    COMPUTE、ESTIMATE ( 缺省)和N。在较早的Oracle版本中,这个参数叫作ANALYZE

    consistent 一个Y/N标志,用于指示是否应保留全部导出对象的读一致版本。在Export处理期间,当相关的表被用户修改时需要这个标志

    log 一个要写导出日志的文件名

    feedback 表导出时显示进度的行数。缺省值是0,所以在一个表全部导出前没有反馈显示

    point_in_time_recover 一个Y/N标志,用于向Oracle指示,是否正在导出用于表空间时间点恢复的元数据。这是个高级恢复技术

    recover_tablespaces 在表空间时间点恢复期间,其元数据应被导出的表空间

    query 导出时用于每个表的where子句

    transport_tablespace 如果正在使用Oracle8i的可移动表空间选项,就设置成Y。和关键字tablespace一起使用

    tablespaces 移动一个表空间时应导出其元数据的表空间

    例如:exp system/manager file=export.dmp compress=Y owner=(HR,THUMPER)

    IMP的参数供你参考:

    userid 需执行导入操作的帐户的用户名/口令。如果这是imp命令后的第一个参数,就不必指定userid关键字

    buffer 取数据行用的缓冲区尺寸。缺省值随系统而定;该值通常设为一个高值(>100000)

    file 要导入的导出转储文件名

    show 一个Y/N标志,指定文件内容显示而不是执行。

    ignore 一个Y/N标志,指定在发出Create命令时遇到的错误是否忽略。若要导入的对象已存在,就使用这个标志

    grants 一个Y/N标志,指定数据库对象上的权限是否导入

    indexes 一个Y/N标志,指定表上的索引是否导入

    constraints 一个Y/N标志,指定表上的约束条件是否导入

    rows 一个Y/N标志,确定行是否导入。若将其设为N,就只对数据库对象执行DDL

    full 一个Y/N标志,如果设置为Y,就导入Full导出转储文件

    fromuser 应从导出转储文件中读取其对象的数据库帐户的列表(当Full=N时)

    touser 导出转储文件中的对象将被导入到的数据库帐户的列表。fromuser和touser不必设置成相同的值

    tables 要导入的表的列表

    recordlength 导出转储文件记录的长度,以字节为单位。除非要在不同的操作系统间转换,否则都用缺省值

    inctype 要被执行导入的类型(有效值是COMPLETE[缺省]、CUMULATIVE和INCREMENTAL)

    commit 一个Y/N标志,确定每个数组导入后Import是否提交(其大小由BUFFER设置),如果设置为N,在每个表导入后都要提交Import。对于大型表,commit=N需要同样大的回滚段

    parfile 传递给Import的一个参数文件名,这个文件可以包含这里所列出的全部参数的条目

    indexfile 这是个非常有效的选项,可以把所有的create table、create cluster和create index命令写到一个文件中,而不是运行它们。几乎所有的create index命令都要改为注释。

    这个文件在以index=N导入后就可以运行(进行少量修改)。这对把表和索引分别放在不同的表空间中非常有用。

    charset 在为v5和v6执行导入操作期间使用的字符集(过时但被保留)

    point_in_time_recover 一个Y/N标志,确定导入是否是表空间时间点恢复的一部分。这是一个高级恢复技术

    destroy 一个Y/N标志,指示是否执行在Full导出转储文件中找到的create tablespace命令(从而破坏正在导入的数据库数据文件)

    logImport日志将要写入的文件名

    skip_unusable_indexes 一个Y/N标志,确定Import是否应跳过那些标有unusable的分区索引。可能要在导入操作期间跳过这些索引,然后用人工创建它们以改善创建索引的性能

    analyze 一个Y/N标志,指示Import是否应执行在导出转储文件中找到的analyze命令

    feedback 表导入时显示进展的行数。缺省值为0,所以在没有完全导入一个表前不显示反馈

    tiod_novalidate 使Import能跳过对指定对象类型的确认。这个选项通常与磁带安装一起使用。可以指定一个或多个对象。

    filesize 如果参数FILESIZE用在Export上,这个标志就是对Export指定的最大转储尺寸

    recalculate_statistics 一个Y/N标志,确定是否应生成优化程序统计

    transport_tablespace 一个Y/N标志,指示可移植的表空间元数据被导入到数据库中

    tablespaces 要传送到数据库中的表空间名字或名字清单

    datafiles 要传送到数据库的数据文件清单

    tts_owner 可移植表空间中数据拥有者的名字或名字清单

    例如:imp system/manager file=export.dmp buffer=64000 commit=Y

    February, 2008

    项目管理的三个重要概念

    检查点(CheckPoint)

    每个一个固定的时间检查项目进度与项目计划之间的差异

    典型的表现为项目小组周例会;

     

    里程碑(MileStone)

    细化一个大的工作的粒度,每一个粒度的工作量的完成称之为一个里程碑

    例如实现一个功能模块将功能模块细化为:需求讲解、设计、设计确认、实现

     

    基线(BaseLine)

    将一个或者一组配置项在生命周期的不同时间点上,通过正式评审后进入正式的受控状态;

    基线控制的配置项是以前一基线为基础,完成预期目标(一个大的里程碑,通过正式评审),而且作为后续工作的基础

    通常为高层的阶段性汇报;

     

    我想基线是某个阶段时一些配置段的集合,如项目计划和准备阶段时一些项目计划经过评审即形成基线。
    而里程碑是指一个时间点,可以将形成某个基线的时间设成一个里程碑,但并不是所有的里程碑都只是产生基线,可能还会产生其他配置项。
    我想我这样说,可能比较通俗一些吧!

    February, 2008

    浅谈Hibernate的flush机制(转)

    随着Hibernate在Java开发中的广泛应用,我们在使用Hibernate进行对象持久化操作中也遇到了各种各样的问题。这些问题往往都是我们对Hibernate缺乏了解所致,这里我讲个我从前遇到的问题及一些想法,希望能给大家一点借鉴。
           这是在一次事务提交时遇到的异常。
           an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
    net.sf.hibernate.AssertionFailure: possible nonthreadsafe access to session
    注:非possible non-threadsafe access to the session (那是另外的错误,类似但不一样)
           这个异常应该很多的朋友都遇到过,原因可能各不相同。但所有的异常都应该是在flush或者事务提交的过程中发生的。这一般由我们在事务开始至事务提交的过程中进行了不正确的操作导致,也会在多线程同时操作一个Session时发生,这里我们仅仅讨论单线程的情况,多线程除了线程同步外基本与此相同。
           至于具体是什么样的错误操作那?我给大家看一个例子(假设Hibernate配置正确,为保持代码简洁,不引入包及处理任何异常)
    SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
    Session s = sf.openSession();
    Cat cat = new Cat();
    Transaction tran = s.beginTransaction(); (1)
    s.save(cat); (2)(此处同样可以为update delete)
    s.evict(cat); (3)
    tran.commit(); (4)
    s.close();(5)
           这就是引起此异常的典型错误。我当时就遇到了这个异常,检查代码时根本没感觉到这段代码出了问题,想当然的认为在Session上开始一个事务,通过Session将对象存入数据库,再将这个对象从Session上拆离,提交事务,这是一个很正常的流程。如果这里正常的话,那问题一定在别处。
            问题恰恰就在这里,我的想法也许没有错,但是一个错误的论据所引出的观点永远都不可能是正确的。因为我一直以为直接在对数据库进行操作,忘记了在我与数据库之间隔了一个Hibernate,Hibernate在为我们提供持久化服务的同时,也改变了我们对数据库的操作方式,这种方式与我们直接的数据库操作有着很多的不同,正因为我们对这种方式没有一个大致的了解造成了我们的应用并未得到预先设想的结果。
    那Hibernate的持久化机制到底有什么不同那?简单的说,Hibernate在数据库层之上实现了一个缓存区,当应用save或者update一个对象时,Hibernate并未将这个对象实际的写入数据库中,而仅仅是在缓存中根据应用的行为做了登记,在真正需要将缓存中的数据flush入数据库时才执行先前登记的所有行为。
    在实际执行的过程中,每个Session是通过几个映射和集合来维护所有与该Session建立了关联的对象以及应用对这些对象所进行的操作的,与我们这次讨论有关的有entityEntries(与Session相关联的对象的映射)、insertions(所有的插入操作集合)、deletions(删除操作集合)、updates(更新操作集合)。下面我就开始解释在最开始的例子中,Hibernate到底是怎样运作的。
    (1)生成一个事务的对象,并标记当前的Session处于事务状态(注:此时并未启动数据库级事务)。
    (2)应用使用s.save保存cat对象,这个时候Session将cat这个对象放入entityEntries,用来标记cat已经和当前的会话建立了关联,由于应用对cat做了保存的操作,Session还要在insertions中登记应用的这个插入行为(行为包括:对象引用、对象id、Session、持久化处理类)。
    (3)s.evict(cat)将cat对象从s会话中拆离,这时s会从entityEntries中将cat这个对象移出。
    (4)事务提交,需要将所有缓存flush入数据库,Session启动一个事务,并按照insert,update,……,delete的顺序提交所有之前登记的操作(注意:所有insert执行完毕后才会执行update,这里的特殊处理也可能会将你的程序搞得一团糟,如需要控制操作的执行顺序,要善于使用flush),现在cat不在entityEntries中,但在执行insert的行为时只需要访问insertions就足够了,所以此时不会有任何的异常。异常出现在插入后通知Session该对象已经插入完毕这个步骤上,这个步骤中需要将entityEntries中cat的existsInDatabase标志置为true,由于cat并不存在于entityEntries中,此时Hibernate就认为insertions和entityEntries可能因为线程安全的问题产生了不同步(也不知道Hibernate的开发者是否考虑到例子中的处理方式,如果没有的话,这也许算是一个bug吧),于是一个net.sf.hibernate.AssertionFailure就被抛出,程序终止。
    我想现在大家应该明白例子中的程序到底哪里有问题了吧,我们的错误的认为s.save会立即的执行,而将cat对象过早的与Session拆离,造成了Session的insertions和entityEntries中内容的不同步。所以我们在做此类操作时一定要清楚Hibernate什么时候会将数据flush入数据库,在未flush之前不要将已进行操作的对象从Session上拆离。
    对于这个错误的解决方法是,我们可以在(2)和(3)之间插入一个s.flush()强制Session将缓存中的数据flush入数据库(此时Hibernate会提前启动事务,将(2)中的save登记的insert语句登记在数据库事务中,并将所有操作集合清空),这样在(4)事务提交时insertions集合就已经是空的了,即使我们拆离了cat也不会有任何的异常了。
    前面简单的介绍了一下Hibernate的flush机制和对我们程序可能带来的影响以及相应的解决方法,Hibernate的缓存机制还会在其他的方面给我们的程序带来一些意想不到的影响。看下面的例子:
    (name为cat表的主键)
    Cat cat = new Cat();
    cat.setName(“tom”);
    s.save(cat);
    cat.setName(“mary”);
    s.update(cat);(6)
    Cat littleCat = new Cat();
    littleCat.setName(“tom”);
    s.save(littleCat);
    s.flush();
    这个例子看起来有什么问题?估计不了解Hibernate缓存机制的人多半会说没有问题,但它也一样不能按照我们的思路正常运行,在flush过程中会产生主键冲突,可能你想问:“在save(littleCat)之前不是已经更改cat.name并已经更新了么?为什么还会发生主键冲突那?”这里的原因就是我在解释第一个例子时所提到的缓存flush顺序的问题,Hibernate按照insert,update,……,delete的顺序提交所有登记的操作,所以你的s.update(cat)虽然在程序中出现在s.save(littleCat)之前,但是在flush的过程中,所有的save都将在update之前执行,这就造成了主键冲突的发生。
    这个例子中的更改方法一样是在(6)之后加入s.flush()强制Session在保存littleCat之前更新cat的name。这样在第二次flush时就只会执行s.save(littleCat)这次登记的动作,这样就不会出现主键冲突的状况。
    再看一个例子(很奇怪的例子,但是能够说明问题)
    Cat cat = new Cat();
    cat.setName(“tom”);
    s.save(cat); (7)
    s.delete(cat);(8)
    cat.id=null;(9)
    s.save(cat);(10)
    s.flush();
    这个例子在运行时会产生异常net.sf.hibernate.HibernateException: identifier of an instance of Cat altered from 8b818e920a86f038010a86f03a9d0001 to null
    这里例子也是有关于缓存的问题,但是原因稍有不同:
    (7)和(2)的处理相同。
    (8)Session会在deletions中登记这个删除动作,同时更新entityEntries中该对象的登记状态为DELETED。
    (9)Cat类的标识符字段为id,将其置为null便于重新分配id并保存进数据库。
    (10)此时Session会首先在entityEntries查找cat对象是否曾经与Session做过关联,因为cat只改变了属性值,引用并未改变,所以会取得状态为DELETED的那个登记对象。由于第二次保存的对象已经在当前Session中删除,save会强制Session将缓存flush才会继续,flush的过程中首先要执行最开始的save动作,在这个save中检查了cat这个对象的id是否与原来执行动作时的id相同。不幸的是,此时cat的id被赋为null,异常被抛出,程序终止(此处要注意,我们在以后的开发过程尽量不要在flush之前改变已经进行了操作的对象的id)。
    这个例子中的错误也是由于缓存的延时更新造成的(当然,与不正规的使用Hibernate也有关系),处理方法有两种:
    1、在(8)之后flush,这样就可以保证(10)处save将cat作为一个全新的对象进行保存。
    2、删除(9),这样第二次save所引起的强制flush可以正常的执行,在数据库中插入cat对象后将其删除,然后继续第二次save重新插入cat对象,此时cat的id仍与从前一致。
    这两种方法可以根据不同的需要来使用,呵呵,总觉得好像是很不正规的方式来解决问题,但是问题本身也不够正规,只希望能够在应用开发中给大家一些帮助,不对的地方也希望各位给与指正。
      总的来说,由于Hibernate的flush处理机制,我们在一些复杂的对象更新和保存的过程中就要考虑数据库操作顺序的改变以及延时flush是否对程序的结果有影响。如果确实存在着影响,那就可以在需要保持这种操作顺序的位置加入flush强制Hibernate将缓存中记录的操作flush入数据库,这样看起来也许不太美观,但很有效。

    January, 2008

    以Web2.0的名义(转)

      互联网最不缺的就是概念。但像Web2.0这样受人追捧,却难以准确定义的概念还真不多。

      互联网最不缺的就是概念。但像Web2.0这样受人追捧,却难以准确定义的概念还真不多。尽管如此,有了新概念还是一定要往上靠的,这也是互联网从诞生起就惯有的传统。

      先是号称向Web2.0转型的猫扑网获得了美国风投AccelPartners的千万美元注资;之后,是爱赶时髦的搜狐宣布将搜狐IT升级到2.0版本;而“博客中国”更名为“博客网”,更是拿Web2.0来说事儿的。

      按照该公司董事长兼CEO方兴东的话来说,目前的互联网已然开始了2.0时代,“全球所有的互联网巨头都开始向2.0时代进军,整个互联网的发展接下来会进入一场新的竞争乃至新的战争!”

      那么,带来如此重大变革、意义如此深远的Web2.0,到底是什么呢?它是一种新的技术么?与之前的Web1.0相比,它又有什么不同?

      方兴东认为,与Web1.0最大的不同就是Web2.0所提倡的个性化,在其中,个人不是作为被动的客体而是作为一种主体参与到了互联网中,个人在作为互联网的使用者之外,还同时成为了互联网主动的传播者、作者和生产者。

      到目前为止,关于Web2.0并没有清晰的定义。业内普遍的一种说法是,微内容是Web2.0的一个关键词。其中,微内容包括个人所形成的任何数据:比如一则网志、一个评论、一幅图片、收藏的书签、喜好的音乐列表、想结交的朋友等等。这些微内容,充斥在了人们的生活、工作和学习的方方面面。而Web2.0重点要解决的正是对这些微内容的重新发现和利用。于是,我们看到了这样的现象,只要是和微内容相关的技术和架构,都是以Web2.0为名义的。

      也有人这么界定,Web2.0是相对Web1.0的新的一类互联网应用的统称。并且由此断言,由Web1.0单纯通过网络浏览器浏览html网页模式向内容更丰富、联系性更强、工具性更强的Web2.0互联网模式的发展已经成为互联网新的发展趋势。就目前的发展情况而言,Web2.0是以 Flickr、Craigslist、Linkedin、Tribes、Ryze、 Friendster、Del.icio.us、43Things.com等网站为代表,以Blog、TAG、SNS、RSS、wiki等应用为核心,依据六度分隔、xml、ajax等新理论和技术实现的互联网新一代模式。

      无论怎样的注脚,我们都不难发现,这个Web2.0的概念,在很大程度上并不是从技术创新角度出发的,或者说与技术没有很大的相关性。如果从应用的角度看,Web2.0或许更好理解一些,它就像一个带着光芒的大筐,凡是于以往集中式互联网不同的模式或者技术,都可以框在其中。

      Web1.0到Web2.0的转变,具体的说,从模式上是从读向写、信息共同创造的一个改变;从基本结构上说,则是是由网页向发表/展示工具演变;从工具上,是由互联网浏览器向各类浏览器、rss阅读器等内容发展;运行机制上,则是自“Client Server”向“Web Services”的转变;由此,互联网内容的缔造者也由专业人士向普通用户拓展。说白了,Web2.0的精髓就是以人为本,提升用户使用互联网的体验。这样远大的理想,这样庞大的范畴,也难怪新锐们凡是都要以Web2.0为名义了。

    December, 2007

    10年跳槽经验总结 高级人才不用找工作(转)

    首先,真正的高级人才是不用找工作的,因为只有被工作找的份。
      但是,难免有些高级人才厌倦了旧的工作环境,或者遇到天花板,没有了发展空间,或者遇到新老板上任后排除异己来提拔自己的亲信等等,如果您真打算自己去找工作,那么至少需注意以下几点:
      1。网上求职尤其需注意那些一天到晚在网上打招聘广告的公司。这类公司通常分成两类:
      一类是垃圾公司,如一些别有用心的保险公司、中介公司等。这类公司以获取你的个人资源和个人信息为目的。
      二类是某些小有名气的公司,但由于用人条件苛刻并且薪资待遇与他们的苛刻要求不匹配,所以一年到头在招人,却总也招不到让他们满意的人。还有一些著名公司,以打广告为目的,招人为幌子,一个破烂职位能放在网上招一两年。
      2。千万小心猎头公司。他们更象是猎狗公司,他们嗅觉灵敏,对打探个人隐私有着狂热而又执着的癖好,往往是工作没给你找成功,却把你现在工作的公司,以前工作的公司闹得沸沸扬扬。如果你不想丢掉现在的工作,不想让你以前的同事议论非非,那么,请慎重选择猎头公司,慎重透露你的隐私给猎头公司。切记切记。
      3。只给你发邮件而不打电话叫你去面试的公司,必须不予理睬。通常是一些垃圾公司,没有能力满足你的基本要求。他们自己也没把握雇得起你,所以连电话费也免了。
      4。第一次电话就让你于某月某日几点钟去哪里面试的公司,必须立刻回绝。因为你到时候到那里一看,一堆刚毕业2、3年的年轻后生正爬在桌子上填写简历。你跟这些人竞争的结果就是你的工资最多只有他们的1倍高,5、6千顶天了。那么应该怎么回答呢?告诉人事经理,我没空,我只有莫月某日下午几点钟才有空,若不然,就不用去了,浪费时间,肯定是低级职位。
      5。第一次面试就让你带好学历学位证书去面试的公司,千万别去,因为不用问,肯定是低级职位。
      6。去公司面试前必须问清楚是谁面试你,如果得知不是总经理或副总经理来面你,那么我劝你立刻回绝这个职位,因为如果面你的是个低三下四的中层干部,那么你的职位肯定是低四下五的低贱职位。总之,打扮得笔挺结果给猪看了,即花钱又浪费时间。
      7。一进门就让你填一堆表格的公司,必须立马走人,因为这是招聘中低等员工的惯用伎俩,特别是对那些喜欢出一些狗屁不通的试卷的公司,千万不要跟他们浪费时间。况且,应聘的人为了得到这份工作,根本就不可能按自己的真实情况回答这种测试卷,废纸一堆,招聘的人根本不懂人事管理。
      8。不要去人才市场找工作,高端职位不是放在菜市场上卖的。
      9。如果公司所在城市离你较远,需要飞机前往,一定要问明公司报销不报销路费。如果不报销,或者说如果录取就报销的公司,建议不要冒险去试。即使十个面试者中最后被你淘汰了九个,你还是会发现该公司的福利待遇极差极差。惨痛教训,切记勿再试。
      10。要知道一个公司的整体面貌和素质如何,那就请留意人事部职员的面貌,尤其是人事经理的素质往往是一个公司整体素质的缩影。如果接待你的人事经理较热心较礼貌周到,那么该公司的工作氛围一般较好,如果人事经理较冷漠或不很礼貌,那么该公司同事关系往往较残酷较冷漠。
      11。不要试图跟新加坡或台湾老板共事,否则你就等着身心接受摧残和扭曲吧。
      12。注意Hr的职业病,几乎每个HR都有窥探癖和多疑症。
      13。最后一条,也是最重要的一条,12年的跳槽经验表明,较好的中国公司及正规的外企正愈来愈倾向于日本企业的终身雇佣制度,即:拒绝跳槽,拒绝人才流动。所有的HR都有一种固执而又变态的心理:他不希望成为你的第一个开苞的男人,但却强烈地希望是你的第二个雇主,并且是在该领域被第一个雇主用了5年到八年之后,同时希望自己是你的最后一个雇主。所以HR对你的跳槽经历往往怀有一种强烈的偏见和关注,对你跳槽原因的研究兴趣近乎变态。所以,对于绝大多数求职者来讲,最好的选择就是:不跳槽。或者至少在一个单位工作5~8年再考虑跳槽,而作为对这5~8年经验的积累的回报,薪水往往应该加倍,否则就是你贱卖了自己。这就是薪水一路加倍的秘密。
      14。最后,祝各位达人职业生涯中薪水加倍加倍再加倍!

    December, 2007

    免费午餐已经结束——软件历史性地向并发靠拢(转)

    免费大餐不久就将结束。对此,你有何打算,做好下一步准备了么?

    对主要的处理器厂商以及架构,包括Intel、AMD和Sparc、PowerPC[译注1]来说,改善CPU性能的传统方法,如提升时钟速度和指令吞吐量,基本已走到尽头,现在开始向超线程和多核架构靠拢。而且这两个特性(特别是多核)已经在部分芯片实现,如PowerPC和Sparc IV;Intel和AMD也将在2005年内赶上。2004年In-Stat/MDR秋季处理器论坛[译注2]的主题就是多核设备,很多公司都展示了改进和新研发的多核处理器。不过,要将2004年称为多核年,显然还不够理直气壮。

    多核将引领软件研发发生基础性变化,特别对接下来几年里那些面向一般应用、运行在PC和低端服务器上的应用软件(在今天已经销售出去的软件里占有很大比例)而言。在这篇文章里,我想就多核为何突然对软件产生重要影响,以及并发巨变如何影响我们和我们未来编写软件方式的问题展开讨论。

    我可以这么说:免费大餐已经结束一两年了,但我们现在才开始意识到这个问题。

    原文作者: Herb Sutter

    原文链接:原文出http://www.gotw.ca/publications/concurrency-ddj.htm

    免费的性能大餐

    业界存在一个有趣的现象:“安迪送,比尔取。”[译注3]无论处理器性能提升多少,软件都有办法迅速吞噬。CPU速度十倍于前,软件就有十倍于前的活要干(或者肆无忌惮猛增软件的工作量,导致性能下降)。在过去几十年里,由于CPU、内存和硬盘特别是CPU厂商强力推进主流系统向更新更快的方向发展,大多数软件不做版本升级,甚至原封不动,就可轻松而持续地享受处理器性能提升的成果。尽管时钟速度不是衡量系统性能的唯一和最好的标尺,但其重要意义不容忽视。我们见证了CPU的发展历史:从500MHz到1GHz,然后再到2GHz,不断提高。今天,主流计算机已经进入3GHz时代。

    不过,有一个很关键的问题:这种提升模式什么时候会走到尽头?尽管莫尔定律预言了历史上的指数式增长,但我们很清楚指数式增长不可能永远维持,因为硬件毕竟受物理极限约束;光速是不可能更快的[译注4]。所以增长必然放缓,最后停滞。顺便说明一点,莫尔定律的主要描述对象是晶体管集成密度,但在一些相关的领域,如时钟速度方面,也出现了类似的指数式增长;甚至在别的领域有更快的增长速度,例如著名的数据存储量爆炸。不过这些重要趋势需要另一篇文章来分析了。

    如果你是一个软件开发人员,那么你可能一直在免费享受桌面计算机性能提升的大餐。某些操作会成为应用程序性能的瓶颈?“你过虑了”,我们对这样的回答耳熟能详,“未来处理器将更为强劲,而现在的应用程序速度倒是日益被非CPU吞吐量和内存速度因素扼杀,比如I/O、网络和数据库等等。”真的是这样么?

    要在过去,这的确没错。但在以后,就完全不对了。

    我有两个消息要告诉大家。第一个是好消息,处理器性能仍然会不断提高。第二个则是坏消息,至少在短时间内,处理器性能的提升,不再能像以往那样让现在的应用程序继续免费获益。

    过去30年里,CPU设计者主要从三个方面提高CPU性能,头两个就是从线性执行流程上考虑的:

    1、时钟速度

    2、执行优化

    3、缓存

    提升时钟速度将增大单位时间的时钟周期数。让CPU跑得更快,就意味着能让同样工作或多或少更快完成。

    优化指令执行,可以在每个时钟周期内完成更多工作。目前的CPU中,一些指令被不同程度地做了优化,如管线、分支预测、同一时钟周期内执行更多指令,甚至指令流再排序支持乱序执行等[译注5]。引入这些技术的目的是让指令流更好、更快执行,降低延迟时间,挖掘每一时钟周期内芯片的工作潜能。

    在这里,有必要对指令再排序作个简单说明。我刚才提到的部分指令优化手段,其实已远非普通意义上的优化。这些优化可能改变程序原意,造成程序不响应程序员的正常要求。这可是个大问题。CPU设计师都是心智健全且经过严格训练的好同志,正常情况下,他们连苍蝇都不愿伤害,自然也无意破坏你的程序。而在最近几年里,尽管知道指令重组有破坏程序语义的风险,但为了提升每个时钟周期内的工作效率,他们已经习惯于积极开展这类有风险的优化工作。难道海德先生[译注6]复活了?当然不是。这种积极性清楚表明,芯片设计师承受了交付速度更快CPU的巨大压力;在这种压力下,为了让软件跑得更快,他们不得不冒改变程序意思,甚至应用崩溃的风险。拿两个有名的例子来说——写操作再排序和读操作再排序[译注7]。允许处理器对写操作再排序是非常令人吃惊的,让大多数程序员意外,一般来说这个特性必须关闭,因为在写操作被处理器武断地再排序条件下,程序员很难保证程序正确执行。读操作再排序也有明显的问题,但大多数情况下这个特性是开启的;因为相对来说它更容易把握一些,而且人们对性能的要求,让操作系统和操作环境 设计师只能选择让程序员在一定程度吃点苦头,毕竟,这比直接放弃性能优化机会的罪责小一些。

    第三个是增大与RAM分离的片内高速缓存。RAM一直比CPU慢很多,因此让数据近可能靠近处理器就很重要——当然那就是片内了[译注8]。片内缓存持续飚升了很多年,现在的主流芯片商出售的CPU都带有2M甚至更高的二级缓存。值得一提的是,今后,三种提升CPU性能的传统手段里,增加缓存将硕果仅存。我会在后面更详细说明缓存的重要性。

    我写这么多的意思是什么呢?

    最重要的是我们必须认识到,传统性能提升方法与并发没有直接关系。过去任何方法带来的速度提升,无论是顺序(非并行的单线程或单进程)、还是并发执行的程序,都能直接受益。这点很重要,我们目前大量的程序都是单线程的,而且在未来仍然有重要的存在价值。

    当然,适当时候,我们重新编译程序,可以利用CPU的新指令(如MMX、SSE[译注9])和新特性提升系统性能。但总的来说,即使放弃使用新指令和新特性,不做任何更改,老程序在新CPU也会跑得更快,让人心花怒放。

    曾经的世界是这般美好,可如今,她就要变了颜色。

    为什么我们今天没有10GHz芯片

    其实,CPU性能提升在两年前就开始碰壁,但大多数人到了最近才有所觉察。

    我这里有份来自Intel的数据(当然你可以从其他厂商得到类似数据)。图中反映了Intel芯片的时钟速度和晶体管集成规模演变历史。晶体管集成数至少就目前而言仍在继续上升,但时钟速度的情况就不同了。

    我们从图中可以看到,大概在2003年初,一路高歌猛进的CPU时钟速度突然急刹车。受制于一些物理学问题,如散热(发热量太大且难以驱散)、功耗(太高)以及泄漏问题[译注10]等,时钟速度的提升已经越来越难。

    你目前在工作站上用的CPU时钟速度是多少?10GHz么? 2001年8月Intel芯片就达到2GHz,按照2003年前的CPU发展趋势推算,到2005年初,我们就能拥有第一块10GHz的Pentium芯片。但实际上没办到。而且情况好像越来越糟——我们根本就不知道到底在什么时候这样的芯片可以出现。

    那么放低期望,4GHz又如何呢?目前我们已到3.4GHz——那么4GHz已经不远了吧?唉,好像4GHz也遥不可及。可能你知道,Intel首先于2004年中将4GHz芯片的发布时间推迟到2005年,而到了2004年秋季,则彻底取消了4GHz计划[译注11]。在本文写作的同时,Intel宣布计划到2005年早期,实现到3.73GHz(即图中的右上最高处)的微量提升。所以,至少就目前来说,时钟速度的竞赛实际上结束了,Intel和其他大多数处理器厂商将把旺盛的精力投入到多核等方向去。

    也许,我们某天在主流PC里能装上4GHz的CPU,但2005年别想。Intel实验室里的确已经有运行在更高速度的芯片——不过代价是惊人的,比如庞大数量的冷却装置。你想不久在你的办公室里就有这样的冷却设备,坐飞机的时候,就把它们放在你膝盖上?别做梦了!

    莫尔定律与新一代处理器

    “没有免费的午餐。”——摘自R. A. Heinlein的小说《The Moon Is a Harsh Mistress》。

    莫尔定律玩完了?这个问题很有趣,严格地讲,还不能这么说。尽管和所有的指数式增长方式一样,莫尔定律总有一天会走到尽头,但最近这些年,还没有这样的危险。芯片工程师在榨取时钟周期内剩余价值时的确碰了壁,不过晶体管集成量仍在暴涨,所以从这个角度说,CPU近期仍将遵循莫尔定律,系统吞吐量继续提高。

    关键的变化,即本文的中心,是今后几代处理器性能提升所走的道路将完全不同。同时,大多数现在的应用软件将不再可能不作大规模重构,就能像过去那样从处理器免费获益。

    接下来数年里,新型芯片的性能提升将主要从三个方面入手,其中仅有一个沿袭是过去的:

    1、超线程

    2、多核

    3、缓存

    超线程,是指在单个CPU内,并行两个或多个线程。超线程CPU已经发布了,支持并行执行一些指令。不过这种CPU还是存在短板,虽然给它增加了部分硬件如寄存器,但它和绝大多数普通CPU一样,缓存、整数和浮点运算器仍然是唯一的。有资料表明,写得较好的多线程应用,在超线程CPU上能获得5%-15%的性能提升;假设趋于理想状态,即多线程程序写得好到极点,那么性能可以提高40%。不错了,不过还是做不到成倍提升,而且对单线程应用毫无帮助。

    多核,主要是指在一块芯片上运行两个或多个处理器。部分芯片如Sparc和PowerPC目前已经推出了多核版本。Intel和AMD也计划在2005年内初步实现,具体时间取决于它们的系统集成水平,功能则是一样的。AMD初期在性能设计可能更具优势,如更好的支持功能单片内集成,而Intel基本上就打算将两颗Xeon胶合在一块片子上了事。所以刚开始的时候,这种双核芯片与一个真正的双CPU系统在性能几乎没有差别,仅仅在价格上前者更为便宜,毕竟它的主板上不需要两个插槽和额外胶合件;另外,即便理想状态,这种架构也无法达到双倍速度,且无益于单线程应用,而只有写得较好的多线程应用能得到好处。

    最后一个是片内缓存,还能像预期那样在近期继续上升。三个方法中,仅有这个可以让现有应用全面受益。片内缓存有令人难以置信的重要性和对大多数现有应用的超高价值,原因很简单,那就是“空间就是速度”。CPU和主存交互的代价是巨大的,如果能避免,那就尽量不要和它打交道。在目前的系统里,从主存获取数据所花时间,通常是从缓存获得数据的10到50倍。很让人吃惊吧,因为很多人都以为内存已经足够快。其实这不过是与硬盘和网络相比,而不是运行在更高速度的片内缓存。应用程序的工作与缓存间的适配程度,和我们是荣辱与共的。很多年来,不重构程序,仅仅提高缓存大小就拯救了现有应用,给它们带来新生。软件操纵的数据和为新增功能而加入的代码越来越多,性能敏感的操作必须继续与缓存适配。套用经济大萧条时期老人常念叨的一句话:“缓存为王。”

    顺带说件发生在我的编译器小组的趣事,算是“空间就是速度”的一个佐证。32位和64位编译器将同样的代码分别编译成32位和64位程序。64位CPU有多得多的寄存器和其他代码优化特性,因此运行其上的64位编译器先天的获得极大性能提升。这当然很好。而数据的情况又如何呢?换到64位平台上,内存中绝大部分数据的大小并未发生变化,唯一例外的就是指针,指针占用了两倍于以前的空间。因此,我们的编译器和绝大多数32位应用相比,挥舞指针就费力得多。现在的指针耗用8个而不是4个字节,空间净增加,结果我们发现64位编译器的工作集[译注12]大小显著增加。工作集增大导致性能下降,差不多抵消了更快的CPU和更多寄存器带来的性能优势。就在我写这篇文章的时候,64和32位编译器正以同样的速度运行,尽管程序代码完全一样而且64位处理器先天能力更强。这就是“空间就是速度”。

    缓存能,但超线程和多核CPU对现在的绝大多数应用,几乎不会有任何影响。

    综上所述,硬件的变化到底会给软件开发方式带来怎样的影响呢?你可能已经有了初步答案了。让我们更深入研究,明白其厉害所在。

    对软件来说,这意味一次巨变

    上世纪90年代初,我们开始学着理解对象。在主流软件开发领域里,从结构化到面向对象编程是过去20甚至可以说30年来最重要的变革。这期间也发生了其他一些变化,例如近来诞生的的确让人着迷的WebServices,但我们中绝大多数人在职业生涯里从未有过见识像面向对象那样基础而深刻改变软件开发方式的机会。

    现在,机会来了。

    也从现在开始,性能大餐就不再免费了。虽然托缓存增大的福,我们还能在半路上捡到普通的性能提升丸,但如果你希望你的应用程序在新的处理器里能继续获得爆炸性的性能提升,那就需要你好好编写并发程序了(通常是多线程的)。说比做容易啊,也不是所有问题都天生可以通过并行解决,而且并发编程的难度也是很大的。

    肯定有人嚷嚷开了:“并发?并不是什么新鲜玩意嘛!人们不早就在写这样的程序了么?”是的,小部分程序员的确写过。

    别忘了,至少从上世纪60年代晚期的Simula开始,人们就在写面向对象程序。但到了90年代,面向对象才成功发动革命并夺取统治地位。为什么呢?工业是受现实需求驱动的,为了解决越来越大的问题,必须编写越来越大的系统,这样的系统需要越来越强劲的CPU和存储设备支持,硬件系统也恰逢其时地逐步提供了这样的支持。面向对象编程擅长抽象和依赖管理,所以成为了开发经济、可靠和可重用的大型软件的必备利器。

    并发编程差不多也有同样漫长的历史可以追溯,很早的时候,我们就开始编写协程、管程[译注13]以及其他与并发有关的东西。近10年来,我们也发现有越来越多的程序员在编写并发应用(有多线程的,也有多进程的)系统。但是发生整体转向性的巨变,目前还不具备条件,需假以时日。现有大量的单线程应用,仍然有巨大的存在价值,这点我会在后面说明。

    说点题外话,当前“下一次软件开发革命”这样的词语多如牛毛,让大家眼花缭乱,其实这是商家宣传自己新技术所作的广告,不要理睬它。新技术通常都很吸引人,有时候也很有用,但软件开发方式的重大变革必然来源于在真正得到爆发式广泛应用前就存在并经过多年缓慢成长、先进而稳定的技术。这个过程是绕不掉的。变革所依赖的基础技术必须足够成熟(包括有固定的厂商和工具支持);通常,这个成熟稳定过程至少要花费7年的时间,新技术在广泛应用时才不会有潜在的性能悬崖和陷阱。所以,像面向对象这样的软件开发变革,也必须在各项技术经过多年甚至几十年磨砺后才能发生。即便在好莱坞,绝大多数的一夜成名,也仍然是多年努力后发生重大突破的表面象征。

    并发将是软件开发史上的又一个重大变革。很多专家仍然在这个变革是否比面向对象还大的问题上争论不休。这样的争论最好还是留给学问家吧。技术工作者最感兴趣的是和面向对象一样,编程方式的变化程度、编程技术的复杂性和学习曲线问题。

    并发之正反二面

    并发技术(特别是多线程)在主流软件里大多应用在两个方面。第一类是天然就彼此独立的、逻辑上分离的控制流程,比如在我设计的数据库复制服务器里,每个复制Session都放在各自的线程里,彼此完全独立的,不会工作于同一条数据记录上。第二类不像第一类那么常见。为了系统提升性能,像利用多CPU平台的能力,挖掘应用程序其他部分的潜能等,我们也会编写并发代码。在我的数据库复制服务器里,多个独立的线程在多CPU平台上就工作得很好。

    然而,并发编程也是要付出代价的。一些很明显的问题相对来说无关紧要,比如锁定。对资源的锁定降低了系统的性能,但如果你能找到办法最小化甚至消除资源共享,让操作真正并行,从而明智得当地使用锁,那么从并发执行得到的收益,要远大于在同步上蒙受的损失。

    更重要的问题,大概就是并非所有应用都适用并行。这点我会在后面说明。

    应该说,最大的问题,就是并发编程本身的难度了。程序员必须将脑子里的编程模型转化为可靠的程序,这比实现顺序执行的传统程序难得多。

    任何学习过并发的人都认为自己已经理解并发,早早结束寻找他们认为不可能但实际潜在的竞争冲突和他们其实仍没闹明白的问题。如果开发人员认真学习和思考并发编程,就会发现通过合理组织的内部测试能发现大多数的竞争冲突问题,这个时候,无论是在知识水平还是心情愉悦度上,他们都能达到一个新的高度。不过,除了经过理解为什么和怎么进行真正压力测试的行家测试过的、已经正式发布的软件,都会存在部分在普通测试中无法捕获的潜伏并发问题。这些问题只有在真正的多处理器系统上才会暴露出来,因为在这样的环境里,多个线程不是在单处理器上切换,而是真正的并发运行,大量新问题就会涌现。而偏偏又有很多人自以为已经真正理解如何编写并发程序,真是让人忐忑不安啊。我见过不少项目组,他们的程序在很多用户那里即便施以极端苛刻的压力测试,都能出色工作,但某天一个用户部署了真正的多处理器机器后,深层次的竞争冲突甚至程序崩溃问题马上出现。CPU发展到今天,重构你的应用,让它们多线程运行在真正的多核计算机上,的确像逼迫初学游泳的人一下子跳入深水——直达终点,似乎有点残忍,但只有真正并行的环境,才能更容易暴露出你的问题。再说了,即使你组织了一个能真正编写可靠并行代码的团队,也不能说就不会出现问题。例如,并发代码运行可能非常安全,但(在多核的机子上)却不比在单核的机子上跑得快。其典型原因就是线程未被合理分离,共享了单一资源,造成程序执行顺序化。这类问题是相当微妙而复杂的。

    结构化程序员学习面向对象(什么是对象?什么是虚函数?我如何使用继承?知道“是什么”和“怎么办”外,还得问一句:“如何保证理论上正确的设计在实践中的正确性?”)是一个飞跃,同样的,顺序思维的程序员学习并发(什么是竞争冲突?什么是死锁?它是怎么出现的,我如何避免它?什么样的构造在我看来是并行的但实际上顺序化了程序?在“是什么”和“怎么办”外,还要回答同样的问题:“如何保证理论上正确的设计在实践中的正确性?”)也是一个飞跃。

    现在的大量程序员并没有真正理解并发,就像15年前大量程序员没有真正理解对象一样。但并发编程模式是可以学习的,尤其是我们要坚持基于消息和锁的编程;一旦真正理解了并发,就会发现它并不比面向对象难多少,很容易觉得那是自然而然的。我们需要为我们自己和我们的团队做好训练投资和时间的准备。

    有必要说明一点,我在上面故意将并发编程模式限定在消息和锁基础上。其实也有在语言级就直接支持的无锁编程,比如Java5和很流行的C++编译器。但对于程序员来说,无锁比有锁并发编程难得多。大多数情况下,只需要系统和库编写者理解无锁编程就行了,虽然事实上每个人都可以从无锁系统和库获益。老实说,即便有锁编程,也有点碰运气的味道呢。

    对我们来说这到底意味着什么

    好了,回到正题,将问题归纳一下。

    1、我们已经讨论清楚的、最重要结论是:如果应用程序想充分利用CPU吞吐增加量,那它们就必然日益需要并发,这种形势逐渐明朗,并将在接下来的数年里深入发展。Intel已经扬言未来他们会推出集成100颗内核的芯片,那么单线程应用最多就只能利用这种芯片1/100的潜在生产力。“哦,性能没那么重要吧,计算机总是跑得越来越快”的论调已经变得天真而可疑,甚至在未来不久将完全错误。

    目前,并不是所有的应用都需要(或者更准确的说,只有应用中重要的作业才需要)并行。像编译这类的问题,是必须要考虑并行的,而其他则不一定。请看这个有趣的例子:一个女人需要九个月才能生产一个小孩,并不代表九个女人能花一个月生出一个孩子。你以前可能接触过类似的推导,但有没有感觉这个问题意犹未尽呢?如果有人再和你就此讨论,我向你推荐一个刁钻的问题:从这个命题你能断定“女人-小孩”是一个非并行问题么?通常,人们会下意识地认为它天然就不是一个并行问题,但实际上并不完全是这样。如果目的是生一个小孩,它的确是一个非并行问题;但如果是生产多个小孩,那么它就是一个标准的并行问题了!所以说,目标不同,结论就大相径庭。在考虑你的软件是否和如何使用并行时,千万别忘了面向目标原则。

    2、可能不那么明显的结论是:CPU将很可能日益成为应用程序性能的瓶颈。当然,不是所有应用都会这样,目前还未明显受制于CPU能力的应用在未来虽然可能受到CPU影响,CPU也不会一夜之间成为它们的镣铐。但“I/O、网络和数据库瓶颈”似乎快走到谷底,因为在这些领域,条件仍在迅速改善(如GB级WiFi网络等等);而与此形成对比的是,CPU性能提升技术已走到峰点。请注意,我们的CPU目前在3GHz徘徊。所以,除了指望缓存在未来继续扩大(这可真是一个大好消息),现在的单线程应用不太可能跑得更快。其他方面的性能提升手段,虽然未来还可能继续发挥作用,不过已经无法和过去相提并论了。芯片设计师正在拼命寻找新办法提高管线利用率,降低数据加载延迟,但在这些领域,长在低枝的果子早已被摘光。而应用程序新需求的增加不但不稍事休息,反而神经质地加速猛冲。我们只好逼迫程序做更多的事情,而程序则只有威逼CPU,压垮它,除非程序能并发执行。

    应对如此巨变,我们现在有两条路可以走。一是面向并发重构应用,二是勤俭持家,小心规划代码,让它吃更少的食,干更多的活。这就引出了第三个有趣的话题。

    3、提升程序效率、优化其性能将越来越重要,而不是反道而行。已经高度重视性能优化的语言将获得重生,其他的语言赶紧奋起直追,朝着效率和优化努力吧。面对长期增长的需求,希望面向性能努力的语言和系统能为我们分忧。

    4、最后一点,编程语言和系统将不得不尽快做好面向并发的准备。Java语言从一开始就支持并发编程,虽然还存在不少问题以致不得不发布多个后续版本提升并发程序的正确性和效率。C++长期以来被用于编写大型多线程系统,但它却没有对并发的标准支持(ISO C++标准甚至有意未提及线程[译注14] ),因此,并发目前只能在一些不可移植的特定平台和库基础上实现(而且实现还不够完善,比如静态变量只能初始化一次,这就要求编译器自动加锁,但很多C++的实现里并不生成锁)。另外,目前还存在多种并行编程标准,例如pthreads和OpenMP[译注15] ,其中一些支持隐式并行,另一些显式支持。让编译器分析单线程程序并自动生成并行代码的隐式并行方式当然美妙而优雅,不过这类自动转化工具的结果代码质量还无法与人工编写的显式并行代码媲美。目前的并发编程主要以锁为基础,这种方式也很难把握,常常要赌几分运气。总之,我们迫切需要一个比目前语言提供的更高抽象层次的、统一的并发编程模式。关于这点,以后我还有更多的话要说。

    总结

    如果你以前对并发未加注意,那么现在是时候了,仔细分析应用的设计,挑出现在和不久就可能过于依赖CPU能力的操作,研究这些部分如何从并发得益。你和你的团队,现在也该深入学习和了解并发编程的要求、不足、风格和专业概念了。

    少部分应用天然适用于并行,但大多数不是的。即便你知道程序受制于CPU的位置,可能也很难找到将这部分操作并行化的办法。所有这些问题,要求我们加快对并行的思考和研究。隐式并行编译器能帮点小忙,但不能指望太多,它不可能比得上尽你所能将顺序化程序转化为显式并行和多线程版本后的效果的。

    感谢仍未停止的缓存扩大和管线少量优化,免费饭菜在今后还能有一点,不过从今天开始,餐馆无偿提供的只有小菜和饭后小点心了。菜谱上仍然有优质可口的鱼片,但现在要享受它就得付费——设计精细化、代码更复杂,而且要加倍测试。对于多数应用来说,这是个好消息,尽管要辛勤耕耘,但回报是丰厚的,因为并发可以让应用继续从处理器能力暴增中充分受益。

    译注1

    处理器发展历史上,精简指令集(RISC,Reduced Instruction Set Computer)阵营曾向微特尔(Wintel,Microsoft+Intel)阵营发动过三波声势浩大的微电脑盟主争霸战。

    上世纪70年代以来,对处理器的要求更为全面,不单是提升速度就能满足,比如低耗能、小体积,加强数值运算、支持多媒体功能等。复杂指令集(CISC,Complex Reduced Instruction Set Computer)的微指令多且长度不统一,造成解码器线路复杂;加上受当时制造工艺的限制,如果在芯片上直接集成高速缓存和其他部件,体积和价格都将变得难以想象。

    RISC应运而生。因为RISC指令精简、线路精简,所以芯片体积和能耗降低,腾出空间也可容纳更多寄存器;另外指令定长,通过硬件加速,还可提升效能。RISC学术界从一开始就分为两派:Berkeley RISC(伯克利大学RISC计划)和Stanford MIPS(斯坦福大学MIPS计划。MIPS,Microprocessor without Interlocked Pipeline Stages,无内部互锁流水级的微处理器。我国龙芯即MIPS架构)。

    Sun公司引进了RISC技术,在此基础上制定了Sparc(Scalable Processor Architecture,可扩展处理器体系结构)微处理器体系结构规范,并于1985年推出了相应处理器。1988年,Sun领头组建了Sparc联盟,口号是“RISC + UNIX vs CISC + DOS”,藉此发动第一次争霸冲击。后因市场迟迟未见销量,加上Sun在技术上又留了一手,导致联盟最后只剩Sun自己和德州仪器。

    1990年,MIPS研究开花结果,开发团队成立了同名公司MIPS,并于1991年建立了ACE(Advanced Computing Environment)联盟,主要成员有Digital(迪吉多)、SGI(Silicon Graphics,视算科技)和Compaq(康柏)。Compaq当时市场低迷,且Intel对Compaq展开了游说,因此Compaq最先退出了联盟;而Digital又忙于Alpha芯片的开发。主要成员离心离德,1992年,第二次联盟以SGI收购MIPS草草收场。

    IBM从1975年开始精简指令集芯片研究,也就是后来的PowerPC。1992年,IBM与Motorola(摩托罗拉)、Apple Computer(苹果)宣布合组Power联盟。刚开始,Power联盟引用学界评比论文,直指x86复杂指令集结构无法应付未来信息需求,而媒体也纷纷发难斥责x86电脑效能低劣,操作介面不便,都表示支持大胆改革的Power联盟。形势危机,当时Intel的总裁葛洛夫甚至认为Intel已陷入死亡之谷,如果应对失措,“Intel”将成为历史名词。Power联盟洋洋得意。

    但在接下来的三年里,Power联盟迟迟不能统一平台标准,操作系统开发进度也严重滞后。到1994年,Intel推出了Pentium芯片,微软的Windows 95也大功告成。尽管当时的Pentium逊于PowerPC,Windows 95界面也不如Macintosh,但前二者相互支援,前后兼容,不断改进,而后二者却不够统一,用户没有安全感。形势急转直下,Power联盟大跌眼镜。

    第一波攻击,Sun退回到工作站;第二波攻击,SGI退回绘图工作站;第三波攻击至今,IBM还在服务端处理器领域靠PowerPC苦撑。

    顺带说一句,Intel和AMD主要走的都是CISC路线。但处理器发展到现在,各方面技术已经相互融合,不存在绝对CISC或者RISC的芯片了。

    译注2

    In-Stat:www.instat.com,全球著名的行业研究机构,Reed Business Information出版集团成员公司之一,Reed Elsevier的战略组成部分;涉及半导体、电信和电子消费品等领域的研究、评估与预测。

    MDR:MicroDesign Resources,原属美国Ziff-Davis电子出版集团,1999年被Reed Elsevier收购。

    In-Stat/MDR主办《Microprocessor Report》(《微处理器报告》)杂志,三周刊;Microprocessor Forum(微处理器论坛)每年10月在加利福尼亚州San Jose举行。

    译注3

    原句为:Andy giveth, and Bill taketh away.

    Andy:Andy Grove,安迪·格鲁夫,1968年和罗伯特·诺宜斯(Robert Noyce)、戈登·摩尔(Gordon Moore)共同创立Intel。

    Bill:Bill Gates,比尔·盖茨,1975年和保罗·艾伦(Paul Allen)创立Microsoft。

    译注4

    大概是2001年看到过一则新闻,到网上搜了一下,内容大致如下:

    澳大利亚教授韦伯领导的研究小组利用位于夏威夷的世界最大的天文望远镜“凯克”观测17颗不同的类星体。由于这些类星体距离地球120亿光年,它们在宇宙形成初期发出的光线到今天才抵达地球。在长途旅行中,部分光线被星系间的气云吸收。光线的吸收情况既能反映星系气云的性质,也能反映出光的变化情况,这其中就包括光的速度以及决定光速的光谱线精细结构常数。结果,韦伯等人发现,精细结构常数发生了微小的变化。从理论上说,这意味着光速有可能发生过改变。消息公布后,不少物理学家对此发现持谨慎态度。韦伯及同事希望用位于智利的另一个大型天文望远镜来证实他们的结果,据称要得出最终结论尚需2至3年的时间。

    不过到目前为止,似乎还没看到他们的最终结论。

    译注5

    管线:pipelining,或流水线。CPU的管线并不是数据输入输出使用的物理线路,而是指指令执行的流程。一条指令必须被分解为多个执行步骤,每个步骤占用一个时钟周期。例如最基础的管线是5级的:(1)取指令,(2)对指令译码,(3)演算出操作数,(4)执行指令,(5)将结果存储到高速缓存。前三步由指令控制器(ICU)完成,后两步由运算器(ALU或FPU)完成。管线可以细化,例如苹果的G4处理器采用了7级管线,AMD 2500+处理器10级,Intel公司的P3到10级,P4到20级,P4-E甚至高达31级。管线加长,则每级任务量减小,执行所需时间缩短,因此时钟周期可以缩短,即时钟速度加快。设管线为N级,时钟速度为TMIPS(T百万次/秒),那么平均完成一条指令所花费时间为N/T(当然要求芯片的确在每个周期内能完成各管线级的任务),因此理论上只要时钟速度加快,则芯片处理能力上升。但问题是管线执行时总有出错(如分支预测失败)的可能,一旦出错,整个管线就要全部清空,然后从第一级重新执行,在这种情况下,长管线的全部花费时间通常比短管线多。这就是部分AMD芯片比Intel相同甚至更高主频的芯片实际速度要快的原因。

    分支预测:branch prediction。解决管线中条件转移引起管线停顿的问题。例如第一条指令是条件转移,那么需要等其判断结果出来后才能执行下一条指令,分支预测可预测判断结果,然后尽快执行其他指令,从而不致管线停顿。当然预测也有出错的时候,预测失败将导致管线清空,从头执行。目前预测准确度可达90%,进一步提高分支预测准确率是正在研究的重要课题。

    乱序执行:out-of-order execution。解决管线中指令相关引起管线停顿的问题。后序指令需要正在执行指令的结果,因此无法立即处理后序指令,这叫做指令相关,造成其他处理单元的停顿,白白损失时钟周期。解决办法是立即找出其他不相关指令来执行,最后由重新排列单元将各执行单元的结果按原来指令顺序重新排列。很显然,乱序执行是有风险的。

    译注6

    出自美国电影《化身博士》(Dr. Jekyll and Mr. Hyde,有1931和1941两个版本)。故事讲述哲基尔医生相信每个人都同时拥有两极化的个性——好的一面与邪恶的一面。如果将这两种个性分开成为截然不同的两个人,这两个灵魂都将获得释放。他随后成功地用化学实验将自身邪恶的一面转化成为海德先生,此先生犯下了可怕的罪行。但当他想要停止用药时,却惊恐的发现一切为时已晚……

    译注7

    读/写操作再排序都属乱序执行。看下面一段原始指令代码:

    (p1)br label //分支判断。若为true则跳转到label,否则继续执行

    ld8 r9 r5 //从r5所指地址空间读取8个字节到r9

    add r2 r9 r3 //将r9和r3中值求和,并存入r2

    其中ld,load;r,register。

    因为load操作较耗时间,通常花费几个时钟周期才能完成。因此从提高效率的角度看,应该在处理器空闲的情况下,尽早加载此操作。比如简单再排序优化后:

    ld8 r9 r5

    (p1)br label

    add r2 r9 r3

    如果分支判断结果为false,流程发生跳转,那么可以直接舍弃r9的结果值,即ld8白做了;但如果未发生跳转,则ld尽早执行,提高了流程整体效率。

    当然,CPU实际工作远比上面例子复杂。比如将ld提前,但如果ld失败怎么办?有依赖关系的指令呢,能否乱序?在多线程应用里,乱序还可能引起其他一些问题,比如Java中的Double-Checked Locking失败

    不光CPU支持乱序执行,现在的很多编译器也开始做乱序优化,而且重心有逐渐从硬件转到软件的趋势。

    有两篇资料可以参看:Scaling Itanium® Architecture for Higher Performance(特别是其中的如何处理指令依赖值得了解)和Verified Optimizations for the Intel IA-64 Architecture

    译注8

    片内,英文为on the die或On-Die。Die:裸芯;Chip:包装后的芯片。类似有On-Chip、Off-Die/Chip。

    译注9

    MMX:MultiMedia eXtension,多媒体扩展。Intel在1996年3月份正式公布了MMX技术的细节,并于1997年1月正式向全球推出基于MMX技术的166MHz和200MHz Pentium芯片,AMD也几乎在同时推出了支持MMX技术的第六代处理器AMD K6。

    MMX技术是Intel针对×86体系的一次重大扩充,使计算机同多媒体相关任务的综合处理能力提高了1.5~2倍。它不仅是Intel自 i386面世以来对CPU体系结构的一次显著改进,同时也是IT界对多媒体数据处理等专用芯片及功能板卡的一次成功挑战。

    SSE:Streaming SIMD(单指令多数据) Extensions,是Intel针对AMD K6-2引入的“3D NOW!”技术,于l999年在Pentium3中引入的SIMD扩展指令集,业界也称为MMX2,在多媒体数据处理(特别是3D)和浮点运算等能力上全面加强。

    译注10

    目前频率最高的处理器是Intel P4 570J,3.8GHz,上升非常缓慢。

    CPU频率越高,所需电能和发热量越多;而晶体管越小,耗电和热量越低。制造工艺进步,可能让晶体管更小,从而让CPU在相同或一定程度内提高的能耗下达到更高频率。从这个角度说,提升CPU频率的瓶颈是制造工艺。

    于是,Intel推出了了90nm工艺的Prescott核心Pentium4,其理论频率将能达到6GHz!然而世事难料,在90nm工艺晶体管里,由于电介质厚度太低无法阻挡电子的穿越,造成了严重的电流泄漏问题,随之带来的就是大量的电能消耗和废热。如果强行提升频率,则发热激增,CPU经不起如此的高烧。

    泄漏电流问题并非不可解决,但绝不能在短时间办到。至此,芯片厂商提升系统性能的思路开始发生重大变化,即转向多核。

    有关电流泄漏和应对策略的详情,可参看:NetBurst的继承者 Core微处理器架构技术解析

    译注11

    在IDF05(Intel Developer Forum 2005)上。Intel首席执行官Craig Barrett就取消4GHz芯片计划一事,半开玩笑当众单膝下跪致歉。

    译注12

    Working Set,记录了操作系统为进程提交的内存的总量。

    译注13

    并发编程语言(Concurrent Language)中的术语。

    协同程序(coroutines),或协程。用以实现协作式多任务,于上世纪60年代提出。同属一个协程的多个进程,在同一时刻只能有一个处于运行状态。协程属于一种并发进程创建方式,其他方式还有Fork/join、Cobegin/coend和进程显式申明(Process declarations)等。

    并发进程之间的通信方式主要有两种方式:变量共享(shared variables)和消息传递(message passing)。其他还包括抽象于更高层次的远程过程调用(remote procedure call,RPC)等。

    通信就离不开同步。同步方法主要包括:信号量(semaphores)、条件临界区(conditional critical regions)、管程(monitors)、互斥(mutual exclusion)、路径表达式(path expressions)、原子事务(atomic transactions)和汇集(rendezvous)等。

    其中的管程是位于低级同步控制手段之上的一种对象化管理工具。信号量的使用是无结构的,很不方便;条件临界区相对于信号量更结构化,但同步控制代码仍然非常分散,不利于管理。因此引入了管程,它实现了共享资源的集中管理,封装了共享资源以及施于其上的操作。

    译注14

    因为某些原因(如竞争条件下静态变量初始化问题),线程还未被列入ISO C++标准。目前在不同的平台上,都有线程的专门实现,短时间内难以完全统一。

    不过Boost线程库目前差不多具有准标准身份。

    译注15

    并行编程中必须考虑的两个问题是被处理数据和任务间通讯。经过用户的选择与市场的淘汰,现在的并行编程标准基本上趋向以下三种:

    1、数据并行。特点,各任务处理的数据彼此分离,任务间通过消息传递进行通讯;数据分离和消息传递工作由编译器完成。

    HPF(High Performance Fortran,高性能Fortran)是典型的数据并行编程语言。因为目前的编译器技术对实际应用中各种不规则问题的解决方案仍不够理想,加上专注于数据并行,因此HPF未获广泛应用。

    2、消息传递。特点,各任务处理的数据彼此分离,任务间通过消息传递进行通讯;数据分离和消息传递工作由程序员和用户完成,因此对程序员要求很高。这种模式非常适用于消息传递的体系结构(如机群系统),用户和程序员主要需考虑的是通讯同步和通讯性能问题。

    并行虚拟机(PVM,Parallel Virtual Machine)和消息传递接口(MPI,Message Passing Interface)是两种广泛使用的消息传递并行编程标准。其中PVM侧重异构环境下的可移植性和互操作性;MPI更强调性能,但在异构环境下有不同的实现。几乎所有的高性能计算系统都支持PVM和MPI。

    3、共享内存。特点,各任务处理的数据实现内存共享,任务间也通过共享数据实现通讯;数据共享可由程序员或编译器完成。共享内存并行编程主要应用在对称多处理器(SMP ,Symmetric Multi Processors)系统上。

    OpenMP(Open MultiProcessing由X3H5发展而来)和PThread(POSIX Thread)都是共享内存并行编程的实现。

    OpenMP由1993年建立的X3H5标准发展而来,目前已成共享内存并行编程的实际工业标准,得到DEC、Intel、IBM和Sun等厂商广泛支持。它在Forthan、C/C++得到了实现,主要支持隐式并行编程,即编译器实现并行。

    PThread主要在Unix系统上使用。Unix的实现系统很多,比如Linux、FreeBSD、Solaris、Mac OS X等。要在众多“类UNIX”上开发跨平台的多线程应用,绝非易事,因此制定了POSIX Thread标准。David R. Butenhof(Boost库发起者之一,ISO C++标准委员会成员)的《Programming with POSIX Threads》这本书,可以说是Unix上编写多线程应用的必备参考书。对其他平台并行程序开发也有很高参考价值。

    总的来说,共享内存并行编程与目前大多数的多线程程序员思维习惯最为接近,是程序员从单核转向多核系统需付代价最小的方案。但专家仍有不同意见,比如Herb Sutter就不看好OpenMP,因为共享内存并行编程本质上并没有太多改进,仍然依赖数据资源的锁定,这会带来性能问题。消息传递并行有性能优势,但对程序员的要求又太高了。所有这些难题,还需要研究并行和各种标准、库的专家继续努力解决。

    December, 2007

    父母最不该对孩子说的10句蠢话!

       一家三口的生活往往就是“你我他”的不停欢喜,摩擦和助长。作为儿女的你,看看以下的文字,你是否熟悉?作为父母的您,看看这些文字,是否似曾相识?没有偏见,没有纷争,也没有挑拨离间,只是对中国式教育的一种分析和不解。

    1 "我们是不行了,孩子,就看你的了!"
    把孩子的发展当成自己唯一的指望,是一种丧失自我的表现.这样的家长往往自己得过且过,患得患失,不断催逼,最后只会落得吃力不讨好.自我丧失感虽然是为人父母的共性,但它并不是一种健康的心理.
    类似语言:"孩子,我们全靠你了,你可要争气啊!""你是我们全家唯一的希望!"

    2 "你看看人家谁谁谁!"
    或许这是家长们最长说的一句话了,而恰恰是孩子们最讨厌的一句话.这种比较对孩子价值观确立是一种极大的干扰,对于孩子的自我评价系统也是一总破坏.这句话对孩子的危害主要在于:它破坏孩子的心里平衡,不利于孩子内心成长,更容易让孩子失去应有的信心.而对于家长来说,常常把这句话放嘴边,证明他们的眼睛总是盯在别人孩子的身上,人家进步了就着急,人家退步了就且喜.人家学钢琴就让自己的孩子也学,人家练书法,自己的孩子至少要练钢笔字帖,丝毫不顾自家孩子是否对其感兴趣,这种盲目的攀比不但会造成精力和时间的浪费,也造成了孩子对父母的心理抵触.得不偿失.
    类似语言:"你怎么就不如别人的孩子呢?""有点出息好不好?"

    2 "没时间管你,不挣钱怎么过日子啊?"
    名人言:"没有时间就意味着没有时间做人."教育孩子不是上课,打个电话可以教育,传递一个眼神也可以教育,和孩子的相处更是好的教育契机.教育的长短不由时间长短决定,关键要看家长用不用心教育,会不会教育.说自己没时间教育孩子的家长,就算有了时间也教育不好孩子.
    类似语言:"我也想管孩子,可实在抽不出时间!""叫你表姐去开家长会吧,我没时间!"

    3 "宝宝,爸爸不听话,打他!"
    经常见到一些父母把孩子当玩具或者宠物,为了好玩,开这样或那样的玩笑.要知道孩子小时候是不辨是非的,什么行为得到鼓励和刺激,什么行为就得到强化.父母不应该随便利用孩子开玩笑.在这些无聊的玩笑中,孩子会养成不良的习惯,自生不良的价值取向.夫妻的打情骂俏最好不要对年幼的孩子开.
    类似语言:"宝宝都会骂人了!好厉害!"

    4 "进了前三名,妈妈给你买...!"
    物质奖励看似是一种增强孩子动力的保障,其实弊大于利.孩子学习不是为家长学的,如果他考砸了你惩罚他,考好了奖励他,他会误以为学习是为家长学的.这样一来,奖励机制就破坏了孩子对知识的正常理解.如果有一天,或者有的孩子不把物质奖励当作一种动力,那么他还如何学习?而且,这种教育方式助长了孩子的功利心,容易把孩子引向灰色地带.
    类似语言:"考了100分,爸爸带你去...!"

    5 "没有原因,我说不行就不行!"
    典型的暴君式教育方式,源于家长头脑中的"棍棒底下出孝子"的传统观念.这不仅会导致亲子之间的对立和冲突,更会破坏孩子的公正心,妨碍孩子的民主意识,协商能力的发展.甚至,还会滋生孩子的暴力取向.
    类似语言:"还敢顶嘴,反了你了!""住嘴,你怎么这么不听话?""屁眼痒痒了是不是?"

    6 "你爱怎么招就怎么找吧,谁管的了你啊?"
    一般父母这样说的时候,并非孩子完全不服管,可能只是不小心旧错从犯,这样说会让孩子很委屈.这种方法用前两次会让孩子很愧疚.但是用多了就会引起孩子的逆反心理,索性将错就错.
    类似语言:"甭问我,我懒得管你!""你怎么样跟我没关系!"

    7 "孩子是我的,我想怎样就怎样!"
    因为孩子是自己养的,所以就把孩子看成是自己的私人财产,把自己的愿望强加于孩子,任由自己的情绪随意发泄到孩子身上,无视孩子的个人意愿.
    类似语言:"孩子是我的,你管不着!""早知道你这么不听话,当初生下来就该捏死你!"

    8 "你怎么这么笨?"
    抱怨和指责会让事情变的更糟!孩子自己也不情愿表现的那么差,肯定自己心里都很自责.作为父母,千万不要往孩子的伤口上撒盐,更不能当着别人面说自己的孩子很笨.父母气急败坏的责骂孩子,会让孩子无地自容,妄自菲薄,不知所措.增加逃避心理.这种言语会毁灭孩子的自信心,也让孩子的心理素质形成恶性循环.
    类似语言:"废物,猪头!""你怎么一点出息也不长?"

    9 "如果爸爸妈妈离婚,你要爸爸还是要妈妈?"
    如果是真离婚在孩子未成年的情况下还是你们自己决定好了,谁更有能力教育好孩子就让孩子跟谁.如果仅仅是个有意无意的玩笑,那就太愚蠢了.孩子会因此产生恐惧心理,他一定会想爸爸妈妈为什么要离婚?他也一定会考虑自己会跟谁?对于一个孩子,母爱和父爱是同等重要的,没有什么喜欢谁不喜欢谁的区别.而实际上,多少还会影响到孩子的婚姻观,何必呢?
    类似语言:"你觉得妈妈好还是爸爸好?"

    10 "你看看人家谁谁谁!"
    或许这是家长们最长说的一句话了,而恰恰是孩子们最讨厌的一句话.这种比较对孩子价值观确立是一种极大的干扰,对于孩子的自我评价系统也是一种破坏.这句话对孩子的危害主要在于:它破坏孩子的心里平衡,不利于孩子内心成长,更容易让孩子失去应有的信心.而对于家长来说,常常把这句话放嘴边,证明他们的眼睛总是盯在别人孩子的身上,人家进步了就着急,人家退步了就窃喜.人家学钢琴就让自己的孩子也学,人家练书法,自己的孩子至少要练钢笔字帖,丝毫不顾自家孩子是否对其感兴趣,这种盲目的攀比不但会造成精力和时间的浪费,也造成了孩子对父母的心理抵触.得不偿失.
    类似语言:"你怎么就不如别人的孩子呢?""有点出息好不好?"

    December, 2007

    java正则表达式; regular expression(转)

    概要
    文本处理经常涉及的根据一个pattern的匹配。尽管java的character和assorted 的String类提供了low-level的pattern-matching支持,这种支持一般带来了复杂的代码。为了帮助你书写简单的pattern-matching代码,java提供了regular expression。在介绍给你术语和java.util.regex包之后,Jeff Friesen explores 了许多那个包的Pattern类支持的正则表达式结构。然后他examines 了Pattern的方法和附加的java.util.regex 类。作为结束,他提供了一个正则表达式的实践应用。
    为察看术语列表,提示与警告,新的homework,上个月homework的回答,这篇文章的相关材料,请访问study guide. (6,000 words; February 7, 2003)
    By Jeff Friesen ,Translated By humx
    文本处理经常的要求依据特定pattern匹配的代码。它能让文本检索,email header验证,从普通文本的自定义文本的创建(例如,用"Dear Mr. Smith" 替代 "Dear Customer"),等等成为可能。Java通过character和assorted string类支持pattern matching。由于low-level的支持一般带来了复杂的pattern-matching代码,java同时提供了regular expression来简代码。
    Regular expressions经常让新手迷惑。然而, 这篇文章驱散了大部分混淆。在介绍了regular expression术语,java.util.regex 包中的类, 和一个regular expression constructs的示例程序之后, 我explore了许多Pattern类支持的regular expression constructs。我也examine了组成Pattern 和java.util.regex 包中其它类的方法。一个practical 的正则表达式的应用程序结束了我的讨论。
    Note
    Regular expressions的漫长历史开始于计算机科学理论领域自动控制原理和formal 语言理论。它的历史延续到Unix和其它的操作系统,在那里正则表达式被经常用作在Unix和Unix-like的工具中:像awk(一个由其创作者,Aho, Weinberger, and Kernighan,命名,能够进行文本分析处理的编程语言), emacs (一个开发工具),和grep (一个在一个或多个文件中匹配正则表达式,为了全局地正则表达式打印的工具。
    什么是正则表达式?
    A regular expression,也被known as regex or regexp,是一个描述了一个字符串集合的pattern(template)。这个pattern决定了什么样的字符串属于这个集合,它由文本字符和元字符(metacharacters,由有特殊的而不是字符含义的字符)组成。为了识别匹配的检索文本的过程—字符串满足一个正则表达式—称作模式匹配(pattern matching)。
    Java's java.util.regex 包通过Pattern,Matcher类和PatternSyntaxException异常支持pattern matching:
    Pattern 对象,被known as patterns,是编译的正则表达式。
    Matcher 对象,或者matchers,在,实现了java.lang.CharSequence接口并作为文本source的字符序列中定位解释matchers的引擎。
    PatternSyntaxException 对象描述非法的regex patterns。
    Listing 1 介绍这些类:
    Listing 1. RegexDemo.java
    // RegexDemo.java
    import java.util.regex.*;
    class RegexDemo {
    public static void main (String [] args) {
    if (args.length != 2)
    System.err.println ("java RegexDemo regex text");
    return;
    }
    Pattern p;
    try {
    p = Pattern.compile (args [0]);
    }
    catch (PatternSyntaxException e) {
    System.err.println ("Regex syntax error: " + e.getMessage ());
    System.err.println ("Error description: " + e.getDescription ());
    System.err.println ("Error index: " + e.getIndex ());
    System.err.println ("Erroneous pattern: " + e.getPattern ());
    return;
    }
    String s = cvtLineTerminators (args [1]);
    Matcher m = p.matcher (s);
    System.out.println ("Regex = " + args [0]);
    System.out.println ("Text = " + s);
    System.out.println ();
    while (m.find ()) {
    System.out.println ("Found " + m.group ());
    System.out.println (" starting at index " + m.start () +
    " and ending at index " + m.end ());
    System.out.println ();
    }
    }
    // Convert \n and \r character sequences to their single character
    // equivalents
    static String cvtLineTerminators (String s) {
    StringBuffer sb = new StringBuffer (80);
    int oldindex = 0, newindex;
    while ((newindex = s.indexOf ("\\n", oldindex)) != -1) {
    sb.append (s.substring (oldindex, newindex));
    oldindex = newindex + 2;
    sb.append ('\n');
    }
    sb.append (s.substring (oldindex));
    s = sb.toString ();
    sb = new StringBuffer (80);
    oldindex = 0;
    while ((newindex = s.indexOf ("\\r", oldindex)) != -1) {
    sb.append (s.substring (oldindex, newindex));
    oldindex = newindex + 2;
    sb.append ('\r');
    }
    sb.append (s.substring (oldindex));
    return sb.toString ();
    }
    }
    RegexDemo's public static void main(String [] args) 方法validates 两个命令行参数:一个指出正则表达式,另外一个指出文本。在创建一个pattern之后,这个方法转换所有的文本参数,new-line and carriage-return line-terminator 字符序列为它们的实际meanings 。例如,一个new-line字符序列(由反斜杠后跟n表示)转换成一个new-line字符(用数字表示为10)。在输出了regex和被转换的命令行文本参数之后,main(String [] args) 方法从pattern创建了一个matcher,它随后查找了所有的matches 。对于每一个match,它所出现的字符和信息的位置被输出。
    为了完成模式匹配,RegexDemo 调用了java.util.regex包中类的不同的方法。不要使你自己现在就理解这些方法;我们将在后边的文章探讨它们。更重要的是,编译 Listing 1: 你需要RegexDemo.class来探索Pattern's regex 结构。
    探索Pattern's regex 构造
    Pattern's SDK 文档提供了一部分正则表达式结构的文档。除非你是一个avid正则表达式使用者,一个最初的那段文档的阅读会让你迷惑。什么是quantifiers,greedy之间的不同是什么, reluctant, 和 possessive quantifiers? 什么是 character classes, boundary matchers, back references, 和 embedded flag expressions? 为了回答这些和其它的问题,我们探索了许多Patter认可的regex constructs或 regex pattern 种类。我们从最简单的regex construct 开始:literal strings。
    Caution
    不要认为Pattern和Perl5的正则表达式结构是一样的。尽管他们有很多相同点,他们也有许多,它们支持的metacharacters结构的不同点。 (更多信息,察看在你的平台上的你的SDK Pattern类的文档。)
    Literal strings
    当你在字处理软件的检索对话框输入一个你指定一个literal string 的时候,你就指定了一个regex expression construct 。执行以下的RegexDemo 命令行来察看一下这个regex construct 的动作:
    java RegexDemo apple applet
    上边的这个命令行确定了apple 作为一个包含了字符a, p, p, l, and e(依次)的字符regex construct。 这个命令行同时也确定了applet 作为pattern-matching的文本。执行命令行以后,看到以下输出:
    Regex = apple
    Text = applet
    Found apple
    starting at index 0 and ending at index 5
    输出的regex 和text 命令行,预示着在applet中一个applet的成功的匹配,并表示了匹配的开始和结束的索引:分别为0和5。开始索引指出了一个pattern match出现的第一个文本的开始位置,结束索引指明了这个match后的第一个text的位置。换句话说,匹配的text的范围包含在开始索引和去掉结束索引之间(不包含结束索引)。
    Metacharacters
    尽管string regex constructs 是有用的,更强大的regex contsruct联合了文本字符和元字符。例如,在a.b,这个句点metacharacter (.) 代表在a个b之间出现的任何字符。 为了察看元字符的动作, 执行以下命令行:
    java RegexDemo .ox "The quick brown fox jumps over the lazy ox."
    以上命令指出.ox 作为regex ,和The quick brown fox jumps over the lazy ox.作为文本源text。RegexDemo 检索text来匹配以任意字符开始以ox结束的match,并产生如下输出:
    Regex = .ox
    Text = The quick brown fox jumps over the lazy ox.
    Found fox
    starting at index 16 and ending at index 19
    Found ox
    starting at index 39 and ending at index 42
    这个输出展示了两个matches:fox和ox。. metacharacter 在第一个match中匹配f ,在第二个match中匹配空格。
    假如我们用前述的metacharacter 替换.ox会怎么样呢?也就是,我们指定java RegexDemo . "The quick brown fox jumps over the lazy ox."会有什么样的输出,因为period metacharacter 匹配任何字符, RegexDemo 在命令行输出每一个匹配字符,包括结尾的period字符。
    Tip
    为了指定.或者任何的元字符作为在一个regex construct 作为literal character,引用—转换meta状态到literal status—用以下两种方法之一:
    在元字符之前放置反斜杠。
    将元字符放在\Q和\E之间(例如:\Q.\E)。
    在每种情形下,不要忘记在string literal(例如:String regex = \\.;
    )中出现时(像 \\. or \\Q.\\E)的双倍的反斜杠。不要在当它在命令行参数中出现的时候用双倍的反斜杠。
    Character classes
    有时我们限定产生的matches到一个特定的字符集和。例如,我们可以检索元音a, e, i, o, and u ,任何一个元音字符的出现都以为着一个match。A character类, 通过在方括号之间的一个字符集和指定的regex construct ,帮我们完成这个任务。Pattern 支持以下的character classes:
    简单字符: 支持被依次放置的字符串并仅匹配这些字符。例如:[abc] 匹配字符a, b, and c。以下的命令行提供了另外一个示例:
    java RegexDemo [csw] cave
    java RegexDemo [csw] cave [csw]中c匹配在cave中的c。没有其它的匹配存在。
    否定: 以^ metacharacter 元字符开始且仅匹配没有在class中出现的字符。例如:[^abc]匹配所有除了a, b, 和c以外的字符,以下的命令行提供了另外一个示例:
    java RegexDemo [^csw] cave
    java RegexDemo [^csw] cave 匹配在cave中遇到的a, v, 和e。没有其它的匹配存在。
    范围: 包含在元字符(-)左侧的字符开始,元字符(-)右侧字符结束的所有字符。仅匹配在范围内的字符。例如: [a-z] 匹配所有的小写字母。以下的命令行提供了另外一个示例:
    java RegexDemo [a-c] clown
    java RegexDemo [a-c] clown 匹配在clown中的c。没有其它的匹配存在。
    Tip
    通过将它们放置在一起来在一个range character class内联合多个范围。例如:[a-zA-Z] 匹配所有的大写和小写字母。
    联合: 由多个嵌套的character classes组成,匹配属于联合结果的所有字符。例如:[a-d[m-p]] 匹配字符a到d和m到p。 characters a through d and m through p。以下的命令行提供了另外一个示例:
    java RegexDemo [ab[c-e]] abcdef
    java RegexDemo [ab[c-e]] abcdef 匹配它们在abcdef中的副本 a, b, c, d, and e。没有其它的匹配存在。
    交集: 由所有嵌套的class的共同部分组成,且仅匹配字符的共同部分。例如:[a-z&&[d-f]] 匹配字符d, e, 和 f。以下的命令行提供了另外一个示例:
    java RegexDemo [aeiouy&&[y]] party
    java RegexDemo [aeiouy&&[y]] party 匹配在party中的y。没有其它的匹配存在。
    差集: 由除了那些在否定嵌套character class中的字符外所有保留的字符组成。例如:[a-z&&[^m-p]] 匹配 字符a到l和q到z。以下的命令行提供了另外一个示例:
    java RegexDemo [a-f&&[^a-c]&&[^e]] abcdefg
    java RegexDemo [a-f&&[^a-c]&&[^e]] abcdefg 匹配在abcdefg中的 d 和f。没有其它的匹配存在。
    预定义的character classes
    一些在regexes 中出现足够次数的character classes 提供了shortcuts。Pattern用预定义的character class提供了这样的shortcuts,如Table 1所示。使用预定义的character classes来简化你的regexes和最小化regex语法错误。
    Table 1. 预定义的character classes
    Predefined character class
    Description
    \d
    A 数字。 相当于[0-9]。
    \D
    A 非数字。相当于[^0-9]。
    \s
    A whitespace character。相当于[ \t\n\x0B\f\r]。
    \S
    A 非空格字符。相当于[^\s]。
    \w
    A 一个字符。相当于[a-zA-Z_0-9]。
    \W
    A 非字符,相当于[^\w]。
    随后的命令行示例使用了\w预定义character class来identify 命令行中的所有word characters。
    java RegexDemo \w "aZ.8 _"
    上边的命令行产生了以下的输出,它展示了句点和space characters 不被考虑为word character:
    Regex = \w
    Text = aZ.8 _
    Found a
    starting at index 0 and ending at index 1
    Found Z
    starting at index 1 and ending at index 2
    Found 8
    starting at index 3 and ending at index 4
    Found _
    starting at index 5 and ending at index 6
    Note
    Pattern的SDK文档引用句点元字符作为匹配除了line terminator,一个或者两个标志一行结束的预定义的标志之外的任何字符,除非 dotall mode (随后讨论)有效。Pattern 识别以下line terminators:
    The 回车符 (\r\)
    The 回行符 (\n)
    The 回车符紧跟回行符 (\r\n)
    The 回行字符 (\u0085)
    The 行分割字符 (\u2028)
    The 段落分割字符 (\u2029)
    捕获组
    Pattern支持在pattern匹配的过程中,一个regex construct 调用capturing group 来保存为了以后recall 的match的字符;此结构是由圆括号包围的字符序列。在捕获的group中的所有字符在匹配的过程中被作为一个单独的单元。例如,(Java) capturing group 结合了字符 J, a, v, 和a为一个单独单元。Capturing group依据在text中Java的出现来匹配Java pattern。每一个match用下一个匹配的Java字符替代了前一个match保存的Java字符。
    Capturing groups 在其它capturing groups内被嵌套。例如:在(Java( language)),( language) 在(Java)内嵌套。每一个嵌套或非嵌套的capturing group有它自己的数字,数字从1开始,Capturing 数字从左边至右。在这个例子中,(Java( language))是capturing group 1,( language)是capturing group 2。在(a)(b),(a)是捕获组1,(b)是捕获组2。
    每一个capturing group随后通过a back reference来recall保存的match。指定跟随在一个反斜杠后的数字来指示一个capturing group,back reference来 recalls 一个capturing group捕获的文本字符。一个back reference 的出现导致了一个matcher 使用the back reference的 capturing group number来recall捕获组保存的match ,然后使用匹配的字符进行进一步的匹配操作。随后的示例示范了为了检查语法错误进行的text 搜索的用法:
    java RegexDemo "(Java( language)\2)" "The Java language language"
    这个例子使用(Java( language)\2) regex为了检查语法错误,来检索字符串The Java language language,那里Java直接地在两个连续出现的language之前。Regex 指定了两个capturing groups: number 1 is (Java( language)\2), 它匹配Java language language,number 2 is ( language), 它匹配由language跟随的space characer。\2 back reference recalls number 2's 保存的match,它允许matcher检索空格后跟随language的第二次出现,which 直接地跟随space character and language的第一次出现。随后的输出显示了RegexDemo's matcher 找到了什么:
    Regex = (Java( language)\2)
    Text = The Java language language
    Found Java language language
    starting at index 4 and ending at index 26
    量词
    Quantifiers大概是理解起来最让人迷惑的regex 结构。一部分混淆来自于尽力的理解18个量词逻辑(六个基本逻辑被组织为三个主要逻辑)。其它的一个混淆来自于费尽的0长度匹配的理解。一旦你理解了这个概念和18 categories, 大部分(假如不是全部)混淆将消失。
    Note
    简要地, 着一部分主要讨论18 个quantifier categories 和zero-length 匹配的概念。为了更详尽的讨论和更多示例,请学习The Java Tutoria 的"Quantifiers"部分。
    一个quantifier 是一个隐式或显示的为一个pattern绑定一个数量值的正则表达式结构。这个数字值解决定了匹配一个pattern的次数。Pattern的六个基本的quantifiers匹配一个pattern一次或者根本不匹配,0次或者多次,一次或者多次,一个精确的数字次数,至少x次和 至少x次但不超过y次。
    六个基本的quantifier categories 在每一个三个主要的类别:greedy, reluctant和 possessive中复制。Greedy quantifiers 尝试找到最长的匹配。与之对照,reluctant quantifiers 尝试找到最短的匹配。Possessive quantifiers 也尝试找到最长的匹配。然而,他们和greedy quantifies在工作方式上不同。尽管greedy 和possessive quantifiers 迫使一个matcher 在进行第一次匹配之前读取整个的text,greedy quantifiers 常常导致为了找到一个match进行多次尝试,然而possessive quantifiers 让一个matcher 仅尝试一个match一次。
    随后的示例描述了六种基本的quantifiers 在greedy category类别下,单个fundamental quantifier 在每一个 reluctant 和 possessive categories类别下的行为。这些示例也描述了0匹配的概念:
    1. java RegexDemo a? abaa: 使用一个greedy quantifier 来在abaa中匹配a 一次或者根本不匹配。以下是输出结果:
    Regex = a?
    Text = abaa
    Found a
    starting at index 0 and ending at index 1
    Found
    starting at index 1 and ending at index 1
    Found a
    starting at index 2 and ending at index 3
    Found a
    starting at index 3 and ending at index 4
    Found
    starting at index 4 and ending at index 4
    这个输出展示了五次匹配。尽管第一、三和四次匹配的出现展示了三次匹配中位置并不奇怪,第一、第五次的匹配大概有点奇怪。这个匹配好像指出a匹配b和文本的结束。然而,不是这种情况。a?并不查找b和文本结尾。相反, 它查找a的出现或者缺失。当a? 查找a失败的时候,它以零长度的匹配返回那个事实(a缺失),在零长度那里起始和结束位置的索引相同。Zero-length matches 发生在空文本, 最后一个文本字符之后,或者任何量个字符之间。
    2. java RegexDemo a* abaa: 使用一个greedy quantifier在abaa 中匹配a零次或多次。以下是输出结果:
    Regex = a*
    Text = abaa
    Found a
    starting at index 0 and ending at index 1
    Found
    starting at index 1 and ending at index 1
    Found aa
    starting at index 2 and ending at index 4
    Found
    starting at index 4 and ending at index 4
    输出展示了四次匹配。像使用 a?,a* 产生了zero-length 匹配。第三个匹配,a* 匹配了aa, 很有趣。不像 a?,a* 匹配一个或者多个连续的a。
    3. java RegexDemo a+ abaa: 使用一个greedy quantifier在abaa 中匹配a一次或多次。以下是输出结果:
    Regex = a+
    Text = abaa
    Found a
    starting at index 0 and ending at index 1
    Found aa
    starting at index 2 and ending at index 4
    输出展示了两个匹配 。不像 a? 和 a*,a+ 没有匹配a的却失。因而,没有零长度匹配产生。像 a*,a+匹配了连续的a。
    4. java RegexDemo a{2} aababbaaaab: 使用greedy quantifier 来匹配中的每一个 aababbaaaab中的 aa序列。以下是输出结果:
    Regex = a{2}
    Text = aababbaaaab
    Found aa
    starting at index 0 and ending at index 2
    Found aa
    starting at index 6 and ending at index 8
    Found aa
    starting at index 8 and ending at index 10
    5. java RegexDemo a{2,} aababbaaaab: 使用了greedy quantifier 来匹配在ababbaaaab中两个或更多的匹配,以下是输出结果:
    Regex = a{2,}
    Text = aababbaaaab
    Found aa
    starting at index 0 and ending at index 2
    Found aaaa
    starting at index 6 and ending at index 10
    6. java RegexDemo a{1,3} aababbaaaab: 使用greedy quantifier 来匹配在aababbaaaab中出现的a、aa或者aaa。以下是输出结果:
    Regex = a{1,3}
    Text = aababbaaaab
    Found aa
    starting at index 0 and ending at index 2
    Found a
    starting at index 3 and ending at index 4
    Found aaa
    starting at index 6 and ending at index 9
    Found a
    starting at index 9 and ending at index 10
    7. java RegexDemo a+? abaa: 使用一个reluctant quantifier 在abaa中一次或多次匹配a。以下是输出结果:
    Regex = a+?
    Text = abaa
    Found a
    starting at index 0 and ending at index 1
    Found a
    starting at index 2 and ending at index 3
    Found a
    starting at index 3 and ending at index 4
    不像在第三个例中的greedy变量,reluctant 示例产生了三个单独的匹配,因为reluctant quantifier尽力的查找最短的匹配。
    8. java RegexDemo .*+end "This is the end": 使用了possessive quantifier 来匹配在this is the end中的以end结尾的任意字符0次或者多次。以下是输出结果:
    Regex = .*+end
    Text = This is the end
    由于这个possessive quantifier consume了整个文本,没有留下任何东西来匹配end,它没有产生匹配。相比之下,在java RegexDemo .*end "This is the end" 的greedy quantifier,因为它每次backing off一个字符直到最右端的end匹配,产生了一个匹配。(这个quantifier与greedy的不同就在后者的匹配过程中一旦匹配的字符,在随后的匹配中就不再使用。因此.*这部分正则表达式就匹配了全部的字符串,就没有字符可以与end匹配了。)
    Boundary matchers
    我们有时想在一行的开始、在单词的边界、文本的结束等等匹配pattern。使用 boundary matcher,一个指定了匹配边界的正则表达式结构,完成这个任务。Table 2 表示了Pattern的边界匹配支持。
    Table 2. Boundary matchers
    Boundary Matcher
    Description
    ^
    一行的开始
    $
    一行的结束
    \b
    单词的边界
    \B
    非单词边界
    \A
    文本的开始
    \G
    前一个匹配的结束
    \Z
    The end of the text (but for the final line terminator, if any)
    \z
    文本结束
    下边的命令行示例使用了^ 边界匹配元字符 ensure 由零个或者多个字符跟随的行开始。
    java RegexDemo ^The\w* Therefore
    ^ 指出了前三个字符必须匹配pattern后的T、h和e字符。可跟随任意数量的字符。以上的命令行产生以下输出:
    Regex = ^The\w*
    Text = Therefore
    Found Therefore
    starting at index 0 and ending at index 9
    把命令行改为java RegexDemo ^The\w* " Therefore"。发生了什么事?因为在therefore前的一个空格,没有匹配被发现。
    Embedded flag expressions
    Matcher假设了确定的却省值,例如大小写敏感的匹配。一个程序可以使用an embedded flag expression 来覆盖缺省值,也就是,使用一个正则表达式结构,圆括号元字符包围一个问号元字符后跟小写字母。Pattern认可以下的embedded flag expressions :
    (?i): enables 大小写不敏感的pattern 匹配。例如:java RegexDemo (?i)tree Treehouse 来匹配tree和Tree。大小写敏感是缺省值。
    (?x): 允许空格和注释用#元字符开始出现在Pattern中。一个matcher 忽略全部它们。例如:java RegexDemo ".at(?x)#match hat, cat, and so on" matter 匹配.at和mat。缺省地,空格和注释式不被允许的;一个matcher 将它们考虑为对match有贡献的字符。
    (?s): 使dotall 方式有效。在这种模式中,句点除了其它字符外还匹配text结束。 例如:java RegexDemo (?s). \n ,. 匹配了 \n。Nondotall 方式是缺省的:不匹配行结尾。
    (?m): 使多行方式有效。在多行模式下 ^ and $ 恰好分别的在一行的终结或末端之后或者之前。例如:java RegexDemo (?m)^.ake make\rlake\n\rtake 匹配 .ake 和 make、 lake与 take。非多行模式是缺省的: ^ and $ match仅在整个文本的开始和结束。
    (?u): enables Unicode-aware case folding. This flag works with (?i) to perform case-insensitive matching in a manner consistent with the Unicode Standard. The default: case-insensitive matching that assumes only characters in the US-ASCII character set match。
    (?d): enables Unix lines mode. In that mode, a matcher recognizes only the \n line terminator in the context of the ., ^, and $ metacharacters. Non-Unix lines mode is the default: a matcher recognizes all terminators in the context of the aforementioned metacharacters。
    Embedded flag expressions 类似于 capturing groups因为两个regex constructs都用圆括号包围字符。不像capturing group,embedded flag expression 没有捕获匹配的字符。因而,一个embedded flag expression是noncapturing group的特例。也就是说,一个不捕获text字符的regex construct ;它指定了由元字符圆括号包围的字符序列。在Pattern's SDK 文档中出现了一些noncapturing groups。
    Tip
    为了在正则表达式中指定多个embedded flag 表达式。或者吧它们并排的放在一起 (e.g., (?m)(?i)) 或者 把它们的小写字母并排的放在一起 (e.g., (?mi))。
    探索java.util.regex 类的方法
    java.util.regex包的三个类提供了为帮我书写更健壮的正则表达式代码和创建强大的text处理工具许多的方法。 我们从Pattern类开始探索这些方法。
    Note
    你也可以explore CharSequence 接口的当你创建一个新的字符序列类要实现的方法。仅实现 CharSequence 接口的类是java.nio.CharBuffer, String, 和 StringBuffer。
    Pattern 方法
    除非代码将一个string编译为Pattern对象一个regex表达式式没有用处的。用以下编辑方法中的一个完成这个任务:
    public static Pattern compile(String regex): 将regex内容编译为在一个新的Pattern对象内存储的树状结构的对象表示。返回那个对象引用。例如:Pattern p = Pattern.compile ("(?m)^\\.");创建了一个,存储了一个编译的表示了匹配以句点开始的行的表示。
    public static Pattern compile(String regex, int flags): 完成前一个方法的相同任务。然而,它同时考虑包含了flag常量(flags指定的)。Flag常量在Pattern中(except the canonical equivalence flag, CANON_EQ)被作为二选一的embedded flag expressions被声明。例如:Pattern p = Pattern.compile ("^\\.", Pattern.MULTILINE);和上一个例子等价,Pattern.MULTILINE 常量和the (?m) embedded flag 表达式完成相同的任务。(参考SDK's Pattern 文档学习其它的常量。) 假如不是这些在Pattern中被定义的常量在flag中出现,方法将抛出IllegalArgumentException 异常。
    假如需要,通过调用以下方法可以得到一个Pattern对象的flag和最初的被编译为对象的正则表达式:
    public int flags(): 返回当正则表达式编译时指定的Pattern的flag。例如:System.out.println (p.flags ()); 输出p引用的的Pattern相关的flag。
    public String pattern(): 返回最初的被编译进Pattern的正则表达式。例如:System.out.println (p.pattern ()); 输出对应p引用Pattern的正则表达式。(Matcher 类包含了类似的返回Matcher相关的Pattern对象的Pattern pattern() 方法。)
    在创建一个Pattern对象后,你一般的通过调用Pattern的公有方法matcher(CharSequence text)获得一个Matcher对象。这个方法需要一个简单的,实现了CharSequence接口的文本对象参数。获得的对象在pattern匹配的过程中扫描输入的文本对象。例如:Pattern p = Pattern.compile ("[^aeiouy]"); Matcher m = p.matcher ("This is a test."); 获得一个在text中匹配所有非元音字母的matcher。
    当你想检查一个pattern是否完全的匹配一个文本序列的时候创建Pattern和Matcher对象是令人烦恼的。幸运的是,Pattern提供了一个方便的方法完成这个任务;public static boolean matches(String regex, CharSequence text)。当且仅当整个字符序列匹配regex的pattern的场合下静态方法返回布尔值true。例如:System.out.println (Pattern.matches ("[a-z[\\s]]*", "all lowercase letters and whitespace only"));返回布尔值true, 指出仅空格字符和小写字符在all lowercase letters and whitespace only中出现。
    书写代码将text分成它的组成部分(例如雇员记录文件到一个字段的set) 是许多开发者发现乏味的任务。Pattern 通过提供一对字符分割方法来减轻那种tedium。
    public String [] split(CharSequence text, int limit): 分割在当前的Pattern对象的pattern匹配周围的text。这个方法返回一个数组,它的每一个条目指定了一个从下一个由pattern匹配(或者文本结束)分开的字符序列;且所有条目以它们在text中出现相同的顺序存储。书组条目的数量依赖于limit,它同时也控制了匹配发生次数。一个正数意味着,至多,limit-1 个匹配被考虑且数组的长度不大于限定的条目数。一个负值以为着所有匹配的可能都被考虑且数组可以任意长。一个0值以为着所有可能匹配的条目都被考虑,数组可以有任意的长度,且尾部的空字符串被丢弃。
    public String [] split(CharSequence text): 用0作为限制调用前边方法,返回方法调用的结果。
    假如你想一个拆分雇员记录,包含了姓名,年龄,街道和工资,为它的组成部分。以下的代码用split(CharSequence text)方法完成了这个任务:
    Pattern p = Pattern.compile (",\\s");
    String [] fields = p.split ("John Doe, 47, Hillsboro Road, 32000");
    for (int i = 0; i < fields.length; i++)
    System.out.println (fields [i]);
    The code fragment above specifies a regex that matches a comma character immediately followed by a single-space character and produces the following output:
    John Doe
    47
    Hillsboro Road
    32000
    Note
    String合并了三个方便的方法调用它们等价的Pattern方法: public boolean matches(String regex), public String [] split(String regex), and public String [] split(String regex, int limit)。
    Matcher 方法
    Matcher对象支持不同类型的pattern匹配操作,例如扫描text查找下一个match;尝试根据一个pattern来匹配整个文本;根据一个pattern尝试匹配部分text。用以下的方法完成这些任务:
    public boolean find(): 扫描text查找下一个match。此方法,或者在text的开始扫描,假如上一次的方法调用返回true且这个matcher没有被reset,在前一个match后的第一个字符开始扫描。假如一个match被找到的话返回布尔值true。Listing 1 展示了一个例子。
    public boolean find(int start): 重新安排matcher扫描下一个match。扫描从start指定的index开始。假如一个match被找到的话返回布尔值true。例如:m.find (1); 从index1开始扫描。(索引0被忽略。)假如start包含了一个负数或者一个超出了matcfher的text长度的值,这个方法抛出IndexOutOfBoundsException异常。
    public boolean matches(): 尝试根据pattern匹配整个text。在这个text匹配的情形下返回true。例如: Pattern p = Pattern.compile ("\\w*"); Matcher m = p.matcher ("abc!"); System.out.println (m.matches ()); 输出false因为整个abc! text 包含了非字母word characters。
    public boolean lookingAt(): 尝试根据pattern匹配text。假如一个match被找到的话返回布尔值true。 不像 matches(), 整个text不需要被匹配。例如:Pattern p = Pattern.compile ("\\w*"); Matcher m = p.matcher ("abc!"); System.out.println (p.lookingAt ()); 输出true因为text abc!的开始部分仅包含word 字符。
    不像Pattern对象,Matcher 包含了状态信息。有时,你在一个pattern 匹配后想reset一个matcher清除那些信息。下边的方法reset了一个matcher:
    public Matcher reset(): 重置了一个matcher的状态,包括matcher的append position(被清除为0)。下一个pattern的匹配操作从matcher新文本的起始开始。返回当前的matcher对象引用。例如:m.reset (); 通过引用m重置了matcher。
    public Matcher reset(CharSequence text): 重新设置一个matcher的状态且设置了matcher的文本内同。下一个pattern的匹配操作在matcher新的文本的起始位置开始。返回当前的matcher对象引用。例如:m.reset ("new text"); 重置m引用的对象并制定 新的text作为matcher的新text。
    一个matcher的append position 决定了matcher的text的追加到一个StringBuffer对象中的开始位置。以下的方法使用了append position:
    public Matcher appendReplacement(StringBuffer sb, String replacement): 读取matcher的text并将它们追加到sb引用的StringBuffer对象。这个方法在前一个pattern match的最后一个字符之后停止读取。此method 然后添加replacement引用的中的characters 到StringBuffer 对象。(替换字符串可以包含上一个匹配捕获的文本的引用,通过dollar-sign characters ($) 和 capturing group 数) 最终,这个方法设置了matcher的append position为最后一个匹配字符的位置加上1。一个当前的matcher对象的引用返回。假如这个matcher对象还没有执行match或者上次的match尝试失败此方法将抛出一个IllegalStateException 异常。假如replacement指定了一个pattern中不存在的capturing group 一个IndexOutOfBoundsException异常将被抛出。
    public StringBuffer appendTail(StringBuffer sb): 追加所有的text 到StringBuffer对象并返回对象引用。在最后一次调用appendReplacement(StringBuffer sb, String replacement) 方法之后,调用appendTail(StringBuffer sb) copy剩余的text到StringBuffer对象。
    随后的例子调用appendReplacement(StringBuffer sb, String replacement) 和 appendTail(StringBuffer sb) 方法来替换所有在one cat, two cats, or three cats on a fence 中出现的cat为caterpillar。一个capturing group 和 在replacement中的capturing group的引用允许在每一个cat匹配后插入erpillar:
    Pattern p = Pattern.compile ("(cat)");
    Matcher m = p.matcher ("one cat, two cats, or three cats on a fence");
    StringBuffer sb = new StringBuffer ();
    while (m.find ())
    m.appendReplacement (sb, "$1erpillar");
    m.appendTail (sb);
    System.out.println (sb);
    此示例产生如下输出:
    one caterpillar, two caterpillars, or three caterpillars on a fence
    其它的两个替换方法使用替换的文本替换第一个match和所有的match成为可能:
    public String replaceFirst(String replacement): 重置matcher,创建一个新的String对象,拷贝所有匹配的文本字符(直到第一个match)到String,追加替换字符到String,拷贝剩余的字符到Strring,并返回对象引用。(替换字符串可以包含上一个匹配捕获的文本的引用,通过dollar-sign characters ($) 和 capturing group 数。)
    public String replaceAll(String replacement): 操作和上一个方法类似。然而,replaceAll(String replacement) 用替换字符替换所有匹配。
    正则表达式\s+ 探测在文本中出现的一次或多次出现的空格。随后的例子使用了这个regex 并调用了replaceAll(String replacement) 方法来从text删除duplicate whitespace :
    Pattern p = Pattern.compile ("\\s+");
    Matcher m = p.matcher ("Remove the \t\t duplicate whitespace. ");
    System.out.println (m.replaceAll (" "));
    此示例产生如下输出:
    Remove the duplicate whitespace.
    Listing 1 包含了System.out.println ("Found " + m.group ());. 注意方法调用group()。此方法是capturing group-oriented 的Matcher方法:
    public int groupCount(): 返回在matcher的pattern中capturing groups 的个数。这个计数没有包含特定的capturing group 数字 0,它捕获前一个match(不管一个pattern包含capturing groups与否。)
    public String group(): 通过capturing group 数字 0记录返回上一个match的字符。此方法可以根据一个空的字符串返回一个空字符串。假如match还没有被尝试或者上次的match操作失败将抛出IllegalStateException异常。
    public String group(int group): 像上一个方法,除了通过group指定的capturing group number返回以前的match字符外。假如没有group number 指定的capturing group在pattern中存在,此方法抛出 一个IndexOutOfBoundsException 异常。
    以下代码示范了the capturing group 方法:
    Pattern p = Pattern.compile ("(.(.(.)))");
    Matcher m = p.matcher ("abc");
    m.find ();
    System.out.println (m.groupCount ());
    for (int i = 0; i <= m.groupCount (); i++)
    System.out.println (i + ": " + m.group (i));
    The example produces the following output:
    3
    0: abc
    1: abc
    2: bc
    3: c
    Capturing group 数字0 保存了previous match 且与has nothing to do with whether 一个capturing group在一个pattern中出现与否没有任何关系。也就是 is (.(.(.)))。其它的三个capturing groups捕获了previous match属于这个capturing groups的字符。例如,number 2, (.(.)), 捕获 bc; and number 3, (.), 捕获 c.
    在我们离开讨论Matcher的方法之前,我们将examine四个match位置方法:
    public int start(): 返回previous match的开始位置。假如match还没有被执行或者上次的match失败,此方法抛出一个IllegalStateException异常。
    public int start(int group): 类似上一个方法,除了返回group指定的capturing group 的相关的previous match 的开始索引外,假如在pattern中没有指定的capturing group number 存在,start(int group) 抛出IndexOutOfBoundsException 异常。
    public int end(): 返回上次match中匹配的字符的索引位置加上1。假如match还没有被尝试或者上次的match操作失败将抛出IllegalStateException异常。
    public int end(int group): 类似上一个方法,除了返回group指定的capturing group 的相关的previous match 的end索引外。假如在pattern中没有指定的capturing group number 存在,end(int group) 抛出IndexOutOfBoundsException 异常。
    下边的示例示范了两个match position 方法,为capturing group number 2报告起始/结束match 位置:
    Pattern p = Pattern.compile ("(.(.(.)))");
    Matcher m = p.matcher ("abcabcabc");
    while (m.find ())
    {
    System.out.println ("Found " + m.group (2));
    System.out.println (" starting at index " + m.start (2) +
    " and ending at index " + m.end (2));
    System.out.println ();
    }
    The example produces the following output:
    Found bc
    starting at index 1 and ending at index 3
    Found bc
    starting at index 4 and ending at index 6
    Found bc
    starting at index 7 and ending at index 9
    输出show我们仅仅对与capturing group number 2相关的matcher感兴趣,也就是这些匹配的起始结束位置。
    Note
    String 引入了两个方便的和调用Matcher等价的方法:public String replaceFirst(String regex, String replacement) 和 public String replaceAll(String regex, String replacement)。
    PatternSyntaxException methods
    Pattern的方法当它们发现非法的正则表达式语法错误的时候抛出PatternSyntaxException 异常。一个异常处理器可以调用PatternSyntaxException 的方法来获得抛出的关于语法错误的PatternSyntaxException 对象的信息。
    public String getDescription(): 返回语法错误描述。
    public int getIndex(): 返回语法错误发生位置的近似索引或-1,假如index是未知的。
    public String getMessage(): 建立一个多行的,包含了其它三个方法返回的信息的综合,以可视的方式指出在pattern中错误的位置字符串。
    public String getPattern(): 返回不正确的正则表达式。
    因为PatternSyntaxException 从java.lang.RuntimeException继承而来,代码不需要指定错误handler。This proves appropriate when regexes are known to have correct patterns。但当有潜在的pattern语法错误存在的时候,一个异常handler是必需的。因而,RegexDemo的源代码(参看 Listing 1) 包含了try { ... } catch (ParseSyntaxException e) { ... },它们调用了PatternSyntaxException四个异常方法中的每一个来获得非法pattern的信息。
    什么组成了非法的pattern?在embedded flag expression 中没有指定结束的元字符结束符号就是一个例。假如你执行java RegexDemo (?itree Treehouse。此命令的非法正则表达式(?tree pattern 导致 p = Pattern.compile (args [0]); 抛出PatternSyntaxException 异常。你将看到如下输出:
    Regex syntax error: Unknown inline modifier near index 3
    (?itree
    ^
    Error description: Unknown inline modifier
    Error index: 3
    Erroneous pattern: (?itree
    Note
    public PatternSyntaxException(String desc, String regex, int index) 构造函数让你创建你自己的PatternSyntaxException对象, That constructor comes in handy should you ever create your own preprocessing compilation method that recognizes your own pattern syntax, translates that syntax to syntax recognized by Pattern's compilation methods, and calls one of those compilation methods. If your method's caller violates your custom pattern syntax, you can throw an appropriate PatternSyntaxException object from that method。
    一个正则表达式应用实践
    Regexes let you create powerful text-processing applications. One application you might find helpful extracts comments from a Java, C, or C++ source file, and records those comments in another file. Listing 2 presents that application's source code:
    Listing 2. ExtCmnt.java
    // ExtCmnt.java
    import java.io.*;
    import java.util.regex.*;
    class ExtCmnt
    {
    public static void main (String [] args)
    {
    if (args.length != 2)
    {
    System.err.println ("usage: java ExtCmnt infile outfile");
    return;
    }
    Pattern p;
    try
    {
    // The following pattern lets this extract multiline comments that
    // appear on a single line (e.g., /* same line */) and single-line
    // comments (e.g., // some line). Furthermore, the comment may
    // appear anywhere on the line.
    p = Pattern.compile (".*/\\*.*\\*/|.*//.*$");
    }
    catch (PatternSyntaxException e)
    {
    System.err.println ("Regex syntax error: " + e.getMessage ());
    System.err.println ("Error description: " + e.getDescription ());
    System.err.println ("Error index: " + e.getIndex ());
    System.err.println ("Erroneous pattern: " + e.getPattern ());
    return;
    }
    BufferedReader br = null;
    BufferedWriter bw = null;
    try
    {
    FileReader fr = new FileReader (args [0]);
    br = new BufferedReader (fr);
    FileWriter fw = new FileWriter (args [1]);
    bw = new BufferedWriter (fw);
    Matcher m = p.matcher ("");
    String line;
    while ((line = br.readLine ()) != null)
    {
    m.reset (line);
    if (m.matches ()) /* entire line must match */
    {
    bw.write (line);
    bw.newLine ();
    }
    }
    }
    catch (IOException e)
    {
    System.err.println (e.getMessage ());
    return;
    }
    finally // Close file.
    {
    try
    {
    if (br != null)
    br.close ();
    if (bw != null)
    bw.close ();
    }
    catch (IOException e)
    {
    }
    }
    }
    }
    在创建Pattern 和Matcher 对象之后,ExtCmnt 逐行的读取一个文本文件的内容。对于每一行,matcher尝试匹配pattern的行,鉴别是一个单行的注释或者多行的注释在一行中出现。假如一行匹配pattern,ExtCmnt 将此行写入另外一个文本文件中。例如,java ExtCmnt ExtCmnt.java out 读取ExtCmnt.java 文件的每一行,根据pattern来尝试着一行,将匹配的行输出到名叫out的文件。 (不要担心理解文件的读写逻辑。我将在将来的文章中explore这些代码。) 在ExtCmnt执行完成,out 文件包含了以下行:
    // ExtCmnt.java
    // The following pattern lets this extract multiline comments that
    // appear on a single line (e.g., /* same line */) and single-line
    // comments (e.g., // some line). Furthermore, the comment may
    // appear anywhere on the line.
    p = Pattern.compile (".*/\\*.*\\*/|.*//.*$");
    if (m.matches ()) /* entire line must match */
    finally // Close file.
    这个输出显示ExtCmnt 并不完美:p = Pattern.compile (".*/\\*.*\\*/|.*//.*$"); 没有描绘一个注释。出现在out中的行因为ExtCmnt的matcher匹配了//字符。
    关于pattern ".*/\\*.*\\*/|.*//.*$"由一些有趣的事,竖线元字符metacharacter (|)。依照SDK documentation,圆括号元字符在capturing group和 竖线元字符是逻辑操作符号。vertical bar 描述了一个matcher,它使用操作符左侧的正则表达式结构来在matcher的文本中定为一个match。假如没有match存在,matcher使用操作符号右侧的正则表达式进行再次的匹配尝试。
    温习
    尽管正则表达式简化了在text处理程序中pattern匹配的代码,除非你理解它们,否则你不能有效的在你的程序中使用正则表达式。这篇文章通过介绍给你regex terminology,the java.util.regex 包和示范regex constructs的程序来让你对正则表达式有一个基本的理解。既然你对regexes有了一个基本的理解,建立在通过阅读additional articles (see Resources)和学习java.util.regex's SDK 文档,那里你可以学习更多的regex constructs ,例如POSIX (Portable Operating System Interface for Unix) 字符类。
    我鼓励你用这篇文章中或者其它以前文章中资料中问题email me。(请保持问题和这个栏目讨论的文章相关性。)你的问题和我的回答将出现在相关的学习guides中。)
    After writing Java 101 articles for 28 consecutive months, I'm taking a two-month break. I'll return in May and introduce a series on data structures and algorithms.
    About the author
    Jeff Friesen has been involved with computers for the past 23 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners—Java 2 by Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932)—and helped write Using Java 2 Platform, Special Edition (Que Publishing, 2001; ISBN: 0789724685). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.
    Resources
    Download this article's source code and resource files:
    http://www.javaworld.com/javaworld/jw-02-2003/java101/jw-0207-java101.zip
    For a glossary specific to this article, homework, and more, see the Java 101 study guide that accompanies this article:
    http://www.javaworld.com/javaworld/jw-02-2003/jw-0207-java101guide.html
    "Magic with Merlin: Parse Sequences of Characters with the New regex Library," John Zukowski (IBM developerWorks, August 2002) explores java.util.regex's support for pattern matching and presents a complete example that finds the longest word in a text file:
    http://www-106.ibm.com/developerworks/java/library/j-mer0827/
    "Matchmaking with Regular Expressions," Benedict Chng (JavaWorld, July 2001) explores regexes in the context of Apache's Jakarta ORO library:
    http://www.javaworld.com/javaworld/jw-07-2001/jw-0713-regex.html
    "Regular Expressions and the Java Programming Language," Dana Nourie and Mike McCloskey (Sun Microsystems, August 2002) presents a brief overview of java.util.regex, including five illustrative regex-based applications:
    http://developer.java.sun.com/developer/technicalArticles/releases/1.4regex/
    In "The Java Platform" (onJava.com), an excerpt from Chapter 4 of O'Reilly's Java in a Nutshell, 4th Edition, David Flanagan presents short examples of CharSequence and java.util.regex methods:
    http://www.onjava.com/pub/a/onjava/excerpt/javanut4_ch04
    The Java Tutorial's "Regular Expressions" lesson teaches the basics of Sun's java.util.regex package:
    http://java.sun.com/docs/books/tutorial/extra/regex/index.html
    Wikipedia defines some regex terminology, presents a brief history of regexes, and explores various regex syntaxes:
    http://www.wikipedia.org/wiki/Regular_expression
    Read Jeff's previous Java 101 column: "Tools of the Trade, Part 3" (JavaWorld, January 2003):
    http://www.javaworld.com/javaworld/jw-01-2003/jw-0103-java101.html?
    Check out past Java 101 articles:
    http://www.javaworld.com/javaworld/topicalindex/jw-ti-java101.html
    Browse the Core Java section of JavaWorld's Topical Index:
    http://www.javaworld.com/channel_content/jw-core-index.shtml
    Need some Java help? Visit our Java Beginner discussion:
    http://forums.devworld.com/webx?50@@.ee6b804
    Java experts answer your toughest Java questions in JavaWorld's Java Q&A column:
    http://www.javaworld.com/javaworld/javaqa/javaqa-index.html
    For Tips 'N Tricks, see:
    http://www.javaworld.com/javaworld/javatips/jw-javatips.index.html
    Sign up for JavaWorld's free weekly Core Java email newsletter:
    http://www.javaworld.com/subscribe
    You'll find a wealth of IT-related articles from our sister publications at IDG.net

    JAVA中正则表达式的应用 (一)(转)

    正则表达式:

    正则表达式是一种可以用于模式匹配和替换的强有力的工具,一个正则表达式就是由普通的字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式,它描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。

    正则表达式在字符数据处理中起着非常重要的作用,我们可以用正则表达式完成大部分的数据分析处理工作,如:判断一个串是否是数字、是否是有效的Email地址,从海量的文字资料中提取有价值的数据等等,如果不使用正则表达式,那么实现的程序可能会很长,并且容易出错。对这点本人深有体会,面对大量工具书电子档资料的整理工作,如果不懂得应用正则表达式来处理,那么将是很痛苦的一件事情,反之则将可以轻松地完成,获得事半功倍的效果。

    由于本文目的是要介绍如何在JAVA里运用正则表达式,因此对刚接触正则表达式的读者请参考有关资料,在此因篇幅有限不作介绍。



    回页首

    JAVA对正则表达式的支持:

    在JDK1.3或之前的JDK版本中并没有包含正则表达式库可供JAVA程序员使用,之前我们一般都在使用第三方提供的正则表达式库,这些第三方库中有源代码开放的,也有需付费购买的,而现时在JDK1.4的测试版中也已经包含有正则表达式库---java.util.regex。

    故此现在我们有很多面向JAVA的正则表达式库可供选择,以下我将介绍两个较具代表性的 Jakarta-OROjava.util.regex,首先当然是本人一直在用的 Jakarta-ORO:



    回页首

    Jakarta-ORO正则表达式库

    1.简介:

    Jakarta-ORO是最全面以及优化得最好的正则表达式API之一,Jakarta-ORO库以前叫做OROMatcher,是由Daniel F. Savarese编写,后来他将其赠与Jakarta Project,读者可在Apache.org的网站 下载该API包。

    许多源代码开放的正则表达式库都是支持Perl5兼容的正则表达式语法,Jakarta-ORO正则表达式库也不例外,他与Perl 5正则表达式完全兼容。

    2.对象与其方法:

    ★PatternCompiler对象:
    我们在使用Jakarta-ORO API包时,最先要做的是,创建一个Perl5Compiler类的实例,并把它赋值给PatternCompiler接口对象。Perl5Compiler是PatternCompiler接口的一个实现,允许你把正则表达式编译成用来匹配的Pattern对象。

    PatternCompiler compiler=new Perl5Compiler();

    ★Pattern对象:
    要把所对应的正则表达式编译成Pattern对象,需要调用compiler对象的compile()方法,并在调用参数中指定正则表达式。举个例子,你可以按照下面这种方式编译正则表达式"s[ahkl]y":

     Pattern pattern=null;
            try {
                    pattern=compiler.compile("s[ahkl]y ");
            } catch (MalformedPatternException e) {
                    e.printStackTrace();
            }

    在默认的情况下,编译器会创建一个对大小写敏感的模式(pattern)。因此,上面代码编译得到的模式只匹配"say"、"shy"、 "sky"和"sly",但不匹配"Say"和"skY"。要创建一个大小写不敏感的模式,你应该在调用编译器的时候指定一个额外的参数:
    pattern=compiler.compile("s[ahkl]y",Perl5Compiler.CASE_INSENSITIVE_MASK);

    Pattern对象创建好之后,就可以通过PatternMatcher类用该Pattern对象进行模式匹配。

    ★PatternMatcher对象:

    PatternMatcher对象依据Pattern对象和字符串展开匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现,它根据Perl 5正则表达式语法进行模式匹配:
    PatternMatcher matcher=new Perl5Matcher();

    PatternMatcher对象提供了多个方法进行匹配操作,这些方法的第一个参数都是需要根据正则表达式进行匹配的字符串:

    1. boolean matches(String input, Pattern pattern):当要求输入的字符串input和正则表达式pattern精确匹配时使用该方法。也就是说当正则表达式完整地描述输入字符串时返回真值。
    2. boolean matchesPrefix(String input, Pattern pattern):要求正则表达式匹配输入字符串起始部分时使用该方法。也就是说当输入字符串的起始部分与正则表达式匹配时返回真值。
    3. boolean contains(String input, Pattern pattern):当正则表达式要匹配输入字符串的一部分时使用该方法。当正则表达式为输入字符串的子串时返回真值。

    但以上三种方法只会查找输入字符串中匹配正则表达式的第一个对象,如果当字符串可能有多个子串匹配给定的正则表达式时,那么你就可以在调用上面三个方法时用PatternMatcherInput对象作为参数替代String对象,这样就可以从字符串中最后一次匹配的位置开始继续进行匹配,这样就方便的多了。

    用PatternMatcherInput对象作为参数替代String时,上述三个方法的语法如下:

    1. boolean matches(PatternMatcherInput input, Pattern pattern)
    2. boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)
    3. boolean contains(PatternMatcherInput input, Pattern pattern)

    ★Util.substitute()方法:
    查找后需要要进行替换,我们就要用到Util.substitute()方法,其语法如下:

    public static String substitute(PatternMatcher matcher,
           Pattern pattern,Substitution sub,String input,
           int numSubs)
    

    前两个参数分别为PatternMatcher和Pattern对象。而第三个参数是个Substiution对象,由它来决定替换操作如何进行。第四个参数是要进行替换操作的目标字符串,最后一个参数用来指定是否替换模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只进行指定次数的替换。

    在这里我相信有必要详细解说一下第三个参数Substiution对象,因为它将决定替换将怎样进行。

    Substiution:
    Substiution是一个接口类,它为你提供了在使用Util.substitute()方法时控制替换方式的手段,它有两个标准的实现类:StringSubstitution与Perl5Substitution。当然,同时你也可以生成自己的实现类来定制你所需要的特殊替换动作。

    StringSubstitution:
    StringSubstitution 实现的是简单的纯文字替换手段,它有两个构造方法:

    StringSubstitution()->缺省的构造方法,初始化一个包含零长度字符串的替换对象。

    StringSubstitution(java.lang.String substitution)->初始化一个给定字符串的替换对象。

    Perl5Substitution:
    Perl5Substitution 是StringSubstitution的子类,它在实现纯文字替换手段的同时也允许进行针对MATH类里各匹配组的PERL5变量的替换,所以他的替换手段比其直接父类StringSubstitution更为多元化。

    它有三个构造器:

    Perl5Substitution()

    Perl5Substitution(java.lang.String substitution)

    Perl5Substitution(java.lang.String substitution, int numInterpolations)

    前两种构造方法与StringSubstitution一样,而第三种构造方法下面将会介绍到。

    Perl5Substitution的替换字符串中可以包含用来替代在正则表达式里由小扩号围起来的匹配组的变量,这些变量是由$1, $2,$3等形式来标识。我们可以用一个例子来解释怎样使用替换变量来进行替换:

    假设我们有正则表达式模式为b\d+:(也就是b[0-9]+:),而我们想把所有匹配的字符串中的"b"都改为"a",而":"则改为"-",而其余部分则不作修改,如我们输入字符串为"EXAMPLE b123:",经过替换后就应该变成"EXAMPLE a123-"。要做到这点,我们就首先要把不做替换的部分用分组符号小括号包起来,这样正则表达式就变为"b(\d+):",而构造Perl5Substitution对象时其替换字符串就应该是"a$1-",也就是构造式为Perl5Substitution("a$1-"),表示在使用Util.substitute()方法时只要在目标字符串里找到和正则表达式" b(\d+): "相匹配的子串都用替换字符串来替换,而变量$1表示如果和正则表达式里第一个组相匹配的内容则照般原文插到$1所在的为置,如在"EXAMPLE b123:"中和正则表达式相匹配的部分是"b123:",而其中和第一分组"(\d+)"相匹配的部分则是"123",所以最后替换结果为"EXAMPLE a123-"。

    有一点需要清楚的是,如果你把构造器Perl5Substitution(java.lang.String substitution,int numInterpolations)

    中的numInterpolations参数设为INTERPOLATE_ALL,那么当每次找到一个匹配字串时,替换变量($1,$2等)所指向的内容都根据目前匹配字串来更新,但是如果numInterpolations参数设为一个正整数N时,那么在替换时就只会在前N次匹配发生时替换变量会跟随匹配对象来调整所代表的内容,但N次之后就以一致以第N次替换变量所代表内容来做为以后替换结果。

    举个例子会更好理解:

    假如沿用以上例子中的正则表达式模式以及替换内容来进行替换工作,设目标字符串为"Tank b123: 85 Tank b256: 32 Tank b78: 22",并且设numInterpolations参数为INTERPOLATE_ALL,而Util.substitute()方法中的numSub变量设为SUBSTITUTE_ALL(请参考上文Util.substitute()方法内容),那么你获得的替换结果将会是: Tank a123- 85 Tank a256- 32 Tank a78- 22

    但是如果你把numInterpolations设为2,并且numSubs依然设为SUBSTITUTE_ALL,那么这时你获得的结果则会是: Tank a123- 85 Tank a256- 32 Tank a256- 22

    你要注意到最后一个替换所用变量$1所代表的内容与第二个$1一样为"256",而不是预期的"78",因为在替换进行中,替换变量$1只根据匹配内容进行了两次更新,最后一次就使第二次匹配时所更新的结果,那么我们可以由此知道,如果numInterpolations设为1,那么结果将是: Tank a123- 85 Tank a123- 32 Tank a123- 22

    3.应用示例:

    刚好前段时间公司准备出一个《伊索预言》的英语学习互动教材,其中有电子档资料的整理工作,我们就以此为例来看一下Jakarta-ORO与JDBC2.0 API结合起来对数据库内的资料进行简单提取与整理的实现。假设由录入部的同事送过来的存放在MS SQLSERVER 7数据库里的电子档的表结构如下(注:或许在不同的DBMS中有相应的正则表达式的应用,但这不在本文讨论范围内):

    表名:AESOP, 表中每条记录包含有三列:
    ID(int):单词索引号
    WORD(varchar):单词
    CONTENT(varchar):存放单词的相关解释与例句等内容

    其中CONTENT列中内容的格式如下:
    [音标] [词性] (解释){(例句一/例句解释/例句中该词的词性: 单词在句中的意思) (例句二/例句解释/例句中该词的词性: 单词在句中的意思)}

    如对应单词Kevin,CONTENT中的内容如下:
    ['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now./凯文现住在珠海/名词: 凯文)}

    我们的例子主要针对CONTENT列中内容进行字符串处理。

    ★查找单个匹配:
    首先,让我们尝试把CONTNET列中的[音标]字段的内容列示出来,由于所有单词的记录中都有这一项并且都在字串开始位置,所以这个查找工作比较简单:

    1. 确定相应的正则表达式:\[[^]]+\]

      这个是很简单的正则表达式,其意思是要求相匹配的字符串必须为以一对中括号包含的所有内容,如['kevin] 、[名词]等,但内容中不包括"]"符号,也就是要避免出现"[][]"会作为一个匹配对象的情况出现(有关正则表达式的基础知识请参照有关资料,这里不再详述)。

      注意,在Java中,你必须对每一个向前的斜杠("\")进行转义处理。所以我们要在上面的正则表达式里每个"\"前面加上一个"\"以免出现编译错误,也就是在JAVA中初始化正则表达式的字符串的语句应该为:

      String restring=" \\[[^]]+\\]";

      并且在表达式里每个符号中间不能有空格,否则就会同样出现编译错误。

    2. 实例化PatternCompiler对象,创建Pattern对象

      PatternCompiler compiler=new Perl5Compiler();

      Pattern pattern=compiler.compile(restring);

    3. 创建PatternMatcher对象,调用PatternMatcher接口的contain()方法检查匹配情况:
        PatternMatcher matcher=new Perl5Matcher();
              if (matcher.contains(content,pattern)) {
                       //处理代码片段
              }
      

      这里matcher.contains(content,pattern)中的参数 content是从数据库里取来的字符串变量。该方法只会查到第一个匹配的对象字符串,但是由于音标项均在CONETNET内容字符串中的起始位置,所以用这个方法就已经可以保证把每条记录里的音标项找出来了,但更为直接与合理的办法是使用boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)方法,该方法验证目标字符串是否以正则表达式所匹配的字串为起始。

      具体实现的完整的程序代码如下:

      package RegularExpressions;
      //import……
      import org.apache.oro.text.regex.*;
      //使用Jakarta-ORO正则表达式库前需要把它加到CLASSPATH里面,如果用IDE是//JBUILDER,那么也可以在JBUILDER里直接自建新库。
      public class yisuo{
        public static void main(String[] args){
        try{     
      //使用JDBC DRIVER进行DBMS连接,这里我使用的是一个第三方JDBC 
      //DRIVER,Microsoft本身也有一个面向SQLSERVER7/2000的免费JDBC //DRIVER,但其性能真的是奇差,不用也罢。
              Class.forName("com.jnetdirect.jsql.JSQLDriver");
                Connection con=DriverManager.getConnection
                ("jdbc:JSQLConnect://kevin:1433","kevin chen","re");
                Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                ResultSet.CONCUR_UPDATABLE);
      //为使用Jakarta-ORO库而创建相应的对象
      String rsstring=" \\[[^]]+\\]"; 
                PatternCompiler orocom=new Perl5Compiler();
                Pattern pattern=orocom.compile(rsstring);
                PatternMatcher matcher=new Perl5Matcher();
                ResultSet uprs = stmt.executeQuery("SELECT * FROM aesop");
                while (uprs.next()) {
      Stirng  word=uprs.getString("word");
                Stirng  content=uprs.getString("content");
                  if(matcher.contains(content,pattern)){
                //或if(matcher.matchesPrefix(content,pattern)){
                      MatchResult result=matcher.getMatch();
                      Stirng pure=result.toString();
                      System.out.println(word+"的音标为:"+pure);
                  }
                }
             }
        catch(Exception e) {
                   System.out.println(e);
             }
        }
      }

      输出结果为:kevin的音标为['kevin]

    在这个处理中我是用toString()方法来取得结果,但是如果正则表达式里是用了分组符号(圆括号),那么就可以用group(int gid)的方法来取得相应各组匹配的结果,如正则表达式改为" (\[[^]]+\])",那么就可以用以下方法来取得结果:pure=result.group(0);

    用程序验证,输出结果同样为:kevin的音标为['kevin]

    而如果正则表达式为(\[[^]]+\])(\[[^]]+\]),则会查找到两个连续的方括号所包含的内容,也就找到[音标] [词性]两项,但是两项的结果分别在两个组里面,分别由下面语句获得结果:

    result.group(0)->返回[音标] [词性]两项内容,也就是与整个正则表达式相匹配的结果字符串,在这里也就为['kevin] [名词]

    result.group(1) ->返回[音标]项内容,结果应是['kevin]

    result.group(2) ->返回[词性]项内容,结果应是[名词]

    继续用程序验证,发现输出并不正确,主要是当内容有中文时就不能成功匹配,考虑到可能是Jakarta-ORO正则表达式库版本不支持中文的问题,回看一下原来我一直用的还是2.0.1的老版本,马上到Jakarta.org上下载最新的2.0.4版本装上再用程序验证,得出的结果就和预期一样正确。

    ★查找多个匹配:
    经过第一步的尝试使用Jakarta-ORO后,我们已经知道了如何正确使用该API包来查找目标字符串里一个匹配的子串,下面我们接着来看一看当目标字符串里包含不止一个匹配的子串时我们如何把它们一个接一个找出来进行相应的处理。

    首先我们先试个简单的应用,假设我们想把CONTNET字段内容里所有用方括号包起来的字串都找出来,很清楚地,CONTNET字段的内容里面就只有两项匹配的内容:[音标]和 [词性],刚才我们其实已经把它们分别找出来了,但是我们所用的方法是分组方法,把"[音标] [词性]"作为一整个正则表达式匹配的内容先找到,再根据分组把[音标]和 [词性]分别挑出来。但是现在我们需要做的是把[音标]和[词性]分别做为与同一个正则表达式匹配的内容,先找到一个接着再找下一个,也就是刚才我们的表达式为(\[[^]]+\])(\[[^]]+\]),而现在应为" \[[^]]+\] "。

    我们已经知道在匹配操作的三个方法里只要用PatternMatcherInput对象作为参数替代String对象就可以从字符串中最后一次匹配的位置开始继续进行匹配,实现的程序片段如下:

    PatternMatcherInput input=new PatternMatcherInput(content);
                while (matcher.contains(input,pattern)) {
                    result=matcher.getMatch();
                    System.out.println(result.group(0)) 
                }
    

    输出结果为:['kevin]
    [名词]

    接着我们来做复杂一点的处理,就是我们要先把下面内容:
    ['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now. /凯文现住在珠海/名词: 凯文)}中的整个例句部分(也就是由大括号所包含的部分)找出来,再分别把例句一和例句二找出,而各例句中的各项内容(英文句、中文句、词性、解释)也要分项列出。

    第一步当然是要定出相应的正则表达式,需要有两个,一是和整个例句部分(也就是由大括号包起来的部分)匹配的正则表达式:"\{.+\}",

    另一个则要和每个例句部分匹配(也就是小括号中的内容),:\(([^)]+\)

    而且由于要把例句的各项分离出来,所以要再把里面的各部分用分组的方法匹配出来:" ([^(]+)/(.+)/(.+):([^)]+) "。

    为了简便起见,我们不再和从数据库里读出,而是构造一个包含同样内容的字符串变量,程序片段如下:

    try{
             String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词:凯文) (Kevin is living in ZhuHai now./凯文现住在珠海/名词: 凯文)}";
             String ps1="\\{.+\\}";
             String ps2="\\([^)]+\\)";
             String ps3="([^(]+)/(.+)/(.+):([^)]+)";
             String sentence;
             PatternCompiler orocom=new Perl5Compiler();
             Pattern pattern1=orocom.compile(ps1);
             Pattern pattern2=orocom.compile(ps2);
             Pattern pattern3=orocom.compile(ps3);
             PatternMatcher matcher=new Perl5Matcher();
    //先找出整个例句部分
                if (matcher.contains(content,pattern1)) {
                MatchResult result=matcher.getMatch();
                String example=result.toString();
                PatternMatcherInput input=new PatternMatcherInput(example);
            //分别找出例句一和例句二
                while (matcher.contains(input,pattern2)){
                    result=matcher.getMatch();
                    sentence=result.toString();
            //把每个例句里的各项用分组的办法分隔出来
                    if (matcher.contains(sentence,pattern3)){
                      result=matcher.getMatch();
                      System.out.println("英文句: "+result.group(1));
                      System.out.println("句子中文翻译: "+result.group(2));
                      System.out.println("词性: "+result.group(3));
                      System.out.println("意思: "+result.group(4));
                    }
                }
            }
           }
      catch(Exception e) {
                 System.out.println(e);
           }
    

    输出结果为:
    英文句: Kevin loves comic.
    句子中文翻译: 凯文爱漫画
    词性: 名词
    意思: 凯文
    英文句: Kevin is living in ZhuHai now.
    句子中文翻译: 凯文现住在珠海
    词性: 名词
    意思: 凯文

    ★查找替换:
    以上的两个应用都是单纯在查找字符串匹配方面的,我们再来看一下查找后如何对目标字符串进行替换。

    例如我现在想把第二个例句进行改动,换为:Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。

    也就是把
    ['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now. /凯文现住在珠海/名词: 凯文)}

    改为:
    ['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}

    之前,我们已经了解Util.substitute()方法与Substiution接口,以及Substiution的两个实现类StringSubstitution和Perl5Substitution,我们就来看看怎么用Util.substitute()方法配合Perl5Substitution来完成我们上面提出的替换要求,确定正则表达式:

    我们要先找到其中的整个例句部分,也就是由大括号包起来的字串,并且把两个例句分别分组,所以正则表达式为:"\{(\([^)]+\))(\([^)]+\))\}",如果用替换变量来代替分组,那么上面的表达式可以看为"\{$1$2\}",这样就可以更容易看出替换变量与分组间的关系。

    根据上面的正则表达式Perl5Substitution类可以这样构造: Perl5Substitution("{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}")

    再根据这个Perl5Substitution对象来使用Util.substitute()方法便可以完成替换了,实现的代码片段如下:

    try{
       String content="['kevin] [名词](人名凯文){(Kevin loves comic.
       /凯文爱漫画/名词: 凯文)(Kevin lives in ZhuHai now./凯文现住在珠海/名词: 凯文)}";
       String ps1="\\{(\\([^)]+\\))(\\([^)]+\\))\\}";
       String sentence;
       String pure;
       PatternCompiler orocom=new Perl5Compiler();
       Pattern pattern1=orocom.compile(ps1);
       PatternMatcher matcher=new Perl5Matcher();
           String result=Util.substitute(matcher,
            pattern1,new Perl5Substitution(
           "{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 
           凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}",1),
            content,Util.SUBSTITUTE_ALL);
            System.out.println(result);
       }
      catch(Exception e) {
                 System.out.println(e);
           }
    

    输出结果是正确的,为:
    ['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}

    至于有关使用numInterpolations参数的构造器用法,读者只要根据上面的介绍自己动手试一下就会清楚了,在此就不再例述。



    回页首

    总结:

    本文首先介绍了Jakarta-ORO正则表达式库的对象与方法,并且接着举例让读者对实际应用有进一步的了解,虽然例子都比较简单,但希望读者们在看了该文后对Jakarta-ORO正则表达式库有一定的认知,在实际工作中有所帮助与启发。

    其实在Jakarta org里除了Jakarta-ORO外还有一个百分百的纯JAVA正则表达式库,就是由Jonathan Locke赠与Jakarta ORG的Regexp,在该包里面包含了完整的文档以及一个用于调试的Applet例子,对其有兴趣的读者可以到此 下载

    参考资料

    December, 2007

    ITSM网上资源导航

    组织机构
    www.itgov.org.cn
    中国IT治理论坛网站。致力于探讨信息化建设中深层次的机制问题,倡导将国际上前沿的IT治理机制及其方法论与中国的国情相结合,走有中国特色的IT治理之路。聚焦领域涵盖信息系统审计、信息化工程监理、IT服务管理、信息安全管理、IT项目管理以及国内外的IT标准和咨询方法论。
    www.itsmportal.cn
    国际IT服务管理门户中国分站。
    www.ogc.gov.uk
    英国政府商务部(OGC)官方网站。OGC是IT服务管理领域事实上的国际标准ITIL的所有者。
    www.itsmf.com
    国际IT服务管理论坛(itSMF)官方网站。ItSMF是国际上唯一被认可的IT服务管理行业组织,在全世界设立了超过16个国家分会。
    www.iosm.com
    IT服务管理学院(Institute of Service Management)网站。IoSM是一个面向IT服务管理专业人士的机构。
    www.bita-center.com
    商业和信息技术整合研究中心(The Business IT Alignment (bITa) Center)网站。该中心致力于商业与信息技术的战略整合,以及各种IT管理标准之间的协调和融合。
    www.thinkhdi.com
    帮助台协会(HDI)官方网站。HDI是世界服务和支持业中最大的协会。
    www.bcs.org.uk
    英国计算机协会(BCS)网站。BCS通过信息系统考试委员会(Information Systems Examination Board)负责英联邦地区的IT服务管理(ITSM)认证考试。
    www.bsi-global.com
    英国标准协会(BSI)网站。BSI是英国IT服务管理国家标准BSI15000的制订者和所有者。BSI15000现正提交给国际标准化组织以成为IT服务管理国际标准。
    新闻、白皮书、术语表和出版物
    http://industry.ccidnet.com
    赛迪网中国信息化子站,关注中国信息化建设的热点、难点和疑点,读者亦可通过此站与本书著者互动交流。
    http://media.ccidnet.com/ccu
    《中国计算机用户周刊》,面向高端用户的IT权威媒体。
    http://En.itsmportal.net
    国际IT服务管理领域最大的独立IT服务管理方面信息交换平台,在多个国家设有分站,包括中国(见www.itsmportal.cn)。
    www.itilpeople.com/
    一个包含大量ITSM类文章、术语、链接和缩略语的网站。
    http://InterPromUSA.com
    包含大量ITSM方面的文档。同时该网站还出版一份电子版的ITSM月刊。
    软件
    www.ca.com.cn
    冠群电脑(中国)有限公司Unicenter软件的官方网站。
    www.openview.hp.com
    HP公司OpenView软件的官方网站。
    www.hiss.cn/jp1/
    日立信息系统(上海)有限公司JP1软件的官方网站。
    www.bmc.com/zh_CN/
    博思软件(中国)有限公司Remedy软件的官方网站。
    http://www-900.ibm.com/cn/software/tivoli/
    IBM公司Tivoli软件的官方网站。
    培训和认证
    www.ccidtraining.com
    training.ccidnet.com/ccid
    中国电子信息产业发展研究院培训中心(简称赛迪培训)。赛迪培训为政府组织和企业提供世界级水准的信息化管理培训服务,其课程体系中立于任何软硬件提供商,围绕着IT治理这一核心理念,从客观的立场提供六大系列紧密相关的课程,即IT服务管理系列、信息系统审计系列、评估和监理系列、信息安全管理系列、管理信息化系列、IT项目管理系列、信息化战略规划系列。
    www.ca.com.cn/education/
    冠群电脑(中国)有限公司网站教育与培训频道。
    www.hp.com.cn/support/education/itm/
    HP公司IT管理学院

    ITSM带来的收益

         关于ITIL和ITSM进入中国之后风靡南北的盛况,我们已经了解很多,但是采用ITSM相关方法之后,企业具体可以在哪些方面得到收益,特别是能够数量化的一些收益指标,由于我们起步较晚,全面应用的先例并不多见,所以这方面的资料也鲜有报道。不过,中国自古就有“他山之石,可以攻玉”的说法,因此,国外的数字也可以作为我们的参考。下图是国际知名咨询公司Gartner Group对美国ITSM市场上,经过ITSM相关流程梳理和改造之后,每个用户每年成本节约得出的平均统计数字:
        从上图我们可以看到,运用ITIL框架标准对流程进行梳理和改造后,总体节约潜能达到48%,这其中包括了硬件和软件方面的成本、日常操作方面的成本、管理成本、用户操作成本和客户最为关心的宕机成本。或许这种统计数字还不够细致,还不足以说明什么问题,我们可以来看一个更加实际的例子,下表是某跨国公司在运用ITIL框架完成ITSM项目之后效果的对比。
        上图最左边一栏是对现状的描述,包括网络覆盖的情况、桌面数、应用数、服务器数量、网络设备数、Email信箱数等以及对项目期间的要求:必须继续增加市场份额,这一点对于项目进行是一个比较严峻的挑战;中间的一栏是项目实施九个月后,服务质量提高方面的情况,包括平均的电话等待时间、客户满意率、服务器的可用性指标以及最重要的-市场份额方面的情况;最右边一栏是项目实施九个月后,成本降低方面的情况,包括成本节约、供应链集成方面的节约、制造成本节约、财务交易流程方面的节约、应用规模缩减所带来的节约等。这样一个非常详尽的ITSM项目实施前后的对比分析,非常有针对性的回答了ITIL或者ITSM项目能够为企业带来的收益的问题。
        上面我们是从一个企业的角度和地区市场的角度来看ITIL和ITSM能够为企业带来的好处。如果从IT的角度看,我们至少可以从以下领域来表明ITIL和ITSM为企业带来的好处:
        首先是支持迅速的变更。我还记得有一次,我在一家银行的经历让我印象深刻。那一次,我在那家银行使用他们的存款机。由于我的疏忽,在现金中夹带了一个小纸条,造成存款机无法工作,我的钱被吞,但存款机并没有把钱数记录在我的卡上,只是吐出了一个记录凭条,说明当次交易失败。我很着急,找到值班小姐,要求清点存款机。按照规定,清点存款机必须在下午的某一个时间,由某些特定的人进行,当时那个时间还没到,服务小姐立即请示了当班领导,由领导指定该小姐会同另外一名工作人员共同打开存款机,检查其机器状态。整个过程大概在30分钟内完成。事实上,这也是一次变更,是对企业相关制度的一次变更。存款机作为运用IT技术的硬件设备,想必应该属于IT部门管辖的设备之一。因此,这次制度的例外变更的执行应该是属于IT服务过程中的一次变更。这次变更到这个环节为止,其流程和方法都是非常完整和完善的,无论是变更的迅速程度,还是变更的服务质量,甚至是变更过程的客户满意程度,都是很好的,也是让作为客户的我感到非常满意的。当然,这家银行后面的后续处理有些不是很完美的地方,那是另外的问题,这里不再赘述。从ITIL的角度看,对于变更的迅速处理和过程跟踪、风险控制都是ITIL和ITSM的重要领域之一。
        其次是减少复杂性,很多企业都有这样的感触,现代技术更新越来越快,系统越上越多,在解决旧的问题的同时,也不断带来新的问题,那就是兼容性、集成性方面的问题。这需要从IT规划的角度对所有系统的内置规则加以梳理和统一,这也是ITIL价值体现的重要方面。
        第三,是可用性提高方面的作用。这是ITIL和ITSM非常核心的一部分作用。但我们经常会遇到的困惑在于如何衡量和计算可用性,ITIL本身其实并没有给出唯一正确的答案,但ITIL的价值在于提供了计算的相关方法,用以产生用户和IT人员之间的“共同语言”。
        第四,是增加业务相关性方面的好处。我们可能经常遇到的一个典型例子就是,银行在采购IT产品的时候,会有技术人员和业务人员两部分人员参加,理论上,技术人员负责技术,业务人员负责商务,确切的说是价格谈判。但是由于我们没有一套内在的共同语言或者说没有在企业内部建立起技术和业务的相关性,所以结果通常是:价格低者中标,也就是说,采购结果是以价格因素为主导,为技术采购的产品或服务,技术反而成了次要角色。而ITIL和ITSM正是致力于改变这样一种状况。
        前面,我们谈了很多企业运用ITIL和ITSM的好处,具体到金融行业,我们应该承认,金融行业,本质上,仍然是一个企业,或者说,对于财务、现金流更加敏感和严格要求的一个现代化的企业,具有现代企业应该具有的一切特性。因此,先进的企业对其IT所提出的一切要求,在金融行业中会有同样的要求,比如对于IT可用性方面的要求,比如对于IT容量规划对于未来业务发展适应性方面的要求,还有服务质量提高方面的要求和服务成本降低方面的要求。因为有了这些要求,ITIL或者ITSM对于金融行业的重要性以及能够给金融行业带来的好处也就不言而喻了。

    November, 2007

    在Spring2.0中集成dwr

    1、在web.xml中增加如下配置段:

    <servlet>
        <servlet-name>dwr</servlet-name>
        <servlet-class>org.directwebremoting.spring.DwrSpringServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dwr</servlet-name>
        <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>

     

    没什么特殊的,不过要注意一下那个Servlet org.directwebremoting.spring.DwrSpringServlet

    2、现在可以不再使用dwr.xml而是直接使用spring的配置,暂时只有一种复杂的方法,增加applicationContext_dwr.xml名字就可以看出:在spring中配置dwr了,内容如下:

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE beans PUBLIC
        "-//SPRING//DTD BEAN//EN"
        "http://www.springframework.org/dtd/spring-beans.dtd">

    <beans default-lazy-init="false">
        <bean id="__dwrConfiguration" class="org.directwebremoting.spring.SpringConfigurator">
            <property name="creators">
                <map>
                    <entry key="jdbcTemplateKey">
                        <bean class="org.directwebremoting.spring.CreatorConfig">
                            <property name="creator">
                                <bean class="org.directwebremoting.spring.BeanCreator">
                                    <property name="bean" ref="jdbcTemplate" />
                                    <property name="scope">
                                        <value>session</value>
                                    </property>
                                    <property name="javascript">
                                        <value>jdbcTemplateKey</value>
                                    </property>
                                </bean>
                            </property>
                        </bean>
                    </entry>
                    <entry key="frameworkViewService">
                        <bean class="org.directwebremoting.spring.CreatorConfig">
                            <property name="creator">
                                <bean class="org.directwebremoting.spring.BeanCreator">
                                    <property name="bean" ref="framework.ViewService" />
                                    <property name="scope">
                                        <value>application</value>
                                    </property>
                                    <property name="javascript">
                                        <value>frameworkViewService</value>
                                    </property>
                                </bean>
                            </property>
                            <property name="includes">
                                <list>
                                    <value>findByPrimaryKey</value>
                                </list>
                            </property>
                        </bean>
                    </entry>
                    <entry key="frameworkMetaManager">
                        <bean class="org.directwebremoting.spring.CreatorConfig">
                            <property name="creator">
                                <bean class="org.directwebremoting.spring.BeanCreator">
                                    <property name="bean">
                                        <bean class="whf.framework.meta.MetaManager" />
                                    </property>
                                    <property name="scope">
                                        <value>application</value>
                                    </property>
                                    <property name="javascript">
                                        <value>frameworkMetaManager</value>
                                    </property>
                                </bean>
                            </property>
                            <property name="includes">
                                <list>
                                    <value>findByBoClass</value>
                                    <value>findByActionClass</value>
                                    <value>findView</value>
                                </list>
                            </property>
                        </bean>
                    </entry>
                </map>
            </property>
            <property name="converters">
                <map>
                    <entry key="whf.framework.bo.IBaseBO">
                        <bean class="org.directwebremoting.spring.ConverterConfig">
                            <property name="type">
                                <value>bean</value>
                            </property>
                        </bean>
                    </entry>
                    <entry key="whf.framework.bo.BaseBO">
                        <bean class="org.directwebremoting.spring.ConverterConfig">
                            <property name="type">
                                <value>bean</value>
                            </property>
                        </bean>
                    </entry>
                    <entry key="whf.framework.meta.bo.Object">
                        <bean class="org.directwebremoting.spring.ConverterConfig">
                            <property name="type">
                                <value>bean</value>
                            </property>
                        </bean>
                    </entry>
                    <entry key="whf.framework.meta.bo.ObjectProperty">
                        <bean class="org.directwebremoting.spring.ConverterConfig">
                            <property name="type">
                                <value>bean</value>
                            </property>
                        </bean>
                    </entry>
                    <entry key="whf.framework.meta.bo.View">
                        <bean class="org.directwebremoting.spring.ConverterConfig">
                            <property name="type">
                                <value>bean</value>
                            </property>
                        </bean>
                    </entry>
                    <entry key="whf.framework.meta.bo.ViewProperty">
                        <bean class="org.directwebremoting.spring.ConverterConfig">
                            <property name="type">
                                <value>bean</value>
                            </property>
                        </bean>
                    </entry>
                </map>
            </property>
        </bean>
    </beans>

    与普通spring配置一样,需要注意的是

    <bean id="__dwrConfiguration" class="org.directwebremoting.spring.SpringConfigurator">
    这个id一定是这样命名的,不知道还有没有命名方法,其他参考上面的就可以了

    3、验证结果:

    http://localhost:8080/framework/dwr,出来三个连接jdbcTemplateKey, frameworkViewService, frameworkMetaManager,点击进入,现在你可以测试你配置dwr过程中暴露出来的method了