自己动手写搜索引擎
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.1 30分钟实现的搜索引擎

首先从一个简单的搜索引擎入手,实现一个简单的指定目录文件的搜索引擎。实现之前需要读者具有Java开发方面的基础知识。

1.1.1 准备工作环境(10分钟)

首先要准备一个Java的开发环境。当前可以使用JDK 1.6。JDK 1.6可以从Sun的官方网站java.sun.com下载得到。使用默认方式安装即可。

然后要使用的是一个用来管理搜索引擎索引库的jar包,叫做Lucene。目前可以从http://lucene.apache.org/java/docs/index.html下载到最新的Lucene,当前的版本是2.3。另外,使用集成开发环境Eclipse,其开发界面如图1-1所示。

图1-1 Java开发界面

如果需要用Web界面搜索,还要下载Tomcat,当前可以从http://tomcat.apache.org/下载到,推荐使用Tomcat 5.5以上的版本。

然后增加Tomcat的内存使用量,防止内存溢出。

如果是在Linux下,可以用vi编辑器修改./catalina.sh文件,增加行:

            JAVA_OPTS=-Xmx600m

如果是在Windows下,可以用文本编辑器“记事本”修改./catalina.bat文件,增加行:

            set JAVA_OPTS=-Xmx600m

【作者提示】如果不增加内存使用量,运行时可能会出现java.lang.OutOfMemoryError异常。

1.1.2 编写代码(15分钟)

搜索引擎的基础在于对全文索引库的管理,在Lucene中,通过IndexWriter来写入索引库。伪代码如下:

1.创建IndexWriter,准备写索引;

2.遍历要索引的路径;

3.优化索引。

下面是主要的实现代码:

            public void go() throws Exception  {
                  long start = System.currentTimeMillis();
                  if (verbose) {
                      System.out.println("Creating index in: " + indexDir);
                      //创建索引目录或者建立增量索引
                      if (incremental) System.out.println("- using incremental mode");
                  }
                  Index = new IndexWriter(new File(indexDir), new StandardAnalyzer(),
                      !incremental);//打开或创建索引库,indexDir是索引存放的路径
                  File dir = new File(sSourceDir);//待索引的文件存放的路径
                  indexDir(dir);//索引路径
                  index.optimize();//索引优化
                  index.close();//关闭索引库
                  if(verbose)
                  System.out.println("index complete in :"+(System. currentTime
        Millis() - start)/1000);
            }

下面这段代码把文件内容加到索引库:

            private void indexFile(File item) {
              if (verbose) System.out.println("Adding FILE: " + item);
              News news = loadFile(item);//把文件中的内容加载到news对象
              if ( news!= null && news.body != null) {
                  Document doc = new Document();
                  //创建网址列
                  Field f = new Field("url", news.URL ,
                        Field.Store.YES, Field.Index.UN_TOKENIZED,
                        Field.TermVector.NO);
                  doc.add(f);
                  //创建标题列
                  f = new Field("title", news.title ,
                        Field.Store.YES, Field.Index.TOKENIZED,
                        Field.TermVector.WITH_POSITIONS_OFFSETS);
                  doc.add(f);
                  //创建内容列
                  f = new Field("body", news.body.toString() ,
                        Field.Store.YES, Field.Index.TOKENIZED,
                        Field.TermVector.WITH_POSITIONS_OFFSETS);
                  doc.add(f);
                  try{
                    //文档增加到索引库
                    index.addDocument(doc);
                  }
                  catch(Exception e)
                  {
                    e.printStackTrace();
                    System.exit(-1);
                  }
              }
        }

完整的代码可以在本书附带的光盘中找到。

运行以后,一般会生成以下三个索引文件:

● _0.cfs

● segments.gen

● segments_2

其中任何索引库都会包括的一个文件是:segments.gen。可以通过判断一个路径下是否包括这个文件来判断一个路径下是否已经存在Lucene索引。

1.1.3 发布运行(5分钟)

把搜索页面index.jsp放到Tomcat中。

            <% if (!query.equals("")) { %>
              <jsp:useBean   id="search"   class="com.bitmechanic.spindle.Search"
        scope="application">
              <!-- Specify the directory that stores our Lucene index created by the
        spindle spider -->
              <% search.init("d:/index"); %>
              </jsp:useBean>
              <jsp:setProperty name="search" property="query"/>
              <list:init name="customers"  listCreator="search" max="10">
              <hr>
              <list:hasResults>
                  <list:iterate>
                          <TABLE width="65%">
                                <TR>
                                <TD>
                                      <a href="<list:iterateProp property="url"/>">
                                      <B><FONT    style="FONT-SIZE:    14px"><list:
                                      iterateProp property="title"/></FONT></B></a>
                                      &nbsp;&nbsp;<FONT   size=-1   color=#6f6f6f>
        (<list:iterateProp     property="source"/>)&nbsp;&nbsp;<list:iterateProp
        property="accessDate"/></FONT>
                                </TD>
                              </TR>
                              <TR>
                                    <TD>
                                            <FONT size=-1><list:iterateProp
                                            property="desc"/></FONT>
                                    </TD>
                              </TR>
                        </TABLE>
                        </list:iterate>
                <pg:pager
                        url="/index.jsp"
                        items="<%=Integer.parseInt(listSize)%>"
                        maxPageItems="10"
                        maxIndexPages="10"
                        export="currentPageNumber=pageNumber"
                        scope="request">

                  <pg:param name="query" value="<%=query%>"/>

                <pg:index export="totalItems=itemCount">

                <pg:page export="firstItem, lastItem">
                <div class="resultInfo">
                当前显示结果 <strong><%= firstItem %>-<%= lastItem %></strong> 找
                到相关文档 <strong><%=listSize%></strong> 篇
                </div>
                </pg:page>

                <div class="rnav">
                <table border=0 cellpadding=0 width=1% cellspacing=0 align=
                center><tr align=center valign=top>
                        <td valign=bottom nowrap><font size=-1>结果页码:&nbsp;

                        <pg:first unless="current">
                        <td nowrap><a href="<%= pageUrl %>" class="rnavLink">
                        <nobr>首页</nobr></a>
                          </pg:first>

                          <pg:prev export="pageUrl">
                          <td nowrap><a href="<%= pageUrl %>" class="rnavLink">&#
                          171;&nbsp;上一页</a>&nbsp;
                          </pg:prev>

                          <pg:pages><%
                            if (pageNumber.intValue() < 10) {
                                %>&nbsp;<%
                            }
                            if (pageNumber == currentPageNumber) {
                                %><b><%= pageNumber %></b><%
                            } else {
                                %><td nowrap>&nbsp;<a href="<%= pageUrl %>"><%=
                                pageNumber %></a>&nbsp;<%
                            }
                          %>
                          </pg:pages>

                          <pg:next export="pageUrl">
                          &nbsp;&nbsp;<td nowrap><a href="<%= pageUrl %>" class=
                          "rnavLink">下一页&nbsp;&#187;</a>
                          </pg:next>

                          <pg:last unless="current">
                          &nbsp;<td nowrap><a href="<%= pageUrl %>" class=
                          "rnavLink"><nobr>末页</nobr></a>
                          </pg:last>
                    </table>
                    </div>

                    </pg:index>

                    </pg:pager>

                  </list:hasResults>

                  <list:hasNoResults>
                        对不起,没有结果返回。<p>建议你换用更少的文字查询以扩大搜索范围。
                  </list:hasNoResults>

            </list:init>

            <% } %>

需要使用Tomcat的版本为5.5.X或6.0。为了让Tomcat支持中文,要修改server.xml文件。设置值URIEncoding:

            <Connector port="8080" maxThreads="150" minSpareThreads="25"
             maxSpareThreads="75" enableLookups="false" redirectPort="8443"
            acceptCount="100" debug="0" connectionTimeout="20000"
            disableUploadTimeout="true" URIEncoding="UTF-8" useBodyEncodingForURI=
            "true" />

同时修改WEB-INF目录下的web.xml文件,设置Filter:

            <filter>
                <filter-name>Set Character Encoding</filter-name>
                <filter-class>filters.SetCharacterEncodingFilter</filter-class>
                <init-param>
                  <param-name>encoding</param-name>
                  <param-value>utf-8</param-value>
                </init-param>
            </filter>
            <filter-mapping>
                <filter-name>Set Character Encoding</filter-name>
                <url-pattern>/*</url-pattern>
                </filter-mapping>

本节通过使用Lucene实现了一个可以在互联网上发布的搜索引擎,而世界上最大的互联网搜索引擎来自Google。