[转载]前端开发必须知道的JS(一) 原型和继承

mikel阅读(933)

[转载]前端开发必须知道的JS(一) 原型和继承 – JayChow – 博客园.

原型和闭包是Js语言的难点,此文主要讲原型及原型实现的继承,在(二)中会讲下闭包,希望对大家有所帮助。若有疑问或不正之处,欢迎提出指正 和讨论。

一. 原型与构造函数

Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,我们讲的 更多是构造函数的原型,但是也不能否定普通函数也有原型。譬如普通函数:

function F(){   ; } alert(F.prototype instanceof Object) //true

构造函数,也即构造对象。首先了解下通过构造函数实例化对象的过程。

function A(x){   this.x=x; } var obj=new A(1);

实例化obj对象有三步:

1. 创建obj对象:obj=new Object();

2. 将obj的内部__proto__指向构造他的函数A的prototype,同 时,obj.constructor===A.prototype.constructor(这个是永远成立的,即使A.prototype不再指向原来 的A原型,也就是说:类的实例对象的constructor属性永远指向”构造函数”的prototype.constructor), 从而使得obj.constructor.prototype指向 A.prototype(obj.constructor.prototype===A.prototype,当A.prototype改变时则不成立, 下文有遇到)。obj.constructor.prototype与的内部_proto_是两码事,实例化对象时用的是_proto_,obj是没有 prototype属性的,但是有内部的__proto__,通过__proto__来取得原型链上的原型属性和原型方法,FireFox公开了 __proto__,可以在FireFox中alert(obj.__proto__);

3. 将obj作为this去调用构造函数A,从而设置成员(即对象属性和对象方法)并初始化。

当这3步完成,这个obj对象就与构造函数A再无联系,这个时候即使构造函数A再加任何成员,都不再影响已经实例化的obj对象了。此 时,obj对象具有了x属性,同时具有了构造函数A的原型对象的所有成员,当然,此时该原型对象是没有成员的。

原型对象初始是空的,也就是没有一个成员(即原型属性和原型方法)。可以通过如下方法验证原型对象具有多少 成员。

var num=0; for(o in A.prototype) {   alert(o);//alert出原型属性名字   num++; } alert("member: " + num);//alert出原型所有成员个数。

但是,一旦定义了原型属性或原型方法,则所有通过该构造函数实例化出来的所有对象,都继承了这些原型属性和原型方法,这是通过内部的 _proto_链来实现的。

譬如

A.prototype.say=function(){alert(“Hi”)};

那所有的A的对象都具有了say方法,这个原型对象的say方法是唯一的副本给大家共享的,而不是每一个对象都有关于say方法的一个副本。

二. 原型与继承

首先,看个简单的继承实现。

1 function A(x){ 2   this.x=x; 3 }
4  function B(x,y){ 5   this.tmpObj=A; 6   this.tmpObj(x); 7   delete this.tmpObj; 8   this.y=y; 9 }

第5、6、7行:创建临时属性tmpObj引用构造函数A,然后在B内部执行,执行完后删除。当在B内部执行了this.x=x后(这里的 this是B的对象),B当然就拥有了x属性,当然B的x属性和A的x属性两者是独立,所以并不能算严格的继承。第5、6、7行有更简单的实现,就是通过 call(apply)方法:A.call(this,x);

这两种方法都有将this传递到A的执行里,this指向的是B的对象,这就是为什么不直接A(x)的原因。这种继承方式即是类继承(js没有类, 这里只是指构造函数),虽然继承了A构造对象的所有属性方法,但是不能继承A的原型对象的成员。而要实现这个目的,就是在此基础上再添加原型继承。

通过下面的例子,就能很深入地了解原型,以及原型参与实现的完美继承。(本文核心在此^_^)

1 function A(x){ 2   this.x = x; 3 } 4 A.prototype.a = "a"; 5 function B(x,y){ 6   this.y = y; 7   A.call(this,x); 8 } 9 B.prototype.b1 = function(){ 10   alert("b1"); 11 } 12 B.prototype = new A(); 13 B.prototype.b2 = function(){ 14   alert("b2"); 15 } 16 B.prototype.constructor = B; 17 var obj = new B(1,3);

这个例子讲的就是B继承A。第7行类继承:A.call(this.x);上面已讲过。实现原型继承的是第12行:B.prototype = new A();

就是说把B的原型指向了 A的1个实例对象,这个实例对象具有x属性,为undefined,还具有a属性,值为”a”。所以B原型也具有了这2个属性(或者说,B和A建立了原型 链,B是A的下级)。而因为方才的类继承,B的实例对象也具有了x属性,也就是说obj对象有2个同名的x属性,此时原型属性x要让位于实例对象属性x, 所以obj.x是1,而非undefined。第13行又定义了原型方法b2,所以B原型也具有了b2。虽然第9~11行设置了原型方法b1,但是你会发 现第12行执行后,B原型不再具有b1方法,也就是obj.b1是undefined。因为第12行使得B原型指向改变,原来具有b1的原型对象被抛弃, 自然就没有b1了。


第12行执行完后,B原 型(B.prototype)指向了A的实例对象,而A的实例对象的构造器是构造函数A,所以B.prototype.constructor就是构造对 象A了(换句话说,A构造了B的原型)。

alert(B.prototype.constructor) 出来后就是”function A(x){…}” 。同样地,obj.constructor也是A构造对象,alert(obj.constructor)出来后就是”function A(x){…}” ,也就是说B.prototype.constructor===obj.constructor(true),但是 B.prototype===obj.constructor.prototype(false),因为前者是B的原型,具有成员:x,a,b2,后者是 A的原型,具有成员:a。如何修正这个问题呢,就在第16行,将B原型的构造器重新指向了B构造函数,那么 B.prototype===obj.constructor.prototype(true),都具有成员:x,a,b2。


如果没有第16行,那是 不是obj = new B(1,3)会去调用A构造函数实例化呢?答案是否定的,你会发现obj.y=3,所以仍然是调用的B构造函数实例化的。虽然 obj.constructor===A(true),但是对于new B()的行为来说,执行了上面所说的通过构造函数创建实例对象的3个步骤,第一步,创建空对象;第二步,obj.__proto__ === B.prototype,B.prototype是具有x,a,b2成员的,obj.constructor指向了 B.prototype.constructor,即构造函数A;第三步,调用的构造函数B去设置和初始化成员,具有了属性x,y。虽然不加16行不影响 obj的属性,但如上一段说,却影响obj.constructor和obj.constructor.prototype。所以在使用了原型继承后,要 进行修正的操作。

关于第12、16行,总 言之,第12行使得B原型继承了A的原型对象的所有成员,但是也使得B的实例对象的构造器的原型指向了A原型,所以要通过第16行修正这个缺陷。


毕了。

作者:JayChow
出处:http://ljchow.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,请在文章页面明显位置给出原文链接。

[转载]打造第二代测试框架TestDriven 2.0(七)

mikel阅读(1106)

[转载]打造第二代测试框架TestDriven 2.0(七)—— 让测试驱动更加的自动吧! – 美丽人生 – 博客园.

——————–

前 言 Preface

——————–

本 文介绍了一种新颖的测试思路,并制作了原型系统展示其效果。

此技术将作为测试驱动框架2.0的一个部分(Testdriven 2.0) 。

而 测试驱动2.0的目的是:让代码之间沟通,让变化更加容易。

——————–

测 试分类 与 本文的讨论对象 Catalog 

——————–

测试包含了很多种, 每一种需要特定的技术去解决,例如:

1. Winform类的界面测试:通常使用钩子等Win32接口去捕捉用户的操作,然后模拟回放进行测试(此技术不在讨论范围)

2. Web界面测试:一般使用JS嵌入测试页面,同样模拟用户操作;或者使用IE内核等调用内部函数实现用户操作(此技术不讨论)

3. Web的Http模拟测试:在.net 2.0比较难实现,到了3.0之后的版本,微软对HttpContext这个庞然大物体做了重构,因此让测试变得稍微简单了。(此技术不讨论)

4. 类库、代码测试:这个是我需要讨论的终点,包含了各种框架、逻辑应用等。现有的技术主要是UNit / Testdriven.net / Mock等。他们也貌似很好的解决了一些问题。但是。。。

所以,本文接下来将针对第4种测试 (类库、代码测试),也是最常见的测试进行讨论。

———————

现有的问题 Problem

———————

现在的测试,很大一 部份是在回归。开发者梦想编写好自动测试代码,日后如果有变动,通过做回归,就知道是否破坏了之前的功能、是否产生了bug。朝着这个目标,诞生了很多测 试工具。可是在我看来,他们只是从一个小坑跳到了另外一个大坑。(也就是我之前说的,掉入自己挖的坑里了)

首先,测试的结 果是否准确,完全取决于测试数据是否全面准确。 那么编写测试数据本身就存在了人为的bug。

其次,测试代码和业务代码紧 密联系,一旦业务代码修改,测试代码往往是全盘否定的。这就出现一个矛盾:如果测试代码写的马虎,日后基本上不能用;如果测试代码写的精细,一旦修改起 来,之前的工作都白费了。

所以,我们真正期望的是:能够自动分析业务代码,自动编写测试代码, 而不是人去写。这个目标现在还很难实现(微软在VS2010里面已经大量引入了Code Gen等技术,可是。。)

可 是难,不等于不行,下面我将尝试迈出一小步。

———————

原理分析 Analysis

———————

要 让测试变得自动,首先需要抽象出测试过程,然后逐一攻破。经过我分析,一段测试代码主要包含了三个方面:

1. 测试数据生成。

直 接决定了测试代码是否有效;因此要求全面、准确、也业务逻辑精密绑定。 这部分工作目前是没有更好的思路。

2. 调用对应方法。

这个很简单,就是调用一个方法,传入测试数据。没有优化的必要。

3. 查看测试结果是否符合预期。

这部分以往是写这非常无聊的Assert.IsEqual等。既无聊,又浪费时间。而恰恰这部分我发现了提升的 空间。

测试结果无非就是字符串、对象等。就是C#的ValueType / class。 而如果是对象,也一定是对象的某些属性(Property) 。而这些数据都是可以被序列化、反序列化的!

因 此 这个过程完全可以被机器代替,从而让测试代码变得更加灵活,立马减少50%的工作量!现在我就展示一下目前的原型系统效果。

———————

原 型系统效果  ProtoType

———————

首先是一段测试代 码。

public void test00001()
{
object pojo = CreatePojo();

Assert.SaveOrVerify(test000 create pojo, pojo);
}

这 段代码的目的是 测试生成的pojo对象是否符合预期。 而一个Pojo对象可能是下面一个接口的实例:

代码

interface IinterfaceWithAllCollection
{
byte[] Image { get;set;}
string Name { get;set;}
double Fee { get;set;}
int Age { get;set;}
IinterfaceWithAllCollection[] pojos {
get;set;}
List
<IinterfaceWithAllCollection> pojos2 { get;set;}
ObjectWithValue objPojo {
get;set;}
ObjectWithValue[] objPojos {
get;set;}
List
<ObjectWithValue> objPojos2 { get;set;}
}

按照以往的做法,一定是要展开这个对象,获 取每一个属性,然后做Assert。现在我仅仅需要一句话就完成了测试 结果验证
Assert.SaveOrVerify(test000 create pojo, pojo);

不 知道您感受到了其中的魅力和惊喜没有?

调用了这句话, 框架首先会搜索保存在磁盘的结果数据,一般是xml文件;这些数据本质上就是预期结果的序列化值。然后逐一和这些值做对比。

那 么您一定好奇,这些期望的值从哪里来?答案就是,代码本身生成。当第一次执行的时候,是没有预期值的,那么框架会给出警告,然后将当前数据持久到磁盘。例 如:

代码

—— Test started: Assembly: Pixysoft.Framework.Configurations.dll ——

WARNING: expected value do not existed. create new one for test000 create pojo
Verifying IinterfaceWithAllCollection.Image.
TRUE: expected
=%01, actual=%01

Verifying IinterfaceWithAllCollection.Name.
TRUE: expected=hello, actual=hello

Verifying IinterfaceWithAllCollection.Fee.

。。。。。 省 略

这个时候,我调用一个配置界面,就可以查看第一次生成的 值,然后修改成为预期值:

这 样一个序列化结构就可以通过 树形结构 展示出来。 现在我们只要查看第一次运行的结果是否正确,调整ExpectedValue。 这样这段测试代码就可以永远被重复调用了。

———————-

小 结 Summary

———————-

因为是原型系统,目前还不能发出第 一个release,不过很快就可以完成了。主要是我写的代码和屎一样烂,需要进行优化才好意思拿出来。

等release 出来时候,所有代码都会采取开源的策略。不过是一种新的开源策略。

如果各位有急着需要的,咱们可以相互讨论交流下。希望大 家多提供些思路,多提供您期望的需求。谢谢!


我们的最新动态 (Bamboo@pixysoft.net)

  • 1.DynamicProxy开发完成.和微软的Realproxy比较.性能提高1.5倍… [2010-6-5]
  • 2.对象反序列化的emit框架完成.性能测试提高了15倍.[2010-6-4]
  • 3. 对象序列化操作使用最新的emit框架.动态编译.性能提高了10倍…[2010-6-3]
  • 我们每天都在努力着!

作者:美丽人生
技术支持:reborn_zhang@hotmail.com

[转载]封装jQuery表格插件jqGrid,控件化jqGrid(二):显示

mikel阅读(1151)

[转载]封装jQuery表格插件jqGrid,控件化jqGrid(二):显示 – 黑曜石 – 博客园.

本文编码数据提供类

1,创建AjaxData类,继承接口IHttpHandler,实现其方法public void ProcessRequest(HttpContext context)和参数public bool IsReusable

2,使用SQLHelper类与数据库进行交互(在MVC中可以使用entity framework提供数据,当然在MVC中,最好就不要使用服务器控件了,可以直接输出字符串)

3,在AjaxData类中,创建两个方法

public string jsonString(HttpContext context)用于处理接收到的基本数据,最后经过以下方法返回数据的json字符串

其代码如下:

 

其中使用到了SQLHelper.cs提供的SQL2005存储过程。

public string DataTableToJson(DataTable dt, int page, int total, int records, XmlDocument xmlDoc)用于DataTable转化为jqGrid格式的字符串,其代码如下:



[转载]封装jQuery表格插件jqGrid,控件化jqGrid(一):显示

mikel阅读(1129)

[转载]封装jQuery表格插件jqGrid,控件化jqGrid(一):显示 – 黑曜石 – 博客园.

先上完结后效果图(点击放大):

image

功能:排序,列宽拖拉,自动行号,添加,编辑,删除,查询等

只要在页面中引入:JQuery.js及

<AllenJqGrid:JqGrid ID=”MyJqGrid” runat=”server” TableName=”diamond” Search=”true” Add=”true” Edit=”true” Del=”true” />

这么一段,上图就将呈现在浏览器中。

注:本控件数据提供仅使用json。

写在前面的,为何要控件化

这里http://www.trirand.com/jqgrid/jqgrid.html是 官方的jqGrid Demo页。

其基本的页面JS如下:

jQuery("#list2").jqGrid({
   	url:'server.php?q=2',
	datatype: "json",
   	colNames:['Inv No','Date', 'Client', 'Amount','Tax','Total','Notes'],
   	colModel:[
   		{name:'id',index:'id', width:55},
   		{name:'invdate',index:'invdate', width:90},
   		{name:'name',index:'name asc, invdate', width:100},
   		{name:'amount',index:'amount', width:80, align:"right"},
   		{name:'tax',index:'tax', width:80, align:"right"},		
   		{name:'total',index:'total', width:80,align:"right"},		
   		{name:'note',index:'note', width:150, sortable:false}		
   	],
   	rowNum:10,
   	rowList:[10,20,30],
   	pager: '#pager2',
   	sortname: 'id',
    viewrecords: true,
    sortorder: "desc",
    caption:"JSON Example"
});
jQuery("#list2").jqGrid('navGrid','#pager2',{edit:false,add:false,del:false});

这只是基本的配置就有这么多,用过的人应该会知道jqGrid有很丰富的API,但常用的其实也就那么几个,值也差不多。

我在一个项目中的表格大部分都是使用jqGrid实现,大部分时候都是Ctrl+C,然后Ctrl+V,最后改改。

改改的时候经常就会发生默名的错误,基本上找了半天,发现就是少了个逗号或JSON出问题之类。

所以为了不复制粘贴,不重复的设置各项功能及其服务器对应的功能代码,控件化有必要。

控件制作正式开始

一,文件准备:

开个项目,ASP.NET服务器控件,这里命名为AspJqGrid,VS2010会自动生成一个cs控件文件,留下

protected override void RenderContents(HtmlTextWriter output),用于输出控件

接下来,将jqGrid需要的文件接入项目

新建文件夹Css,将ui.jqgrid.css复制进去

新建文件夹Script,将grid.locale-cn.js和JQuery.jqGrid.min.js复制进去,前一个文件用于本地化,也是 不可缺的

为使用系统资源,调整复制进来的三个文件的属性,如图:

image

另外在Properties中的AssemblyInfo.cs中声明如下:

[assembly: WebResource(“AspJqGrid.Css.ui.jqgrid.css”, “text/css”)]
[assembly: WebResource(“AspJqGrid.Scripts.grid.locale-cn.js”, “application/x-JavaScript”, PerformSubstitution = true)]
[assembly: WebResource(“AspJqGrid.Scripts.jQuery.jqGrid.min.js”, “application/x-JavaScript”, PerformSubstitution = true)]

这样,文件类的资源准备完毕。

二,编码控件主体:

1)根据jqGrid表体的各项参数,设置参数

如:参数_rowNum声明,其它的_sortname,_sortorder,_rowList等都一样

//每页显示行数默认50
        private int _rowNum = 50;
        [Description("每页显示行数")]
        public int RowNum
        {
            get { return _rowNum; }
            set { _rowNum = value; }
        }

2)在控件输出之前,需要向页面输出一些必要的文件资源

protected override void OnPreRender(EventArgs e)
        {
            this.Page.PreRenderComplete += new EventHandler(Page_PreRenderComplete);
            base.OnPreRender(e);
        }

        //向页面注册css和js
        void Page_PreRenderComplete(object sender, EventArgs e)
        {
            HtmlLink jqGridStyle = new HtmlLink();
            jqGridStyle.Attributes["type"] = "text/css";
            jqGridStyle.Attributes["rel"] = "stylesheet";
            jqGridStyle.Attributes["href"] = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "AspJqGrid.Css.ui.jqgrid.css");
            this.Page.Header.Controls.Add(jqGridStyle);

            this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), "AspJqGrid.Scripts.grid.locale-cn.js");
            this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), "AspJqGrid.Scripts.jquery.jqGrid.min.js");
        }

3)构造控件输出到页面的JS,使用StringBuilder,将第一步的各参数融入,以下是部分

StringBuilder sb = new StringBuilder();
            sb.Append("\n<script type=\"text/javascript\">\n");
            sb.Append("$(function(){\n");
            //-----------------------------------------------jqGrid js构1造ì开a始?
            sb.Append("$(\"#" + this.ID + "Table\").jqGrid({\n");
            sb.Append("url:\"data.ashx?tablename=" + TableName + "&action=view\",\n");
            sb.Append("datatype: 'json',\n");   //此?处鋦原-为adataType,?则ò无T法ぁ?处鋦理え?数簓据Y状痢?态?,?大洙?小?写′敏?感D
            sb.Append("rowNum: " + RowNum + ",\n");
            sb.Append("pager: '#" + this.ID + "Pager',\n");
            sb.Append("sortname: '" + Sortname + "',\n");
            sb.Append("sortorder: '" + Sortorder + "',\n");

其中页面构造最复杂的是colModel,这里使用XML文件配置colModel,为此需要新建类,用于自动生成基本的XML配置文件。

新建一个表体列属性的model类,命名为jqGridColumns

包含属性:

private string columnName;

private int columnSize;

private string dataType;

private bool isIdentity = false;

private List<jqGridColumns> jqGridColumnsList = new List<jqGridColumns>();

新建一个配置文件生成类,命名为jqGridColumnsConfig

包含两个方法:

public List<jqGridColumns> GetColumnsList(string fields, string tableName)

其核心为使用SQLDataReader的GetSchemaTable方法,获得表体的列属性

public void BuildColModelXML(string xmlDocPath,string fields,string tableName)

使用第一个方法后得到表体的列属性,生成colModel的XML配置文件,包含基本的colModel API,完成colModel配置文件的自动生成。

接下来继续主体控件输入JS编程,决断是否存在配置文件,否则使用上述提供的方法,自动生成colModel的配置文件

然后读取XML配置文件,以colModel格式输出JS。

4)最后输出控件,包含页面JS和表体框架

protected override void RenderContents(HtmlTextWriter output)
        {
            string jqGridTag = "\n<table id=\"" + this.ID + "Table\"></table>\n<div id=\"" + this.ID + "Pager\"></div>";
            output.Write(BuildJqGridHtml() + jqGridTag);
        }

至些,完成控件主体编码工作,下一篇完成为jqGrid提供数据的类

[转载]C#断点续传原理与实现

mikel阅读(940)

在了解HTTP断点续传的原理之前,让我们先来了解一下HTTP协议,HTTP协议是一种基于tcp的简单协议,分为请求和回复两种。请求协议是由客户机 (浏览器)向服务器(WEB SERVER)提交请求时发送报文的协议。回复协议是由服务器(web server),向客户机(浏览器)回复报文时的协 议。请求和回复协议都由头和体组成。头和体之间以一行空行为分隔。

以下是一个请求报文与相应的回复报文的例子:

GET /image/index_r4_c1.jpg HTTP/1.1
Accept: */*
Referer: http://192.168.3.120:8080
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)
Host: 192.168.3.120:8080
Connection: Keep-Alive

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Tue, 24 Jun 2003 05:39:40 GMT
Content-Type: image/jpeg
Accept-Ranges: bytes
Last-Modified: Thu, 23 May 2002 03:05:40 GMT
ETag: “bec48eb862c21:934″
Content-Length: 2827
下面我们就来说说” 断点续传”,顾名思义,断点续传就是在上一次下载时断开的位置开始继续下载。
在HTTP协议中,可以在请求报文头中加入Range段,来表 示客户机希望从何处继续下载。

比如说从第1024字节开始下载,请求报文如下:
GET /image/index_r4_c1.jpg HTTP/1.1
Accept: */*
Referer: http://192.168.3.120:8080
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)
Host: 192.168.3.120:8080
Range:bytes=1024-
Connection: Keep-Alive

.NET 中的相关类

明白了上面的原理,那么,我们来看看.NET FRAMEWORK中为我们提供了哪些类可以来做这些事。

完 成HTTP请求 System.Net.HttpWebRequest

HttpWebRequest 类 对 WebRequest 中定义的属性和方法提供支持,也对使用户能够直接与使用 HTTP 的服务器交互的附加属性和方法提供支持。

HttpWebRequest 将 发送到 Internet 资源的公共 HTTP 标头值公开为属性,由方法或系统设置。下表包含完整列表。可以将 Headers 属性中的其他标头设 置为名称/值对。但是注意,某些公共标头被视为受限制的,它们或者直接由 API公开,或者受到系统保护,不能被更改。Range也属于被保护之列,不 过,.NET为开发者提供了更方便的操作,就是 AddRange方法,向请求添加从请求数据的开始处或结束处的特定范围的字节范围标头

完 成文件访问 System.IO.FileStream

FileStream 对象支持使用Seek方法对文件进行随机访 问, Seek 允许将读取/写入位置移动到文件中的任意位置。这是通过字节偏移参考点参数完成的。字节偏移量是相对于查找参考点而言的,该参考点可以是 基础文件的开始、当前位置或结尾,分别由SeekOrigin类的三个属性表示。

代码实现

了 解了.NET提供的相关的类,那么,我们就可以方便的实现了。

代码如下:

static void Main(string[] args)
{

    string StrFileName = "c:\\aa.zip"; //根据实际情况设置 
    string StrUrl = "http://www.xxxx.cn/xxxxx.zip"; //根据实际情况设置 

    //打开上次下载的文件或新建文件 
    long lStartPos = 0;
    System.IO.FileStream fs;
    if (System.IO.File.Exists(StrFileName))
    {
        fs = System.IO.File.OpenWrite(StrFileName);
        lStartPos = fs.Length;
        fs.Seek(lStartPos, System.IO.SeekOrigin.Current); //移动文件流中的当前指针 
    }
    else
    {
        fs = new System.IO.FileStream(StrFileName, System.IO.FileMode.Create);
        lStartPos = 0;
    }

    //打开网络连接 
    try
    {
        System.Net.HttpWebRequest request = (System.Net.HttpWebRequest) System.Net.HttpWebRequest.Create(StrUrl);
        if (lStartPos > 0)
            request.AddRange((int) lStartPos); //设置Range值 

        //向服务器请求,获得服务器回应数据流 
        System.IO.Stream ns = request.GetResponse().GetResponseStream();

        byte[] nbytes = new byte[512];
        int nReadSize = 0;
        nReadSize = ns.Read(nbytes, 0, 512);
        while (nReadSize > 0)
        {
            fs.Write(nbytes, 0, nReadSize);
            nReadSize = ns.Read(nbytes, 0, 512);
        }
        fs.Close();
        ns.Close();
        Console.WriteLine("下载完成");
    }
    catch (Exception ex)
    {
        fs.Close();
        Console.WriteLine("下载过程中出现错误:" + ex.ToString());
    }
}

[转载]C#动态生成EXCLE并进行添加内容

mikel阅读(967)

[转载]C#动态生成EXCLE并进行添加内容 – 章术 – 博客园.

Microsoft Excel是Microsoft Office的一个组件,是功能强大的电子表格处理软件,它与文本处理软件的差别在于它能够运算复杂的公式,并且有条理地显示结果。Microsoft Excel是除了Microsoft Word之外最常用的办公软件之一,本节将介绍如何使用C#创建Excel文档。
与在C#中添加Word文档的方法类似,添加Excel文档时需要为项目添加对 Microsoft Excel X Object Library的引用,其中的X对应为版本号。Excel 2007对应12.0。在Microsoft Excel X Object Library中,一个Excel文档由MSExcel.Workbook表示。

using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MSEXCLE = Microsoft.Office.Interop.Excel;//定义MSEXCLE
using System.Reflection;
namespace 动态创建EXCLE并添加文字图表
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MSEXCLE.Application excleapp;//定义excleapp应用程序
private MSEXCLE.Workbook excleWok;//定义exclework文档
private void button1_Click(object sender, EventArgs e)
{
object filename = “f:\\aa.xls”;
//若f:\\aa.xls中文件存在那么就删除这个文件
if (File.Exists((string)filename))
{
File.Delete((string)filename);
}
object Nothing=System.Reflection.Missing.Value;//定义Nothing
excleapp = new MSEXCLE.ApplicationClass();//对excleapp进行初始化
excleWok = excleapp.Workbooks.Add(Nothing);//对exclework进行初始化
MSEXCLE.Worksheet ws = (MSEXCLE.Worksheet)excleWok.Worksheets[1];//定义ws为工作文档中的第一个sheet
MSEXCLE.Range range1 = ws.get_Range(“A1”, “A1”);//选定(A1,A1)这个单元格
range1.Value2 = “3”;//对这个单元格进行填充内容
range1 = ws.get_Range(“A2”, “A2”);//同上
range1.Value2 = “5.7”;//同上
range1 = ws.get_Range(“A3”, “A3”);//同上
range1.Value2 = “4.8”;//同上
range1 = ws.get_Range(“A4”, “A4”);//同上
range1.Value2 = “9.2”;//同上
range1 = ws.get_Range(“A5”, “A5”);//同上
range1.Value2 = “6.4”;//同上
excleWok.Charts.Add(Nothing, Nothing, Nothing, Nothing);//添加一个图表
excleWok.ActiveChart.ChartType = MSEXCLE.XlChartType.xl3DColumnClustered  ;//设置图表的类型是三维柱状图
excleWok.ActiveChart.SetSourceData(ws.get_Range(“A1”, “A5”), MSEXCLE.XlRowCol.xlColumns  );//设置这个三维柱状图的数据源
excleWok.ActiveChart.Location(MSEXCLE.XlChartLocation.xlLocationAsObject, “sheet1”);//设置你要将这个柱状图添加到什么地方
excleWok.ActiveChart.HasTitle = true;//设置柱状图是否有标题
excleWok.ActiveChart.ChartTitle.Text = “创建图表”;//设置标题的内容为“创建图表”
excleWok.ActiveChart.HasDataTable = false;//设置柱状图是否有数据表
excleWok.SaveAs(filename, Nothing , Nothing, Nothing, Nothing, Nothing, MSEXCLE.XlSaveAsAccessMode.xlExclusive, Nothing, Nothing, Nothing, Nothing, Nothing);//保存动态生成的excle表
//释放资源
excleapp.Application.Quit();
if (excleapp != null)
excleapp = null;
}
}
}

[转载]IIS5和Tomcat6整合,实现IIS5支持ASP(.NET)+PHP+JSP

mikel阅读(1202)

[转载]IIS5和Tomcat6整合,实现IIS5支持ASP(.NET)+PHP+JSP – 水稻的梦想 – 博客园.

环 境描述:

Windows XP SP3

IIS5.Net1.12.03.03.54.0PHP5

任 务目标:

使本机IIS可以支持JSP,也就是可以同样使用80端口而不是8080端口就可以访问JSP

通 关物品:

Tomcat6

isapi_redirector2.dll

workers2.propertiesjk2.properties文件

说 明:

通关后,IIS5的主目录设为C:\Inetpub\wwwroot\ROOTTomcat6appBase则指向C:\Inetpub\wwwroot

开 始通关:

1、安装Tomcat6,安装成功后浏览器访 问http://localhost:8080,显示Tomcat欢迎页,安装成功。(本文默认本机Tomcat6安装到C:\Program Files\Apache Software Foundation\Tomcat 6.0目录下)

2、下载isapi_redirector2.dll文件并保存到C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf\connector。(事实上保存到哪里都可以,配置过程中只要修改相应的指向路径即可)

3、C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf下建立workers2.propertiesjk2.properties两个文件(新建文本重命名即可),内容如下:

workers2.properties

[shm]
# file的值要根据实际情况做修改
file=C:/Program Files/Apache Software Foundation/Tomcat 6.0/logs/jk2.log
size=1048576

# Example socket channel, override port and host.
[channel.socket:localhost:8009]
port=8009
host=127.0.0.1

# define the worker
[ajp13:reynir_net:8009]
channel=channel.socket:localhost:8009

# Uri mapping
[uri:/*.jsp]
[uri:/web/*]
[uri:/view/*]
worker=ajp13:localhost:8009

# define the worker
[status:status]

# Uri mapping
[uri:/jkstatus/*]
worker=status:status

jk2.properties

request.tomcatAuthentication=false

4、修改注册表,为isapi_redirector2.dll提供必须的参数:
在任意位置新建注册表文件isapi_redirect.2.0.reg,内容如 下:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Jakarta Isapi Redirector]
[HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Jakarta Isapi Redirector\2.0]
“serverRoot”=”C:\\Program Files\\Apache Software Foundation\\Tomcat 6.0”
“extensionUri”=”/jakarta/isapi_redirector2.dll”
“workersFile”=”C:\\Program Files\\Apache Software Foundation\\Tomcat 6.0\\conf\\workers2.properties”
“logLevel”=”Debug
其中,serverRootworkerFile的值要根据实际情况做相应修改,本机绝对路径中使用“\\”来分隔,extensionUri的值不要修改。
执行这个注册表文件将值导入系统注册表。

5、修改IIS设置:
打 开“
Internet 信息 服务”,在“你的计算机”“网 站”“默认网站”上点击右 键,选“属性”,点击“ISAPI筛选器”选项卡,点“添加”按钮,名称为“jakarta”,可执行文件就是“isapi_redirector2.dll”文件的完整路径。


完成之后一路“确定”退出所有对话框。
还是在“默认网站”上点击右键,新建虚拟目录,别名一定 是“jakarta”,路径是“isapi_redirector2.dll”文件所在的目录,最后要加上“执行”权限,再一路“确定”退出所有对话框。

6、统一IIS5Tomcat6的根目录:
文本编辑器打开C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf\server.xml,找到<Host />节,参考如下修改:
<Host name=”localhost”  appBase=”C:\\Inetpub\\wwwroot\\”
unpackWARs=”true” autoDeploy=”true”
xmlValidation=”false” xmlNamespaceAware=”false” ></Host>
name属性为主机 名,就是俗称网址,一个站点可以绑定多个主机名,每一个主机名就是一条<Host />节;
appBase属性就是Tomcat的主目录,初始值为“webapps”,是相对路径,相对于Tomcat的安装目录,需要注意,当设置绝对路径时要用“\\”来分隔,而且Tomcat的站点根目录是在这里设置的主目录下的ROOT目录中,所以这里虽然设置为“C:\\Inetpub\\wwwroot\\”,实际上要在wwwroot下新建一个名为ROOT的文件夹。
修 改好后保存server.xml文件,重启Tomcat6,随便写一个JSP测试页保存到c:\inetpub\wwwroot\ROOT下,例 如:index.jsp,内 容为Now time is: <%=new java.util.Date()%>,然后浏览器访问http://localhost:8080/index.jsp,看到当前时间,说明Tomcat主目录设置成功。
打开“Internet 信息服务”,在“你的计算机”网站“默认网站”上点击右键,选“属性”,在“主目录”选项卡中把“本地路径”修改为“C:\Inetpub\wwwroot\ROOT”,一 路“确定”退出对话框。至此,你可以再次重启一下Tomcat6IIS5,然后进入下一步。

7、激动人心的一刻:
在确保IIS5Tomcat6服务都正常开启的情况下,浏览器访问http://localhost/index.jsp,看到了吗?再开一个网页,访问http://localhost:8080/index.jsp,怎么样,两个页面结果是不是一样的?是的,已经成功了,至此,一个即支持ASP.NET),又支持PHP,同时还支持JSPIIS已经在你的机器上运行起来了!
不放心?
ROOT目录下再新建一个index.php,内容为<?php phpinfo(); ?>,浏览器访问http://localhost/index.php,将会显示php配置信息。
ROOT目录下新建一个index.asp,内容为<%=”Hello WorldIIS仍然可以解析ASP文件!“%>,浏览器访问http://localhost/index.asp, 怎么样?

本次通过记录暂到这里。

由于配置历时太长,而且在我 写这篇文章时页面还停止响应一次,所以文章中难免会有点疏漏,欢迎交流指正。

[转载]Lucene学习总结之十:Lucene的分词器Analyzer

mikel阅读(973)

[转载]Lucene学习总结之十:Lucene的分词器Analyzer – 觉先 – 博客园.

1、抽象类Analyzer

其主要包含两个接口,用于生成TokenStream:

  • TokenStream tokenStream(String fieldName, Reader reader);
  • TokenStream reusableTokenStream(String fieldName, Reader reader) ;

所谓 TokenStream,后面我们会讲到,是一个由分词后的Token结果组成的流,能够不断的得到下一个分成的Token。

为了提高性 能,使得在同一个线程中无需再生成新的TokenStream对象,老的可以被重用,所以有reusableTokenStream一说。

所 以Analyzer中有CloseableThreadLocal< Object > tokenStreams = new CloseableThreadLocal< Object >();成员变量,保存当前线程原来创建过的TokenStream,可用函数setPreviousTokenStream设定,用函数 getPreviousTokenStream得到。

在reusableTokenStream函数中,往往用 getPreviousTokenStream得到老的TokenStream对象,然后将TokenStream对象reset以下,从而可以从新开始 得到Token流。

让我们看一下最简单的一个Analyzer:

public final class SimpleAnalyzer extends Analyzer {

@Override

public TokenStream tokenStream(String fieldName, Reader reader) {

//返回的是将字符串最小化,并且按照空格分隔的 Token

return new LowerCaseTokenizer(reader);

}

@Override

public TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException {

//得到上一次使用的TokenStream,如果没有则生成新的,并且用 setPreviousTokenStream放入成员变量,使得下一个可用。

Tokenizer tokenizer = (Tokenizer) getPreviousTokenStream();

if (tokenizer == null) {

tokenizer = new LowerCaseTokenizer(reader);

setPreviousTokenStream(tokenizer);

} else

// 如果上一次生成过TokenStream,则reset。

tokenizer.reset(reader);

return tokenizer;

}

}

2、TokenStream抽象类

TokenStream主要包含以下几个方法:

  • boolean incrementToken()用于得到下一个Token。
  • public void reset() 使得此TokenStrean可以重新开始返回各个分词。

和原来的TokenStream返回一个Token对象不 同,Lucene 3.0的TokenStream已经不返回Token对象了,那么如何保存下一个Token的信息呢。

在Lucene 3.0中,TokenStream是继承于AttributeSource,其包含Map,保存从class到对象的映射,从而可以保存不同类型的对象的 值。

在TokenStream中,经常用到的对象是TermAttributeImpl,用来保存Token字符 串;PositionIncrementAttributeImpl用来保存位置信息;OffsetAttributeImpl用来保存偏移量信息。

所以当生成TokenStream的时候,往往调用AttributeImpl tokenAtt = (AttributeImpl) addAttribute(TermAttribute.class)将TermAttributeImpl添加到Map中,并保存一个成员变量。

在incrementToken()中,将下一个Token的信息写入当前的tokenAtt,然后使用 TermAttributeImpl.term()得到Token的字符串。

3、几个具体的TokenStream

在索引的时候,添加域的时候,可以指定Analyzer,使其生成TokenStream,也可以直接指定TokenStream:

public Field(String name, TokenStream tokenStream);

下面介绍两个单独使用的 TokenStream

3.1、NumericTokenStream

上一节介绍 NumericRangeQuery的时候,在生成NumericField的时候,其会使用NumericTokenStream,其 incrementToken如下:

public boolean incrementToken() {

if (valSize == 0)

throw new IllegalStateException(“call set???Value() before usage”);

if (shift >= valSize)

return false;

clearAttributes();

//虽然NumericTokenStream欲保存数字,然而Lucene 的Token只能保存字符串,因而要将数字编码为字符串,然后存入索引。

final char[] buffer;

switch (valSize) {

//首先分配TermBuffer, 然后将数字编码为字符串

case 64:

buffer = termAtt.resizeTermBuffer(NumericUtils.BUF_SIZE_LONG);

termAtt.setTermLength(NumericUtils.longToPrefixCoded(value, shift, buffer));

break;

case 32:

buffer = termAtt.resizeTermBuffer(NumericUtils.BUF_SIZE_INT);

termAtt.setTermLength(NumericUtils.intToPrefixCoded((int) value, shift, buffer));

break;

default:

throw new IllegalArgumentException(“valSize must be 32 or 64”);

}

typeAtt.setType((shift == 0) ? TOKEN_TYPE_FULL_PREC : TOKEN_TYPE_LOWER_PREC);

posIncrAtt.setPositionIncrement((shift == 0) ? 1 : 0);

shift += precisionStep;

return true;

}

public static int intToPrefixCoded(final int val, final int shift, final char[] buffer) {

if (shift>31 || shift<0)

throw new IllegalArgumentException(“Illegal shift value, must be 0..31”);

int nChars = (31-shift)/7 + 1, len = nChars+1;

buffer[0] = (char)(SHIFT_START_INT + shift);

int sortableBits = val ^ 0x80000000;

sortableBits >>>= shift;

while (nChars>=1) {

//int按照每七位组成一个utf-8的编码,并且字符串大小比较的顺序同 int大小比较的顺序完全相同。

buffer[nChars–] = (char)(sortableBits & 0x7f);

sortableBits >>>= 7;

}

return len;

}

3.2、SingleTokenTokenStream

SingleTokenTokenStream顾 名思义就是此TokenStream仅仅包含一个Token,多用于保存一篇文档仅有一个的信息,如id,如time等,这些信息往往被保存在一个特殊的 Token(如ID:ID, TIME:TIME)的倒排表的payload中的,这样可以使用跳表来增加访问速度。

所以 SingleTokenTokenStream返回的Token则不是id或者time本身,而是特殊的Token,”ID:ID”, “TIME:TIME”,而是将id的值或者time的值放入payload中。

//索引的时候

int id = 0; //用户 自己的文档号

String tokenstring = “ID”;

byte[] value = idToBytes(); //将id装换为byte数组

Token token = new Token(tokenstring, 0, tokenstring.length);

token.setPayload(new Payload(value));

SingleTokenTokenStream tokenstream = new SingleTokenTokenStream(token);

Document doc = new Document();

doc.add(new Field(“ID”, tokenstream));

……

//当得 到Lucene的文档号docid,并不想构造Document对象就得到用户的文档号时

TermPositions tp = reader.termPositions(“ID:ID”);

boolean ret = tp.skipTo(docid);

tp.nextPosition();

int payloadlength = tp.getPayloadLength();

byte[] payloadBuffer = new byte[payloadlength];

tp.getPayload(payloadBuffer, 0);

int id = bytesToID(); //将payloadBuffer转换为用户id

4、Tokenizer也是一种TokenStream

public abstract class Tokenizer extends TokenStream {

protected Reader input;

protected Tokenizer(Reader input) {

this.input = CharReader.get(input);

}

public void reset(Reader input) throws IOException {

this.input = input;

}

}

以 下重要的Tokenizer如下,我们将一一解析:

  • CharTokenizer
    • LetterTokenizer
      • LowerCaseTokenizer
    • WhitespaceTokenizer
  • ChineseTokenizer
  • CJKTokenizer
  • EdgeNGramTokenizer
  • KeywordTokenizer
  • NGramTokenizer
  • SentenceTokenizer
  • StandardTokenizer

4.1、CharTokenizer

CharTokenizer 是一个抽象类,用于对字符串进行分词。

在构造函数中,生成了TermAttribute和OffsetAttribute两个属性,说明 分词后除了返回分词后的字符外,还要返回offset。

offsetAtt = addAttribute(OffsetAttribute.class);

termAtt = addAttribute(TermAttribute.class);

其 incrementToken函数如下:

public final boolean incrementToken() throws IOException {

clearAttributes();

int length = 0;

int start = bufferIndex;

char[] buffer = termAtt.termBuffer();

while (true) {

//不断读取reader中的字符到buffer中

if (bufferIndex >= dataLen) {

offset += dataLen;

dataLen = input.read(ioBuffer);

if (dataLen == -1) {

dataLen = 0;

if (length > 0)

break;

else

return false;

}

bufferIndex = 0;

}

//然后逐一遍历 buffer中的字符

final char c = ioBuffer[bufferIndex++];

//如果是一个token字符,则normalize后接着取下一个字符,否则当前token结束。

if (isTokenChar(c)) {

if (length == 0)

start = offset + bufferIndex – 1;

else if (length == buffer.length)

buffer = termAtt.resizeTermBuffer(1+length);

buffer[length++] = normalize(c);

if (length == MAX_WORD_LEN)

break;

} else if (length > 0)

break;

}

termAtt.setTermLength(length);

offsetAtt.setOffset(correctOffset(start), correctOffset(start+length));

return true;

}

CharTokenizer 是一个抽象类,其isTokenChar函数和normalize函数由子类实现。

其子类WhitespaceTokenizer实现了 isTokenChar函数:

// 当遇到空格的时候,当前token结束

protected boolean isTokenChar(char c) {

return !Character.isWhitespace(c);

}

其子类LetterTokenizer如下实现isTokenChar函数:

protected boolean isTokenChar(char c) {

return Character.isLetter(c);

}

LetterTokenizer的子类LowerCaseTokenizer实现了normalize函数,将字符串转换为小写:

protected char normalize(char c) {

return Character.toLowerCase(c);

}

4.2、ChineseTokenizer

其在初始化的时候,添加TermAttribute和 OffsetAttribute。

其incrementToken实现如下:

public boolean incrementToken() throws IOException {

clearAttributes();

length = 0;

start = offset;

while (true) {

final char c;

offset++;

if (bufferIndex >= dataLen) {

dataLen = input.read(ioBuffer);

bufferIndex = 0;

}

if (dataLen == -1) return flush();

else

c = ioBuffer[bufferIndex++];

switch(Character.getType(c)) {

//如果是英文下小写字母或数字的时候,则属于同一个Token,push到buffer中

case Character.DECIMAL_DIGIT_NUMBER:

case Character.LOWERCASE_LETTER:

case Character.UPPERCASE_LETTER:

push(c);

if (length == MAX_WORD_LEN) return flush();

break;

//中文属于OTHER_LETTER,当出现中文字符的时候,则上一个Token结束,并将当前字符push 到buffer中

case Character.OTHER_LETTER:

if (length>0) {

bufferIndex–;

offset–;

return flush();

}

push(c);

return flush();

default:

if (length>0) return flush();

break;

}

}

}

4.3、KeywordTokenizer

KeywordTokenizer是将整个字符作为一个 Token返回的。

其incrementToken函数如下:

public final boolean incrementToken() throws IOException {

if (!done) {

clearAttributes();

done = true;

int upto = 0;

char[] buffer = termAtt.termBuffer();

//将字符串全部读入buffer,然后返回。

while (true) {

final int length = input.read(buffer, upto, buffer.length-upto);

if (length == -1) break;

upto += length;

if (upto == buffer.length)

buffer = termAtt.resizeTermBuffer(1+buffer.length);

}

termAtt.setTermLength(upto);

finalOffset = correctOffset(upto);

offsetAtt.setOffset(correctOffset(0), finalOffset);

return true;

}

return false;

}

4.4、CJKTokenizer

其 incrementToken函数如下:

public boolean incrementToken() throws IOException {

clearAttributes();

while(true) {

int length = 0;

int start = offset;

while (true) {

//得到当前的字符,及其所属的Unicode块

char c;

Character.UnicodeBlock ub;

offset++;

if (bufferIndex >= dataLen) {

dataLen = input.read(ioBuffer);

bufferIndex = 0;

}

if (dataLen == -1) {

if (length > 0) {

if (preIsTokened == true) {

length = 0;

preIsTokened = false;

}

break;

} else {

return false;

}

} else {

c = ioBuffer[bufferIndex++];

ub = Character.UnicodeBlock.of(c);

}

// 如果当前字符输入ASCII码

if ((ub == Character.UnicodeBlock.BASIC_LATIN) || (ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS)) {

if (ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {

int i = (int) c;

if (i >= 65281 && i <= 65374) {

//将半型及全型形式Unicode转变为普通的 ASCII码

i = i – 65248;

c = (char) i;

}

}

//如果当前字符是字符或者”_” “+” “#”

if (Character.isLetterOrDigit(c) || ((c == ‘_’) || (c == ‘+’) || (c == ‘#’))) {

if (length == 0) {

start = offset – 1;

} else if (tokenType == DOUBLE_TOKEN_TYPE) {

offset–;

bufferIndex–;

if (preIsTokened == true) {

length = 0;

preIsTokened = false;

break;

} else {

break;

}

}

//将当前字符放入 buffer

buffer[length++] = Character.toLowerCase(c);

tokenType = SINGLE_TOKEN_TYPE;

if (length == MAX_WORD_LEN) {

break;

}

} else if (length > 0) {

if (preIsTokened == true) {

length = 0;

preIsTokened = false;

} else {

break;

}

}

} else {

//如果非ASCII字符

if (Character.isLetter(c)) {

if (length == 0) {

start = offset – 1;

buffer[length++] = c;

tokenType = DOUBLE_TOKEN_TYPE;

} else {

if (tokenType == SINGLE_TOKEN_TYPE) {

offset–;

bufferIndex–;

break;

} else {

//非ASCII码字符,两个字符作为一个Token

//(如”中华人民共和国”分词为”中华”,”华人”,”人民”,”民共”,”共和”,”和国”)

buffer[length++] = c;

tokenType = DOUBLE_TOKEN_TYPE;

if (length == 2) {

offset–;

bufferIndex–;

preIsTokened = true;

break;

}

}

}

} else if (length > 0) {

if (preIsTokened == true) {

length = 0;

preIsTokened = false;

} else {

break;

}

}

}

}

if (length > 0) {

termAtt.setTermBuffer(buffer, 0, length);

offsetAtt.setOffset(correctOffset(start), correctOffset(start+length));

typeAtt.setType(TOKEN_TYPE_NAMES[tokenType]);

return true;

} else if (dataLen == -1) {

return false;

}

}

}

4.5、 SentenceTokenizer

其是按照如下的标点来拆分句子:”。,!?;,!?;”

让我们来看下面的例子:

String s = “据纽约时报周三报道称,苹果已经超过微软成为美国最有价值的  科技公司。这是一个不容忽视的转折点。”;

StringReader sr = new StringReader(s);

SentenceTokenizer tokenizer = new SentenceTokenizer(sr);

boolean hasnext = tokenizer.incrementToken();

while(hasnext){

TermAttribute ta = tokenizer.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = tokenizer.incrementToken();

}

结果为:

据纽约时报周三报道称,
苹果已经超过微软成为美国最有价值的
科技公司。
这是一个不容忽视的转折点。

其incrementToken函数如下:

public boolean incrementToken() throws IOException {

clearAttributes();

buffer.setLength(0);

int ci;

char ch, pch;

boolean atBegin = true;

tokenStart = tokenEnd;

ci = input.read();

ch = (char) ci;

while (true) {

if (ci == -1) {

break;

} else if (PUNCTION.indexOf(ch) != -1) {

//出现标点符号,当前句子结束,返回当前Token

buffer.append(ch);

tokenEnd++;

break;

} else if (atBegin && Utility.SPACES.indexOf(ch) != -1) {

tokenStart++;

tokenEnd++;

ci = input.read();

ch = (char) ci;

} else {

buffer.append(ch);

atBegin = false;

tokenEnd++;

pch = ch;

ci = input.read();

ch = (char) ci;

//当连续出现两个空格,或者\r\n的时候,则当前句子结束,返回当前 Token

if (Utility.SPACES.indexOf(ch) != -1

&& Utility.SPACES.indexOf(pch) != -1) {

tokenEnd++;

break;

}

}

}

if (buffer.length() == 0)

return false;

else {

termAtt.setTermBuffer(buffer.toString());

offsetAtt.setOffset(correctOffset(tokenStart), correctOffset(tokenEnd));

typeAtt.setType(“sentence”);

return true;

}

}

5、TokenFilter也是一种 TokenStream

来对Tokenizer后的Token作过滤,其使用的是装饰者模式。

public abstract class TokenFilter extends TokenStream {

protected final TokenStream input;

protected TokenFilter(TokenStream input) {

super(input);

this.input = input;

}

}

5.1、ChineseFilter

其incrementToken函数如下:

public boolean incrementToken() throws IOException {

while (input.incrementToken()) {

char text[] = termAtt.termBuffer();

int termLength = termAtt.termLength();

//如果不被停词表过滤掉

if (!stopTable.contains(text, 0, termLength)) {

switch (Character.getType(text[0])) {

//如果是英文 且长度超过一,则算一个Token,否则不算一个Token

case Character.LOWERCASE_LETTER:

case Character.UPPERCASE_LETTER:

if (termLength>1) {

return true;

}

break;

//如果是中文则算一个Token

case Character.OTHER_LETTER:

return true;

}

}

}

return false;

}

举例:

String s = “Javaeye: IT外企那点儿事。1.外企也就那么会儿事。”;

StringReader sr = new StringReader(s);

ChineseTokenizer ct = new ChineseTokenizer(sr);

ChineseFilter filter = new ChineseFilter(ct);

boolean hasnext = filter.incrementToken();

while(hasnext){

TermAttribute ta = filter.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = filter.incrementToken();

}

结果为:

javaeye














5.2、LengthFilter

其incrementToken函数如下:

public final boolean incrementToken() throws IOException {

while (input.incrementToken()) {

int len = termAtt.termLength();

//当当前字符串的长度在指定范围内的时候 则返回。

if (len >= min && len <= max) {

return true;

}

}

return false;

}

举例如下:

String s = “a it has this there string english analyzer”;

StringReader sr = new StringReader(s);

WhitespaceTokenizer wt = new WhitespaceTokenizer(sr);

LengthFilter filter = new LengthFilter(wt, 4, 7);

boolean hasnext = filter.incrementToken();

while(hasnext){

TermAttribute ta = filter.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = filter.incrementToken();

}

结果如下:

this
there
string
english

5.3、LowerCaseFilter

其incrementToken函数如下:

public final boolean incrementToken() throws IOException {

if (input.incrementToken()) {

final char[] buffer = termAtt.termBuffer();

final int length = termAtt.termLength();

for(int i=0;i<length;i++)

//转小写

buffer[i] = Character.toLowerCase(buffer[i]);

return true;

} else

return false;

}

5.4、NumericPayloadTokenFilter

public final boolean incrementToken() throws IOException {

if (input.incrementToken()) {

if (typeAtt.type().equals(typeMatch))

//设置payload

payloadAtt.setPayload(thePayload);

return true;

} else {

return false;

}

}

5.5、PorterStemFilter

其成员变量PorterStemmer stemmer,其实现著名的stemming算法是The Porter Stemming Algorithm,其主页为http://tartarus.org/~martin/PorterStemmer/, 也可查看其论文http://tartarus.org/~martin/PorterStemmer/def.txt

通过以下网页可以进行简单的测试:Porter’s Stemming Algorithm Online[http://facweb.cs.depaul.edu/mobasher/classes/csc575/porter.html]

cars –> car

driving –> drive

tokenization –> token

其incrementToken函数如下:

public final boolean incrementToken() throws IOException {

if (!input.incrementToken())

return false;

if (stemmer.stem(termAtt.termBuffer(), 0, termAtt.termLength()))

termAtt.setTermBuffer(stemmer.getResultBuffer(), 0, stemmer.getResultLength());

return true;

}

举例:

String s = “Tokenization is the process of breaking a stream of text up into meaningful elements called tokens.”;

StringReader sr = new StringReader(s);

LowerCaseTokenizer lt = new LowerCaseTokenizer(sr);

PorterStemFilter filter = new PorterStemFilter(lt);

boolean hasnext = filter.incrementToken();

while(hasnext){

TermAttribute ta = filter.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = filter.incrementToken();

}

结果为:

token
is
the
process
of
break
a
stream
of
text
up
into
meaning
element
call
token

5.6、 ReverseStringFilter

public boolean incrementToken() throws IOException {

if (input.incrementToken()) {

int len = termAtt.termLength();

if (marker != NOMARKER) {

len++;

termAtt.resizeTermBuffer(len);

termAtt.termBuffer()[len – 1] = marker;

}

//将token反转

reverse( termAtt.termBuffer(), len );

termAtt.setTermLength(len);

return true;

} else {

return false;

}

}

public static void reverse( char[] buffer, int start, int len ){

if( len <= 1 ) return;

int num = len>>1;

for( int i = start; i < ( start + num ); i++ ){

char c = buffer[i];

buffer[i] = buffer[start * 2 + len – i – 1];

buffer[start * 2 + len – i – 1] = c;

}

}

举例:

String s = “Tokenization is the process of breaking a stream of text up into meaningful elements called tokens.”;

StringReader sr = new StringReader(s);

LowerCaseTokenizer lt = new LowerCaseTokenizer(sr);

ReverseStringFilter filter = new ReverseStringFilter(lt);

boolean hasnext = filter.incrementToken();

while(hasnext){

TermAttribute ta = filter.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = filter.incrementToken();

}

结果为:

noitazinekot
si
eht
ssecorp
fo
gnikaerb
a
maerts
fo
txet
pu
otni
lufgninaem
stnemele
dellac
snekot

5.7、SnowballFilter

其包含成员变量SnowballProgram stemmer,其是一个抽象类,其子类有EnglishStemmer和PorterStemmer等。

public final boolean incrementToken() throws IOException {

if (input.incrementToken()) {

String originalTerm = termAtt.term();

stemmer.setCurrent(originalTerm);

stemmer.stem();

String finalTerm = stemmer.getCurrent();

if (!originalTerm.equals(finalTerm))

termAtt.setTermBuffer(finalTerm);

return true;

} else {

return false;

}

}

举例:

String s = “Tokenization is the process of breaking a stream of text up into meaningful elements called tokens.”;

StringReader sr = new StringReader(s);

LowerCaseTokenizer lt = new LowerCaseTokenizer(sr);

SnowballFilter filter = new SnowballFilter(lt, new EnglishStemmer());

boolean hasnext = filter.incrementToken();

while(hasnext){

TermAttribute ta = filter.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = filter.incrementToken();

}

结果如下:

token
is
the
process
of
break
a
stream
of
text
up
into
meaning
element
call
token

5.8、 TeeSinkTokenFilter

TeeSinkTokenFilter可以使得已经分好词的Token全部或者部分的被保存下 来,用于生成另一个TokenStream可以保存在其他的域中。

我们可用如下的语句生成一个TeeSinkTokenFilter:

TeeSinkTokenFilter source = new TeeSinkTokenFilter(new WhitespaceTokenizer(reader));

然后使用函数newSinkTokenStream()或者newSinkTokenStream(SinkFilter filter)生成一个SinkTokenStream:

TeeSinkTokenFilter.SinkTokenStream sink = source.newSinkTokenStream();

其中在 newSinkTokenStream(SinkFilter filter)函数中,将新生成的SinkTokenStream保存在TeeSinkTokenFilter的成员变量sinks中。

在 TeeSinkTokenFilter的incrementToken函数中:

public boolean incrementToken() throws IOException {

if (input.incrementToken()) {

//对于每一个Token,依次遍历 成员变量sinks

AttributeSource.State state = null;

for (WeakReference<SinkTokenStream> ref : sinks) {

// 对于每一个SinkTokenStream,首先调用函数accept看是否接受,如果接受则将此Token也加入此SinkTokenStream。

final SinkTokenStream sink = ref.get();

if (sink != null) {

if (sink.accept(this)) {

if (state == null) {

state = this.captureState();

}

sink.addState(state);

}

}

}

return true;

}

return false;

}

SinkTokenStream.accept调用SinkFilter.accept,对于默认的ACCEPT_ALL_FILTER则接受所 有的Token:

private static final SinkFilter ACCEPT_ALL_FILTER = new SinkFilter() {

@Override

public boolean accept(AttributeSource source) {

return true;

}

};

这样 SinkTokenStream就能够保存下所有WhitespaceTokenizer分好的Token。

当我们使用比较复杂的分成系 统的时候,分词一篇文章往往需要耗费比较长的时间,当分好的词需要再次使用的时候,再分一次词实在太浪费了,于是可以用上述的例子,将分好的词保存在一个 TokenStream里面就可以了。

如下面的例子:

String s = “this is a book”;

StringReader reader = new StringReader(s);

TeeSinkTokenFilter source = new TeeSinkTokenFilter(new WhitespaceTokenizer(reader));

TeeSinkTokenFilter.SinkTokenStream sink = source.newSinkTokenStream();

boolean hasnext = source.incrementToken();

while(hasnext){

TermAttribute ta = source.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = source.incrementToken();

}

System.out.println(“———————————————“);

hasnext = sink.incrementToken();

while(hasnext){

TermAttribute ta = sink.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = sink.incrementToken();

}

结果为:

this
is
a
book
———————————————
this
is
a
book

当然有时候我们想在分好词的一系列Token中,抽取我们想要的一些实体,保存下来。

如下面的例子:

String s = “Japan will always balance its national interests between China and America.”;

StringReader reader = new StringReader(s);

TeeSinkTokenFilter source = new TeeSinkTokenFilter(new LowerCaseTokenizer(reader));

//一个集合,保存所有的国家名称

final HashSet<String> countryset = new HashSet<String>();

countryset.add(“japan”);

countryset.add(“china”);

countryset.add(“america”);

countryset.add(“korea”);

SinkFilter countryfilter = new SinkFilter() {

@Override

public boolean accept(AttributeSource source) {

TermAttribute ta = source.getAttribute(TermAttribute.class);

//如果在国家名称列表中,则保留

if(countryset.contains(ta.term())){

return true;

}

return false;

}

};

TeeSinkTokenFilter.SinkTokenStream sink = source.newSinkTokenStream(countryfilter);

//由 LowerCaseTokenizer对语句进行分词,并把其中的国家名称保存在SinkTokenStream中

boolean hasnext = source.incrementToken();

while(hasnext){

TermAttribute ta = source.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = source.incrementToken();

}

System.out.println(“———————————————“);

hasnext = sink.incrementToken();

while(hasnext){

TermAttribute ta = sink.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = sink.incrementToken();

}

}

结果为:

japan
will
always
balance
its
national
interests
between
china
and
america
———————————————
japan
china
america

6、不同的Analyzer就是组合不同的Tokenizer和TokenFilter得到最后的TokenStream

6.1、ChineseAnalyzer

public final TokenStream tokenStream(String fieldName, Reader reader) {

//按字分词,并过滤停词,标点,英文

TokenStream result = new ChineseTokenizer(reader);

result = new ChineseFilter(result);

return result;

}

举例:”This year, president Hu 科学发展观” 被分词为 “year”,”president”,”hu”,”科”,”学”,”发”,”展”,”观”

6.2、CJKAnalyzer

public final TokenStream tokenStream(String fieldName, Reader reader) {

//每两个字 组成一个词,并去除停词

return new StopFilter(StopFilter.getEnablePositionIncrementsVersionDefault(matchVersion), new CJKTokenizer(reader), stopTable);

}

举例:”This year, president Hu 科学发展观” 被分词为”year”,”president”,”hu”,”科学”,”学发”,”发展”,”展观”。

6.3、 PorterStemAnalyzer

public TokenStream tokenStream(String fieldName, Reader reader) {

// 将转为小写的token,利用porter算法进行stemming

return new PorterStemFilter(new LowerCaseTokenizer(reader));

}

6.4、SmartChineseAnalyzer

public TokenStream tokenStream(String fieldName, Reader reader) {

//先分句子

TokenStream result = new SentenceTokenizer(reader);

// 句子中分词组

result = new WordTokenFilter(result);

//用porter算法进行stemming

result = new PorterStemFilter(result);

//去停词

if (!stopWords.isEmpty()) {

result = new StopFilter(StopFilter.getEnablePositionIncrementsVersionDefault(matchVersion), result, stopWords, false);

}

return result;

}

6.5、SnowballAnalyzer

public TokenStream tokenStream(String fieldName, Reader reader) {

//使用标准的分词器

TokenStream result = new StandardTokenizer(matchVersion, reader);

//标准的过滤器

result = new StandardFilter(result);

//转换为小写

result = new LowerCaseFilter(result);

//去停词

if (stopSet != null)

result = new StopFilter(StopFilter.getEnablePositionIncrementsVersionDefault(matchVersion), result, stopSet);

//根据设定的stemmer进行stemming

result = new SnowballFilter(result, name);

return result;

}

7、Lucene的标 准分词器

7.1、StandardTokenizerImpl.jflex

和QueryParser类似,标准分 词器也需要词法分析,在原来的版本中,也是用javacc,当前的版本中,使用的是jflex。

jflex也是一个词法及语法分析器的生 成器,它主要包括三部分,由%%分隔:

  • 用户代码部分:多为package或者import
  • 选项及词法 声明
  • 语法规则声明

用于生成标准分词器的flex文件尾 StandardTokenizerImpl.jflex,如下:

import org.apache.lucene.analysis.Token;

import org.apache.lucene.analysis.tokenattributes.TermAttribute;

%% //以上是用户代码部分,以下是选项及词法声明

%class StandardTokenizerImpl // 类名

%unicode

%integer //下面函数的返回值

%function getNextToken //进行词法及语法分析的函数

%pack

%char

%{ //此之间的代码之间拷贝到生成的java文件中

public static final int ALPHANUM          = StandardTokenizer.ALPHANUM;

public static final int APOSTROPHE        = StandardTokenizer.APOSTROPHE;

public static final int ACRONYM           = StandardTokenizer.ACRONYM;

public static final int COMPANY           = StandardTokenizer.COMPANY;

public static final int EMAIL             = StandardTokenizer.EMAIL;

public static final int HOST              = StandardTokenizer.HOST;

public static final int NUM               = StandardTokenizer.NUM;

public static final int CJ                = StandardTokenizer.CJ;

public static final int ACRONYM_DEP       = StandardTokenizer.ACRONYM_DEP;

public static final String [] TOKEN_TYPES = StandardTokenizer.TOKEN_TYPES;

public final int yychar()

{

return yychar;

}

final void getText(Token t) {

t.setTermBuffer(zzBuffer, zzStartRead, zzMarkedPos-zzStartRead);

}

final void getText(TermAttribute t) {

t.setTermBuffer(zzBuffer, zzStartRead, zzMarkedPos-zzStartRead);

}

%}

THAI       = [\u0E00-\u0E59]

//一系列字母 和数字的组合

ALPHANUM   = ({LETTER}|{THAI}|[:digit:])+

// 省略符号,如you’re

APOSTROPHE =  {ALPHA} (“‘” {ALPHA})+

// 缩写,如U.S.A.

ACRONYM    =  {LETTER} “.” ({LETTER} “.”)+

ACRONYM_DEP    = {ALPHANUM} “.” ({ALPHANUM} “.”)+

// 公司名称如AT&T,Excite@Home.

COMPANY    =  {ALPHA} (“&”|”@”) {ALPHA}

// 邮箱地址

EMAIL =  {ALPHANUM} ((“.”|”-“|”_”) {ALPHANUM})* “@” {ALPHANUM} ((“.”|”-“) {ALPHANUM})+

// 主机名

HOST  =  {ALPHANUM} ((“.”) {ALPHANUM})+

NUM  = ({ALPHANUM} {P} {HAS_DIGIT}

| {HAS_DIGIT} {P} {ALPHANUM}

| {ALPHANUM} ({P} {HAS_DIGIT} {P} {ALPHANUM})+

| {HAS_DIGIT} ({P} {ALPHANUM} {P} {HAS_DIGIT})+

| {ALPHANUM} {P} {HAS_DIGIT} ({P} {ALPHANUM} {P} {HAS_DIGIT})+

| {HAS_DIGIT} {P} {ALPHANUM} ({P} {HAS_DIGIT} {P} {ALPHANUM})+)

// 标点

P  = (“_”|”-“|”/”|”.”|”,”)

//至少包含一个数字 的字符串

HAS_DIGIT  = ({LETTER}|[:digit:])* [:digit:] ({LETTER}|[:digit:])*

ALPHA  = ({LETTER})+

//所谓字 符,即出去所有的非字符的ASCII及中日文。

LETTER = !(![:letter:]|{CJ})

//中文或者日文

CJ  = [\u3100-\u312f\u3040-\u309F\u30A0-\u30FF\u31F0-\u31FF\u3300-\u337f\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff65-\uff9f]

//空格

WHITESPACE = \r\n | [ \r\n\t\f]

%% //以下是语法规则部分,由于是分词器,因而不需要进行语法分析,则全部原样返回

{ALPHANUM}                                                     { return ALPHANUM; }

{APOSTROPHE}                                                   { return APOSTROPHE; }

{ACRONYM}                                                      { return ACRONYM; }

{COMPANY}                                                      { return COMPANY; }

{EMAIL}                                                        { return EMAIL; }

{HOST}                                                         { return HOST; }

{NUM}                                                          { return NUM; }

{CJ}                                                           { return CJ; }

{ACRONYM_DEP}                                                  { return ACRONYM_DEP; }

下面我们 看下面的例子,来说明StandardTokenizerImpl的功能:

String s = “I’m Juexian, my email is forfuture1978@gmail.com. My ip address is 192.168.0.1, AT&T and I.B.M are all great companies.”;

StringReader reader = new StringReader(s);

StandardTokenizerImpl impl = new StandardTokenizerImpl(reader);

while(impl.getNextToken() != StandardTokenizerImpl.YYEOF){

TermAttributeImpl ta = new TermAttributeImpl();

impl.getText(ta);

System.out.println(ta.term());

}

结果为:

I’m
Juexian
my
email
is
forfuture1978@gmail.com
My
ip
address
is
192.168.0.1
AT&T
and
I.B.M
are
all
great
companies

7.2、StandardTokenizer

其有一个成员变量 StandardTokenizerImpl scanner;

其incrementToken函数如下:

public final boolean incrementToken() throws IOException {

clearAttributes();

int posIncr = 1;

while(true) {

//用词法分析器得到下 一个Token以及Token的类型

int tokenType = scanner.getNextToken();

if (tokenType == StandardTokenizerImpl.YYEOF) {

return false;

}

if (scanner.yylength() <= maxTokenLength) {

posIncrAtt.setPositionIncrement(posIncr);

//得到Token 文本

scanner.getText(termAtt);

final int start = scanner.yychar();

offsetAtt.setOffset(correctOffset(start), correctOffset(start+termAtt.termLength()));

//设置类型

typeAtt.setType(StandardTokenizerImpl.TOKEN_TYPES[tokenType]);

return true;

} else

posIncr++;

}

}

7.3、 StandardFilter

其incrementToken函数如下:

public final boolean incrementToken() throws java.io.IOException {

if (!input.incrementToken()) {

return false;

}

char[] buffer = termAtt.termBuffer();

final int bufferLength = termAtt.termLength();

final String type = typeAtt.type();

//如果是省略符号,如He’s,则去掉’s

if (type == APOSTROPHE_TYPE && bufferLength >= 2 &&

buffer[bufferLength-2] == ‘\” && (buffer[bufferLength-1] == ‘s’ || buffer[bufferLength-1] == ‘S’)) {

termAtt.setTermLength(bufferLength – 2);

} else if (type == ACRONYM_TYPE) {

//如果是缩略语I.B.M.,则去掉.

int upto = 0;

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

char c = buffer[i];

if (c != ‘.’)

buffer[upto++] = c;

}

termAtt.setTermLength(upto);

}

return true;

}

7.4、StandardAnalyzer

public TokenStream tokenStream(String fieldName, Reader reader) {

//用词法分析器分词

StandardTokenizer tokenStream = new StandardTokenizer(matchVersion, reader);

tokenStream.setMaxTokenLength(maxTokenLength);

//用标准过滤器过滤

TokenStream result = new StandardFilter(tokenStream);

//转换为小写

result = new LowerCaseFilter(result);

//去停词

result = new StopFilter(enableStopPositionIncrements, result, stopSet);

return result;

}

举例如下:

String s = “He’s Juexian, His email is forfuture1978@gmail.com. He’s an ip address 192.168.0.1, AT&T and I.B.M. are all great companies.”;

StringReader reader = new StringReader(s);

StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);

TokenStream ts = analyzer.tokenStream(“field”, reader);

boolean hasnext = ts.incrementToken();

while(hasnext){

TermAttribute ta = ts.getAttribute(TermAttribute.class);

System.out.println(ta.term());

hasnext = ts.incrementToken();

}

结果为:

he
juexian
his
email
forfuture1978@gmail.com
he
ip
address
192.168.0.1
at&t
ibm
all
great
companies

8、不同的域使用不同的分词器

8.1、PerFieldAnalyzerWrapper

有 时候,我们想不同的域使用不同的分词器,则可以用PerFieldAnalyzerWrapper进行封装。

其有两个成员函数:

  • Analyzer defaultAnalyzer:即当域没有指定分词器的时候使用此分词器
  • Map<String,Analyzer> analyzerMap = new HashMap<String,Analyzer>():一个从域名到分词器的映射,将根据域名使用相应的分词器。

其 TokenStream函数如下:

public TokenStream tokenStream(String fieldName, Reader reader) {

Analyzer analyzer = analyzerMap.get(fieldName);

if (analyzer == null) {

analyzer = defaultAnalyzer;

}

return analyzer.tokenStream(fieldName, reader);

}

举例说明:

String s = “Hello World”;
PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(new SimpleAnalyzer());
analyzer.addAnalyzer(“f1”, new KeywordAnalyzer());
analyzer.addAnalyzer(“f2”, new WhitespaceAnalyzer());

TokenStream ts = analyzer.reusableTokenStream(“f1”, new StringReader(s));
boolean hasnext = ts.incrementToken();
while(hasnext){
TermAttribute ta = ts.getAttribute(TermAttribute.class);
System.out.println(ta.term());
hasnext = ts.incrementToken();
}

System.out.println(“———————————————“);

ts = analyzer.reusableTokenStream(“f2”, new StringReader(s));
hasnext = ts.incrementToken();
while(hasnext){
TermAttribute ta = ts.getAttribute(TermAttribute.class);
System.out.println(ta.term());
hasnext = ts.incrementToken();
}

System.out.println(“———————————————“);

ts = analyzer.reusableTokenStream(“none”, new StringReader(s));
hasnext = ts.incrementToken();
while(hasnext){
TermAttribute ta = ts.getAttribute(TermAttribute.class);
System.out.println(ta.term());
hasnext = ts.incrementToken();
}

结果为:

Hello World
———————————————
Hello
World
———————————————
hello
world

[转载]一步步学会使用ASP.NET 4 WEB应用程序中使用URL Routing(翻译)

mikel阅读(930)

[转载]一步步学会使用ASP.NET 4 WEB应用程序中使用URL Routing(翻译) – longgel – 博客园.

创建路由


路由就是将URL路径映射到具体的物理文件。若要将路由添加到网站中, 请使用 RouteCollection.MapPageRoute 方法将它们添加到RouteTable类的静态Routes属性。

将 用于添加路由的方法添加到 Global.asax 文件中
  1. 如果网站还没有 Global.asax 文件,请通过执行下列步骤添加一个这样的文件:
    1. 右击“解决方案资源管理器”中的 Web 项目,然后选择“添加新项”。
    2. 选择“全局应用程序类”,再单击“添加”。
  2. 打开 Global.asax 文件。
  3. 在Application指令的后面,添加用于 System.Web.Routing命名空间的Import指令,如下面的示例所示:
    <%@ Import Namespace="System.Web.Routing" %>
    
  4. 在 Session_End方法的后面添加以下代码:
    void RegisterRoutes(RouteCollection routes)
    {
    }

    在下面的过程中,您将向此方法中添加用于创建路由的代码。

  5. 在 Application_Start 方法中,调用RegisterRoutes添加路由规则,如下面的示例所示:
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}

上面的过程添加了用于注册路由的空方法。现在,将使用此方法将路由添加到网站中。

添加路由
  1. 在 RegisterRoutes 方法中,添加以下代码:
    routes.MapPageRoute(“”,
    “SalesReportSummary/{year}”,
    “~/sales.aspx”);

    此代码添加了一个未命名的路由,该路由具有URL匹配模式,该模式包含文本值“SalesReportSummary”和名为year的占位符 (URL参数)。它将路由映射到名为 Sales.aspx 的文件。

  2. 在 RegisterRoutes 方法中,添加以下代码:
    routes.MapPageRoute(“SalesRoute”,
    “SalesReport/{locale}/{year}”,
    “~/sales.aspx”);

    此代码添加名为SalesRoute的路由。如果对路由规则进行了命名,那么在下面创建了相同的路由规则的时候,可以使用名称区分它们。

  3. 在 RegisterRoutes 方法中,添加以下代码:
    routes.MapPageRoute(“ExpensesRoute”,
    “ExpenseReport/{locale}/{year}/{*extrainfo}”,
    “~/expenses.aspx”, true,
    new RouteValueDictionary {
    { “locale”, “US” },
    { “year”, DateTime.Now.Year.ToString() } },
    new RouteValueDictionary {
    { “locale”, “[a-z]{2}” },
    { “year”, @”\d{4}” } });

    此 代码添加名为 ExpensesRoute 的路由。此路由包括一个普通匹配参数 extrainfo。此代码将 locale 参数的默认值设置为“US”,将 year 参数的默认值设置为今年。约束指定 locale 参数必须由两个字母字符组成year 参数必须由四个数字组成。

使用路由创建超链接


当向网页中添加超链接时,如果希望指定路由 URL 而不是物理文件,则您有两个选择:

  • 可以对路由URL进行硬编码。
  • 可以指定路由参数名称和值,并让 ASP.NET 生成对应的 URL。如有必要,还可以指定路由名称,以便唯一标识路由。如果稍后更改路由URL规则,则必须更新所有硬编码的URL,但是如果让 ASP.NET生成URL,则始终自动生成正确的URL(除非模式中的参数已更改)。

在下面的过程中,将使用硬编码 URL 的超链接添加到网页。

创建硬编码的 URL
  1. 在“Solution”中,右击 Web 项目,然后单击”Add Item”。

    显示”Add Item”对话框。

  2. 选择“Web 窗体”模板,确保选中“Place code in separate file”,将名称设置为“Links.aspx”,然后单击“添加”。

    即会在“源”视图中打开Links.aspx页。

  3. 在开始和结束 <div> 标记之间添加以下标记:
    <asp:HyperLink ID=”HyperLink1″ runat=”server”
    NavigateUrl
    =”~/salesreportsummary/2010″>
    Sales Report – All locales, 2010
    </asp:HyperLink>
    <br />
    <asp:HyperLink ID=”HyperLink2″ runat=”server”
    NavigateUrl
    =”~/salesreport/WA/2011″>
    Sales Report – WA, 2011
    </asp:HyperLink>
    <br />
    <asp:HyperLink ID=”HyperLink3″ runat=”server”
    NavigateUrl
    =”~/expensereport”>
    Expense Report – Default Locale and Year (US, current year)
    </asp:HyperLink>
    <br />

    此标记中使用硬编码URL创建三个HyperLink控件。第一个超链接匹配销售情况汇总路由的URL模式,第二个超链接匹配名为 SalesRoute 的路由,第三个超链接匹配名为ExpensesRoute 的路由。由于没有为第三个超链接的 URL 指定参数,为该路由定义的默认值将传递给Expenses.aspx。

接下来,将添加标记(这些标记创建用于指定路由参数和路由名称的超链接)以创建路由URL。

使用标记创建自动生成的URL
  • 在“源”视图中保持Links.aspx 打开,在上一过程中创建的HyperLink控件后添加以下代码:
    <asp:HyperLink ID=”HyperLink4″ runat=”server”
    NavigateUrl
    =”<%$RouteUrl:year=2011%>”>
    Sales Report – All locales, 2011
    </asp:HyperLink>
    <br />
    <asp:HyperLink ID=”HyperLink5″ runat=”server”
    NavigateUrl
    =”<%$RouteUrl:locale=CA,year=2009,routename=salesroute%>”>
    Sales Report – CA, 2009
    </asp:HyperLink>
    <br />

    此标记使用 RouteUrl 表达式创建名为SalesSummaryRoute和SalesRoute的URL。第二个 RouteUrl 表达式指定路由的名称,因为代码中提供的参数列表可以匹配 ExpensesRoute URL模式或 SalesRoute URL模式。ExpensesRoute URL 模式具有 SalesRoute URL模式所没有的 extrainfo 占位符,但是 extrainfo 是一个可用于放置各种信息的占位符,这意味着它是可选的。

在下面的过程中,将添加用于创建超链接的标记,并通过指定路由参数和路由名称,使用代码生成超链接的 URL。

使用代码创建自动生成的 URL
  1. 在“源”视图中保持 Links.aspx 打开,在上一过程中创建的 HyperLink控件后添加以下代码:
    <asp:HyperLink ID=”HyperLink6″ runat=”server”>
    Expense Report – CA, 2008
    </asp:HyperLink>
    <br />

    此标记不设置 NavigateUrl 属性,因为该属性将在代码运行时生成。

  2. 在“Solution”中,展开 Links.aspx,然后打开 或 Links.aspx.cs。
  3. 为 System.Web.Routing命名空间添加 using 语句,如下面的示例所示:
    using System.Web.Routing;
    
  4. 在Page_Load方法中,添加以下代码:
    
    

    此代码创建包含三个参数的 RouteValueDictionary 类的实例。第三个参数为 category,它不在 URL 模式中。由于它不在 URL 模式中,category 参数及其值将呈现为查询字符串参数。

  5. 在上一步中添加的代码后面,添加以下代码:
    VirtualPathData vpd =
    RouteTable.Routes.GetVirtualPath(null, “ExpensesRoute”, parameters);

    此代码通过调用RouteCollection类的GetVirtualPath方法,实例化VirtualPathData对象。由于 SalesRoute URL 模式和 ExpensesRoute URL 模式具有相似的占位符,因此它调用接受路由名称并指定 ExpensesRoute 值的重载。

  6. 在上一步中添加的代码后面,添加以下代码以设置超链接的 NavigateUrl 属性:
    HyperLink6.NavigateUrl = vpd.VirtualPath;

在 ASP.NET 页中访问 URL 参数值


在 ASP.NET 路由已调用的 ASP.NET 页中,可以在标记或代码中检索 URL 参数的值。例如,SalesReport 路由包括名为 locale 和 year 的参数,当收到匹配此模式的 URL 请求时,Sales.aspx 页中的代码可能需要将这些参数的值传递给 SQL 查询。

在下面的过程中,将使用标记访问 URL 参数值。此方法可用于在网页中显示参数值。

使用标记访问 URL 参数值
  1. 右击 Web 项目,然后单击“添加新项”。

    显示“添加新项”对话框。

  2. 选择 Web 窗体模板,然后将名称设置为“Expenses.aspx”。

    即会在“源”视图中打开 Expenses.aspx 页。

  3. 在开始和结束 <div> 标记之间添加以下标记:
    <h1>
    Expense Report for
    <asp:Literal ID=”Literal1″
    Text
    =”<%$RouteValue:locale%>”
    runat
    =”server”></asp:Literal>,
    <asp:Literal ID=”Literal2″
    Text
    =”<%$RouteValue:year%>”
    runat
    =”server”></asp:Literal>
    </h1>

    此标记使用 RouteValue 表达式提取并显示传递到页面的 URL 参数的值。

在下面的过程中,将使用代码访问参数值。此方法在您必须以某种方式(例如通过按此过程所示将 null 值转换为默认值,或通过将信息传递给 SQL 查询)处理数据时非常有用。

使用代码访问 URL 参数值
  1. 右击 Web 项目,然后单击“Add New Item”。

    显示“New Item”对话框。

  2. 选择“Web 窗体”模板,确保选中“将代码放在单独的文件中”,将名称设置为“Sales.aspx”,然后单击“添加”。

    即会在“源”视图中打开 Sales.aspx 页。

  3. 在开始和结束 <div> 标记之间添加以下标记:
    <h1>
    Sales Report for
    <asp:Literal ID=”LocaleLiteral” runat=”server”></asp:Literal>,
    <asp:Literal ID=”YearLiteral” runat=”server”></asp:Literal>
    </h1>

    此标记包括 Literal 控件,但不设置其 Text 属性,因为将在代码中设置这些属性。

  4. 在“Solution”中,展开 Sales.aspx,然后打开Sales.aspx.cs。
  5. 在 Page_Load 方法中,添加以下代码,将第一个 Literal 控件的 Text 属性设置为下列值之一:
    • 文本“所有区域设置”(如果 locale 参数为 null)。
    • locale 参数的值(如果 locale 参数不为 null)。
      LocaleLiteral.Text = Page.RouteData.Values["locale"] == null ?
          "All locales" : Page.RouteData.Values["locale"].ToString();
      
  6. 在 Page_Load 方法中,添加以下代码,以将第一个Literal控件的Text 属性设置为 year URL 参数的值:
    YearLiteral.Text = Page.RouteData.Values["year"].ToString();

测试路由


现在,可以测试路由。

测试路由
  1. 在“解决方案资源管理器”中右击 Links.aspx,并选择“在浏览器中查看”。

    该页将在浏览器中显示,如下图所示:

    Links.aspx 页面的屏幕快照

  2. 单击每个超链接。

    请注意,每个超链接转到一个其标题对应于该超链接的文本的页面。

  3. 回到 Links.aspx 页,选择浏览器的“查看源”命令,检查最后三个超链接的 URL。

    您将看到下面的自动生成的 URL:

    • http://[server]/[application]/SalesReportSummary/2011
    • http://[server]/[application]/SalesReport/CA/2009
    • http://[server]/[application]/ExpenseReport/CA/2008?category=recreation
  4. 将以 SalesReport/CA/2009 结束的 URL 复制到 Windows 剪贴板,然后关闭“查看源”窗口。
  5. 将该 URL 粘贴到浏览器的地址栏中,将 CA 更改为“invalidlocale”,将 2009 更改为“invalidyear”,然后按 Enter。

    将显示类似下图的页面:

    显示无效参数值的 SalesReport.aspx

    您可以看到显示 invalidlocale 和 invalidyear 值的销售报表页。由于没有为 SalesRoute 路由指定任何约束,因此接受无效数据。

  6. 再次将该 URL 粘贴到浏览器的地址栏中,将 CA 更改为“invalidlocale”,将 2009 更改为“invalidyear”,将 SalesReport 更改为“ExpenseReport”,然后按 Enter。

    将显示类似下图的页面:

    当费用报表 URL 违反约束时的错误

    由于 URL 未解析为路由,您会看到“未找到”错误。ExpenseReport 路由将仅接受具有两个字母字符的 locale 参数和具有四位数字的 year 参数。

原文链接

[转载]使用 ExtJS 实现 ASP.NET MVC 2 客户端验证

mikel阅读(1139)

[转载]使用 ExtJS 实现 ASP.NET MVC 2 客户端验证 – Beginor – 博客园.

ASP.NET MVC 2 中, 客户端表单验证信息不再是直接调用微软自己提供的方法, 而是将客户端表单验证的元数据放到了一个变量 (window.mvcClientValidationMetadata) 之中, 为实现第三方的客户端验证提供了可能, 由于工作中大量的使用到了 ExtJS ,于是抽时间用 ExtJS 实现了 ASP.NET MVC 2 客户端验证机制,主要有如下特点:

  • 只依赖 Ext core 即可使用,不需要完整版本的 ExtJS, 当然,如果有完整版 ExtJS 的话, 还可以调用 Ext.form.VTypes 的表单验证方法;
  • 使用方法完全遵循 ASP.NET MVC 2 提供的客户端验证机制,实现了默认的 required 、regularExpression 、 number 、range 、stringLength 客户端验证函数;
  • 可以根据 ASP.NET MVC 2 提供的验证扩展机制进行相应的扩展。

使用方法如下:

1、在要进行验证的 Model 上添加验证标记, 代码如下:

01 public class RegisterModel {
02
03 [Required(ErrorMessage = "用户名必须填写!")]
04 [DisplayName("用户名:")]
05 public string UserName {
06 get;
07 set;
08 }
09
10 [Required(ErrorMessage = "密码必须 填写!")]
11 [DisplayName("密码:")]
12 [DataType(DataType.Password)]
13 public string Password {
14 get;
15 set;
16 }
17
18 [Required(ErrorMessage = "邮箱必须 填写")]
19 [RegularExpression("", ErrorMessage = "邮件格式不正确!")]
20 [DisplayName("邮箱:")]
21 public string Email {
22 get;
23 set;
24 }
25
26 [Range(0, 100, ErrorMessage = " 年龄必须在1~100之间!")]
27 [DisplayName("年龄:")]
28 [DefaultValue(20)]
29 public int Age {
30 get;
31 set;
32 }
33
34 }

2、在 View 中添加下面的代码,除了要使用 ExtJS 的脚本之外, 与普通的 View 没有什么区别, 代码如下:

01 <!-- 先引入ExtJS -->
02 <link rel="Stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-3.2.1/resources/css/ext-all.css">
04 <script src="http://extjs.cachefly.net/ext-3.2.1/ext-all.js" type="text/JavaScript"></script>
05 <script src="/Assets/Scripts/Ext.ux.mvc.validation.js" type="text/javascript"></script>
06 <link href="/Assets/Site.css" rel="stylesheet" type="text/css">;
07
08 <!-- 下面开始表单 -->
09 <h2>ExtJS 实现 ASP.NET MVC 2 客户端验证</h2>
10 <% Html.EnableClientValidation(); %>;
11 <%= Html.ValidationSummary(true, "输入信息不完整,无法完成注册。") %>
12 <% using (Html.BeginForm()) { %>
13 <%= Html.EditorForModel() %>
14 <input value="注册" type="submit">
15 <input value="取消" type="reset">
16 <% } %>;

运行效果如下图所示:
2010-06-05_150039

如果要做扩展自定义验证的话,需要做完成下面两部分:

1、参考 msdn 文档,添加服务端验证扩展, 代码如下:

01 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
02 public class CustomAttribute : ValidationAttribute {
03
04 public override bool IsValid(object value) {
05 var val = value as string;
06 if (string.IsNullOrEmpty(val)) {
07 return false;
08 }
09 return val.Equals("Hello,world!", StringComparison.OrdinalIgnoreCase);
10 }
11 }
12
13 public class CustomValidator : DataAnnotationsModelValidator<customattribute> {
14
15 public CustomValidator(ModelMetadata metadata, ControllerContext context, CustomAttribute attribute)
16 : base(metadata, context, attribute) {
17 }
18
19 public override IEnumerable<modelclientvalidationrule> GetClientValidationRules() {
20 return new[] {
21 new ModelClientValidationRule {
22 ErrorMessage = "输入: Hello,world!",
23 ValidationType = "custom"
24 }
25 };
26 }
27 }
28 </modelclientvalidationrule></customattribute>

2、添加对应的客户端验证实现,代码如下:

1 Ext.apply(Ext.ux.mvc.VTypes, {
2 custom: function(val, param) {
3 return val.toLowerCase() == 'hello,world!';
4 }
5 });

3、在 Model 上添加属性,使用扩展验证,代码如下:

4、在 App_Start 注册该扩展,代码如下:

1 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CustomAttribute), typeof(CustomValidator));

5、添加扩展之后的运行效果如下图:
2010-06-05_150121

如果你工作中也用到了 ExtJS 和 ASP.NET MVC 2 的话,可以下载这个文件来 尝试一下。