• 2009-11-07

    REST系列分享 - [developer]

    我在公司发起了一个关于REST的系列分享,现在已经进行到第二期了。主要关注于RESTFul应用的开发,基本上会从RESTFul的概念入手,慢慢深入的讨论如何在项目中如何开发RESTFul的应用、如何更好的理解RESTFul的理念。

    这两期的分享主题分别是《什么是REST风格应用》,《使用JSR311规范快速构建REST应用》。基本上每次的分享时间都有两个小时左右。虽然我会把每次分享的PPT都会给大家,但是毕竟PPT只是一个简单的大纲而已,更多的内容还是在大家互相讨论交流中。

    REST系列分享活动简介如下:
        这个活动是由程序员自动发起,非官方的身份决定我们不会有太多的资源来支持活动,希望大家能够在完成(安排好)手头的工作之后来加入活动。
        同时,可能由于时间的关系,我们允许分享人可以不准备ppt,但是要保证可以通过别的方式(如白板)来将你要分享的内容表达清楚。
        我们希望通过我们的分享和交流能够让大家对REST有更深入的了解,毕竟REST也是一个很庞大的话题,不是一次两次的培训和分享能够说明的。
        我们欢迎有自己的想法,有疑问,习惯质疑,喜欢分享的程序员加入活动中来。
        在活动中有任何的疑问和反对意见都可以随时提出来。

    下面是我之前分享内容的ppt。

     

  • 我们在Windows下使用Maven来编译项目的时候,经常会发现Maven给出这样的提示:[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!

    当你看见这样的提示的时候,你就要小心你项目中资源文件的中文会出现乱码的现象了。你可能会有疑问,我的文件的格式是UTF-8的,文件编码也是UTF-8的,为什么还会出现中文乱码的问题呢?

    其实原因Maven已经告诉你了。由于我们使用的Windows默认是GBK编码的,那用GBK的编码重新读写UTF-8的文件,那肯定是会出现乱码的。

    那么我们怎么样才能解决这个问题呢?其实很简单,只需要在相应的项目的pom.xml中加上一个Maven插件就可以了。

    <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                     <encoding>UTF-8</encoding>
                  </configuration>
    </plugin>


  • 2009-10-27

    Unix 设计哲学 - [developer]

    现在所有常用的操作系统基本上都是在Unix的基础上衍生(最少是借鉴)出来的,而且基本上大学的操作系统的课程也是拿Unix系统来讲解的。在这里我转载了Unix的设计哲学和思路,一起学习参考。

    原文地址:http://cgfundamentalism.spaces.live.com/Blog/cns!A938DB4347919AD9!406.entry

    (节选自 《Unix 编程艺术》)

    Unix哲学中更多的内容不是这些先哲们口头表述出来的,而是由他们所作的一切和Unix本身所作出的榜样体现出来的。从整体上来说,可以概括为以下几点:

    1. 模块原则:使用简洁的接口拼合简单的部件。
    2. 清晰原则:清晰胜于机巧。
    3. 组合原则:设计时考虑拼接组合。
    4. 分离原则:策略同机制分离,接口同引擎分离。
    5. 简洁原则:设计要简洁,复杂度能低则低。
    6. 吝啬原则:除非确无他法,不要编写庞大的程序。
    7. 透明性原则:设计要可见,以便审查和调试。
    8. 健壮原则:健壮源于透明与简洁。
    9. 表示原则:把知识叠入数据以求逻辑质朴而健壮。
    10.通俗原则:接口设计避免标新立异。
    11.缄默原则:如果一个程序没什么好说的,就沉默。
    12.补救原则:出现异常时,马上退出并给出足够错误信息。
    13.经济原则:宁花机器一分,不花程序员一秒。
    14.生成原则:避免手工hack,尽量编写程序去生成程序。

  • 这两天看了一个这样的文章《软件工程师的十个“不职业”行为》,是陈尚义老师整理分享的。看完之后,抚心自问,自己还是比较职业的软件工程师。

    现在将这十条不职业的行为摘录下来,时刻提醒自己。

    行为一:对外交付半成品。

    行为二:不遵守标准和规范。

    行为三:不积极帮助他人。

    行为四:版权意识不敏感。(这点要注意)

    行为五:对待计划不严肃。

    行为六:公事私事想混淆。

    行为七:不注意更新自己。

    行为八:不主动与人沟通。

    行为九:不遵守职场规则。

    行为十:不够诚实和正直

    原文地址:http://www.programmer.com.cn/1041/

  • 今天给大家分享了一下,在实现社区积分系统时使用设计模式的思路,同时也分享了一下我们怎么样去选择设计模式。

    谢谢大家的支持和讨论。

    在这里把刚才分享的Strategy、State、Chain of Responsibility的不同的地方概括说明一下:

    Strategy:主要应用于在一次操作中存在着多种算法的选择。

    State:主要应用于在同一事物状态转换的描述。

    Chain of Responsiblity:主要用于对某一次操作有一系列可选择的处理。

     

  • google的spider爬的真是快啊,我上篇blog刚刚写了半个小时左右,就已经能够在google上搜索到这篇文章了(有图为证)。而baidu、sogou等搜索引擎估计要再等段时间才能进入他们的索引了。

    想起在friendfeed中看到的一句话:google有8000个工程师,baidu有8000个营销人员,作为聪明的使用者,你会选择哪个作为默认的搜索引擎?

     

  • org.w3c.dom.Document是sun推出的Java API for XML Parsing (JAXP) 接口包中包含的一个类。

    我们一般会用它来表现一个xml文件,然后通过DOM来直接对这个xml进行操作。

    那我们怎么通过Jersey直接返回Document呢?是向下面那样吗?

    @GET
    @Produces("application/xml")
    public Document get() { ... return doc; };

    那只能返回下面的结果

    org.apache.xerces.internal.dom.DeferredDocumentImpl, and MIME
    media type, application/xml, was not found

    那怎样才是正确的呢?

    @GET
    @Produces("application/xml")
    public DOMSource get() {
       Document d = ..
       return new DOMSource(d);
    }

    有兴趣的同学可以试试。

  •         在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。

           其实可以想象一下银行卡的例子,除非用户主动对银行提出销卡,否则银行绝对不会轻易删除用户的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。

            然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的 HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。

           恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。

           Session机制说明:

           session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

           当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

           保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于 SEEESIONID,而。比如tomcat对于web应用程序生成的 cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。

           由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。

  • Jersey,一个REST风格服务的开发框架。

    现在jersey从1.0升级了到了1.0.1,解决了广大的jerseyer抱怨的初始化Client时的耗时太长的问题。

    以前使用jersey Client的时候,必须要先通过Client.create()来创建一下Client,我测试了一下,当我循环创建1000个Client时,耗时基本上达到了1分钟左右。

    现在我们完全可以通过spring来注入一个Client,这样我们完全省略了Client.create()一步了。

    这个是以前的代码
    public Class ClientTestOld(){
        public String getHelloWorld(){
            String url = "http://localhost/helloworld";
            Client c = Client.create();
            WebResource r = c.resource(url);
            return r.get(String.class);
        }
    }

    现在的代码
    public Class ClientTestNew(){
        @Resource
        private Client client;
        public String getHelloWorld(){
            String url = "http://localhost/helloworld";
            WebResource r = client.resource(url);
            return r.get(String.class);
        }
    }

    当然还需要在applicationContext.xml中加入如下配置:
    <bean id="jerseyClient" class="com.sun.jersey.api.client.Client" />

    <bean id="clientTest" class="com.tianji.www.jersey.client.ClientTestNew"
        p:client-ref="jerseyClient"
    />

    pom.xml中jersey依赖也要改成1.0.1,不过jersey-spring现在还只是1.0.1-SNAPSHOT,要注意哦!

  • 所谓绑定时间(binding time)就是把变量和它的值绑定在一起的时间。

    编写代码时绑定

     

    Java代码 
    1. titleBar.color = 0xFF;//0xFF is hex value for color blue  

     由于0xFF是硬编码(hard-coded)在程序里的数值,在编写代码的时候它就会绑定到titleBar.color变量上。这种硬编码技术通常是很糟糕的,因为一旦要修改这个0xFF,那么这个新值就无法同代码中其他那些必须和它一样的0xFF值保持一致了。

     

    编译程序时绑定

    Java代码 
    1. private static final int COLOR_BLUE = 0xFF;  
    2. private static final int TITLE_BAR_COLOR = COLOR+BULE;  
    3. ....  

     titleBar.color = TITLE_BAR_COLOR;
    TITLE_BAR_COLOR是一个具名常量,编译器会在编译时把它替换成一个数值。如果你用的语言支持这种特性,那么这种方法几乎总是要好于硬编码。
    由于TITLE_BAR_COLOR比0xFF更能反映出所代表的信息,因此增加了可读性。它也能使修改标题栏颜色变得更容易,因为一处改动就能对所有位置生效。同时也不会影响运行期的性能。

     

    运行时绑定

    Java代码 
    1. titleBar.color = ReadTitleBarColor();  

     ReadTitleBarColor()是一个能在程序运行期间读入数值的子程序,数值来源可能是配置文件,也可能来自spring的注入。
    与硬编码相比,这样的代码更具可读性和灵活性。无须通过修改程序来改变titleBar.color,只要简单修改ReadTitleColor()子程序要读取的数据源内容即可。

     

    对象实例化时绑定

    Java代码 
    1. public TitleBar ConstructTitleBar(Config config){  
    2.     ...  
    3.     titleBar.color = config.ReadTitleBarColor();  
    4.     ...  
    5.     return titleBar;  
    6. }  

     ConstructTitleBar()是一个能在对象实例化的时候通过配置中相应的方法来设置color。

  • 传递依赖是maven最有特色的、最为方便的优点之一,可以省了很多配置。如a 依赖 b,b 依赖c  默认 a也会依赖 c。但也会带来隐患,如版本冲突。当然maven也考虑到解决办法,可以使用exclusions来排除相应的重复依赖。

    但是我们还会遇到一个严重的问题,那就是,我怎么知道是哪个包的传递依赖产生的冲突 ?那该怎么办呢?当然,maven也会有相应的解决方案。

     首先,你要在pom.xml中加上maven-project-info-reports-plugin插件。

    <reporting>
      <plugins>
       <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>
         maven-project-info-reports-plugin
        </artifactId>
       </plugin>
     </reporting>

     然后再执行:mvn project-info-reports:dependencies 。生成项目依赖的报表,这样你就能够在报表中找出你版本冲突的相关性依赖了。

    最后在相应的dependency中加上exclusions来排除相关的传递依赖。

    例:

                  <dependency>
                            <groupId>jaxen</groupId>
                            <artifactId>jaxen</artifactId>
                            <version>1.1.1</version>
                            <exclusions>
                                    <exclusion>
                                            <groupId>com.ibm.icu</groupId>
                                            <artifactId>icu4j</artifactId>
                                    </exclusion>
                            </exclusions>
                            <scope>runtime</scope>
                    </dependency>

  • 其实以前也碰到过类似的问题,也解决了问题,没想到还是没有记性,这次居然还是在同样的地方卡了壳。现在记录下来,希望下次不会在出现类似的问题。

     在sping中multipartResolver主要用来处理文件上传,它支持Commons FileUploadCOS FileUpload。

    默认,spring是没有multipart处理,如果你想是用spring的multipart,需要在web应用的context中添加multipart解析器。这样,每个请求就会被检查十分包含multipart。然而,如果请求中包含multipart,你的context中定义的multipartResolver就会去解析它。这样,请求中的multipart属性就会象其他属性一样被处理。

     multipartResolve的配置如下:

    <bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 
            <property name="maxUploadSize"><value>1048576</value></property> 
        </bean> 

    同时需要在pom.xml中加上commons-fileupload和commons-io的依赖

     <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.2</version>
          <scope>runtime</scope>
        </dependency>
        <dependency>
          <groupId>commons-io</groupId>
          <artifactId>commons-io</artifactId>
          <version>1.2</version>
          <scope>runtime</scope>
        </dependency>

  • 我们在开发一些web应用的时候,经常会有一些需要改变默认浏览器行为的需求。

    比如:当我们点击某一个链接的时候,我们需要的不是请求链接的href,而是执行一些我们自己定义的一些行为时。我们一般会直接在a标签的onclick事件中加入javascript代码。

    <a href="#" onclick="javascript:alert("test");">test</a> 

    但是这样写的代码,浏览器并没有只执行onclick事件中的javascript代码,而是在onclick之后也执行了a标签的默认行为,对href的url进行了请求,不过href
    中的url是一个相对地址,浏览器会解析成当前的url加上#。比如在“http://wolfchina.blogbus.com/”这个url下有这么一个a标签,那浏览器会请求"http://wolfchina.blogbus.com/#"这个url。

    所以当我们只要浏览器响应onclick事件的时候,就会达不到我们的要求了。

    其实要达到这样的要求也挺简单的。W3C和IE都提供了相应的方式,我们下面的javascript代码就展示了防止发生默认浏览器行为的通用函数。

    function stopDefault(e){
        //防止默认浏览器行为(W3C)
        if(e && e.preventDefault)
            e.preventDefault();
        //IE中阻止浏览器行为的捷径
        else
            window.enent.returnValue=false;

        return false;
    }


    那如果我们需要点击网页中所有的链接的时候,浏览器都不做请求,而执行它的onclick事件时,我们应该如何去写代码呢?

    //定位页面上所有的<a>元素
    var a = document.getElementByTagName("a");

    for(var i=0;i<a.length;i++){
        //为<a>绑定onclick处理函数
        a[i].onclink=function(e){
            alert(this.href);
            //阻止浏览器对<a>的默认行为
            return stopDefault(e);
        }
    }

  • 刚刚安装了ubuntu8.04,于是将常用的软件安装好,开始工作了。

     突然发现maven不听“使唤”了 。当我执行mvn clean时,老是报错。后来查了资料才知道,原来是maven clean plugin的配置文件中没有配置相应的版本号。

     错误信息如下:

    [ERROR] BUILD ERROR
    [INFO] ------------------------------------------------------------------------
    [INFO] The plugin 'org.apache.maven.plugins:maven-clean-plugin' does not exist or no valid version could be found
    [INFO] ------------------------------------------------------------------------
    [INFO] For more information, run Maven with the -e switch
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: < 1 second
    [INFO] Finished at: Fri May 09 16:41:33 CST 2008
    [INFO] Final Memory: 1M/4M

    解决办法如下:

    1、通过mvn的相应的参数来下载相应版本的plugin,现在clean的版本是2.2。(点击查询maven clean的版本

    mvn org.apache.maven.plugins:maven-clean-plugin:2.2:clean

    2、修改配置文件来下载相应的版本

    配置文件地址:

    $HOME/.m2/repository/org/apache/maven/plugins/maven-clean-plugin/maven-metadata-central.xml

    <?xml version="1.0"?><metadata>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-clean-plugin</artifactId>
      <versioning>
        <latest>2.2</latest>
        <release>2.2</release>
        <versions>
          <version>2.0-beta-1</version>
          <version>2.0-rc1</version>
          <version>2.0</version>
          <version>2.1</version>
          <version>2.1.1</version>
          <version>2.2</version>
        </versions>
        <lastUpdated>20071129233456</lastUpdated>
      </versioning>
    </metadata>

     

    update:

    同理,maven的其他plugin的同样问题解决方法也应该差不多,以此类推就可以了。

  • 我们经常会发现在Linux下,java应用程序的中文会变成一个一个的“口”字,这样会给我们的使用带来很多的困扰。但是我们又不愿意放弃Linux和java的便捷。

    怎么来解决这个问题呢?

    其实,要解决这个问题也挺简单。

    咱们首先来说说产生这个问题的原因。java程序启动的时候会去在$JAVA_HOME/jre/lib/fonts目录下寻找相应的字体来显示。由于JDK默认没有中文字体,所以我们需要手工的来设置一下,让java应用程序能够找到相应的中文字体。这样就能够解决问题了。

    现在思路已经很清晰了,那我们就来着手解决问题吧。

    基本步骤如下:
    1)cd $JAVA_HOME/jre/lib/fonts
    2)mkdir fallback
    3)cp xxx.ttf fallback    #xxx.ttf代表你想要的中文字体文件
    4)cd fallback
    5)mkfontscale    
    6)mkfontdir        

    其实,我们可以一条命令解决这个问题。将Linux系统的字体目录作为JDK下面的一个字体目录连接。

    ln -s $FONTS_PATH/FONT_DIR $JAVA_HOME/jre/lib/fonts/fallback

    你在打开你的java应用程序就会看到久违的中文了。
  •     为了开发和测试的方便,我们自己的在公司局域网内部建立了一个DNS,但是同时也会有不方便的时候。
        比如,我们从开发和测试的环境转到真正的生产环境的时候,就需要手工的去本地的修改DNS设置,反之既然。其实有时候仅仅只是为了在测试环境上部署一下新的应用而已。每次都改太麻烦了,没有什么好的办法之间知道相应的域名的IP?
        当然是有办法了,尤其是在linux下。
        在linux下有两个可以查看域名信息的强大的工具,nslookup和host。这次,我们并不是去研究和讨论它们有多强大,不去讨论它们众多的功能。仅仅只是针对我们现在遇到的问题谈论怎么使用它们来解决。
        其实很简单。
        比如,我们现在有两台DNS服务器,一个是192.168.1.1,一个是192.168.1.2。这两台DNS上都有一条指向wolfchina.blogbus.com这个域名的DNS记录,但是所指向的IP地址却有不同,一个是真正的IP,另外一个是局域网的一个IP。
        那如果,我们使用的是192.168.1.1来解析的话,我们能够直接访问到真正的wolfchina.blogbus.com个网站,但是如果我们用192.168.1.2这个DNS来解析的话,那么我们只能访问到局域网的那台机器了。
    其实我们可以直接通过nslookup或者host来得知使用192.168.1.1或192.168.1.2解析的wolfchina.blogbus.com这个域名的IP是多少。
        host wolfchina.blogbus.com 192.168.1.1 #使用192.168.1.1来解析
        host wolfchina.blogbus.com 192.168.1.2 #使用192.168.1.2来解析
        nslookup wolfchina.blogbus.com 192.168.1.1 #使用192.168.1.1来解析
        nslookup wolfchina.blogbus.com 192.168.1.2 #使用192.168.1.2来解析

  • 关注ROR已经很长时间了,但是一直没有好好看看。现在公司已经有两个同事,用ROR做了一个成功的项目,于是计划接下来的一个月里,好好学习学习ROR。(好歹有人可以探讨和请教)

    按照Agile Web Development with Rails给的例子来做。一开始就出现了错误。

    错误提示是:“no such file to load -- sqlite3”。估计是没有找到sqlite的数据库,但是我并没有设置连结sqlite啊?打开database.yml,发现Rails已经自动给我加上了sqlite的配置了。

    而且,还给出了安装sqlite驱动的命令。但是,我不想使用sqlite。上网查了查,原来Rails升级到2.0.2之后使用sqlite3作为默认的数据库。但是如果,你想使用别的数据库当然也是可以的。有两种方式可以解决。

    1.自己手工的修改database.yml
    2.可以加上rails的参数
        -d, --database=name              Preconfigure for selected database (options:mysql/oracle/postgresql/sqlite2/sqlite3).
                            Default: mysql
                                        
        例:rails -d mysql demo
  • 我们在使用iBATIS时会经常用到#这个符号。

    比如:
    select * from member where id =#id#

    然后,我们会在程序中给id这个变量传递一个值,iBATIS会自动将#id#转成我们传递的内容。

    但是我最近碰到一个奇怪的问题。我在批量删除或修改的时候,居然SQL失效了。

    SQL如下:
    update user set flag=#flag# where id in (#id#)

    delete from user where id in (#id#)

    传递的id为1,2,3。但是数据却没有任何的修改。

    后来查找了半天,原来原因就是这个#的问题。因为iBATIS默认会把“#”中间的变量作为字符串来处理。这样,就会出现这样的SQL

    update user set flag='1' where id in ('1,2,3')

    delete from user where id in ('1,2,3')

    这样的SQL数据库当然是不会执行的。那我们只有绕开iBATIS了吗?

    其实不用,iBATIS其实还提供了另外一种方式,那就是使用$来传递值。你使用$将你的变量括起来,iBATIS不会给这个变量做任何的处理,直接生成你要的SQL

    update user set flag=$flag$ where id in ($id$)

    update user set flag=1  where id in (1,2,3)

    delete from user where id in ($id$)

    delete from user where id in (1,2,3)
  •      我们在数据库插入一条数据的时候,经常是需要返回插入这条数据的主键。但是数据库供应商之间生成主键的方式都不一样。

        有些是预先生成(pre-generate)主键的,如Oracle和PostgreSQL;有些是事后生成(post-generate)主键的,如MySQL和SQL Server。但是不管是哪种方式,我们都可以用iBATIS的节点来获取语句所产生的主键。
             
        例子如下:
    xml 代码
     
    1. <insert id="insertProduct-ORACLE" parameterClass="product">
    2.   <selectKey resultClass="int" type="pre" keyProperty="Id" >
    3.      SELECT STOCKIDSEQUENCE.NEXTVAL AS VALUE FROM DUAL
    4.   <selectKey>
    5.   insert into PRODUCT (PRD_ID,PRD_DESCRIPTION) values (#id#,#description#)
    6. <insert>
    7. <insert id="insertProduct-MS-SQL" parameterClass="product">
    8.   insert into PRODUCT (PRD_DESCRIPTION) values (#description#)
    9.   <selectKey resultClass="int" type="post" keyProperty="id" >
    10.     select @@IDENTITY as value
    11.   <selectKey>
    12. <insert>
    13. <insert id="insertProduct-MYSQL" parameterClass="product">
    14.   insert into PRODUCT (PRD_DESCRIPTION) values (#description#)
    15.   <selectKey resultClass="int" type="post" keyProperty="id" >
    16.     select LAST_INSERT_ID() as value
    17.   <selectKey>
    18. <insert>
  • 2007-12-12

    html 中meta属性 - [developer]

        meta是用来在HTML文档中模拟HTTP协议的响应头报文。meta 标签用于网页的<head>与</head>中,meta 标签的用处很多。meta 的属性有两种:name和http-equiv。name属性主要用于描述网页,对应于content(网页内容),以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)。这其中最重要的是description(站点在搜索引擎上的描述)和 keywords(分类关键词),所以应该给每页加一个meta值
    name 属性

      1、<meta name="Generator" contect="">用以说明生成工具(如Microsoft FrontPage 4.0)等;

      2、<meta name="KEYWords" contect="">向搜索引擎说明你的网页的关键词;

      3、<meta name="DEscription" contect="">告诉搜索引擎你的站点的主要内容;

      4、<meta name="Author" contect="你的姓名">告诉搜索引擎你的站点的制作的作者;

      5、<meta name="Robots" contect= "all|none|index|noindex|follow|nofollow">

      其中的属性说明如下:

      设定为all:文件将被检索,且页面上的链接可以被查询;

      设定为none:文件将不被检索,且页面上的链接不可以被查询;

      设定为index:文件将被检索;

      设定为follow:页面上的链接可以被查询;

      设定为noindex:文件将不被检索,但页面上的链接可以被查询;

      设定为nofollow:文件将不被检索,页面上的链接可以被查询。

      http-equiv属性

      1、<meta http-equiv="Content-Type" contect="text/html";charset=gb_2312-80">

    和 <meta http-equiv="Content-Language" contect="zh-CN">用以说明主页制作所使用的文字以及语言;

      又如英文是ISO-8859-1字符集,还有BIG5、utf-8、shift-Jis、Euc、Koi8-2等字符集;

      2、<meta http-equiv="Refresh" contect="n;url=http://yourlink">定时让网页在指定的时间n内,跳转到页面http://yourlink;

      3、<meta http-equiv="Expires" contect="Mon,12 May 2001 00:20:00 GMT">可以用于设定网页的到期时间,一旦过期则必须到服务器上重新调用。需要注意的是必须使用GMT时间格式;

      4、<meta http-equiv="Pragma" contect="no-cache">是用于设定禁止浏览器从本地机的缓存中调阅页面内容,设定后一旦离开网页就无法从Cache中再调出;

      5、<meta http-equiv="set-cookie" contect="Mon,12 May 2001 00:20:00 GMT">cookie设定,如果网页过期,存盘的cookie将被删除。需要注意的也是必须使用GMT时间格式;

      6、<meta http-equiv="Pics-label" contect="">网页等级评定,在IE的internet选项中有一项内容设置,可以防止浏览一些受限制的网站,而网站的限制级别就是通过meta属性来设置的;

      7、<meta http-equiv="windows-Target" contect="_top">强制页面在当前窗口中以独立页面显示,可以防止自己的网页被别人当作一个frame页调用;

      8、<meta http-equiv="Page-Enter" contect="revealTrans(duration=10,transtion= 50)">和<meta http-equiv="Page-Exit" contect="revealTrans(duration=20,transtion=6)">设定进入和离开页面时的特殊效果,这个功能即 FrontPage中的"格式/网页过渡",不过所加的页面不能够是一个frame页面。
  • 2007-12-12

    HTTP header 详解 - [developer]

    HTTP(Hyper Text Transfer Protocol)是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC 2616。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。

    通常HTTP消息包括客户机向服务器的请求消息和服务器向客户机的响应消息。这两种类型的消息由一个起始行,一个或者多个头域,一个只是头域结束的空行和可选的消息体组成。HTTP的头域包括通用头,请求头,响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。域名是大小写无关的,域值前可以添加任何数量的空格符,头域可以被扩展为多行,在每行开始处,使用至少一个空格或制表符。

    2.1 通用头域


    通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、Connection、Date、Pragma、Transfer- Encoding、Upgrade、Via。对通用头域的扩展要求通讯双方都支持此扩展,如果存在不支持的通用头域,一般将会作为实体头域处理。下面简单介绍几个在UPnP消息中使用的通用头域。

    2.1.1 Cache-Control头域

    Cache -Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、 proxy-revalidate、max-age。各个消息中的指令含义如下:Public    指示响应可被任何缓存区缓存。
    Private    指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
    no-cache    指示请求或响应消息不能缓存
    no-store    用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
    max-age    指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
    min-fresh    指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
    max-stale    指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。


    2.1.2 Date头域

    Date头域表示消息发送的时间,时间的描述格式由rfc822定义。例如, Date: Mon, 31 Dec 2001 04:25:57 GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。

    2.1.3 Pragma头域

    Pragma头域用来包含实现特定的指令,最常用的是Pragma: no-cache。在HTTP/1.1协议中,它的含义和Cache-Control: no-cache相同。

    2.2 请求消息


    请求消息的第一行为下面的格式:
    Method SP Request-URI SP HTTP-Version CRLF Method表示对于Request-URI完成的方法,这个字段是大小写敏感的,包括OPTIONS、GET、HEAD、POST、PUT、 DELETE、TRACE。方法GET和HEAD应该被所有的通用WEB服务器支持,其他所有方法的实现是可选的。GET方法取回由Request- URI标识的信息。HEAD方法也是取回由Request-URI标识的信息,只是可以在响应时,不返回消息体。POST方法可以请求服务器接收包含在请求中的实体信息,可以用于提交表单,向新闻组、BBS、邮件群组和数据库发送消息。

    SP表示空格。Request-URI遵循URI格式,在此字段为星号(*)时,说明请求并不用于某个特定的资源地址,而是用于服务器本身。HTTP-Version表示支持的HTTP版本,例如为HTTP/1.1。CRLF表示换行回车符。请求头域允许客户端向服务器传递关于请求或者关于客户机的附加信息。请求头域可能包含下列字段Accept、Accept-Charset、Accept- Encoding、Accept-Language、Authorization、From、Host、If-Modified-Since、If- Match、If-None-Match、If-Range、If-Range、If-Unmodified-Since、Max-Forwards、 Proxy-Authorization、Range、Referer、User-Agent。对请求头域的扩展要求通讯双方都支持,如果存在不支持的请求头域,一般将会作为实体头域处理。

    典型的请求消息:GET http://download.microtool.de:80/somedata.exe
    Host: download.microtool.de
    Accept: */*
    Pragma: no-cache
    Cache-Control: no-cache
    Referer: http://download.microtool.de/
    User-Agent: Mozilla/4.04 [en] (Win95; I ;Nav)
    Range: bytes=554554-



    上例第一行表示HTTP客户端(可能是浏览器、下载程序)通过GET方法获得指定URL下的文件。棕色的部分表示请求头域的信息,绿色的部分表示通用头部分。

    2.2.1 Host头域

    Host头域指定请求资源的Intenet主机和端口号,必须表示请求url的原始服务器或网关的位置。HTTP/1.1请求必须包含主机头域,否则系统会以400状态码返回。

    2.2.2 Referer头域

    Referer 头域允许客户端指定请求uri的源资源地址,这可以允许服务器生成回退链表,可用来登陆、优化cache等。他也允许废除的或错误的连接由于维护的目的被追踪。如果请求的uri没有自己的uri地址,Referer不能被发送。如果指定的是部分uri地址,则此地址应该是一个相对地址。

    2.2.3 Range头域

    Range头域可以请求实体的一个或者多个子范围。例如,
    表示头500个字节:         bytes = 0 - 499
    表示第二个500字节:       bytes = 500 - 999
    表示最后500个字节:       bytes = -500
    表示500字节以后的范围:   bytes = 500-
    第一个和最后一个字节:     bytes = 0-0 , -1
    同时指定几个范围:         bytes = 500-600, 601-999

    但是服务器可以忽略此请求头,如果无条件GET包含Range请求头,响应会以状态码206(Partial Content)返回而不是以200(OK)。

    2.2.4 User-Agent头域

    User-Agent头域的内容包含发出请求的用户信息。

    2.3 响应消息


    响应消息的第一行为下面的格式:HTTP-Version SP Status-Code SP Reason-Phrase CRLF



    HTTP -Version表示支持的HTTP版本,例如为HTTP/1.1。Status-Code是一个三个数字的结果代码。Reason-Phrase给 Status-Code提供一个简单的文本描述。Status-Code主要用于机器自动识别,Reason-Phrase主要用于帮助用户理解。 Status-Code的第一个数字定义响应的类别,后两个数字没有分类的作用。第一个数字可能取5个不同的值:

    1xx : 信息响应类,表示接收到请求并且继续处理
    2xx : 处理成功响应类,表示动作被成功接收、理解和接受
    3xx : 重定向响应类,为了完成指定的动作,必须接受进一步处理
    4xx : 客户端错误,客户请求包含语法错误或者是不能正确执行
    5xx : 服务端错误,服务器不能正确执行一个正确的请求

    响应头域允许服务器传递不能放在状态行的附加信息,这些域主要描述服务器的信息和Request-URI进一步的信息。响应头域包含Age、 Location、Proxy-Authenticate、Public、Retry-After、Server、Vary、Warning、WWW- Authenticate。对响应头域的扩展要求通讯双方都支持,如果存在不支持的响应头域,一般将会作为实体头域处理。

    典型的响应消息:HTTP/1.0 200 OK
    Date: Mon, 31 Dec 2001 04:25:57 GMT
    Server: Apache/1.3.14 (Unix)
    Content-type: text/html
    Last-modified: Tue, 17 Apr 2001 06:46:28 GMT
    Etag: "a030f020ac7c01:1e9f"
    Content-length: 39725426
    Content-range: bytes 554554-40279979/40279980



    上例第一行表示HTTP服务端响应一个GET方法。棕色的部分表示响应头域的信息,绿色的部分表示通用头部分,红色的部分表示实体头域的信息。

    2.3.1 Location响应头

    Location响应头用于重定向接收者到一个新URI地址。

    2.3.2 Server响应头

    Server响应头包含处理请求的原始服务器的软件信息。此域能包含多个产品标识和注释,产品标识一般按照重要性排序。

    2.4 实体


    请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括Allow、Content- Base、Content-Encoding、Content-Language、Content-Length、Content-Location、 Content-MD5、Content-Range、Content-Type、Etag、Expires、Last-Modified、 extension-header。extension-header允许客户端定义新的实体头,但是这些域可能无法未接受方识别。实体可以是一个经过编码的字节流,它的编码方式由Content-Encoding或Content-Type定义,它的长度由Content-Length或Content -Range定义。

    2.4.1 Content-Type实体头

    Content-Type实体头用于向接收方指示实体的介质类型,指定HEAD方法送到接收方的实体介质类型,或GET方法发送的请求介质类型。

    2.4.2 Content-Range实体头

    Content-Range实体头用于指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式:Content-Range: bytes-unit SP first-byte-pos -last-byte-pos/entity-legth



    例如,传送头500个字节次字段的形式:Content-Range: bytes 0-499/1234 如果一个http消息包含此节(例如,对范围请求的响应或对一系列范围的重叠请求),Content-Range表示传送的范围,Content-Length表示实际传送的字节数。

    2.4.3 Last-modified实体头

    Last-modified实体头指定服务器上保存内容的最后修订时间。




    RFC 2616:
    关于超文本传输协议(HTTP 1.1)原文IETF的RFC文档 http://www.ietf.org/rfc/rfc2616.txt?number=2616
  •    来源:http://www.blogjava.net/calvin/archive/2007/01/27/96318.html     

          Unix系统永远只会越来越多,开发人员就没必要特意学习它们的安装、配置和管理了,就全部交给集成人员吧。
        但开发人员行走于Unix之间,依然有四样东西要熟练。
        一、VI

        虽然Unix上的文本编辑器已经越来越好用,但不在Console前面,网速也不够连XWindows的时候,还是要依赖VI。
        回想VI的时代背景,发现VI对开发人员已经周到得离谱了,热键多到你双手不离键盘就能完成大半编辑工作。
        建议自己制作一张自己认为有用,但又经常忘记的命令的sheet,拿出考试的力气把它背熟。
        二、文本处理

           开发人员在Unix下干得最多的除了Make和除Bug外,大概就是处理日志文件、业务文件进行查错和统计了。
         只会more和grep是不够的,开发老手会把awk,sed,grep,sort,uniq,wc,head,tail这些文本处理命令,通过管道玩具式的拆卸拼装,最后完成一件原本以为非编写大段代码不可的工作。周到的参数设定,让人再一次感叹那个简单的年代,这样复杂到极致的设计.......怪不得《Unix 编程艺术》的作者有那么骄傲的自觉。

         比如车东的每月访问TOP10 统计脚本:
     awk -F '\ t' '{ print   $ 4 }' 2004_2 . txt| grep chedong . com / tech / | uniq -c| sort  -rn|head - 10   
    awk -F '\t' 将2004_2.txt访问纪录文件,用TAB分割,打印第4列
    grep chedong.com/tech 只列出chedong.com/tech笔记目录下的文档
    uniq -c 汇总计数
    sort -rn 按数值排序
    head -10 TOP 10
        三、Bash Shell 编程

        编程是开发人员的天赋本能,不论什么语言,看看参考手册应该就能上手。

        见Bash新手指南中文版,一份写给新手看的包含很多老手知识的指南。
        四、Make与AutoMake

        用过Java的Ant后,想起Make就觉得很烦,很厌倦。总归还是会的,见GNU Make 3.8.0 中文手册     

         不过即使make已经精通到变态,每个人写出来的MakeFile还是千奇百怪,再看看开源项目们个个都是automake+autoconf了,我们自己也长进一点吧。手工编写MakeFile.am,让auotomake变成MakeFile.in,再让用户./configure 生成最终的MakeFile。
        
        生成的MakeFile既能跨越平台,又是标准的写法,最重要的是,编写MakeFile.am的工作量比MakeFile少多了,只要简单的定义目标文件,先要处理的子目录,需要的源文件,头文件与库文件就可以了。如果看完下面两篇还是不懂,直接看ACE里的Makefile.am就懂了。

        入门文章:使用AutoMake轻松生成Makefile
        进阶文章:IBM DW:例解 autoconf 和 automake 生成 Makefile 文件
        完整的免费电子书: GNU Autoconf, Automake and Libtool

        另外,ACE里还贡献了一个更厉害的MPC(Makefile, Project, and Workspace Creator ),  自动的生成了MakeFile.am或者VC的项目文件。

        附录A:我的VI易忘命令手册

        上下左右:
        ctrl+u/d 上下半屏,ctrl+f/b,上下一屏
        H/G屏幕头/文章末 ,0/$ 行首行末
        
        增删改:
        yy/dd 复制/删除 一行,p/P:将yy/dd的内容paste出来
        I/A 在行首/末添加, o/O 开新行,d0/d$ 删除到行首,行末
        u:undo

        查:
        ? 向前查找, n/N 重复上一次查找
    附录B: 文本处理命令小结

       awk:处理结构化的文本(每行以固定符号分成若干列),提取打印某些字段,如:
        ls -l|awk '{print $1}'  --将ls-l结果的第一列打印出来
        awk -F":" '{print $1"  "$6}' /etc/passwd ,将以:分割的/etc/passwd文件的第1,6列打印出来,中间以空格分开
        详见IBM DW中国的AWK实例(共3篇) 或 Bash新手指南中文版第6章。

        grep:过滤,大家用得最多的命令,支持正则表达式。参数有:
        -i忽略大小写,-n显示line number,-c 统计在每个文件的出现次数,-l只显示符合的文件的名字。

        sed:流编辑器,主要用于替换,如:
        sed -e '1,10s/foo/bar/g' myfile2.txt 将1到10行的文本中的foo 替换成bar,s代表替换,g代表全局替换
        支持正则的替换字符串,可以只替换某个范围内的内容。
        用法不算简单,详见IBM DW中国的Sed实例(共3篇)或 Bash新手指南中文版第5章。
        
        sort:排序,参数有:
        -r逆序, -n 数字比较 , -M 日历比较 Feb,Dec, -f 忽略大小写
        同样支持结构化文件,如
        sort -t : -k 1,1 /etc/passwd,以: 分割,只按第1列排序
        sort -t : -k 1,1 -k2.2,3.4 /etc/passwd ,以:分割,先按第1列排序,再按第2列的第二个字符到第3列的第4个字符排序。

        uniq:去除重复行。
        除了正常用法外,还有-c统计重复次数,和-u (唯一)和 -d (重复)两个参数,只显示唯一的和重复的行。

        wc: 统计。
        -l 行,-m 字符,-w 单词
  • 要求:编写脚本循环取出文件 a.txt 中每一行,并将其赋予 aa

    脚本如下:
    #!/bin/sh
    for i in `cat a.txt`
    do
        aa = $i
        echo aa
    done

    但是上面的脚本有这样一个问题.如果这一行只有一个词的话,没有问题.如果这一个有多个词,则会将这一行分解成多行.

    改写脚本如下:
    #!/bin/sh
    cat a.txt | while read aa
    do   echo $aa
    done
  • abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。 abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于 abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图 的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

    理解抽象类

    abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

    在 面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来 描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的 具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这 样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是 不能够实例化的。

    在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有 任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一 个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设 计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。


    从语法定义层面看abstract class和interface

    在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。

    使用abstract class的方式定义Demo抽象类的方式如下:

    abstract class Demo {
    abstract void method1();
    abstract void method2();



    使用interface的方式定义Demo抽象类的方式如下:

    interface Demo {
    void method1();
    void method2();

    }

    在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的 不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊 形式的abstract class。

    从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。

    首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

    其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

    在 抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时 间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。

    同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中, 违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。


    从设计理念层面看abstract class和interface

    上 面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

    前 面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使 论述便于理解,下面将通过一个简单的实例进行说明。

    考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

    使用abstract class方式定义Door:

    abstract class Door {
    abstract void open();
    abstract void close();
    }


    使用interface方式定义Door:


    interface Door {
    void open();
    void close();
    }


    其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

    如 果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些 不同的方案进行分析。

    解决方案一:

    简单的在Door的定义中增加一个alarm方法,如下:

    abstract class Door {
    abstract void open();
    abstract void close();
    abstract void alarm();
    }


    或者

    interface Door {
    void open();
    void close();
    void alarm();
    }


    那么具有报警功能的AlarmDoor的定义方式如下:

    class AlarmDoor extends Door {
    void open() { … }
    void close() { … }
    void alarm() { … }
    }


    或者

    class AlarmDoor implements Door {
    void open() { … }
    void close() { … }
    void alarm() { … }


    这 种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅 依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

    解决方案二:

    既 然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用 abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

    显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

    如 果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是 Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致 的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。

    如 果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢? 前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定 义。如下所示:

    abstract class Door {
    abstract void open();
    abstract void close();
    }
    interface Alarm {
    void alarm();
    }
    class AlarmDoor extends Door implements Alarm {
    void open() { … }
    void close() { … }
    void alarm() { … }
    }


    这 种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有 Door的功能,那么上述的定义方式就要反过来了。



    结论

    abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概 念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法。
  • 最近在研究Spring的的MVC,其中有个问题比较困扰(问题比较低级,各位高手不要笑话啊!)。现在将思路整理一下。

          在Spring的MVC中Controller接口会返回一个ModelAndView对象。ModelAndView中包含一个viewName,还可以包含String类型的modelName和Object的modelObject。当你在相应的View取值时,你可以用EL标签直接取值,如${modelName}。但是你不如不用EL标签来取值呢?那应该怎么做?(虽然在JSTL 1.1 规范中, JSP2.0 容器已经能够独立的理解任何 EL 表达式。)

          HttpServletRequest.getParameter("modelName");能取到想要的modelObject吗?经过测试之后,发现是不能的。后来想想,其他道理挺简单的,当两个Web组件之间为转发关系时,转发源会将要共享request范围内的数据先用setAttribute将数据放入到HttpServletRequest对象中,然后转发目标通过getAttribute方法来取得要共享的数据。而MVC中用的就是Web组件之间的转发啊!真是笨,怎么当时没有想到呢?

          下面整理一下getParameter和getAttribute的区别和各自的使用范围。

          (1)HttpServletRequest类有setAttribute()方法,而没有setParameter()方法

          (2)当两个Web组件之间为链接关系时,被链接的组件通过getParameter()方法来获得请求参数,例如假定welcome.jsp和authenticate.jsp之间为链接关系,welcome.jsp中有以下代码:

          <a  href="authenticate.jsp?username=wolf">authenticate.jsp  </a>

          或者:

          <form  name="form1"  method="post"  action="authenticate.jsp">
              请输入用户姓名:<input  type="text"  name="username">
              <input  type="submit"  name="Submit"  value="提交">
          </form>

           在authenticate.jsp中通过request.getParameter("username")方法来获得请求参数username:

           <%  String  username=request.getParameter("username");  %>

           (3)当两个Web组件之间为转发关系时,转发目标组件通过getAttribute()方法来和转发源组件共享request范围内的数据。

            假定  authenticate.jsp和hello.jsp之间为转发关系。authenticate.jsp希望向hello.jsp传递当前的用户名字,  如何传递这一数据呢?先在authenticate.jsp中调用setAttribute()方法:

            <%
            String  username=request.getParameter("username");
            request.setAttribute("username",username);
            %>

            <jsp:forward  page="hello.jsp"  />

            在hello.jsp中通过getAttribute()方法获得用户名字:

            <%  String  username=(String)request.getAttribute("username");  %>
            Hello:  <%=username  %>

            从更深的层次考虑,request.getParameter()方法传递的数据,会从Web客户端传到Web服务器端,代表HTTP请求数据。request.getParameter()方法返回String类型的数据。

            request.setAttribute()和getAttribute()方法传递的数据只会存在于Web容器内部,在具有转发关系的Web组件之间共享。这两个方法能够设置Object类型的共享数据。

            request.getParameter()取得是通过容器的实现来取得通过类似post,get等方式传入的数据。

            request.setAttribute()和getAttribute()只是在web容器内部流转,仅仅是请求处理阶段。

            getAttribute是返回对象,getParameter返回字符串

            总的来说:request.getAttribute()方法返回request范围内存在的对象,而request.getParameter()方法是获取http提交过来的数据。
  • 来源:车东
    Apache上的防mp3盗链的参考配置如下:
    RewriteEngine on
    RewriteCond %{HTTP_REFERER} !^http://(www\.)?niernier\.com/.*$ [NC]
    RewriteRule \.(mp3|rar)$ http://www.niernier.com/archives/000445.html [R=301,L]
    #RewriteLog "logs/rewrite.log"
    #RewriteLogLevel 3


    功能:
    不仅屏蔽/禁止非本网站的盗链访问,还将盗链请求转给自身的网站,让读者知道谁是真正的原创作者。

    说明:
    RewriteCond 条件:意思就是所有的mp3和rar访问如果referer不是本网站niernier.com或www.niernier.com,
    RewriteRule 规则:自动转向到原作宿主页: http://www.niernier.com/archives/000445.html
    注释掉的部分是测试rewrite engine用的:对于调试mod_rewrite很有用。

    效果:字节流量比原来下降一半,网站独立用户访问量上升了1倍
    日期 参观人次 网页数 文件数 字节
    2006年 十一月 16 685 897 4641 865.99 M字节
    2006年 十一月 17 728 1173 5522 1.28 G字节
    2006年 十一月 18 648 934 5535 1.46 G字节
    2006年 十一月 19 704 1020 7120 1.40 G字节
    2006年 十一月 20 882 1176 5286 1.23 G字节
    2006年 十一月 21 2380 3526 6076 568.01 M字节
    2006年 十一月 22 2146 3003 5791 458.13 M字节
    2006年 十一月 23 2092 3177 6131 518.99 M字节

    具体的数据和商业模式分析附后:

    我已经放弃了对妮妮的芝华士这篇文章的追赶:截至目前已经有460个留言。这篇文章给服务器带来的负载如果不看日志分析也是很难想像的:
    今年以来:通过这个/archives/000445.html 页面找到访问的有 7.7万次 而,网站上hosting的MP3总体流量流量:缺高达57万次(172G流量),这些流量大部分来自跨网站的MP3下载盗链和在线播放器的直接播放:
    mp3 Audio file 574061 36.8 % 172.19 G字节 60.1 %
    jpg Image 306660 19.6 % 49.93 G字节 17.4 %
    html HTML or XML static page 214103 13.7 % 20.93 G字节 7.3 %
    css Cascading Style Sheet file 151239 9.7 % 1.80 G字节 0.6 %
    rdf 140569 9 % 878.08 M字节 0.2 %
    rar Archive 133265 8.5 % 38.63 G字节 13.5 %

    200G流量是什么概念?按照目前业内最高性价比的Amazon S3在线存储/发布服务:200G * 0.2$/G = 40$,不要小看这40$,这一个文件的发布费用已经超过了niernier.com网站目前规模靠AdSense可能获得的收入的一半。(年收入¥=日独立用户数),而带宽资源上目前还没有免费的午餐。

    浏览器的分布:可以看到有大量来自在线播放的流量。
    MS Internet Explorer 否 987915 63.4 %
    NetShow Player (media player) 否 286855 18.4 %
    Real player or compatible (media player) 否 37872 2.4 %
    Windows Media Player (media player) 否 4721 0.3 %
    如果仔细分析会发现:很多是包在基于FLASH的在线播放器中的,下载==>在线收听是在线流媒体过去2年最大的变化之一。

    盗链对于商业模式的损害:
    对于发布MP3/图片的网站来说,如果MP3/图片被盗链主要是以下2个来源:
    MP3搜索引擎 ==> 在线收听/MP3下载
    非宿主网页盗链 ==> 在线收听/下载

    都是将跳过了MP3发布宿主网页的浏览:除了提供带宽下载外,MP3宿主网站没有可能得到任何收益(因为潜在收益主要来自发布MP3的页面上的广告)。 目前各种新型模式中:无论是免费视频网站之所以能成为一种模式:都是因为有了在flash播放器中能在视频介绍页面和通过flash在视频播放前后(中)插入广告的机会。所以如果没有宿主网站flash的包装,就必须将所有没有看过宿主网站页面的MP3和RAR下载请求转向回宿主网站页面(只有通过宿主网站页面才能进行MP3在线下载试听)。如果不能将这些MP3盗链流量转换成看页面PV,网站自身就是数量级的收入损失。

    缺点:
    宿主站内的在线播放器也无法支持了(播放器不传REFERER),但是鱼和熊掌不可兼得,等未来播放器也支持referer标准吧……

    参考:
    Apache 防盗链指南
  • 通过Java来调动Linux自己的系统命令一直是我比较困扰的问题,当然和我的水平有很大的关系。今天上网查了查资料发现其实Java很容易就可以做到和操作系统的系统命令交互的问题。

    下面就写了这么一个简单的例子来介绍Java是如何和OS的系统命令来交互的。

    import java.io.*;

    public class getLinuxTxtCount {
    /**
    * 取文本文件有多少行
    * @return 返回行数
    */
    public String getTxtCount(){
    String count = "";
    try {
    Runtime r = Runtime.getRuntime();
    String[] args = new String[] {"sh","-c","wc -l /usr/local/apache2/logs/*"};//用这个命令可以把所有文本中的行数都取出来.这个地方应该是取出来的路径变量,我在例子里写死了.
    Process p1 = r.exec(args);
    BufferedReader in = new BufferedReader(new InputStreamReader(p1.getInputStream()));
    String line = "";
    int index = 0;
    while ( (line = in.readLine()) != null) {
    if (line.indexOf("总用量")>=0) { //总用量这个字是系统自动生成的,需要看linux操作系统,我这个版本是中文的.
    index = line.indexOf("总用量");
    count = line.substring(0,index);
    count = count.trim();
    }
    }
    }catch(Exception e){
    System.out.println(e);
    }
    return count;
    }

    public static void main(String[] args)
    {
    getLinuxTxtCount txtcount = new getLinuxTxtCount();
    System.out.println(txtcount.getTxtCount());
    }
    }
  • Java对输入输入首先有一个“字节流”到“字符流”之间的编码/解码过程,这个设置是根据系统配置决定的,为什么PHP之类的应用很少有字符集问题而Java有很好的国际化机制,却经常出现乱码问题呢?

    简单的举例:
    有一个包含“你好”这2个中文字的文件实际上是4个字节组成的:C4 E3 BA C3
    在英文操作系统中缺省的编码解码方式是缺省编码方式是ISO8859,所以直接从文件中读取的结果是4个的字节,按ISO8859解码后在程序中操作的是4个Java字符,虽然每个JAVA字符是16位Unicode,但每个字符仍是8位字节的映射\u00C4\u00E3\u00BA\u00C3,因此处理的仍是“英文”。而显示过程中,是浏览器将字节流正确的显示成了相应的中文。

    而一个Java应用在GBK编码方式的操作系统中,直接从文件中读取4个的字节后,按GBK方式解码后是2个16位的Java字符\u4F60\u597D,每个字都是相应Unicode的CJK区块所对应的中文。

    更多的例子请参考:Java的中文处理学习笔记

    这也就是为什么在php等应用很少出字符集问题的原因:在服务器端环境缺省一般是英文(ISO8859-1),等于全部处理使用的都是按字节方式处理的。数据输入输出过程中编码方式完全不被改动,因此乱码问题很少出现。而Java实际上提供了把每个中文直接当成1个“字”而不是2个字节处理的机制,主要的乱码问题往往是输入输出时编码解码方式不一致造成的。而且通过Unicode机制,程序除了实现程序界面根据本地化的适应外,甚至程序处理的内容本身的在不同字符集的操作系统中也是可以通用的,比如:在繁体中文操作系统中编辑的内容,在简体中文操作系统中也能正常的查询。以下例子可能更说明问题:
    JAVA应用的中文问题:如何通过GNU/Linux系统的本地化设置让JAVA应用支持中文
    Java Web应用设计中的中文问题:通过web.xml设置解决URLEncoder.encode()方法和系统缺省编码方式相关的问题
    以GOOGLE的搜索引擎为例:说明如何将国际化和本地化应用到自己的应用设计中(UniCode inside Localization outsite)

    通过GNU/Linux系统的本地化设置让JAVA应用支持中文

    Java 编程技术中汉字问题的分析及解决这篇直到最近还经常被一些网站转贴的文章中有一个例子说明了很多中国程序员遇到汉字乱码问题的思路:"GB2312 it"(汉化)

    原文如下:
    >>>>>>>
    ......前不久,我的一位技术上的朋友发信给我说,他终于找到了 Java Servlet 中文问题的根源。两周以来,他一直为 Java Servlet 的中文问题所困扰,因为每面对一个含有中文字符的字符串都必须进行强制转换才能够得到正确的结果(这好象是大家公认的唯一的解决办法)。后来,他确实不想如此继续安分下去了,因为这样的事情确实不应该是高级程序员所要做的工作,他就找出 Servlet 解码的源代码进行分析,因为他怀疑问题就出在解码这部分。经过四个小时的奋斗,他终于找到了问题的根源所在。原来他的怀疑是正确的, Servlet 的解码部分完全没有考虑双字节,直接把 %XX 当作一个字符。(原来 Java Soft 也会犯这幺低级的错误!)

    如果你对这个问题有兴趣或者遇到了同样的烦恼的话,你可以按照他的步骤对 Servlet.jar 进行修改:

    找到源代码 HttpUtils 中的 static private String parseName ,在返回前将 sb(StringBuffer) 复制成 byte bs[] ,然后 return new String(bs,”GB2312”)。作上述修改后就需要自己解码了:

    HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者

    form=HttpUtils.parsePostData(……)

    千万别忘了编译后放到 Servlet.jar 里面。
    ......
    <<<<<<<<<

    请问这位“高级”程序员几个问题:
    如果这是一个商业产品的话,难道客户需要你Hacking过的Servlet.jar才运行这个应用吗?
    难道这个产品只能用在中文平台的GB2312上吗?如果是日文应用怎么办,如法Hacking吗?

    也许我错了,但我的感觉是犯低级错误的不是JAVA:
    首先,在Servlet层就不应该考虑中文输出的问题,因为,在MVC的设计模式中,Servlet主要的角色是Contrallor,所以,在这一层中,数据应该最好还是Unicode形式,在最后让Jsp或者通过xslt做针对客户端浏览器的输出时,再需要考虑本地字符集编码的问题。
    作为一个标准的国际化应用,JAVA应用的缺省编码方式不应该是在WEB应用这一层设置的,而是JVM根据系统缺省编码方式根据操作系统的环境设置(locale, 包括字符集,日期格式等本地化环境)改变来实现。

    如何设置可以让GNU/Linux从系统层次就支持中文编码(让JVM缺省的file.encoding就按照中文GB2312或者GBK进行编码解码)呢?

    以上这篇文章发表在2000年年底,当时的GNU/Linux的内核是基于glibc-2.1开发的,而GLIBC2.1对中文的locale支持还有限,因此,在GNU/Linux上不能根据locale的设置将系统缺省的编码方式变成GB2312,从而改变JVM缺省的编码方式。关于GNU/Linux对l10n的支持请看:GNU/Linux程序员必读:中文化与GB18030标准。所以在redhat6.x下,无论你怎么设置locale,系统缺省的缺省file.encoding都是ISO_8859_1。

    在redhat7.x 系统内核所基于的glibc-2.2.x对l10n有了更完整的支持,所以可以通过设置
    LC_ALL=zh_CN.GB2312;export LC_ALL
    LANG=zh_CN.GB2312;export LANG
    让系统缺省的编码方式变成GB2312。JVM会根据系统的缺省编码方式设置系统的file.encoding属性。之后,应用中任何字节流到字符流的转换,JVM都会按照这一系统缺省编码方式进行转换。因此在基于glibc-2.2以上的GNU/Linux上是可以通过locale的设置来改变系统缺省的编码方式,从而改变上层Java应用的缺省编码、解码方式的。

    locale设置对JVM file.encoding的影响可以我通过做过的一个试验说明:hello_unicode.html

    由此可见:
    GNU/Linux是依靠GNU的工具发展起来的:没有GNU就没有GNU/Linux。所以GNU/Linux对本地化的支持,也是在核心的glibc-2.2.x对中文locale有了更好的支持以后才逐步发展起来的。
    GNU/Linux对国际化的支持远远落后于WINDOWS SOLARIS等商业操作系统:2年甚至更多。

    通过web.xml设置解决URLEncoder.encode()方法和系统缺省编码方式相关的问题

    在我所理解的范围内,J2SE 1.3中非常不符合Java的国际化规范的是在使用URLEncoder的时候:
    比如在中文WIN98上运行的应用,使用URLEncoder.encode(String s)时:比如“中文”这2个字符直接被Encoding的话结果是"%3F%3F"=>"??"。原因很简单,“中文”在encode()过程中应该先按GBK编码方式编码成4个字节(\u00d6\u00d0\u00ce\u00c4)后再进行URLEncoding才是正确的。这个在J2SE1.4中也修正了。方法encode(String s)已经不鼓励使用,取而代之的是除了需要进行URLEncoding的字符串外,同时需要指定字符串编码方式的encode(String s, String enc)。这样,URLEncoder就可以和系统缺省的编码方式无关了。

    在J2SE1.3下一个WEB应用如果有这个问题可以通过在WEB-INF/web.xml中设置character-encoding来解决:
    <web-app character-encoding="your_system_default_file.encoding">
    ...
    </web-app>

    比如:产品是在中文WINDOWS98中运行,系统缺省字符集是用GBK,则这个应用的web.xml需要设置成:
    <web-app character-encoding="GBK">
    ...
    </web-app>

    UniCode inside, Localization outsite

    以上2个方法仍然只是让应用更方便地本地化了,而应用本身并不是真正的国际化应用。设想一下如何设计一个全球的论坛系统:可以让中文和日文的用户都可以方便的浏览发表呢?在数据中间处理阶段应该以那种字符集存储呢?答案很简单:UniCode。以前很多文章都有关于如何设计一个国际化界面的介绍,只是应用的本地化界面输出,但很少提及数据在中间处理过程中如何适应国际化。

    输入和存储阶段就用UniCode方式进行处理和存储,以方便应用以后的国际化。GOOGLE的设计就是一个非常好的国际化应用榜样,我以GOOGLE搜索引擎的国际化支持为例说明如何实现国际化应用的设计。

    GOOGLE用户经常有这样的感觉:
    为什么我第一次去GOOGLE,出现的就是中文的界面?
    为什么在所有网站中查中文:有时候还会匹配到日文网站的结果?比如:就以"google 秘密"这个查询为例:我们在输入框输入"google 秘密"
    http://www.google.com/search?hl=zh-CN&newwindow=1&q=google+%C3%D8%C3%DC&btnG=Google%CB%D1%CB%F7&lr=

    首先我将GOOGLE对查询的处理流程简单的说明如下:
    客户端浏览器输入;
    查询字符串按客户端系统编码方式(GBK)转换成字节流,并URL Encode后传给GOOGLE;
    GOOLGE将输入的字符串URL Decode后,按照客户端的系统编码方式将这个字符串(字节串)解码成UniCode
    查询过程,完全是基于UniCode的匹配过程,比如对于“中文”这2个字在简体繁体中文和日文里都有,因此无论是何种语言的页面包含这2个字的页面都能匹配上。
    结果集输出:将查询结果集的内容(UNICODE)按客户端系统编码方式(GBK)“编码”成的字节流,返回给浏览器

    具体说明:
    GOOGLE如何识别出浏览器使用的“界面语言”:GOOGLE获得这个查询字符串的同时,一般会根据hl=zh-CN这个参数,知道了客户端使用的字符集编码方式,如果用户第一次访问:GOOGLE会根据浏览器的发送的请求中包含的Accept language: zh_cn这个头信息来判别,这就是为什么现在很多用户第一次去GOOGLE的时候它就能自动识别出来的原因。这个参数在之后的查询和翻页过程中通过cookie保存,并通过get方式一直传递给GOOGLE(因此你也可以使用使用偏好设置界面语言),从而可靠地识别出客户端的编码方式。
    GOOGLE如何查询:也许从URL上你可以看到:传过去的“秘密”这个查询实际上是%C3%D8%C3%DC=>"秘密"这2个字按GBK(WINDOWS客户端缺省的编码方式)编码方式的4个字节然后再URLEncode后的形式(关于中文编码方式请参考:汉字的编码方式),GOOGLE将查询字符串按这个编码方式解码并转成UniCode,然后用这个UniCode编码方式的字符串进行内部的查询操作。而任何语言的页面都是先转换成UniCode后存储在GOOGLE的数据索引库里的。在UniCode中日文和中文写法一样的字,用的是同样的编码。因此,如果你没有指定语言过滤的话,日文网页的结果就首先被命中了;因此,对于中文客户端的查询:如果相应字符在UniCode中和繁体,日文映射的字一样,就可以匹配到相应的日文网页,繁体中文网页...,GOOGLE的查询结果也首先是UniCode的,最后将UniCode结果按照客户端的编码方式转换成字节流,返回到客户端。

    从以上的分析中我们可以看出:UniCode非常漂亮的解决了应用的国际化问题。对于应用前端来说,剩下的工作就是根据本地编码环境进行本地化的过程了。
    数据从输入的开始,就全部先转换成UniCode,然后再进行处理,并按照UniCode方式集中存储(UniCode inside)
    数据输出过程中,只是在最后输出到客户端的时候,按照客户端的本地化设置将UniCode数据转换成本地字符集,并配以相应语言/字符的界面(Localization outside)

    如果应用的开发只是满足于在国内市场自给自足,“汉化”的思路的大量出现是很自然的。但要是把“汉化”比作UCDOS和RichWin的话,那么这种汉化方式迟早要被内核汉化的WIN95淘汰的。毕竟核心级别对国际化的支持才是一个真正简化前端应用设计、通用的解决方案。Microsoft和Sun等国际化大公司的产品从一开始就是为全球市场设计的,因此对国际化的支持一致非常重视。相比之下国内软件行业对相应国际标准显然重视不足,也很少积极地参与相关标准制定。


     

    参考文档:
    Java i18n
    http://java.sun.com/docs/books/tutorial/i18n/index.html

    Linux 国际化本地化和中文化
    http://www.linuxforum.net/doc/i18n-new.html

    GNU/Linux 程序员必读:中文化与GB18030标准
    http://www.ccidnet.com/tech/os/2001/07/31/58_2811.html

    UniCode FAQ
    http://www.cl.cam.ac.uk/~mgk25/UniCode.html
    http://www.linuxforum.net/books/UTF-8-UniCode.html (中文版)

    Java 编程技术中汉字问题的分析及解决
    http://www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml

    汉字的编码方式:
    http://www.unihan.com.cn/cjk/ana17.htm

    *注释:l10n i18n都是缩写:用的是英文单词的首尾字母和其间字母个数
    l10n: localization 本地化
    i18n: internationalization 国际化

    原文出处:<a href="http://www.chedong.com/tech/unicode_java.html">http://www.chedong.com/tech/unicode_java.html</a>
  • .互联网TCP/IP的基本结构

          今天的互联网(Internet)的原形是1969年建立的APARNET。在互联网发展史上具有决定意义的一件事是在1983年1月1日,APARNET正式转换成TCP/IP协议的网络。正是TCP/IP的出现,才使得互联网得以在全世界的范围内迅速发展并具有今天的规模。
    根据TCP/IP协议,互联网分为4层,加上最底层的硬件层一共是5层:



    物理层:
          对应于网络的基本硬件,这也是Internet物理构成,即我们可以看得见的硬件设备,如PC机、互连网服务器、网络设备等,必须对这些硬件设备的电气特性作一个规范,使这些设备都能够互相连接并兼容使用。网络接口层:它定义了将数据组成正确帧的规程和在网络中传输帧的规程,帧是指一串数据,它是数据在网络中传输的单位。

    互联网层:
          本层定义了互联网中传输的“信息包”格式,以及从一个用户通过一个或多个路由器到最终目标?quot;信息包"转发机制。

    传输层:
          为两个用户进程之间建立、管理和拆除可靠而又有效的端到端连接。

    应用层:
          它定义了应用程序使用互联网的规程。电子邮件的SMTP 协议就建立在这一层。Internet 的核心层是网络层和传输层,相应的核心协议是IP协议和TCP协议。IP 协议的主要功能包括无连结数据报传送﹑数据报寻径以及差错处理三部分。IP协议的特点是点到点的,IP对等实体间的通信不经过中间机器,对等实体所在的机器位于同一物理网络,对等机器之间有直接的物砹�印�P层的主要功能是屏蔽下面物理层的差别,向上一层提供一致的数据格式。所有要传输的数据,被按照一定的格式分组封装层IP数据报,数据报单元通过寻径等机制进行传输,在接收方数据报进行重组,得到最初要传送的数据。由于IP协议是不可靠的数据传输协议,由于网络的拥塞而发生的数据丢失等情况是不可避免的,因此Internet 还必须有一定的控制重传机制,这就是差错与控制报文协议(ICMP)。尽管计算机通过安装IP软件,从而保证了计算机之间可以发送和接收数据,但IP协议还不能解决数据分组在传输过程中可能出现的问题。因此,若要解决可能出现的问题,还需要TCP协议来提供可靠的并且无差错的通信服务。TCP协议被称作一种端对端协议。这是因为它为两台计算机之间的连接起了重要作用:当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接、发送和接收数据以及终止连接。传输控制协议TCP协议利用重发技术和拥塞控制机制,向应用程序提供可靠的通信连接,使它能够自动适应网上的各种变化。即使在 Internet 暂时出现堵塞的情况下,TCP也能够保证通信的可靠.Internet 是一个庞大的国际性网络,网路上的拥挤和空闲时间总是交替不定的,加上传送的距离也远近不同,所以传输数据所用时间也会变化不定。TCP协议具有自动调整"超时值"的功能,能很好地适应 Internet 上各种各样的变化,确保传输数值的正确。

      IP协议只保证计算机能发送和接收分组数据,而TCP协议则可提供一个可靠的、可流控的、全双工的信息流传输服务。虽然IP和TCP这两个协议的功能不尽相同,也可以分开单独使用,但它们是在同一时期作为一个协议来设计的,并且在功能上也是互补的。只有两者的结合,才能保证 Internet 在复杂的环境下正常运行。凡是要连接到 Internet 的计算机,都必须同时安装和使用这两个协议,因此在实际中常把这两个协议统称作TCP/IP协议。 TCP/IP 协议除了TCP协议和IP协议,还包含物理接口和IP层之间的ARP/RARP协议,应用层的FTP协议﹑SMTP协议和BOOTP协议等,所用的这些协议构成Intenet 的TCP/IP 协议族。

    2.SMTP的基本结构

          SMTP (Simple Mail Transfer Protocol)协议是为了保证电子邮件的可靠和高效传送。TCP/IP 协议的应用层中包含有SMTP协议,但事实上它与传输系统和机制无关,仅要求一个可靠的数据流通道。它可以工作在TCP上,也可以工作在NCP, NITS 等协议上。在TCP上,它使用端口25进行传输。SMTP的一个重要特点是可以在可交互的通信系统中转发邮件。

    2.1 SMTP的模型
          SMTP提供了一种邮件传输的机制,当收件方和发件方都在一个网络上时,可以把邮件直传给对方;当双方不在同一个网络上时,需要通过一个或几个中间服务器转发。SMTP首先由发件方提出申请,要求与接收方SMTP建立双向的通信渠道,收件方可以是最终收件人也可以是中间转发的服务器。收件方服务器确认可以建立连接后,双发就可以开始通信。下面是SMTP的模型示意图。

          发件方SMTP向收件方发处MAIL命令,告知发件方的身份;如果收件方接受,就会回答OK。发件方再发出RCPT命令,告知收件人的身份,收件方SMTP确认是否接收或转发,如果同意就回答OK;接下来就可以进行数据传输了。通信过程中,发件方SMTP与收件方SMTP 采用对话式的交互方式,发件方提出要求,收件方进行确认,确认后才进行下一步的动作。整个过程由发件方控制,有时需要确认几回才可以。

          为了保证回复命令的有效,SMTP要求发件方必须提供接收方的服务器及邮箱。邮件的命令和答复有严格的语法定义,并且回复具有相应的数字代码。所有的命令由ASCII码组成。命令代码是大小写无关的,如MAIL和 mail ﹑mAIL是等效的。

    2.2 SMTP的基本命令
    SMTP定义了14个命令,它们是:

    HELO <SP> <domain> <CRLF>
    MAIL <SP> FROM:<reverse-path> <CRLF>
    RCPT <SP> TO:<forward-path> <CRLF>
    DATA <CRLF>
    RSET <CRLF>
    SEND <SP> FROM:<reverse-path> <CRLF>
    SOML <SP> FROM:<reverse-path> <CRLF>
    SAML <SP> FROM:<reverse-path> <CRLF>
    VRFY <SP> <string> <CRLF>
    EXPN <SP> <string> <CRLF>
    HELP [<SP> <string>] <CRLF>
    NOOP <CRLF>
    QUIT <CRLF>
    TURN <CRLF>
    其中使得SMTP工作的基本的命令有7个,分别为:HELO﹑MAIL﹑RCPT﹑DATA﹑REST﹑NOOP和QUIT.下面分别介绍如下。

    HELO--发件方问候收件方,后面是发件人的服务器地址或标识。收件方回答OK时标识自己的身份。问候和确认过程表明两台机器可以进行通信,同时状态参量被复位,缓冲区被清空。

    MAIL--这个命令用来开始传送邮件,它的后面跟随发件方邮件地址(返回邮件地址)。它也用来当邮件无法送达时,发送失败通知。为保证邮件的成功发送,发件方的地址应是被对方或中间转发方同意接受的。这个命令会清空有关的缓冲区,为新的邮件做准备。

    RCPT --这个命令告诉收件方收件人的邮箱。当有多个收件人时,需要多次使用该命令,每次只能指明一个人。如果接收方服务器不同意转发这个地址的邮件,它必须报550错误代码通知发件方。如果服务器同意转发,它要更改邮件发送路径,把最开始的目的地(该服务器)换成下一个服务器。

    DATA--收件方把该命令之后的数据作为发送的数据。数据被加入数据缓冲区中,以单独一行是"<CRLF>.<CRLF>"的行结束数据。结束行对于接收方同时意味立即开始缓冲区内的数据传送,传送结束后清空缓冲区。如果传送接受,接收方回复OK。

    REST--这个命令用来通知收件方复位,所有已存入缓冲区的收件人数据,发件人数据和待传送的数据都必须清除,接收放必须回答OK.

    NOOP--这个命令不影响任何参数,只是要求接收放回答OK, 不会影响缓冲区的数据。

    QUIT--SMTP要求接收放必须回答OK,然后中断传输;在收到这个命令并回答OK前,收件方不得中断连接,即使传输出现错误。发件方在发出这个命令并收到OK答复前,也不得中断连接。

    下面是SMTP答复中用到的代码和含义:

    500 Syntax error, command unrecognized
    [This may include errors such as command line too long]
    501 Syntax error in parameters or arguments
    502 Command not implemented
    503 Bad sequence of commands
    504 Command parameter not implemented
    211 System status, or system help reply
    214 Help message
    [Information on how to use the receiver or the meaning of a
    particular non-standard command; this reply is useful only to the human user]
    220 <domain> Service ready
    221 <domain> Service closing transmission channel
    421 <domain> Service not available, closing transmission channel
    [This may be a reply to any command if the service knows it must shut down]
    250 Requested mail action okay, completed
    251 User not local; will forward to <forward-path>
    450 Requested mail action not taken: mailbox unavailable
    [E.g., mailbox busy]
    550 Requested action not taken: mailbox unavailable
    [E.g., mailbox not found, no access]
    451 Requested action aborted: error in processing
    551 User not local; please try <forward-path>
    452 Requested action not taken: insufficient system storage
    552 Requested mail action aborted: exceeded storage allocation
    553 Requested action not taken: mailbox name not allowed
    [E.g., mailbox syntax incorrect]
    354 Start mail input; end with <CRLF>.<CRLF>
    554 Transaction failed
    最后,让我们看一个RFC821中给出的例子。这封信是Smith在主机Alpha.ARPA 发给主机Beta.ARPA上的
    Jones,Green和 Brown.并且假定两台主机在同一个网络上。

    S: MAIL FROM:<Smith@Alpha.ARPA>
    R: 250 OK
    S: RCPT TO:<Jones@Beta.ARPA>
    R: 250 OK
    S: RCPT TO:<Green@Beta.ARPA>
    R: 550 No such user here
    S: RCPT TO:<Brown@Beta.ARPA>
    R: 250 OK
    S: DATA
    R: 354 Start mail input; end with <CRLF>.<CRLF>
    S: Blah blah blah...
    S: ...etc. etc. etc.
    S: <CRLF>.<CRLF>
    R: 250 OK
    邮件最后被对方接受。

    3.电子邮件的工作原理


    电子邮件与普通邮件有类似的地方,发信者注明收件人的姓名与地址(即邮件地址),发送方服务器把邮件传到收件方服务器,收件方服务器再把邮件发到收件人的邮箱中。如下图所示:



    更进一步的解释涉及到以下几个概念:

    MUA -- Mail User Agent, 邮件用户代理,帮助用户读写邮件;

    MTA -- Mail Transport Agent, 邮件传输代理,负责把邮件由一个服务器传到另一个服务

    器或邮件投递代理;

    MDA -- Mail Delivery Agent, 邮件投递代理,把邮件放到用户的邮箱里。

    整个邮件传输过程如下:

    目前使用的SMTP 协议是存储转发协议,意味着它允许邮件通过一系列的服务器发送到最终目的地。服务器在一个队列中存储到达的邮件,等待发送到下一个目的地。下一个目的地可以是本地用户,或者是另一个邮件服务器,如下图所示。



    如果下游的服务器暂时不可用,MTA 就暂时在队列中保存信件,并在以后尝试发送。


    4. 电子邮件的信头结构及分析

    4.1 邮件的结构
    在最高层,邮件的结构是非常简单的,用户从终端机上看到的邮件格式一般为:

    1. From: user1@domain1.com

    2. To: user2@domain2.com

    3. Subject: Explaination of mail format

    4. Date: Thu, 1 Apr 1999. 10:00:00 GMT

    5. Hi, Jack

    7. This mail is to explain you the mail format

    8. - - - -

    9. Thanks

    10. Bob

    其中, 1~~4 行称作信件信头(message header) 6~~10行描述信件要表达的内容,称为信体 (message body)。第5行是空行,根据RFC822的要求,信头和信体之间必须加入一空行。[i]信头通常包含字段From, To, Subject 和Date,有的邮件还包含cc,bcc等字段。

    4.2 邮件的信头

    事实上,邮件在传输过程中,服务器要把它打包成一个数据对象,包括上面的信件和一个信封。邮件的投递是依靠信封上的地址或信封信头(envelop address 或envelop header),而不是上面讲的信件上的地址。

    从表面上看,一封邮件是从发件人的机器直接传送到收件人的机器,但通常这并不正确,一封邮件发送和接受过程至少要经过四台计算机。参考下图所示。用户通常在自己的电脑前编写阅读邮件,我们把它叫做客户端 (client 1~~4 )。大部分组织里,都是用一台专门的机器处理邮件,称作邮件服务器 (SMTP1, SMTP2). 如果用户是从家里拨号上网,那末邮件服务器是ISP 提供的。



    当某个用户在自己的电脑 Client1 前编写完一个邮件,然后把它发送到他的ISP 的邮件服务器SMTP1。此时她的机器已经完成了所有的工作,但邮件服务器SMTP1还必须想法把邮件发送到目的地。SMTP1 通过阅读信头或信封上的地址,找到收件认得邮件服务器SMTP2, 然后与该服务器建立连接,把邮件发到收件人的服务器上,等待收件人来取阅。

    下面我们将通过一个例子说明整个邮件传送过程及邮件的信头变化。假设发件人的名字叫 Sender, email地址是 sender@domain1.com 使用的电脑名字叫 client1, IP 地址是 [111.11.1.1] (假设的地址)。 收件人的名字叫 receipt, email 地址是 receipt@domain2.com, 使用的电脑的名字叫 client2,IP 地址是 [222.22.2.2] (假设的地址)。当邮件编辑完传送给其邮件服务器mail.domain1.com 时,邮件的信头格式为:

    From: sender@domain1.com

    To: receipt@domain2.com

    Date: Tue, Mar 18 1998 15:36:24 GMT

    X-mailer:Sendmail 8.9.0

    Subject: Greetings

    当邮件服务器 mail.domain1.com 把邮件传到接收方的服务器 mail.domain2.com 时,接受方服务器会在信头上记录下有关的计算机信息,邮件的信头变成:

    Received: from client1.domain1.com (client1.domain1.com [111.11.1.1]) by mail.domain1.com (8.8.5) id 004A21; Tue, Mar 18 1998 15:3 7:24 GMT

    From: sender@domain1.com

    To: receipt@domain2.com

    Date: Tue, Mar 18 1998 15:36:24 GMT

    Message-Id: <client1254556544-45556454@mail.domain1.com>

    X-mailer:Sendmail 8.9.0

    Subject: Greetings

    当收件人服务器mail.domain2.com 把邮件接收并存初下来,等待收件人来阅读时,邮件的信头将会再加入一条记录:

    Received: from mail.domain1.com (mail.domain1.com [111.11.1.0] ) by mail.domain2.com (8.8.5/8.7.2) with ESMTP id LAA20869; Tue, Mar 18 1998 15:39:44 GMT

    Received: from client1.domain1.com (client1.domain1.com [111.11.1.1]) by mail.domain1.com (8.8.5) id 004A21; Tue, Mar 18 1998 15:37:24 GMT

    From: sender@domain1.com

    To: receipt@domain2.com

    Date: Tue, Mar 18 1998 15:36:24 GMT

    Message-Id: <client1254556544-45556454@mail.domain1.com>

    X-mailer:Sendmail 8.9.0

    Subject: Greetings

    上面整个记录就将是收件人看到的完整的邮件信头。让我们逐行看一下信头中各行的含义:

    Received: from mail.domain1.com (mail.domain1.com [111.11.1.0] ) by mail.domain2.com (8.8.5/8.7.2) with ESMTP id LAA20869; Tue, Mar 18 1998 15:39:44 GMT

    这封信是从一台自称为 mail.domain1.com 的机器上接收的;这台机器的IP 地址是[111.11.1.0],真实名字就是标称名字 mail.domain1.com; 接收方的机器名称是 mail.domain2.com, 运行的邮件服务器是 Sendmail, 版本(8.8.5/8.7.2) 。接收方机器给邮件的编号是ESMTP id LAA20869, 接收到的时间是 Tue, Mar 18 1998 15:39:44 GMT。

    Received: from client1.domain1.com (client1.domain1.com [111.11.1.1]) by mail.domain1.com (8.8.5) id 004A21; Tue, Mar 18 1998 15:37:24 GMT

    这条记录表明信件是由机器client1.domain1.com ( IP 地址是 [111.11.1.1]) 在Tue, Mar 18 1998 15:37:24 GMT交给mail.domain1.com,并赋给编号id 004A21。

    From,TO ,Date和Subject 都易于理解,分别指明发件人,收件人,信件编辑日期及信件主题。

    Message-Id: <client1254556544-45556454@mail.domain1.com>

    这是由发件方邮件服务器赋给这封邮件的编号。与其它编号不同,这个编号自始至终跟随邮件。

     
    第二章 OPEN RELAY 的原理及测试


    1.OPEN RELAY 的原理

    由于技术的原因,在80年代前,网络还不是很健全,机器之间很少能直接对话发送邮件,人们必须得找出一条有效的连接通路来,然后信件沿着通路一步一步传送到目的地。SMTP协议中就明确指出当邮件在不同的网络间传送时,需要借助中间服务器的RELAY。

    邮件在收件方和发件方之间会经过毫不相干的第三方服务器,这就是邮件转发 (RELAY)。 如下图所示:



    图中的 MAIL SERVER 是可以对要求转发的邮件进行限制的,如只转发来自某个域的邮件或来自于某些IP 得邮件。如果转发没有任何限制,就被称为 OPEN RELAY 或 THIRD PARTY RELAY。
    从历史上看,relay 曾经发挥过重要作用。而且当时这些工作主要靠手工来做,就像我们今天通过邮局发一封信一样。假如我想从沈阳发一封信件到深圳,我再信封上写好收信地址深圳,邮局就需要找到定义的运送路线: 沈阳,北京,郑州,长沙,广州,深圳。甚至还要长一些。其中很重要一点是每一个中继站都能很好的理解这封信将被送到哪里,下一个接收站是谁。在电子邮件里,这就相当于每个中继服务器清楚下一个服务起是谁,这就是邮件的转发。

    目前,正常邮件转发已经不再必要,相反,无限制转发常常被发送垃圾邮件的人利用,隐藏真实的邮件来源,让别人以为是从另外的ISP 发出的信件;同时,也把大量的处理工作转移到别人机器上。
    由于前面提到的历史的原因,最初的绝大多数邮件服务器都允许OPEN RELAY的。今天,大部分邮件服务器升级版本已经在缺省设置中关闭了OPEN RELAY, 如Sendmail 从8.9.3版本开始,Exchange Server从5.5版本开始关闭了open relay。有的服务器虽然没有相应的升级版本,也都提供了关闭open relay 的方法,如在NOTES SERVER的配置文件notes.ini 中加入一行:SMTPMTA_REJECT_RELAYS=1。但由于很多服务器管理员的疏忽而没能及时的修补这些安全漏洞,被利用来转发垃圾邮件。

    2 如何确认邮件服务器是否RELAY

    假设要测试的IP是202.112.0.0. 可以使用下列命令进行测试,文中的绿色斜题字为测试邮件服务器的反馈信息:

    #telnet 202.112.0.0 25
    Trying 202.112.0.0...
    Connected to 202.112.0.0.
    Escape character is '^]'.
    220 dns.ccert.edu.cn ESMTP Sendmail 8.11.1/8.11.1; Sat, 30 Jun 2001 21:07:10 +0800
    helo mydomain
    250 dns.ccert.edu.cn Hello point.ccert.edu.cn [202.112.50.3], pleased to meet you
    mail from:nobody@yahoo.com
    250 2.1.0 nobody@#yahoo.com... Sender ok
    rcpt to:nobody@hotmail.com
    550 5.7.1 nobody@hotmail.com... Relaying denied

    最后的Relaying denied 表明该服务器已经安全设置,不会再relay无关邮件了。如果显示的结果是下面的样子,这表明服务器可以转发任何人的邮件。

    rcpt to:nobody@hotmail.com
    250 nobody@hotmail.com... Recipient ok
    data
    354 Enter mail, end with "." on a line by itself
    this is a test of the relay
    .
    250 VAA00289 Message accepted for delivery

    除了用上面的命令行的方法测试外,下面的链接提供了一个测试工具,只需输入IP即可。 http://www.abuse.net/relay.html
  • 2007-12-12

    Grep学习笔记 - [developer]

    来源:http://man.chinaunix.net/newsoft/grep/open.htm

    1. grep简介
    grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。Unix的grep家族包括grep、egrep和fgrep。egrep和fgrep的命令只跟grep有很小不同。egrep是grep的扩展,支持更多的re元字符, fgrep就是fixed grep或fast grep,它们把所有的字母都看作单词,也就是说,正则表达式中的元字符表示回其自身的字面意义,不再特殊。linux使用GNU版本的grep。它功能更强,可以通过-G、-E、-F命令行选项来使用egrep和fgrep的功能。

    grep的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到屏幕,不影响原文件内容。

    grep可用于shell脚本,因为grep通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。我们利用这些返回值就可进行一些自动化的文本处理工作。

    2. grep正则表达式元字符集(基本集)
    ^
    锚定行的开始 如:'^grep'匹配所有以grep开头的行。

    $
    锚定行的结束 如:'grep$'匹配所有以grep结尾的行。

    .
    匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。

    *
    匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。 .*一起用代表任意字符。

    []
    匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。

    [^]
    匹配一个不在指定范围内的字符,如:'[^A-FH-Z]rep'匹配不包含A-R和T-Z的一个字母开头,紧跟rep的行。

    \(..\)
    标记匹配字符,如'\(love\)',love被标记为1。

    \<
    锚定单词的开始,如:'\<grep'匹配包含以grep开头的单词的行。

    \>
    锚定单词的结束,如'grep\>'匹配包含以grep结尾的单词的行。

    x\{m\}
    重复字符x,m次,如:'0\{5\}'匹配包含5个o的行。

    x\{m,\}
    重复字符x,至少m次,如:'o\{5,\}'匹配至少有5个o的行。

    x\{m,n\}
    重复字符x,至少m次,不多于n次,如:'o\{5,10\}'匹配5--10个o的行。

    \w
    匹配文字和数字字符,也就是[A-Za-z0-9],如:'G\w*p'匹配以G后跟零个或多个文字或数字字符,然后是p。

    \W
    \w的反置形式,匹配一个或多个非单词字符,如点号句号等。

    \b
    单词锁定符,如: '\bgrepb\'只匹配grep。

    3. 用于egrep和 grep -E的元字符扩展集
    +
    匹配一个或多个先前的字符。如:'[a-z]+able',匹配一个或多个小写字母后跟able的串,如loveable,enable,disable等。

    ?
    匹配零个或多个先前的字符。如:'gr?p'匹配gr后跟一个或没有字符,然后是p的行。

    a|b|c
    匹配a或b或c。如:grep|sed匹配grep或sed

    ()
    分组符号,如:love(able|rs)ov+匹配loveable或lovers,匹配一个或多个ov。

    x{m},x{m,},x{m,n}
    作用同x\{m\},x\{m,\},x\{m,n\}

    4. POSIX字符类
    为了在不同国家的字符编码中保持一至,POSIX(The Portable Operating System Interface)增加了特殊的字符类,如[:alnum:]是A-Za-z0-9的另一个写法。要把它们放到[]号内才能成为正则表达式,如[A- Za-z0-9]或[[:alnum:]]。在linux下的grep除fgrep外,都支持POSIX的字符类。

    [:alnum:]
    文字数字字符

    [:alpha:]
    文字字符

    [:digit:]
    数字字符

    [:graph:]
    非空字符(非空格、控制字符)

    [:lower:]
    小写字符

    [:cntrl:]
    控制字符

    [:print:]
    非空字符(包括空格)

    [:punct:]
    标点符号

    [:space:]
    所有空白字符(新行,空格,制表符)

    [:upper:]
    大写字符

    [:xdigit:]
    十六进制数字(0-9,a-f,A-F)

    5. Grep命令选项
    -?
    同时显示匹配行上下的?行,如:grep -2 pattern filename同时显示匹配行的上下2行。

    -b,--byte-offset
    打印匹配行前面打印该行所在的块号码。

    -c,--count
    只打印匹配的行数,不显示匹配的内容。

    -f File,--file=File
    从文件中提取模板。空文件中包含0个模板,所以什么都不匹配。

    -h,--no-filename
    当搜索多个文件时,不显示匹配文件名前缀。

    -i,--ignore-case
    忽略大小写差别。

    -q,--quiet
    取消显示,只返回退出状态。0则表示找到了匹配的行。

    -l,--files-with-matches
    打印匹配模板的文件清单。

    -L,--files-without-match
    打印不匹配模板的文件清单。

    -n,--line-number
    在匹配的行前面打印行号。

    -s,--silent
    不显示关于不存在或者无法读取文件的错误信息。

    -v,--revert-match
    反检索,只显示不匹配的行。

    -w,--word-regexp
    如果被\<和\>引用,就把表达式做为一个单词搜索。

    -V,--version
    显示软件版本信息。

    6. 实例
    要用好grep这个工具,其实就是要写好正则表达式,所以这里不对grep的所有功能进行实例讲解,只列几个例子,讲解一个正则表达式的写法。

    $ ls -l | grep '^a'
    通过管道过滤ls -l输出的内容,只显示以a开头的行。

    $ grep 'test' d*
    显示所有以d开头的文件中包含test的行。

    $ grep 'test' aa bb cc
    显示在aa,bb,cc文件中匹配test的行。

    $ grep '[a-z]\{5\}' aa
    显示所有包含每个字符串至少有5个连续小写字符的字符串的行。

    $ grep 'w\(es\)t.*\1' aa
    如果west被匹配,则es就被存储到内存中,并标记为1,然后搜索任意个字符(.*),这些字符后面紧跟着另外一个es(\1),找到就显示该行。如果用egrep或grep -E,就不用"\"号进行转义,直接写成'w(es)t.*\1'就可以了。