﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-Tinysun-随笔分类-Linux 多线程</title><link>http://www.blogjava.net/tinysun/category/45154.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 04 Oct 2010 18:20:36 GMT</lastBuildDate><pubDate>Mon, 04 Oct 2010 18:20:36 GMT</pubDate><ttl>60</ttl><item><title>全文检索的基本原理</title><link>http://www.blogjava.net/tinysun/archive/2010/10/04/333723.html</link><dc:creator>何克勤</dc:creator><author>何克勤</author><pubDate>Mon, 04 Oct 2010 02:36:00 GMT</pubDate><guid>http://www.blogjava.net/tinysun/archive/2010/10/04/333723.html</guid><wfw:comment>http://www.blogjava.net/tinysun/comments/333723.html</wfw:comment><comments>http://www.blogjava.net/tinysun/archive/2010/10/04/333723.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tinysun/comments/commentRss/333723.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tinysun/services/trackbacks/333723.html</trackback:ping><description><![CDATA[<p><span style="font-size: small"><strong><span style="color: #000080">全文转载：<a href="http://blog.csdn.net/forfuture1978/archive/2009/10/22/4711308.aspx">http://blog.csdn.net/forfuture1978/archive/2009/10/22/4711308.aspx</a> </span></strong></span></p>
<p><span style="font-size: small"><strong><span style="color: #000080">作者：forfuture1978 (from CSDN)</span> </strong></span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff0000">我们都知道，Lucene<span>是一个开放源代码</span> <span>的全文检索引擎工具包。那么全文检索到底是什么？</span> </span><span style="color: #ff0000">这要从我们生活中的数据说起。</span> </strong></p>
<p>&nbsp;</p>
<p>我们生活中的数据总体分为两种：<span style="color: #0000ff"><strong>结构化数据</strong> </span>和<span style="color: #0000ff"><strong>非结构化数据</strong> </span>。</p>
<ul>
    <li><strong>结构化数据：</strong> 指具有固定格式或有限长度的数据，如数据库，元数据等。
    <li><strong>非结构化数据：</strong> 指不定长或无固定格式的数据，如邮件，word文档等。 </li>
</ul>
<p>当然有的地方还会提到第三种，半结构化数据，如XML，HTML等，当根据需要可按结构化数据来处理，也可抽取出纯文本按非结构化数据来处理。</p>
<p><span style="color: #0000ff"><strong>非结构化数据又一种叫法叫全文数据。</strong> </span></p>
<p><strong></strong></p>
<p>&nbsp;按照数据的分类，搜索也分为两种：</p>
<ul>
    <li><strong>对结构化数据的搜索</strong> ：如对数据库的搜索，用SQL语句。再如对元数据的搜索，如利用windows搜索对文件名，类型，修改时间进行搜索等。
    <li><strong>对非结构化数据的搜索</strong> ：如利用windows的搜索也可以搜索文件内容，Linux下的grep命令，再如用Google和百度可以搜索大量内容数据。 </li>
</ul>
<p>对非结构化数据也即对全文数据的搜索主要有两种方法：</p>
<p>&nbsp;</p>
<p>一种是<span style="color: #0000ff"><strong>顺序扫描法</strong> </span><strong><span style="color: #0000ff">(Serial Scanning)</span> ：</strong> 所谓顺序扫描，比如要找内容包含某一个字符串的文件，就是一个文档一个文档的看，对于每一个文档，从头看到尾，如果此文档包含此字符串，则此文档为我们要找的文件，接着看下一个文件，直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容，只是相当的慢。如果你有一个80G硬盘，如果想在上面找到一个内容包含某字符串的文件，不花他几个小时，怕是做不到。Linux下的grep命令也是这一种方式。大家可能觉得这种方法比较原始，但对于小数据量的文件，这种方法还是最直接，最方便的。但是对于大量的文件，这种方法就很慢了。</p>
<p>&nbsp;</p>
<p>有人可能会说，对非结构化数据顺序扫描很慢，对结构化数据的搜索却相对较快（由于结构化数据有一定的结构可以采取一定的搜索算法加快速度），那么把我们的非结构化数据想办法弄得有一定结构不就行了吗？</p>
<p>&nbsp;</p>
<p>这种想法很天然，却构成了全文检索的基本思路，也即将非结构化数据中的一部分信息提取出来，重新组织，使其变得有一定结构，然后对此有一定结构的数据进行搜索，从而达到搜索相对较快的目的。</p>
<p>这部分从非结构化数据中提取出的然后重新组织的信息，我们称之<strong><span style="color: #0000ff"><strong>索引</strong> </span></strong>。</p>
<p>&nbsp;</p>
<p>这种说法比较抽象，举几个例子就很容易明白，比如字典，字典的拼音表和部首检字表就相当于字典的索引，对每一个字的解释是非结构化的，如果字典没有音节表和部首检字表，在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理，比如读音，就比较结构化，分声母和韵母，分别只有几种可以一一列举，于是将读音拿出来按一定的顺序排列，每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音，然后按其指向的页数，便可找到我们的非结构化数据——也即对字的解释。</p>
<p><strong></strong></p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>这种先建立索引，再对索引进行搜索的过程就叫全文检索(Full-text Search)</strong> <strong>。</strong> </span></p>
<p><strong></strong></p>
<p>下面这幅图来自《Lucene in action》，但却不仅仅描述了Lucene的检索过程，而是描述了全文检索的一般过程。<img height="479" alt="" src="http://dl.javaeye.com/upload/picture/pic/55997/9512a770-0784-3231-9da5-608a46014b2d.png" width="544" /></p>
<p>&nbsp;</p>
<p>全文检索大体分两个过程，<span style="color: #0000ff"><strong>索引创建</strong> <strong>(Indexing)</strong> </span>和<span style="color: #0000ff"><strong>搜索索引</strong> <strong>(Search)</strong> </span>。</p>
<ul>
    <li>索引创建：将现实世界中所有的结构化和非结构化数据提取信息，创建索引的过程。
    <li>搜索索引：就是得到用户的查询请求，搜索创建的索引，然后返回结果的过程。 </li>
</ul>
<p>于是全文检索就存在三个重要问题：</p>
<p><span style="color: #ff6600"><strong>1. </strong><strong>索引里面究竟存些什么？(Index)</strong> </span></p>
<p><span style="color: #ff6600"><strong>2. </strong><strong>如何创建索引？(Indexing)</strong> </span></p>
<p><span style="color: #ff6600"><strong>3. </strong><strong>如何对索引进行搜索？(Search)</strong> </span></p>
<p>下面我们顺序对每个个问题进行研究。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: medium"><strong><span style="color: #ff0000">一、</span> </strong></span><span style="font-size: medium; color: #ff0000"><strong>索引里面究竟存些什么？(Index)</strong> </span></p>
<p>&nbsp;</p>
<p>首先我们来看为什么顺序扫描的速度慢：</p>
<p>&nbsp;</p>
<p>其实是由于我们想要搜索的信息和非结构化数据中所存储的信息不一致造成的。</p>
<p>非结构化数据中所存储的信息是每个文件包含哪些字符串，也即已知文件，欲求字符串相对容易，也即是从文件到字符串的映射。而我们想搜索的信息是哪些文件包含此字符串，也即已知字符串，欲求文件，也即从字符串到文件的映射。两者恰恰相反。于是如果索引总能够保存从字符串到文件的映射，则会大大提高搜索速度。</p>
<p>&nbsp;</p>
<p>由于从字符串到文件的映射是文件到字符串映射的反向过程，于是保存这种信息的索引称为<span style="color: #0000ff"><strong>反向索引</strong> </span>。</p>
<p>&nbsp;</p>
<p>反向索引的所保存的信息一般如下：</p>
<p>假设我的文档集合里面有100篇文档，为了方便表示，我们为文档编号从1到100，得到下面的结构<img height="153" alt="" src="http://hxraid.javaeye.com/upload/picture/pic/55999/25d81474-0689-344e-9172-e55f8c4d620a.jpg" width="544" /></p>
<p>&nbsp;</p>
<p>左边保存的是一系列字符串，称为<span style="color: #0000ff"><strong>词典</strong> 。</span> </p>
<p>每个字符串都指向包含此字符串的文档(Document)链表，此文档链表称为<span style="color: #0000ff"><strong>倒排表</strong> (Posting List)</span> 。</p>
<p>有了索引，便使保存的信息和要搜索的信息一致，可以大大加快搜索的速度。</p>
<p>&nbsp;</p>
<p>比如说，我们要寻找既包含字符串&#8220;lucene&#8221;又包含字符串&#8220;solr&#8221;的文档，我们只需要以下几步：</p>
<p>1. 取出包含字符串&#8220;lucene&#8221;的文档链表。</p>
<p>2. 取出包含字符串&#8220;solr&#8221;的文档链表。</p>
<p>3. 通过合并链表，找出既包含&#8220;lucene&#8221;又包含&#8220;solr&#8221;的文件。<img height="55" alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56001/7e04a727-6cdb-3434-a260-023bc163b497.jpg" width="544" /></p>
<p>看到这个地方，有人可能会说，全文检索的确加快了搜索的速度，但是多了索引的过程，两者加起来不一定比顺序扫描快多少。的确，加上索引的过程，全文检索不一定比顺序扫描快，尤其是在数据量小的时候更是如此。而对一个很大量的数据创建索引也是一个很慢的过程。</p>
<p>&nbsp;</p>
<p>然而两者还是有区别的，顺序扫描是每次都要扫描，而创建索引的过程仅仅需要一次，以后便是一劳永逸的了，每次搜索，创建索引的过程不必经过，仅仅搜索创建好的索引就可以了。</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>这也是全文搜索相对于顺序扫描的优势之一：一次索引，多次使用。</strong> </span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: medium; color: #ff0000">二、</span> <span style="font-size: medium; color: #ff0000"><strong>如何创建索引？(Indexing)</strong> </span></p>
<p>&nbsp;</p>
<p>全文检索的索引创建过程一般有以下几步：</p>
<p>&nbsp;</p>
<p><span style="color: #ff6600"><strong>1、一些要索引的原始文档(Document)</strong> </span></p>
<p>&nbsp;</p>
<p>为了方便说明索引创建过程，这里特意用两个文件为例：</p>
<p>文件一：Students should be allowed to go out with their friends, but not allowed to drink beer.</p>
<p>文件二：My friend Jerry went to school to see his students but found them drunk which is not allowed.</p>
<p>&nbsp;</p>
<p><span style="color: #ff6600"><strong>2、将原始文档传给分词组件(Tokenizer)</strong> </span></p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>分词组件(Tokenizer)会做以下几件事情(</strong> <strong>此过程称为Tokenize)</strong> <strong>：</strong> </span></p>
<p><span style="color: #0000ff"><strong>1. </strong><strong>将文档分成一个一个单独的单词。</strong> </span></p>
<p><span style="color: #0000ff"><strong>2. </strong><strong>去除标点符号。</strong> </span></p>
<p><span style="color: #0000ff"><strong>3. </strong><strong>去除停用词(Stop word)</strong> <strong>。</strong> </span></p>
<p>&nbsp;</p>
<p>所谓<strong><span style="color: #0000ff">停用词(Stop word)</span> </strong>就是一种语言中最普通的一些单词，由于没有特别的意义，因而大多数情况下不能成为搜索的关键词，因而创建索引时，这种词会被去掉而减少索引的大小。</p>
<p>英语中挺词(Stop word)如：&#8220;the&#8221;,&#8220;a&#8221;，&#8220;this&#8221;等。</p>
<p>对于每一种语言的分词组件(Tokenizer)，都有一个停词(stop word)集合。</p>
<p><strong></strong></p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>经过分词(Tokenizer)</strong> <strong>后得到的结果称为词元(Token)</strong> <strong>。</strong> </span></p>
<p>&nbsp;</p>
<p>在我们的例子中，便得到以下词元(Token)：</p>
<p>&#8220;Students&#8221;，&#8220;allowed&#8221;，&#8220;go&#8221;，&#8220;their&#8221;，&#8220;friends&#8221;，&#8220;allowed&#8221;，&#8220;drink&#8221;，&#8220;beer&#8221;，&#8220;My&#8221;，&#8220;friend&#8221;，&#8220;Jerry&#8221;，&#8220;went&#8221;，&#8220;school&#8221;，&#8220;see&#8221;，&#8220;his&#8221;，&#8220;students&#8221;，&#8220;found&#8221;，&#8220;them&#8221;，&#8220;drunk&#8221;，&#8220;allowed&#8221;。</p>
<p>&nbsp;</p>
<p><span style="color: #ff6600"><strong>3、将得到的词元(Token)传给语言处理组件(Linguistic Processor)</strong> </span></p>
<p>&nbsp;</p>
<p>语言处理组件(linguistic processor)主要是对得到的词元(Token)做一些同语言相关的处理。</p>
<p><span style="color: #0000ff"><strong>对于英语，语言处理组件(Linguistic Processor)</strong> <strong>一般做以下几点：</strong> </span></p>
<p><span style="color: #0000ff"><strong>1. </strong><strong>变为小写(Lowercase)</strong> <strong>。</strong> </span></p>
<p><span style="color: #0000ff"><strong>2. </strong><strong>将单词缩减为词根形式，如&#8220;cars</strong> <strong>&#8221;到&#8220;car</strong> <strong>&#8221;等。这种操作称为：stemming</strong> <strong>。</strong> </span></p>
<p><span style="color: #0000ff"><strong>3. </strong><strong>将单词转变为词根形式，如&#8220;drove</strong> <strong>&#8221;到&#8220;drive</strong> <strong>&#8221;等。这种操作称为：lemmatization</strong> <strong>。</strong> </span></p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>Stemming 和 lemmatization的异同：</strong> </span></p>
<ul>
    <li>相同之处：Stemming和lemmatization都要使词汇成为词根形式。
    <li>两者的方式不同：
    <ul>
        <li>Stemming采用的是&#8220;缩减&#8221;的方式：&#8220;cars&#8221;到&#8220;car&#8221;，&#8220;driving&#8221;到&#8220;drive&#8221;。
        <li>Lemmatization采用的是&#8220;转变&#8221;的方式：&#8220;drove&#8221;到&#8220;drive&#8221;，&#8220;driving&#8221;到&#8220;drive&#8221;。 </li>
    </ul>
    <li>两者的算法不同：
    <ul>
        <li>Stemming主要是采取某种固定的算法来做这种缩减，如去除&#8220;s&#8221;，去除&#8220;ing&#8221;加&#8220;e&#8221;，将&#8220;ational&#8221;变为&#8220;ate&#8221;，将&#8220;tional&#8221;变为&#8220;tion&#8221;。
        <li>Lemmatization主要是采用保存某种字典的方式做这种转变。比如字典中有&#8220;driving&#8221;到&#8220;drive&#8221;，&#8220;drove&#8221;到&#8220;drive&#8221;，&#8220;am, is, are&#8221;到&#8220;be&#8221;的映射，做转变时，只要查字典就可以了。 </li>
    </ul>
    <li>Stemming和lemmatization不是互斥关系，是有交集的，有的词利用这两种方式都能达到相同的转换。 </li>
</ul>
<p><strong></strong></p>
<p><span style="color: #0000ff"><strong>语言处理组件(linguistic processor)的结果称为词(Term)</strong> <strong>。</strong> </span></p>
<p>在我们的例子中，经过语言处理，得到的词(Term)如下：</p>
<p>&#8220;student&#8221;，&#8220;allow&#8221;，&#8220;go&#8221;，&#8220;their&#8221;，&#8220;friend&#8221;，&#8220;allow&#8221;，&#8220;drink&#8221;，&#8220;beer&#8221;，&#8220;my&#8221;，&#8220;friend&#8221;，&#8220;jerry&#8221;，&#8220;go&#8221;，&#8220;school&#8221;，&#8220;see&#8221;，&#8220;his&#8221;，&#8220;student&#8221;，&#8220;find&#8221;，&#8220;them&#8221;，&#8220;drink&#8221;，&#8220;allow&#8221;。</p>
<p>也正是因为有语言处理的步骤，才能使搜索drove，而drive也能被搜索出来。</p>
<p><span style="color: #ff6600"><br />
</span></p>
<p><span style="color: #ff6600"><strong>4、将得到的词(Term)传给索引组件(Indexer)</strong> </span></p>
<p>&nbsp;</p>
<p>索引组件主要是为了得到下面这个图：<img alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56003/7ca0f9b1-e82a-35c1-a97e-e40c2d4a70c9.jpg" /></p>
<p>&nbsp;</p>
<p>在此表中，有几个定义：</p>
<ul>
    <li><strong><span style="color: #0000ff">Document Frequency 即文档频次，表示总共有多少文件包含此词(Term)。 </span></strong>
    <li><strong><span style="color: #0000ff">Frequency 即词频率，表示此文件中包含了几个此词(Term)。 </span></strong></li>
</ul>
<p>所以对词(Term) &#8220;allow&#8221;来讲，总共有两篇文档包含此词(Term)，从而词(Term)后面的文档链表总共有两项，第一项表示包含&#8220;allow&#8221;的第一篇文档，即 1号文档，此文档中，&#8220;allow&#8221;出现了2次，第二项表示包含&#8220;allow&#8221;的第二个文档，是2号文档，此文档中，&#8220;allow&#8221;出现了1次。</p>
<p>&nbsp;</p>
<p>到此为止，索引已经创建好了，我们可以通过它很快的找到我们想要的文档。</p>
<p>&nbsp;</p>
<p>而且在此过程中，我们惊喜地发现，搜索&#8220;drive&#8221;，&#8220;driving&#8221;，&#8220;drove&#8221;，&#8220;driven&#8221;也能够被搜到。因为在我们的索引中，&#8220;driving&#8221;，&#8220;drove&#8221;，&#8220;driven&#8221;都会经过语言处理而变成&#8220;drive&#8221;，在搜索时，如果您输入&#8220;driving&#8221;，输入的查询语句同样经过我们这里的一到三步，从而变为查询&#8220;drive&#8221;，从而可以搜索到想要的文档。</p>
<p>&nbsp;</p>
<p><span style="font-size: medium; color: #ff0000">三、</span> <span style="font-size: medium; color: #ff0000"><strong>如何对索引进行搜索？(Search)</strong> </span></p>
<p>&nbsp;</p>
<p>到这里似乎我们可以宣布&#8220;我们找到想要的文档了&#8221;。</p>
<p>&nbsp;</p>
<p>然而事情并没有结束，找到了仅仅是全文检索的一个方面。不是吗？如果仅仅只有一个或十个文档包含我们查询的字符串，我们的确找到了。然而如果结果有一千个，甚至成千上万个呢？那个又是您最想要的文件呢？</p>
<p>&nbsp;</p>
<p>打开Google吧，比如说您想在微软找份工作，于是您输入&#8220;Microsoft job&#8221;，您却发现总共有22600000个结果返回。好大的数字呀，突然发现找不到是一个问题，找到的太多也是一个问题。在如此多的结果中，如何将最相关的放在最前面呢？</p>
<p>&nbsp;</p>
<p>当然Google做的很不错，您一下就找到了jobs at Microsoft。想象一下，如果前几个全部是&#8220;Microsoft does a good job at software industry&#8230;&#8221;将是多么可怕的事情呀。</p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff6600">如何像Google一样，在成千上万的搜索结果中，找到和查询语句最相关的呢？</span> </strong></p>
<p><strong><span style="color: #ff6600">如何判断搜索出的文档和查询语句的相关性呢？</span> </strong></p>
<p>&nbsp;</p>
<p>这要回到我们第三个问题：如何对索引进行搜索？</p>
<p>搜索主要分为以下几步：</p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff6600">1、用于输入查询语句</span> </strong></p>
<p>&nbsp;</p>
<p>查询语句同我们普通的语言一样，也是有一定语法的。</p>
<p>不同的查询语句有不同的语法，如SQL语句就有一定的语法。</p>
<p>查询语句的语法根据全文检索系统的实现而不同。最基本的有比如：AND, OR, NOT等。</p>
<p>举个例子，用户输入语句：lucene AND learned NOT hadoop。</p>
<p>说明用户想找一个包含lucene和learned然而不包括hadoop的文档。</p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff6600">2、对查询语句进行词法分析，语法分析及语言处理</span> </strong></p>
<p>&nbsp;</p>
<p>由于查询语句有语法，因而也要进行语法分析，语法分析及语言处理。</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong><strong>词法分析主要用来识别单词和关键字。</strong> </strong></span></p>
<p>如上述例子中，经过词法分析，得到单词有lucene，learned，hadoop, 关键字有AND, NOT。</p>
<p>如果在词法分析中发现不合法的关键字，则会出现错误。如lucene AMD learned，其中由于AND拼错，导致AMD作为一个普通的单词参与查询。</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>语法分析主要是根据查询语句的语法规则来形成一棵语法树。</strong> </span></p>
<p>如果发现查询语句不满足语法规则，则会报错。如lucene NOT AND learned，则会出错。</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>语言处理同索引过程中的语言处理几乎相同。</strong> </span></p>
<p>如learned变成learn等。</p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff6600">3、搜索索引，得到符合语法树的文档</span> </strong></p>
<p>&nbsp;</p>
<p>此步骤有分几小步：</p>
<p>首先，在反向索引表中，分别找出包含lucene，learn，hadoop的文档链表。<strong></strong> </p>
<p>其次，对包含lucene，learn的链表进行合并操作，得到既包含lucene又包含learn的文档链表。<strong></strong> </p>
<p>然后，将此链表与hadoop的文档链表进行差操作，去除包含hadoop的文档，从而得到既包含lucene又包含learn而且不包含hadoop的文档链表。<strong></strong> </p>
<p>最后，此文档链表就是我们要找的文档。 </p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff6600">4、根据得到的文档和查询语句的相关性，对结果进行排序。</span> </strong></p>
<p>虽然在上一步，我们得到了想要的文档，然而对于查询结果应该按照与查询语句的相关性进行排序，越相关者越靠前。</p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff6600">如何计算文档和查询语句的相关性呢？</span> </strong></p>
<p>&nbsp;</p>
<p>不如我们把查询语句看作一片短小的文档，对文档与文档之间的相关性(relevance)进行打分(scoring)，分数高的相关性好，就应该排在前面。</p>
<p><strong><span style="color: #ff6600"><br />
</span></strong></p>
<p><strong><span style="color: #ff6600">那么又怎么对文档之间的关系进行打分呢？</span> </strong></p>
<p>&nbsp;</p>
<p><span style="color: #0000ff"><strong>首先，一个文档有很多词(Term)组成</strong> ，</span> 如search, lucene, full-text, this, a, what等。</p>
<p><span style="color: #0000ff"><strong>其次对于文档之间的关系，不同的Term重要性不同</strong> ，</span> 比如对于本篇文档，search, Lucene, full-text就相对重要一些，this, a , what可能相对不重要一些。所以如果两篇文档都包含search, Lucene，fulltext，这两篇文档的相关性好一些，然而就算一篇文档包含this, a, what，另一篇文档不包含this, a, what，也不能影响两篇文档的相关性。</p>
<p>因而判断文档之间的关系，首先找出哪些词(Term)对文档之间的关系最重要，如search, Lucene, fulltext。然后判断这些词(Term)之间的关系。</p>
<p><span style="color: #0000ff"><strong>找出词(Term)</strong> <strong>对文档的重要性的过程称为计算词的权重(Term weight)</strong> <strong>的过程。</strong> </span></p>
<p>计算词的权重(term weight)有两个参数，第一个是词(Term)，第二个是文档(Document)。</p>
<p>词的权重(Term weight)表示此词(Term)在此文档中的重要程度，越重要的词(Term)有越大的权重(Term weight)，因而在计算文档之间的相关性中将发挥更大的作用。</p>
<p><span style="color: #0000ff"><strong>判断词(Term)</strong> <strong>之间的关系从而得到文档相关性的过程应用一种叫做向量空间模型的算法(Vector Space Model)</strong> <strong>。</strong> </span></p>
<p><strong></strong></p>
<p>下面仔细分析一下这两个过程：</p>
<h4><span style="color: #0000ff"><strong>1. 计算权重(Term weight)的过程。</strong> </span></h4>
<p>影响一个词(Term)在一篇文档中的重要性主要有两个因素：</p>
<ul>
    <li><span style="color: #ff6600">Term Frequency (tf)：即此Term在此文档中出现了多少次。tf 越大说明越重要。 </span>
    <li><span style="color: #ff0000"><span style="color: #ff6600">Document Frequency (df)：即有多少文档包含次Term。df 越大说明越不重要</span> 。</span> </li>
</ul>
<p>容易理解吗？词(Term)在文档中出现的次数越多，说明此词(Term)对该文档越重要，如&#8220;搜索&#8221;这个词，在本文档中出现的次数很多，说明本文档主要就是讲这方面的事的。然而在一篇英语文档中，this出现的次数更多，就说明越重要吗？不是的，这是由第二个因素进行调整，第二个因素说明，有越多的文档包含此词(Term), 说明此词(Term)太普通，不足以区分这些文档，因而重要性越低。</p>
<p>&nbsp;</p>
<p>这也如我们程序员所学的技术，对于程序员本身来说，这项技术掌握越深越好（掌握越深说明花时间看的越多，tf越大），找工作时越有竞争力。然而对于所有程序员来说，这项技术懂得的人越少越好（懂得的人少df小），找工作越有竞争力。人的价值在于不可替代性就是这个道理。</p>
<p>&nbsp;</p>
<p>道理明白了，我们来看看公式：</p>
<p><img alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56007/25a56cde-fe88-38dd-83eb-687f3a6d2059.bmp" /></p>
<p>&nbsp;</p>
<p>这仅仅只term weight计算公式的简单典型实现。实现全文检索系统的人会有自己的实现，Lucene就与此稍有不同。</p>
<h4><span style="color: #0000ff"><strong>2. 判断Term之间的关系从而得到文档相关性的过程，也即向量空间模型的算法(VSM)。</strong> </span></h4>
<p>我们把文档看作一系列词(Term)，每一个词(Term)都有一个权重(Term weight)，不同的词(Term)根据自己在文档中的权重来影响文档相关性的打分计算。</p>
<p>于是我们把所有此文档中词(term)的权重(term weight) 看作一个向量。</p>
<p>Document = {term1, term2, &#8230;&#8230; ,term N}</p>
<p>Document Vector = {weight1, weight2, &#8230;&#8230; ,weight N}</p>
<p>同样我们把查询语句看作一个简单的文档，也用向量来表示。</p>
<p>Query = {term1, term 2, &#8230;&#8230; , term N}</p>
<p>Query Vector = {weight1, weight2, &#8230;&#8230; , weight N}</p>
<p>我们把所有搜索出的文档向量及查询向量放到一个N维空间中，每个词(term)是一维。</p>
<p><img alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56009/f97383c8-385c-3a32-87a0-44dc23d6283a.jpg" /></p>
<p>&nbsp;</p>
<p>我们认为两个向量之间的夹角越小，相关性越大。</p>
<p>所以我们计算夹角的余弦值作为相关性的打分，夹角越小，余弦值越大，打分越高，相关性越大。</p>
<p>有人可能会问，查询语句一般是很短的，包含的词(Term)是很少的，因而查询向量的维数很小，而文档很长，包含词(Term)很多，文档向量维数很大。你的图中两者维数怎么都是N呢？</p>
<p>在这里，既然要放到相同的向量空间，自然维数是相同的，不同时，取二者的并集，如果不含某个词(Term)时，则权重(Term Weight)为0。</p>
<p>相关性打分公式如下：</p>
<p><img alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56011/f1373bbe-5914-3bf4-90ad-f219c042b410.png" /></p>
<p>&nbsp;</p>
<p>根据这个公式就能算出文档与查询之间的相似程度了。</p>
<p>&nbsp;</p>
<p><span style="font-size: medium"><strong><span style="color: #ff0000">四、总结</span> </strong></span></p>
<p>对上述索引创建和搜索过程所一个总结，如图： </p>
<p>此图参照<a href="http://www.lucene.com.cn/about.htm">http://www.lucene.com.cn/about.htm</a> 中文章《开放源代码的全文检索引擎Lucene》</p>
<p><img alt="" src="http://hxraid.javaeye.com/upload/picture/pic/56013/8b75b3cb-a459-3f5a-8e9e-83813430a23c.jpg" /></p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff0000"><strong>1. 索引过程：</strong> </span></strong></p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff0000"><strong>a </strong><strong>有一系列被索引文件</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>b) </strong><strong>被索引文件经过语法分析和语言处理形成一系列词(Term)</strong> <strong>。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>c) </strong><strong>经过索引创建形成词典和反向索引表。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>d) </strong><strong>通过索引存储将索引写入硬盘。</strong> </span></strong></p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff0000"><strong>2. 搜索过程：</strong> </span></strong></p>
<p>&nbsp;</p>
<p><strong><span style="color: #ff0000"><strong>a) </strong><strong>用户输入查询语句。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>b) </strong><strong>对查询语句经过语法分析和语言分析得到一系列词(Term)</strong> <strong>。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>c) </strong><strong>通过语法分析得到一个查询树。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>d) </strong><strong>通过索引存储将索引读入到内存。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>e) </strong><strong>利用查询树搜索索引，从而得到每个词(Term)</strong> <strong>的文档链表，对文档链表进行交，差，并得到结果文档。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>f) </strong><strong>将搜索到的结果文档对查询的相关性进行排序。</strong> </span></strong></p>
<p><strong><span style="color: #ff0000"><strong>g) </strong><strong>返回查询结果给用户。</strong> </span></strong></p>
<img src ="http://www.blogjava.net/tinysun/aggbug/333723.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tinysun/" target="_blank">何克勤</a> 2010-10-04 10:36 <a href="http://www.blogjava.net/tinysun/archive/2010/10/04/333723.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用户态非抢占式线程库实现 (转)</title><link>http://www.blogjava.net/tinysun/archive/2010/05/30/322278.html</link><dc:creator>何克勤</dc:creator><author>何克勤</author><pubDate>Sun, 30 May 2010 06:49:00 GMT</pubDate><guid>http://www.blogjava.net/tinysun/archive/2010/05/30/322278.html</guid><wfw:comment>http://www.blogjava.net/tinysun/comments/322278.html</wfw:comment><comments>http://www.blogjava.net/tinysun/archive/2010/05/30/322278.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tinysun/comments/commentRss/322278.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tinysun/services/trackbacks/322278.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 平台 i386 ， win32 ， msvc 2003 代码简单介绍： 调度算法：轮转法。。，可修改 内存模型：每个线程拥有各自独立的堆栈。启动线程的时候，切换到对应的堆栈再启动，使得线程之间的堆栈互不干扰 调度方式：线程调用 schedule 函数， schedule 用 setjmp 保存当前堆栈，选择一个新的线程之后用 longjmp 跳转过去。 线程退出：线程函数的返回...&nbsp;&nbsp;<a href='http://www.blogjava.net/tinysun/archive/2010/05/30/322278.html'>阅读全文</a><img src ="http://www.blogjava.net/tinysun/aggbug/322278.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tinysun/" target="_blank">何克勤</a> 2010-05-30 14:49 <a href="http://www.blogjava.net/tinysun/archive/2010/05/30/322278.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用户态多线程实现的基本原理</title><link>http://www.blogjava.net/tinysun/archive/2010/05/30/322275.html</link><dc:creator>何克勤</dc:creator><author>何克勤</author><pubDate>Sun, 30 May 2010 06:33:00 GMT</pubDate><guid>http://www.blogjava.net/tinysun/archive/2010/05/30/322275.html</guid><wfw:comment>http://www.blogjava.net/tinysun/comments/322275.html</wfw:comment><comments>http://www.blogjava.net/tinysun/archive/2010/05/30/322275.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tinysun/comments/commentRss/322275.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tinysun/services/trackbacks/322275.html</trackback:ping><description><![CDATA[<p><font style="background-color: #cde9d1">本文参考了用户态非抢占式线程库实现 一文以及GNU Pth 。前者是一种用户态线程库的简单实现，属于一个很好的demo，后者就是大家熟知的Pthread的用户态实现，比较完善。</font></p>
<p><font style="background-color: #cde9d1">Keywords: User-Space MultiThreading, Pth</font></p>
<p><font style="background-color: #cde9d1">所谓多线程，简单讲就是能够让几个不同的代码片段轮流执行。内核实现多线程的方法比较直观，在每次时钟中断到来时或者用户调用syscall陷入内核时进行上下文切换即可。用户态切换线程要解决两个问题：</font></p>
<p><font style="background-color: #cde9d1">1、时机，即何时切换线程？</font></p>
<p><font style="background-color: #cde9d1">2、方法，即怎样切换上下文？</font></p>
<p><font style="background-color: #cde9d1">为了决定切换时机，需要确定所设计的线程库是可剥夺(preemptive)的还是不可剥夺(non-preemptive)的。一般，用户态线程库都选择使用不可剥夺的设计方案，这么做的好处是将控制权交给用户，而用户最了解何时需要放弃执行权。这么做减少了系统切换次数，实现了最高的CPU利用率，非常适合用于科学计算环境。但是，另一方面这么做也存在缺点：其它线程的响应速度变慢，他们必须等到当前CPU放弃执行权后才能被执行。能不能将用户线程设计成可剥夺的呢？这应该也是可行的。每个用户线程运行一段时间后会被迫暂停执行，被动地将控制权交回给调度器。怎样才能&#8220;被迫暂停执行&#8221;？这是方法问题，下面就要讲到。</font></p>
<p><font style="background-color: #cde9d1">对于不可剥夺方式，需要用户编写程序的时候主动调用诸如thread_yeild(), thread_wai()之类的函数，这些函数会将当前用户线程的执行上下文保存起来，然后让调度器选择一个新的用户线程投入执行。底层操作系统提供了一些列的机制支持上下文的获得和切换，如setjmp,longjmp,getcontext,swapcontext等。用户态非抢占式线程库实现 一文使用了前面两个函数，GNU Pth 使用了后面两个函数。可剥夺方式的实现需要更多的操作系统支持，如可以利用alarm函数周期性地产生用户态中断，每次中断到来的时候线程库进行线程切换。</font></p>
<p><font style="background-color: #cde9d1">单纯的用户态线程库一般都是基于一个单线程进程实现的，一旦用户线程阻塞，这个进程就被阻塞，进而导致整个用户态线程组得不到CPU。基于单进程实现的用户态线程库在SMP/多CPU环境下性能很差，多出来的核无法被线程库利用，这跟当前的微处理器架构发展趋势十分不符。为了解决这个问题，可以考虑使用一种混合结构：在少量内核线程的基础上实现大量的用户线程。</font></p>
<p><font style="background-color: #cde9d1">&nbsp;</font></p>
<p><font style="background-color: #cde9d1">最后思考一个问题：为什么用户态线程库比内核态线程库具有更高的性能呢？其实用户态线程库同样离不开进入内核态这一过程，以getcontext,swapcontex实现方式为例，getcontext要进出一次内核，swapcontex又要进出一次内核，而内核线程切换则只需要一次时钟中断，只进出内核一次即可。这么说来，用户态线程库的性能应该劣于内核态线程库。但是，注意到每次时钟中断所做的工作远远不止上下文切换这么简单，这应该是用户态线程库更优的原因吧。基于这个分析，如果应用需要创建的线程数并不多，二者应该性能相当。但是，一旦线程数巨大且切换频繁，用户态线程库的优势就能体现出来了。</font></p>
<p><font style="background-color: #cde9d1"></font>&nbsp;</p>
<p><font style="background-color: #cde9d1">本文来自CSDN博客，转载请标明出处：http://blog.csdn.net/maray/archive/2009/12/05/4946245.aspx</font></p>
<img src ="http://www.blogjava.net/tinysun/aggbug/322275.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tinysun/" target="_blank">何克勤</a> 2010-05-30 14:33 <a href="http://www.blogjava.net/tinysun/archive/2010/05/30/322275.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>线程控制－－私有数据</title><link>http://www.blogjava.net/tinysun/archive/2010/05/29/322210.html</link><dc:creator>何克勤</dc:creator><author>何克勤</author><pubDate>Sat, 29 May 2010 07:16:00 GMT</pubDate><guid>http://www.blogjava.net/tinysun/archive/2010/05/29/322210.html</guid><wfw:comment>http://www.blogjava.net/tinysun/comments/322210.html</wfw:comment><comments>http://www.blogjava.net/tinysun/archive/2010/05/29/322210.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tinysun/comments/commentRss/322210.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tinysun/services/trackbacks/322210.html</trackback:ping><description><![CDATA[&nbsp;在多线程环境下，进程内的所有线程共享进程的数据空间，因此全局变量为所有线程共有。在程序设计中有时需要保存线程自己的全局变量，这种特殊的变量仅在某个线程内部有效。如常见的变量errno，它返回标准的出错代码。errno不应该是一个局部变量，几乎每个函数都应该可以访问它；但它又不能作为一个全局变量，否则在一个线程里输出的很可能是另一个线程的出错信息，这个问题可以通过创建线程的私有数据（Thread-specific Data，或TSD）来解决。在线程内部，线程私有数据可以被各个函数访问，但它对其他线程是屏蔽的。<br />
&nbsp;&nbsp;&nbsp; 线程私有数据采用了一种被称为一键多值的技术，即一个键对应多个数值。访问数据时都是通过键值来访问，好像是对一个变量进行访问，其实是在访问不同的数据。使用线程私有数据时，首先要为每个线程数据创建一个相关联的键。在各个线程内部，都使用这个公用的键来指代线程数据，但是在不同的线程中，这个键代表的数据是不同的。操作线程私有数据的函数主要有4个：pthread_key_create（创建一个键），pthread_setspecific（为一个键设置线程私有数据），pthread_getspecific（从一个键读取线程私有数据），pthread_key_delete（删除一个键）。这几个函数的声明如下：<br />
#include &lt;pthread.h&gt;<br />
int pthread_key_create(pthread_key_t *key,void (*destr_function)(void *));<br />
int pthread_setspecific(pthread_key_t key,const void *pointer));<br />
void *pthread_getspecific(pthread_key_t key);<br />
int pthread_key_delete(pthread_key_t key);<br />
&nbsp;&nbsp;&nbsp; pthread_key_create：从Linux的TSD池中分配一项，将其值赋给key供以后访问使用，它的第一个参数key为指向键值的指针，第二个参数为一个函数指针，如果指针不为空，则在线程退出时将以key所关联的数据为参数调用destr_function()，释放分配的缓冲区。<br />
&nbsp;&nbsp;&nbsp; key一旦被创建，所有线程都可以访问它，但各线程可以根据自己的需要往key中填入不同的值，这就相当于提供了一个同名而不同值的全局变量，一键多值。一键多值靠的是一个关键数据结构数组，即TSD池其结构如下：<br />
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};<br />
&nbsp;&nbsp;&nbsp; 创建一个TSD就相当于将结构数组中的某一项设置为&#8220;in_use&#8221;，并将其索引返回给＊key，然后设置destructor函数destr_function。<br />
&nbsp;&nbsp;&nbsp; pthread_setspecific：该函数将pointer的值（不是内容）与key相关联。用pthread_setspecific为一个键指定新的线程数据时，线程必须先释放原有的线程数据用以回收空间。<br />
&nbsp;&nbsp;&nbsp; pthread_getspecific：通过该函数得到与key相关联的数据。<br />
&nbsp;&nbsp;&nbsp; pthread_key_delete：该函数用来删除一个键，键所占用的内存将被释放。需要注意的是，键占用的内存被释放，与该键关联的线程数据所占用的内存并不被释放。因此，线程数据的释放必须在释放键之前完成。<br />
&nbsp;&nbsp;&nbsp; 例8－4将实现如何创建和使用线程的私有数据，具体代码如下所示。<br />
&nbsp;&nbsp;&nbsp; 例8－4<br />
#include &lt;stdio.h&gt;<br />
#include &lt;string.h&gt;<br />
#include &lt;pthread.h&gt;<br />
<br />
pthread_key_t key;<br />
<br />
void * thread2(void *arg)<br />
{<br />
int tsd = 5;<br />
printf("thread %d is running\n",pthread_self());<br />
pthread_setspecific(key,(void *)tsd);<br />
printf("thread %d returns %d\n",pthread_self(),pthread_getspecific(key));<br />
}<br />
<br />
void * thread1(void *arg)<br />
{<br />
int tsd = 0;<br />
pthread_t thid2;<br />
<br />
printf("thread %d is running\n",pthread_self());<br />
pthread_setspecific(key,(void *)tsd);<br />
pthread_create(&amp;thid2,NULL,thread2,NULL);<br />
sleep(2);<br />
printf("thread %d return %d\n",pthread_self(),pthread_getspecific(key));<br />
}<br />
<br />
int main(void)<br />
{<br />
pthread_t thid1;<br />
printf("main thread begins running\n");<br />
pthread_key_create(&amp;key,NULL);<br />
pthread_create(&amp;thid1,NULL,thread1,NULL);<br />
sleep(5);<br />
pthread_key_delete(key);<br />
printf("main thread exit\n");<br />
return 0;<br />
}<br />
&nbsp;&nbsp;&nbsp; 编译并执行，结果如下：<br />
$ gcc -o 8-4 8-4.c -g -l pthread<br />
$ ./8-4<br />
main thread begins running<br />
thread -1209746544 is running<br />
thread -1218139248 is running<br />
thread -1218139248 returns 5<br />
thread -1209746544 return 0<br />
main thread exit<br />
&nbsp;&nbsp;&nbsp; 程序说明：程序中，主线程创建了线程thread1，线程thread1创建了线程thread2。两个线程分别将tsd作为线程私有数据。从程序运行结果可以看出，两个线程tsd的修改互不干扰，可以看出thread2先于thread1结束，线程在创建thread2后，睡眠3s等待thread2执行完毕。主线程睡眠5s等待thread1结束。可以看出thread2对tsd的修改并没影响到thread1的tsd的取值。<br />
 <img src ="http://www.blogjava.net/tinysun/aggbug/322210.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tinysun/" target="_blank">何克勤</a> 2010-05-29 15:16 <a href="http://www.blogjava.net/tinysun/archive/2010/05/29/322210.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>