首页 > 架构 > 利用Xapian构建自己的搜索引擎

利用Xapian构建自己的搜索引擎

一、简介

Xapian与开源

       Xapian的官方网站是http://www.xapian.org,这是一个非常优秀的开源搜索引擎项目,搜索引擎其实只是一个通俗的说法,正式的说法其实是IR(Information Retrieval)系统。Xapian的License是GPL,这意味着允许使用者自由地修改其源码并发布之。Xapian的中文资料非常少,可以说现在互联网上连一篇完整详细的Xapian中文介绍文档,更别说中文API文档了。其实,Xapian的英文资料也不多,除了官方网站上的Docs和Wiki外,还有一些网站上的邮件列表,在这方面跟Lucene没得比。当然,Lucene现在已经发展到2.x版本了,而Xapian的最新版本才1.012,国外开源项目一般对版本号控制得比较严格,一个项目一般到了1.x才算稳定和成熟的。
Xapian可以运行在那些平台?

      Xapian由C++编写,但可以绑定到Perl, Python, PHP, Java, Tcl, C# 和Ruby甚至更多的语言,Xapian可以说是STL编程的典范,在这里您可以找到熟悉的引用计数型智能指针、容器和迭代器,甚至连命名也跟STL相似,相信一定能引起喜好C++和STL的你的共鸣(实际上,很少C++程序员完全不使用STL)。由于Xapian使用的是STL和C运行时库,因此具有高度可移值性,官方说法是可以运行在Linux、 Mac OS X、 FreeBSD、 NetBSD、 OpenBSD、Solaris,、HP-UX,、Tru64和IRIX,,甚至其它的Unix平台,在Microsoft Windows上也跑得很好。当然,并不能像Java那样“一次编译,到处可以运行”,当移植到其它平台时,一般来说是需要重新编译的。至于如何在Windows32位系统下编译Xapian,请查阅我以前写的文章《nmake在windows平台下编译xapian》。
Xapian的特性

依官方的说法,Xapian是一个允许开发人员轻易地添加高级索引和搜索功能到他们的应用系统的高度可修改的工具,它在支持概率论检索模型的同时也支持布尔型操作查询集。

从功能特性上来说。Xapian和Lucene有点相似,两者都具有Term、Value(在Lucene里称为SortField)、Posting、Position和Document,不过Xapian没有Field的概念,这直接导致Xapian在使用上比Lucene麻烦了那么一点。但这完全不是问题,通过一些小技巧,完全可以自己在Xapian中实现Filed的概念。在Lucene里还有一个叫Payload的元素,即词条 (Term) 的元数据或称载荷。举一个例子,“回家吃饭吧”和“快回家吃饭”这两个句子都带有“吃饭”这个词语,但在检索的时候怎样才能将语气表达出来呢?虽然可以添加Term来解决这个问题,但由于Term的索引信息和存储信息是分开放的,相对来说I/O性能较差,Payload就是应这个问题而生的,因为Payload信息是直接放在索引里的。由于对Xapian的研究还不是很深,Xapian里是否有类似Payload这个概念,还需要继续研究。
Xapian与搜索

      搜索的目的是将结果数据展现给终端用户,搜索引擎与普通的数据库查询最大的区别就在于查询。Xapian提供了多种的查询机制。
概率性搜索排名 – 重要的词语会比不那么重要的词语得到更多的权重,因此与权重高的词语关联的Documents会排到结果列表的更前面。
相关度反馈 – 通过给予一个或多个Documents, Xapian可以显示最相关的Terms以便扩展一个Query,及显示最相关的Documents。
词组和邻近搜索 — 用户可以搜索一个精确短语或指定数组的词组。
全方位的布尔型搜索器,例如 ("stock NOT market", etc)。
支持提取搜索关键字的词干,例如当搜索“football”的时候,当Documents中含有"footballs" 或"footballer"的时候也被认作符合。这有助于找到相关结果,否则可能错过之。词干提取器现在支持Danish、Dutch、 English、 Finnish、 French、 German、 Hungarian、Italian、 Norwegian、Portuguese、Romanian、 Russian、Spanish、Swedish和Turkish。
支持通配符查询,例如“xap*”。
支持别名查询,打个比方,C++会自动转为CPlusPlus,C#则自动转为CSharp。
Xapian支持拼写纠正,例如xapian会被纠正为xapain,当然这必须基于词组已经被索引了。这特性跟Google提供的“你是不是想搜索xxx”有点相似。
Xapian的存储系统

      Xapian现在的版本默认是使用flint作为存储系统,flint是以块的形式来存储,默认每块是8K,理论上每一个文件最大可以达到2048GB。当然,在旧式的文件系统,例如FAT/FAT32是不可能实现的。熟悉Windows内存管理机制的朋友一定知道使用Windows32位系统每个进程的总虚拟地址空间只有4GB,而用户模式连2GB都不够(Windows2003可以将用户模式扩展到3GB左右),因此应用程序不可能一次过将整个Database文件读取到内存中,通常的做法是使用内存映射文件,先预订地址空间,在真正使用的时候才调拨内存,而内存分页粒度是4k,也就是说内存中每一页是4k,而在IA64系统中,内存分页粒度是8k。在内存中,除了页外,还有区块,X86和IA64的内存区块的粒度都是64k。Xapian这样存储数据估计是为了在各个平台上都能实现数据对齐,数据对齐对于cpu运算寻址是非常重要的,而8和64都是4的倍数,因此大胆猜想Xapian以8k作为存储系统的默认块大小是为了在性能和兼容性中取得最平衡和最优值。

Xapian使用unsigned 32-bit ints作为Documents的id值,因此在每个Xapian的Database中,最多可容纳40亿个Documents。而Xapian的Terms和Documents都是使用B-树来存储的,其实很多数据库系统(这里所指的是关系数据库)的索引都是用B-树或B+树来存储的,具有增删改查比较方便迅速的特点,缺点则是如果索引被删除后的空间不能重复利用,为了提高性能,通常要经常重建索引。
Xapian的性能

      搜索引擎的性能是用户非常关心的一部分,Xapian的性能如何?官方的原话如下:The short answer is "very well" – a previous version of the software powered BrightStation’s Webtop search engine, which offered a search over around 500 million web pages (around 1.5 terabytes of database files). Searches took less than a second.。在5亿个网页共1.5TB大小的文件中,搜索只需要小于一秒就完事了。当然,这跟运行的平台和机器是密切相关,在我们自己构建好Xapian搜索引擎应用后,我们也可以测测具体的速度。
Xapian的绝佳范例

Xapian的官方网站上有一个绝佳的使用范例,这个称为Omega的项目甚至可以开箱即用作为一个CGI应用程序。Omega附带了Omindex和ScriptIndex这两个索引生成工具,可以将硬盘上的html,pdf,图片甚至视频影片索引起来并生成Database,通过操作这些由Omindex或ScriptIndex生成的Database,Omega提供了搜索这些文件的功能。
关于《利用Xapian构建自己的搜索引擎》系列

在使用Xapian的过程中,我一般是查阅http://www.xapian.org/docs/上的Doc、API Doc和Wiki,遇到困难时则查阅Omega的源代码并互相印证之。实在没办法的时候只能从Google上找找一些网站的邮件列表,可以说是磕磕碰碰地将Xapian的大部分功能玩了一遍。有一些专有名词我虽然知道大概意思,但无法准确地翻译出来,因此《利用Xapian构建自己的搜索引擎》这一系列的内容可能会错漏百出。不过如果这一系列文章可以引起大家对Xapian的兴趣,它所得到的批评才是它最大的价值。

在后续文章中,我会从Xapian的Database开始一步一步构建搜索引擎应用,并配以自己的理解,请大家一起讨论。

由于工作原因,一般只有晚上才有时间写文章,在写的过程中还要不断印证自己的想法是否正确,免得经常作无谓猜测而导致欠缺严谨性,因此不能保证每天都能更新,请大家见谅。

二、Database

     在Xapian1.0之前,是使用quartz作为database文件格式的,不过自从1.0之后,便改用Flint作为database的文件格式了。有时候,我们会将database称为“索引”,在Xapian中,索引通常比被索引的documents还要多,这表示Xapian做一个信息检索系统比做一个信息存储系统更适合。
  Database的存储结构

Xapian的database是所有用于检索的信息表的集合,以下的表是必需的:

l        posting list table 保存了被每一个term索引的document,实际上保存的应该是document在database中的Id,此Id是唯一的。

l        record table 保存了每一个document所关联的data,data不能通过query检索,只能通过document来获取。

l        term list table 保存了索引每个document的所有的term。

以下的表是可选的,即当有以下的类型的数据需要被存储的时候才会出现(在1.0.1以前,position和value表就算是没有数据的时候也会被创建,而spelling和synonym表是1.0.2后才出现的)。

l        position list table 保存了每一个Term出现在每一个document中的位置

l        value table 保存了每一个document的values,values是用作保存、排序或其它作用的。

l        spelling table 保存了拼写纠正的数据。

l        synonym table 保存术语的字典,例如NBA、C#或C++等。

以上的每一个集合是保存在独立的文件中,以便允许管理员查看其中的数据。刚刚说了,有一些表不是必需的,例如当您不需要词组搜索的时候,没必要存储任何的postionlist信息。

如果你看过Xapian的database,你会发现以上的每一个表其实是使用了2到3个文件的,如果您正在使用“flint”作为database的存储格式,那么termlist表会被存储为以下三个文件“termlist.baseA”、“termlist.baseB”、“termlist.dB”。在这些文件中,其实只有”.db”文件存储了真实的数据,“.baseA”和“baseB”文件是用作跟踪如果于“.dB”文件中查找数据。通常只会出现一个“.baseA”文件和一个“.baseB”文件。

在前一篇《利用Xapian构建自己的搜索引擎:简介》中提到过,Xapian现在的版本默认是使用flint作为存储系统,“.dB”文件是以块的形式来存储,默认每块是8K,第一块是用作信息头,如果使用UltraEdit等二进制查看工具,会发现所有“.dB”文件的前三个字节都是0x00。因此,当“.dB”中仅有一条数据的时候整个文件也会有16KB时,切莫大惊小怪。

改变“.dB”文件的默认块大小会导致性能变化,但结果很难说是好是坏,因为这是跟所承载的硬件平台与操作系统平台有关的。一般来说,B树的分支因子(即每个结点能容纳的关键字的数量)越大,B树的查找性能就越强;但由于通常情况下,B树的结点都是存储在永久存储系统(例如硬盘/磁带)中,每次访问某个结点都会将整个结点由永久存储系统读入到内存中,这是一个博弈的过程:假设一棵数据量很大的B树,将B树的分支因子设到很大,这棵B树会长得很矮,从理论上来说查找性能可能很高。但这样就带来了一个弊端,每个结点所占的内存非常多,如果在一个并发访问量很大的IR系统中采用这种方式的话所使用的内存必定是非常可观的。因此在调整“.dB”文件的默认块大小的时候一定要充分考虑cpu体系和操作系统平台,以便调整到最佳性能。
原子性修改

Xapian能保证对database的所有修改都是原子性的,这意味着:

l        从一个独立的进程(或一个独立的database对象在同一个进程)角度来看,在读取数据库的时候,直到修改成功提交,所有对数据库的修改都是不可见的。

l        Database在硬盘中的状态始终是保持一致的。

l        如果在修改的过程中系统发生中断,只要硬件不发生故障(硬盘损坏),就算电源被切断,database应该总是被还原到有效的状态。

提交一个修改需要几次的系统调用,以便使所有缓存的修改能刷新到硬盘上,这样能确保就算系统在任何时候发生错误,database也能处于一致的状态。当然,这样相对于说会慢了一点(因为系统已经准备好往硬盘上写数据了),因此将几次的修改组合在一起往硬盘上写会有一定的性能提升。

多个修改操作可以显式地组合在一个事务中,如果一个应用程序不显式地使用事务来保护修改操作,Xapian会将这些修改组合在一个事务中,然后批量进行修改。请注意,Xapian现在暂时还不能跨database进行事务操作。

如果要想迅速地生成非常大的database,请使用“DANGEROUS”关键字搜索Xapian的邮件列表,其提供了可以重新编译Xapian而不采用原子性修改的方法,这功能已经不再整合在xapian的标准版本中了。
Single writer, multiple reader

Xapian实现了“单写多读”的模式,这意味着任何时候,同一时刻只允许一个对象可以修改database,但允许多个对象可以读取database。

在*nix系统下,Xapian使用“lock-files”强制约束来实现此模式,在一个flint database中,每一个Xapian的database目录包含了一个名为“flintlock”的文件以作锁定用途。此文件总在存在于database的目录里,当database被打开用作写入的时候,此文件会被fcntl()方法锁定。每一个WritableDatabase打开的时候,都会产生一个子进程以便进行锁定操作。如果某个database写入器(一般是指WritableDatabase)还没有机会执行释放锁的清除操作便退出了(例如这个WritableDatabase所在的应用程被杀死了),fcntl()产生的锁会自动被操作系统释放。

在Microsoft Windows下,使用的是另一种锁定的技术,此技术不需要产生了子进程来进行锁定操作,但同理,当写入器退出时,操作系统依然会自动释放锁。熟悉Windows机制的朋友知道,Windows是使用文件句柄来操作文件的,而文件句柄是属于内核资源的一种,在任何情况下,Windows都能保证应用程序在退出时能释放所有的资源。
网络文件系统

Xapian现在可以工作在一个网络文件系统中,但存在着大量的潜在问题。因此建议在部署前要大量地在特定的网络中测试。

请注意,Xapian是非常依赖I/O操作的,除非处于一个性能非常优秀的网络,在一个网络文件系统中进行操作会相对的慢一点。

Xapian需要可以在database的目录里创建一个lock file,在某些网络文件系统中(例如NFS),这需要一个锁定的守护进程在运行,也就是上面所提到的子进程。

创建database

      说了一堆的理论,下面我们来实战创建一个database。Xapian里的所有类都处于Xapian这个命名空间里,Xapian::Database是所有Database的基类,实际上,它只有一个子类,那就是WritableDatabase。从面向对象的角度来看,这两个类设计得非常好,Xapian::Database拥有大部分只读或内存操作的方法,而Xapian::WritableDatabase则拥有事务操作,刷新数据到硬盘等方法。

      有几种创建Database的方法:

l        Flint 如果你是使用远程后端(指网络文件系统),请使用Xapian::Flint::open()方法来创建database。使用此方法你能得到更多的控制,例如创建只读的database,或创建可写的database。

l        Auto auto并不是一种database格式,你可以创建一个“database存根”文件,此文件能列出一到多个database的路径,这些路径可以作为Xapian::Database的构造函数的参数,从而被自动检测是哪种类型的database。还有,如果将一个文件路径名称而非目录名称作为参数传入到Xapian::Database的构造函数中,Xapian::Database会认为你传入了一个“database存根”文件;当然,你也可以使用Xapian::Auto::open_stub()来显式打开一个存根文件。上面说的可能有点绕口,“database存根文件”的格式是每个database一行,例如:

remote localhost:23876
flint /var/spool/xapian/webindex

这下该明白了。

l        Inmemory 还可以创建内存databse,这种类型的database是保存在内存中。请注意,通过Xapian::InMemory::open()返回的类型是WritableDatabase,这意味着这是一个可刷新到硬盘上的database,它最初是为测试之用,但在建立临时的小数据库也可能是有用的。

实际上,创建一个database还有更通用的方法,例如通过将一个database所在的完整目录作为一个字符串传入到Xapian::Database的构造函数中实例化一个对象后,即可得到一个只读的database。而Xapian::WritableDatabase则复杂一点,除了要传入database的路径外,还需要设定如何打开database。有以下几个参数:

l        Xapian::DB_CREATE_OR_OPEN 打开以便读写,如果不存在则创建。

l        Xapian::DB_CREATE 总是创建新的database,如果存在则失败。

l        Xapian::DB_CREATE_OR_OVERWRITE 如果database存在的话则覆盖之,如果不存在则创建。

l        Xapian::DB_OPEN 打开以便读写,如果不存在则失败。

成功打开一个datababse是Xapian所有后续操作如检索,写入的基础。你甚至可以将通过add_database()方法则多个database组合在一起访问;如果想将database刷新到硬盘中,则执行flush()方法则可。最后,如果不想使用database了,将database对象销毁即可。
小结

在这一章里,似乎并没有多少具体的操作,但database是Xapian的存储系统,在Xapian所有操作的基础,只有清楚明白了Xapian的存储方式才能更好更高效地构造自己的搜索引擎。同时,如果您之前并没有对大型的文件存储系统有所了解的话,这篇文件可以多多少少带给您一些启示。在下一章里,我会继续介绍Document和Term等Xapian的组成部分。

三、Document、Term和Value

在上一篇《利用Xapian构建自己的搜索引擎:Database》里指出database是Xapian的基础,而这一篇里讲到的documents、terms和values则是索引和查询的必要组成部分。
Documents 、terms and posting

在信息检索(IR)中,我们企图要获取的项称之为“document”,每一个document是被一个terms集合所描述的。 “document”和“term”这两个词汇是IR中的术语,它们是来自“图书馆管理学”的。通常一个document认为是一块文本,. Usually a document is thought of as a piece of text, most likely in a machine readable form, 而一个term则是一个词语或短语以用作描述document的,在document中大多数会存在着多个term,例如某个document是跟口腔卫生相关的,那么可能会存在着以下的terms:“tooth”、“teeth”、“toothbrush”、“decay”、 “cavity”、“plaque”或“diet”等等。

如果在一个IR系统中,存在一个名为D的document,此document被一个名为t的term所描述,那么t被认为索引了D,可以用以下式子表示:t->D。在实际应用的一个IR系统中通常是多个documents,如D1, D2, D3 …组成的集合,且有多个term,如t1, t2, t3 …组成的集合,从而有以下关系:ti -> Dj。

如果某个特定的term索引了某个特定的document,那么称之为posting,说白了posting就是带position信息的term,在相关度检索中可能有一定的用途的。

给定一个名为D的document,存在着一个terms列表索引着它,我们称之为D的term list。

给定一个名为t的term,它索引着一个documents列表,这称之为t的posting list(使用“Document list”可能会在叫法上更一致,但听起来过于空泛)。

在一个存在于计算机的IR系统中,terms是存储于索引文件中的。term可以用作有效地查找它的posting list,在posting list里,每一个document带有一个很短的标识符,就是document id。简单来说,一个posting list可以被认为是一个由document ids组成的集合,而term list则是一个字符串组成的集合。在某些IR系统的内部是使用数字来表示term的,因此在这些系统中,term list则是数字组成的集合,而Xapian则不是这样,它使用原汁原味的term,而使用前缀来压缩存储空间。

Terms不一定是要是document中出现的词语,通常它们会被转换为小写,而且往往它们被词干提取算法处理过,因此通过一个值为“connect”的term可能会检索出一系列的词语,例如“connect”、“connects”、“connection”或“connected”等,而一个词语也可能产生多个的terms,例如你会将提取出的词干和未提取的词语都索引起来。当然,这可能只适用于英语、法语或拉丁语等欧美系列的语言,而中文的分词则有很大的区别,总的来说,欧美语系的语言分词与中文分词有以下的区别:

l        拿英语来说,通常情况下英语的每一个词语之间是用空格来隔开的,而中文则不然,甚至可以极端到整篇文章都不出现空格或标点符号。

l        像上面提到的,“connect”、“connects”、“connection”或“connected”分别的意思“动词性质的连接”、“动词性质的第三人称的连接”、“名称性质的连接”或“连接的过去式”,但在中文里,用“连接”就可以表示全部了,几乎不需要词干提取。这意味着英语的各种词性大部分是有章可循的,而中文的词性则是天马行空的。

l        第二点只是中文分词非常困难的一个缩影,要完全正确地标识出某个句子的语意是很困难的,例如“中华人民共和国成立了”这个句子,可以分出“中华”、“华人”、“人民”、“共和国”、“成立”等词语,不过其中“华人”跟这个句子其实关系不大。咋一眼看上去很简单,但机器那有这么容易懂这其中的奥妙呢?

Values

Values是附加在document上一种元数据,每一个document可以有多个values,这些values通过不同的数字来标识。Values被设计成在匹配过程中快速地访问,它们可以用作排序、排队多余重复的document和范围检索等用途。虽然values并没有长度限制,但最好让它们尽可能短,如果你仅仅是想存储某个字段以便作为结果显示,那么建议您最好将它们保存在document的data中。
Document data

      每一个Document只有一个data,可以是任意类型格式的数据,当然在存储的时候请先转换为字符串。这听上去可能有点古怪,实情是这样的:如果要存储的数据是文本格式,则可以直接存储;如果要存储的数据是各种的对象,请先序列化成二进制流再保存,而在读取的时候反序列化读取。
UTF-8与Unicode

      Xapian里的所有东西是用UTF-8来保存的,UTF-8是Unicode的一种实现。现在很多人用VC为了方便是将编码设成“未设置”或“多字节”的,也就是说用的是系统内码(GB2312/GBK/ GB18030),这样的话则将数据保存到Xapian前要先转码为UTF-8,而从Xapian里读出的数据则要转码为GB2312/GBK/ GB18030才能正确显示,这里推荐用iconv,这是一个非常方便的库。
分词

     很多文章都说现在的中文分词已经很成熟的,但据实际考察,google或百度等大公司的分词引擎都是自己开发或有专门的公司开发的,的确已经算比较成熟。但市场上提供免费甚至开源的分词引擎不多,中科院研发的ictclas30分词精确度和分词速度都非常不错,而且还有词性标注和自定义添加词的功能,可惜不开源。另外比较受欢迎的还有libmmseg和SCWS,因此都是开源的,不过经测试libmmseg的分词精度似乎不高,而SCWS由于使用了大量的递归,在生成词库的时候经常导致栈溢出(我是用vc2005编译的),需要自己将递归修改为循环,从演示的情况来看,SCWS的分词精度来算可以。
实战

由于Xapian并不像Lucene那样有Field的概念,因此一般采用以大写字母作为Term和posting的前缀,但单个字母的前缀对程序员太不友好了,所以一般的做法是自定义一个用户前缀到term前缀的映射,如Title=>T,而Xapian的QueryParser也支持这种映射,QueryParser是查询解释器,能将一段字符串解释为Xapian的Query,后面会陆续提到。

添加document的例子:

Xapian::Document doc;

   doc.add_term("K你好");

   doc.add_term("K那里");

   //posting是带position的term

   doc.add_posting("K吃饭", 14);

   doc.add_posting("K玩耍", 8);

   /*

   这里最好先用一个map<string, int>放置value的名称和索引的配对

   这里使用起来像Lucene的SortField一样了。

   */

   doc.add_value(1, "1");

   doc.set_data("你好啊,在那里玩耍呢?还没吃饭吗?");

   //创建一个可写的db

   Xapian::WritableDatabase db("c:\\db");

   //将document加入到db中,返回document的id,此id在db中是唯一的

   Xapian::docid id = db.add_document(doc);

   //刷新到硬盘中

   db.flush();

获取document信息的例子:

//获取

   Xapian::Document doc = db.get_document(id);

   string v = doc.get_value(1);

   printf(v);//输出

   string data = doc.get_data();

   printf(data);//输出"你好啊,在那里玩耍呢?还没吃饭吗?"

   for (Xapian::TermIterator iter = doc.termlist_begin(); iter != doc.termlist_end(); ++iter)

   {

       printf(*iter);//依次输出term和posting

   }

上面的两个例子比较简单,如果要想更深入请查阅Omega的代码,里面有更复杂的应用。值得一提的Xapian里有一个TermGenerator,可以更方便地索引数据,不过这个类有两个不知道算不算缺点的特点:首先是依赖Stem,对于中文来说除非自己实现了一个Stem,否则TermGenerator用处不大;另外TermGenerator会自动将生成的term或posting添加“Z”前缀。

在这里要提一下一个名为“Xapwrap”的东东,这是某个外国人用python写的一个封装Xapian的类库,里面某些思想还是不错的,只可惜只兼容Xapian 1.x之前的版本。我自己封装的类有一部分就是参考Xapwrap的。

下面是一段我正在用的代码:

//CXapianDocument是封装过的Xapian::Document

void  doSegment(CXapianDocument& document, const char* lpszInput, string strUserPrefix)

{

   //先分词,这里使用的是中科院的分词引擎

   int nCount = ICTCLAS_GetParagraphProcessAWordCount(lpszInput);

   result_t *result =(result_t*)malloc(sizeof(result_t)*nCount);

   //获取分词结果

   ICTCLAS_ParagraphProcessAW(nCount,result);

   string termPrefix;

   //通过用户前缀取得term前缀,这是我自定义的一个宏

   GetTermPrefixFromMap(this->m_userPrefixToTermPrefixMap, strUserPrefix, termPrefix)

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

   {

       //忽略标点符号,标点符号的词性标注为w开头的

       if(result[i].sPOS[0] == ‘w’)

       {

           continue;

       }

       char buf[100];

       memset(buf, 0, 100);

       int index = result[i].start;

       memcpy(buf,(void *)(lpszInput+index), result[i].length);

       //添加posting

       document.AppendPosting(termPrefix, buf, result[i].start);

   }
   free(result);
}

原文引用:http://visualcatsharp.javaeye.com/blog/392326

分类: 架构 标签:
  1. 2013年7月26日13:44 | #1

    Hey there! I’ve been following your site for some time now and finally got the courage to go ahead and give you a shout out from Kingwood Texas! Just wanted to mention keep up the good work!

  1. 本文目前尚无任何 trackbacks 和 pingbacks.

=5加7(必填)请输入两数相加的结果。