??xml version="1.0" encoding="utf-8" standalone="yes"?>
作者:forfuture1978 (from CSDN)
我们都知道,Lucene是一个开放源代码 的全文检索引擎工具包。那么全文检索到底是什么? q要从我们生zM的数据说赗?/span>
我们生活中的数据M分ؓ两种Q?span style="color: #0000ff">l构化数?/strong> ?span style="color: #0000ff">非结构化数据 ?/p>
当然有的地方q会提到W三U,半结构化数据Q如XMLQHTML{,当根据需要可按结构化数据来处理,也可抽取出纯文本按非l构化数据来处理?/p>
非结构化数据又一U叫法叫全文数据?/strong>
按照数据的分c,搜烦也分ZU: 寚wl构化数据也卛_全文数据的搜索主要有两种ҎQ?/p>
一U是序扫描?/strong> (Serial Scanning) Q?/strong> 所谓顺序扫描,比如要找内容包含某一个字W串的文Ӟ是一个文档一个文档的看,对于每一个文档,从头看到,如果此文档包含此字符Ԍ则此文档为我们要扄文gQ接着看下一个文Ӟ直到扫描完所有的文g。如利用windows的搜索也可以搜烦文g内容Q只是相当的慢。如果你有一?0G盘Q如果想在上面找C个内容包含某字符串的文gQ不׃几个时Q怕是做不到。Linux下的grep命o也是q一U方式。大家可能觉得这U方法比较原始,但对于小数据量的文gQ这U方法还是最直接Q最方便的。但是对于大量的文gQ这U方法就很慢了?/p>
有h可能会说Q对非结构化数据序扫描很慢Q对l构化数据的搜烦却相对较快(׃l构化数据有一定的l构可以采取一定的搜烦法加快速度Q,那么把我们的非结构化数据惛_法弄得有一定结构不p了吗Q?/p>
q种x很天Ӟ却构成了全文索的基本思\Q也卛_非结构化数据中的一部分信息提取出来Q重新组l,使其变得有一定结构,然后Ҏ有一定结构的数据q行搜烦Q从而达到搜索相对较快的目的?/p>
q部分从非结构化数据中提取出的然后重新组l的信息Q我们称?strong>索引 q种说法比较抽象QD几个例子很Ҏ明白Q比如字典,字典的拼韌和部首检字表q当于字典的烦引,Ҏ一个字的解释是非结构化的,如果字典没有韌表和部首字表Q在茫茫辞v中找一个字只能序扫描。然而字的某些信息可以提取出来进行结构化处理Q比如读韻I比较结构化Q分声母和韵母,分别只有几种可以一一列DQ于是将读音拿出来按一定的序排列Q每一读音都指向此字的详l解释的|。我们搜索时按结构化的拼x到读韻I然后按其指向的页敎ͼ便可扑ֈ我们的非l构化数据——也卛_字的解释?/p>
q种先徏立烦引,再对索引q行搜烦的过E就叫全文检?Full-text Search) ?/strong>
下面q幅图来自《Lucene in action》,但却不仅仅描qCLucene的检索过E,而是描述了全文检索的一般过E?img height="479" alt="" src="http://dl.javaeye.com/upload/picture/pic/55997/9512a770-0784-3231-9da5-608a46014b2d.png" width="544" /> 全文索大体分两个q程Q?span style="color: #0000ff">索引创徏 (Indexing) ?span style="color: #0000ff">搜烦索引 (Search) ?/p>
于是全文索就存在三个重要问题Q?/p>
1. 索引里面I竟存些什么?(Index) 2. 如何创徏索引Q?Indexing) 3. 如何对烦引进行搜索?(Search) 下面我们序Ҏ个个问题q行研究?/p>
一?/span> 索引里面I竟存些什么?(Index) 首先我们来看Z么顺序扫描的速度慢: 其实是由于我们想要搜索的信息和非l构化数据中所存储的信息不一致造成的?/p>
非结构化数据中所存储的信息是每个文g包含哪些字符Ԍ也即已知文gQ欲求字W串相对ҎQ也x从文件到字符串的映射。而我们想搜烦的信息是哪些文g包含此字W串Q也卛_知字W串Q欲求文Ӟ也即从字W串到文件的映射。两者恰恰相反。于是如果烦引总能够保存从字符串到文g的映,则会大大提高搜烦速度?/p>
׃从字W串到文件的映射是文件到字符串映的反向q程Q于是保存这U信息的索引UCؓ反向索引 ?/p>
反向索引的所保存的信息一般如下: 假设我的文档集合里面?00文档,Z方便表示Q我们ؓ文档~号??00Q得C面的l构 左边保存的是一pd字符ԌUCؓ词典 ?/span> 每个字符串都指向包含此字W串的文?Document)链表Q此文档链表UCؓ倒排?/strong> (Posting List) ?/p>
有了索引Q便使保存的信息和要搜烦的信息一_可以大大加快搜烦的速度?/p>
比如_我们要寻找既包含字符?#8220;lucene”又包含字W串“solr”的文档,我们只需要以下几步: 1. 取出包含字符?#8220;lucene”的文档链表?/p>
2. 取出包含字符?#8220;solr”的文档链表?/p>
3. 通过合ƈ链表Q找出既包含“lucene”又包?#8220;solr”的文件?img height="55" alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56001/7e04a727-6cdb-3434-a260-023bc163b497.jpg" width="544" /> 看到q个地方Q有人可能会_全文索的加快了搜烦的速度Q但是多了烦引的q程Q两者加h不一定比序扫描快多。的,加上索引的过E,全文索不一定比序扫描快,其是在数据量小的时候更是如此。而对一个很大量的数据创建烦引也是一个很慢的q程?/p>
然而两者还是有区别的,序扫描是每ơ都要扫描,而创建烦引的q程仅仅需要一ơ,以后便是一x逸的了,每次搜烦Q创建烦引的q程不必l过Q仅仅搜索创建好的烦引就可以了?/p>
q也是全文搜索相对于序扫描的优势之一Q一ơ烦引,多次使用?/strong> 二?/span> 如何创徏索引Q?Indexing) 全文索的索引创徏q程一般有以下几步Q?/p>
1、一些要索引的原始文?Document) Z方便说明索引创徏q程Q这里特意用两个文gZQ?/p>
文g一QStudents should be allowed to go out with their friends, but not allowed to drink beer. 文g二:My friend Jerry went to school to see his students but found them drunk which is not allowed. 2、将原始文档传给分词lg(Tokenizer) 分词lg(Tokenizer)会做以下几g事情( 此过E称为Tokenize) Q?/strong> 1. 文档分成一个一个单独的单词?/strong> 2. 去除标点W号?/strong> 3. 去除停用?Stop word) ?/strong> 所?strong>停用?Stop word)
p中挺?Stop word)如:“the”,“a”Q?#8220;this”{?/p>
对于每一U语a的分词组?Tokenizer)Q都有一个停?stop word)集合?/p>
l过分词(Tokenizer) 后得到的l果UCؓ词元(Token) ?/strong>
在我们的例子中,便得C下词?Token)Q?/p>
“Students”Q?#8220;allowed”Q?#8220;go”Q?#8220;their”Q?#8220;friends”Q?#8220;allowed”Q?#8220;drink”Q?#8220;beer”Q?#8220;My”Q?#8220;friend”Q?#8220;Jerry”Q?#8220;went”Q?#8220;school”Q?#8220;see”Q?#8220;his”Q?#8220;students”Q?#8220;found”Q?#8220;them”Q?#8220;drunk”Q?#8220;allowed”?/p>
3、将得到的词?Token)传给语言处理lg(Linguistic Processor)
语言处理lg(linguistic processor)主要是对得到的词?Token)做一些同语言相关的处理?/p>
对于pQ语a处理lg(Linguistic Processor) 一般做以下几点Q?/strong>
1. 变ؓ写(Lowercase) ?/strong>
2. 单词羃减ؓ词根形式Q如“cars ”?#8220;car ”{。这U操作称为:stemming ?/strong>
3. 单词{变ؓ词根形式Q如“drove ”?#8220;drive ”{。这U操作称为:lemmatization ?/strong>
Stemming ?lemmatization的异同:
语言处理lg(linguistic processor)的结果称(Term) ?/strong>
在我们的例子中,l过语言处理Q得到的?Term)如下Q?/p>
“student”Q?#8220;allow”Q?#8220;go”Q?#8220;their”Q?#8220;friend”Q?#8220;allow”Q?#8220;drink”Q?#8220;beer”Q?#8220;my”Q?#8220;friend”Q?#8220;jerry”Q?#8220;go”Q?#8220;school”Q?#8220;see”Q?#8220;his”Q?#8220;student”Q?#8220;find”Q?#8220;them”Q?#8220;drink”Q?#8220;allow”?/p>
也正是因为有语言处理的步骤,才能使搜索droveQ而drive也能被搜索出来?/p>
4、将得到的词(Term)传给索引lg(Indexer)
索引lg主要是ؓ了得C面这个图Q?img alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56003/7ca0f9b1-e82a-35c1-a97e-e40c2d4a70c9.jpg" />
在此表中Q有几个定义Q?/p>
所以对?Term) “allow”来讲Qd有两文档包含此?Term)Q从而词(Term)后面的文档链表d有两,W一表C包?#8220;allow”的第一文档,?1h档,此文档中Q?#8220;allow”出现?ơ,W二表C包?#8220;allow”的第二个文档Q是2h档,此文档中Q?#8220;allow”出现?ơ?/p>
到此为止Q烦引已l创建好了,我们可以通过它很快的扑ֈ我们惌的文档?/p>
而且在此q程中,我们惊喜地发玎ͼ搜烦“drive”Q?#8220;driving”Q?#8220;drove”Q?#8220;driven”也能够被搜到。因为在我们的烦引中Q?#8220;driving”Q?#8220;drove”Q?#8220;driven”都会l过语言处理而变?#8220;drive”Q在搜烦Ӟ如果您输?#8220;driving”Q输入的查询语句同样l过我们q里的一C步,从而变为查?#8220;drive”Q从而可以搜索到惌的文档?/p>
三?/span> 如何对烦引进行搜索?(Search)
到这里似乎我们可以宣?#8220;我们扑ֈ惌的文档了”?/p>
然而事情ƈ没有l束Q找C仅仅是全文检索的一个方面。不是吗Q如果仅仅只有一个或十个文档包含我们查询的字W串Q我们的找C。然而如果结果有一千个Q甚x千上万个呢?那个又是您最惌的文件呢Q?/p>
打开Google吧,比如说您惛_微Y找䆾工作Q于是您输入“Microsoft job”Q您却发现d?2600000个结果返回。好大的数字呀Q突然发现找不到是一个问题,扑ֈ的太多也是一个问题。在如此多的l果中,如何最相关的放在最前面呢?
当然Google做的很不错,您一下就扑ֈ了jobs at Microsoft。想象一下,如果前几个全部是“Microsoft does a good job at software industry…”是多么可怕的事情呀?/p>
如何像Google一P在成千上万的搜烦l果中,扑ֈ和查询语句最相关的呢Q?/span>
如何判断搜烦出的文档和查询语句的相关性呢Q?/span>
q要回到我们W三个问题:如何对烦引进行搜索?
搜烦主要分ؓ以下几步Q?/p>
1、用于输入查询语?/span>
查询语句同我们普通的语言一P也是有一定语法的?/p>
不同的查询语句有不同的语法,如SQL语句有一定的语法?/p>
查询语句的语法根据全文检索系l的实现而不同。最基本的有比如QAND, OR, NOT{?/p>
举个例子Q用戯入语句:lucene AND learned NOT hadoop?/p>
说明用户x一个包含lucene和learned然而不包括hadoop的文档?/p>
2、对查询语句q行词法分析Q语法分析及语言处理
׃查询语句有语法,因而也要进行语法分析,语法分析及语a处理?/p>
词法分析主要用来识别单词和关键字?/strong>
如上qC子中Q经q词法分析,得到单词有luceneQlearnedQhadoop, 关键字有AND, NOT?/p>
如果在词法分析中发现不合法的关键字,则会出现错误。如lucene AMD learnedQ其中由于AND拼错Q导致AMD作ؓ一个普通的单词参与查询?/p>
语法分析主要是根据查询语句的语法规则来Ş成一语法树?/strong>
如果发现查询语句不满法规则,则会报错。如lucene NOT AND learnedQ则会出错?/p>
语言处理同烦引过E中的语a处理几乎相同?/strong>
如learned变成learn{?/p>
3、搜索烦引,得到W合语法树的文档
此步骤有分几步Q?/p>
首先Q在反向索引表中Q分别找出包含luceneQlearnQhadoop的文档链表?strong>
其次Q对包含luceneQlearn的链表进行合q操作,得到既包含lucene又包含learn的文档链表?strong>
然后Q将此链表与hadoop的文档链表进行差操作Q去除包含hadoop的文档,从而得到既包含lucene又包含learn而且不包含hadoop的文档链表?strong>
最后,此文档链表就是我们要扄文档?
4、根据得到的文档和查询语句的相关性,对结果进行排序?/span>
虽然在上一步,我们得到了想要的文档Q然而对于查询结果应该按照与查询语句的相x进行排序,相兌越靠前?/p>
如何计算文档和查询语句的相关性呢Q?/span>
不如我们把查询语句看作一片短的文档Q对文档与文档之间的相关?relevance)q行打分(scoring)Q分数高的相x好Q就应该排在前面?/p>
那么又怎么Ҏ档之间的关系q行打分呢?
首先Q一个文档有很多?Term)l成 Q?/span> 如search, lucene, full-text, this, a, what{?/p>
其次对于文档之间的关p,不同的Term重要性不?/strong> Q?/span> 比如对于本篇文档Qsearch, Lucene, full-textq寚w要一些,this, a , what可能相对不重要一些。所以如果两文档都包含search, LuceneQfulltextQ这两篇文档的相x好一些,然而就一文档包含this, a, whatQ另一文档不包含this, a, whatQ也不能影响两篇文档的相x?/p>
因而判断文档之间的关系Q首先找出哪些词(Term)Ҏ档之间的关系最重要Q如search, Lucene, fulltext。然后判断这些词(Term)之间的关pR?/p>
扑և?Term) Ҏ档的重要性的q程UCؓ计算词的权重(Term weight) 的过E?/strong> 计算词的权重(term weight)有两个参敎ͼW一个是?Term)Q第二个是文?Document)?/p>
词的权重(Term weight)表示此词(Term)在此文档中的重要E度Q越重要的词(Term)有越大的权重(Term weight)Q因而在计算文档之间的相x中发挥更大的作用?/p>
判断?Term) 之间的关pM而得到文档相x的q程应用一U叫做向量空间模型的法(Vector Space Model) ?/strong>
下面仔细分析一下这两个q程Q?/p>
影响一个词(Term)在一文档中的重要性主要有两个因素Q?/p>
Ҏ理解吗??Term)在文档中出现的次数越多,说明此词(Term)对该文档重要,?#8220;搜烦”q个词,在本文档中出现的ơ数很多Q说明本文档主要是讲这斚w的事的。然而在一英语文档中Qthis出现的次数更多,p明越重要吗?不是的,q是q二个因素q行调整Q第二个因素说明Q有多的文档包含此?Term), 说明此词(Term)太普通,不以区分这些文档,因而重要性越低?/p>
q也如我们程序员所学的技术,对于E序员本w来_q项技术掌握越p好(掌握深说明花时间看的越多,tf大Q,扑ַ作时有竞争力。然而对于所有程序员来说Q这Ҏ术懂得的越好(懂得的hdf)Q找工作有竞争力。h的h值在于不可替代性就是这个道理?/p>
道理明白了,我们来看看公式: q仅仅只term weight计算公式的简单典型实现。实现全文检索系l的Z有自q实现QLucene׃此稍有不同?/p>
我们把文档看作一pd?Term)Q每一个词(Term)都有一个权?Term weight)Q不同的?Term)Ҏ自己在文档中的权重来影响文档相关性的打分计算?/p>
于是我们把所有此文档中词(term)的权?term weight) 看作一个向量?/p>
Document = {term1, term2, …… ,term N} Document Vector = {weight1, weight2, …… ,weight N} 同样我们把查询语句看作一个简单的文档Q也用向量来表示?/p>
Query = {term1, term 2, …… , term N} Query Vector = {weight1, weight2, …… , weight N} 我们把所有搜索出的文档向量及查询向量攑ֈ一个Nl空间中Q每个词(term)是一l?/p>
我们认ؓ两个向量之间的夹角越,相关性越大?/p>
所以我们计夹角的余ug为相x的打分Q夹角越,余uD大,打分高Q相x越大?/p>
有h可能会问Q查询语句一般是很短的,包含的词(Term)是很的Q因而查询向量的l数很小Q而文档很长,包含?Term)很多Q文档向量维数很大。你的图中两者维数怎么都是N呢? 在这里,既然要放到相同的向量I间Q自然维数是相同的,不同Ӟ取二者的qQ如果不含某个词(Term)Ӟ则权?Term Weight)??/p>
相关性打分公式如下: Ҏq个公式p出文档与查询之间的怼E度了?/p>
四、ȝ 对上q烦引创建和搜烦q程所一个ȝQ如图: 此图参照http://www.lucene.com.cn/about.htm 中文章《开放源代码的全文检索引擎Lucene?/p>
1. 索引q程Q?/strong> a 有一pd被烦引文?/strong> b) 被烦引文件经q语法分析和语言处理形成一pd?Term) ?/strong> c) l过索引创徏形成词典和反向烦引表?/strong> d) 通过索引存储烦引写入硬盘?/strong> 2. 搜烦q程Q?/strong> a) 用户输入查询语句?/strong> b) Ҏ询语句经q语法分析和语言分析得到一pd?Term) ?/strong> c) 通过语法分析得到一个查询树?/strong> d) 通过索引存储烦引读入到内存?/strong> e) 利用查询树搜索烦引,从而得到每个词(Term) 的文档链表,Ҏ档链表进行交Q差Qƈ得到l果文档?/strong> f) 搜索到的结果文档对查询的相x进行排序?/strong> g) q回查询l果l用戗?/strong> Keywords: User-Space MultiThreading, Pth 所谓多U程Q简单讲是能够让几个不同的代码片段轮流执行。内核实现多U程的方法比较直观,在每ơ时钟中断到来时或者用戯用syscall陷入内核时进行上下文切换卛_。用h切换线E要解决两个问题Q?/font> 1、时机,即何时切换线E? 2、方法,x样切换上下文? Z军_切换时机Q需要确定所设计的线E库是可剥夺(preemptive)的还是不可剥?non-preemptive)的。一般,用户态线E库都选择使用不可剥夺的设计方案,q么做的好处是将控制权交l用P而用h了解何时需要放弃执行权。这么做减少了系l切换次敎ͼ实现了最高的CPU利用率,非常适合用于U学计算环境。但是,另一斚wq么做也存在~点Q其它线E的响应速度变慢Q他们必ȝ到当前CPU攑ּ执行权后才能被执行。能不能用LE设计成可剥夺的呢?q应该也是可行的。每个用LE运行一D|间后会被q暂停执行,被动地将控制权交回给调度器。怎样才能“被迫暂停执行”Q这是方法问题,下面p讲到?/font> 对于不可剥夺方式Q需要用L写程序的时候主动调用诸如thread_yeild(), thread_wai()之类的函敎ͼq些函数会将当前用户U程的执行上下文保存hQ然后让调度器选择一个新的用LE投入执行。底层操作系l提供了一些列的机制支持上下文的获得和切换Q如setjmp,longjmp,getcontext,swapcontext{。用h非抢占式线E库实现 一文用了前面两个函数QGNU Pth 使用了后面两个函数。可剥夺方式的实现需要更多的操作pȝ支持Q如可以利用alarm函数周期性地产生用户态中断,每次中断到来的时候线E库q行U程切换?/font> 单纯的用h线E库一般都是基于一个单U程q程实现的,一旦用LE阻塞,q个q程pdQ进而导致整个用h线E组得不到CPU。基于单q程实现的用h线E库在SMP/多CPU环境下性能很差Q多出来的核无法被线E库利用Q这跟当前的微处理器架构发展势十分不符。ؓ了解册个问题,可以考虑使用一U合结构:在少量内核线E的基础上实现大量的用户U程?/font> 最后思考一个问题:Z么用h线E库比内核态线E库h更高的性能呢?其实用户态线E库同样M开q入内核态这一q程Q以getcontext,swapcontex实现方式ZQgetcontext要进Zơ内核,swapcontex又要q出一ơ内核,而内核线E切换则只需要一ơ时钟中断,只进出内怸ơ即可。这么说来,用户态线E库的性能应该劣于内核态线E库。但是,注意到每ơ时钟中断所做的工作q远不止上下文切换这么简单,q应该是用户态线E库更优的原因吧。基于这个分析,如果应用需要创建的U程数ƈ不多Q二者应该性能相当。但是,一旦线E数巨大且切换频J,用户态线E库的优势就能体现出来了?/font> 本文来自CSDN博客Q{载请标明出处Qhttp://blog.csdn.net/maray/archive/2009/12/05/4946245.aspx1. 计算权重(Term weight)的过E?/strong>
2. 判断Term之间的关pM而得到文档相x的q程Q也卛_量空间模型的法(VSM)?/strong>
]]>
]]>
]]>
#include <pthread.h>
int pthread_key_create(pthread_key_t *key,void (*destr_function)(void *));
int pthread_setspecific(pthread_key_t key,const void *pointer));
void *pthread_getspecific(pthread_key_t key);
int pthread_key_delete(pthread_key_t key);
pthread_key_createQ从Linux的TSD池中分配一,其Dlkey供以后访问用,它的W一个参数key为指向键值的指针Q第二个参数Z个函数指针,如果指针不ؓI,则在U程退出时以key所兌的数据ؓ参数调用destr_function()Q释攑ֈ配的~冲区?br />
key一旦被创徏Q所有线E都可以讉K它,但各U程可以Ҏ自己的需要往key中填入不同的|q就相当于提供了一个同名而不同值的全局变量Q一键多倹{一键多值靠的是一个关键数据结构数l,即TSD池其l构如下Q?br />
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};
创徏一个TSDq当于结构数l中的某一设|ؓ“in_use”Qƈ其索引q回l*keyQ然后设|destructor函数destr_function?br />
pthread_setspecificQ该函数pointer的|不是内容Q与key相关联。用pthread_setspecificZ个键指定新的U程数据ӞU程必须先释攑֎有的U程数据用以回收I间?br />
pthread_getspecificQ通过该函数得Ckey相关联的数据?br />
pthread_key_deleteQ该函数用来删除一个键Q键所占用的内存将被释放。需要注意的是,键占用的内存被释放,与该键关联的U程数据所占用的内存ƈ不被释放。因此,U程数据的释攑ֿd释放键之前完成?br />
?Q?实现如何创建和使用U程的私有数据,具体代码如下所C?br />
?Q?
#include <stdio.h>
#include <string.h>
#include <pthread.h>
pthread_key_t key;
void * thread2(void *arg)
{
int tsd = 5;
printf("thread %d is running\n",pthread_self());
pthread_setspecific(key,(void *)tsd);
printf("thread %d returns %d\n",pthread_self(),pthread_getspecific(key));
}
void * thread1(void *arg)
{
int tsd = 0;
pthread_t thid2;
printf("thread %d is running\n",pthread_self());
pthread_setspecific(key,(void *)tsd);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(2);
printf("thread %d return %d\n",pthread_self(),pthread_getspecific(key));
}
int main(void)
{
pthread_t thid1;
printf("main thread begins running\n");
pthread_key_create(&key,NULL);
pthread_create(&thid1,NULL,thread1,NULL);
sleep(5);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
~译q执行,l果如下Q?br />
$ gcc -o 8-4 8-4.c -g -l pthread
$ ./8-4
main thread begins running
thread -1209746544 is running
thread -1218139248 is running
thread -1218139248 returns 5
thread -1209746544 return 0
main thread exit
E序说明Q程序中Q主U程创徏了线Ethread1Q线Ethread1创徏了线Ethread2。两个线E分别将tsd作ؓU程U有数据。从E序q行l果可以看出Q两个线Etsd的修改互不干扎ͼ可以看出thread2先于thread1l束Q线E在创徏thread2后,睡眠3s{待thread2执行完毕。主U程睡眠5s{待thread1l束。可以看出thread2对tsd的修改ƈ没媄响到thread1的tsd的取倹{?br />
]]>