`
fuyangchang
  • 浏览: 147204 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

利用Lucene搜索Java源代码

阅读更多
 某些网站允许软件开发社团通过发布开发者指南、白皮书、FAQs【常见问题解答】和源代码以实现信息的共享。随着信息量的增长,和几个开发者贡献出自己的 知识库,于是网站提供搜索引擎来搜索站点上现有的所有信息。虽然这些搜索引擎对文本文件的搜索可以做的很好,但对开发者搜索源代码做了比较严格的限制。搜 索引擎认为源代码就是纯文本文件,因此,在这一点上,与成熟的可以处理大量源文件的工具――grep相比没有什么不同。

在这篇文章中,我推荐使用Lucene,它是基于Java的开源搜索引擎,通过提取和索引相关的源码元素来搜索源代码。这里,我仅限定搜索Java源代码。然而,Lucene同样可以做到对其他编程语言的源代码的搜索。

文章给出了在Lucene环境下搜索引擎重点方面的简短概述。要了解更多细节信息,参考Resources部分。

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Renuka;Knightchen(作者的blog:http://blog.matrix.org.cn/page/Knightchen)
原文:http://www.matrix.org.cn/resource/article/44/44362_Lucene+Java.html
关键字:Lucene;Java

概述
Lucene 是最流行的开源搜索引擎库之一。它由能文本索引和搜索的核心API组成。Lucene能够对给出一组文本文件创建索引并且允许你用复杂的查询来搜索这些索 引,例如:+title:Lucene -content:Search、search AND Lucene、+search +code。在进入搜索细节之前,先让我来介绍一下Lucene的一些功能。

在Lucene中索引文本

搜 索引擎对所有需要被搜索的数据进行扫描并将其存储到能有效获取的一个结构里。这个最有名的结构被称为倒排索引。例如,现在考虑对一组会议记录进行索引。首 先,每个会议记录的文件被分为几个独立的部分或者域:如标题、作者、email、摘要和内容。其次,每一域的内容被标记化并且提取出关键字或者术语。这样 就可以建立如下表所示会议记录的倒排索引。
image
        ....                 

对 于域中的每一术语而言,上图存储了两方面的内容:该术语在文件中出现的数量(即频率【DF】)以及包含该术语的每一文件的ID。对于每个术语保存的其它细 节:例如术语在每个文件中出现的次数以及出现的位置也被保存起来。无论如何,对于我们非常重要的一点是要知道:利用Lucene检索文件意味着将其保存为 一种特定格式,该格式允许高效率查询及获取。

分析被索引的文本

Lucene使用分析器来处理被索引的文 本。在将其存入索引之前,分析器用于将文本标记化、摘录有关的单词、丢弃共有的单词、处理派生词(把派生词还原到词根形式,意思是把bowling、 bowler和bowls还原为bowl)和完成其它要做的处理。Lucene提供的通用分析器是:
        SimpleAnalyzer:用字符串标记一组单词并且转化为小写字母。
        StandardAnalyzer:用字符串标记一组单词,可识别缩写词、email地址、主机名称等等。并丢弃基于英语的stop words (a, an, the, to)等、处理派生词。

检索(搜索索引)
索 引结构建立后,可以通过指定被搜索的字段和术语构造复杂的查询来对索引进行检索。例如,用户查询abstract:system AND email:abc@mit.edu得到的结果是所有在摘要中包含system、在email地址中有abc@mit.edu的文件。也就是说,如果在前 面倒排索引表的基础上搜索就返回Doc15。与查询匹配的文件是按照术语在文件中出现的次数以及包含该术语的文档的数量进行排列的。Lucene执行一种 顺序排列机制并且提供了给我们更改它的弹性。

源代码搜索引擎

现在我们知道了关于搜索引擎的基本要点,下面让我们看一看用于搜索源代码的搜索引擎应如何实现。下文中展示在搜索Java示例代码时,开发者主要关注以下Java类:
继承一个具体类或实现一个接口。
调用特定的方法。
使用特定的Java类。

综 合使用上述部分的组合可以满足开发者获取他们正在寻找相关代码的需要。因此搜索引擎应该允许开发者对这些方面进行单个或组合查询。IDEs【集成开发环 境】有另一个局限性:大部分可使用的工具仅仅基于上述标准之一来支持搜索源代码。在搜索中,缺乏组合这些标准进行查询的灵活性。

现在我们开始建立一个支持这些要求的源代码搜索引擎。

编写源代码分析器
第 一步先写一个分析器,用来提取或去除源代码元素,确保建立最佳的索引并且仅包含相关方面的代码。在Java语言中的关键字--public,null, for,if等等,在每个.java文件中它们都出现了,这些关键字类似于英语中的普通单词(the,a,an,of)。因而,分析器必须把这些关键字从 索引中去掉。

我们通过继承Lucene的抽象类Analyzer来建立一个Java源代码分析器。下面列出了 JavaSourceCodeAnalyzer类的源代码,它实现了tokenStream(String,Reader)方法。这个类定义了一组 【stop words】,它们能够在索引过程中,使用Lucene提供的StopFilter类来被去除。tokenStream方法用于检查被索引的字段。如果该 字段是“comment”,首先要利用LowerCaseTokenizer类将输入项标记化并转换成小写字母,然后利用StopFilter类除去英语 中的【stop words】(有限的一组英语【stop words】),再利用PorterStemFilter移除通用的语形学以及词尾后缀。如果被索引的内容不是“comment”,那么分析器就利用 LowerCaseTokenizer类将输入项标记化并转换成小写字母,并且利用StopFilter类除去Java关键字。

package com.infosys.lucene.code JavaSourceCodeAnalyzer.;

import java.io.Reader;
import java.util.Set;
import org.apache.lucene.analysis.*;

public class JavaSourceCodeAnalyzer extends Analyzer {
      private Set javaStopSet;
      private Set englishStopSet;
      private static final String[] JAVA_STOP_WORDS = {
         "public","private","protected","interface",
            "abstract","implements","extends","null""new",
           "switch","case", "default" ,"synchronized" ,
            "do", "if", "else", "break","continue","this",
           "assert" ,"for","instanceof", "transient",
            "final", "static" ,"void","catch","try",
            "throws","throw","class", "finally","return",
            "const" , "native", "super","while", "import",
            "package" ,"true", "false" };
     private static final String[] ENGLISH_STOP_WORDS ={
            "a", "an", "and", "are","as","at","be" "but",
            "by", "for", "if", "in", "into", "is", "it",
            "no", "not", "of", "on", "or", "s", "such",
            "that", "the", "their", "then", "there","these",
            "they", "this", "to", "was", "will", "with" };
     public SourceCodeAnalyzer(){
            super();
            javaStopSet = StopFilter.makeStopSet(JAVA_STOP_WORDS);
            englishStopSet = StopFilter.makeStopSet(ENGLISH_STOP_WORDS);
     }
     public TokenStream tokenStream(String fieldName, Reader reader) {
            if (fieldName.equals("comment"))
                     return   new PorterStemFilter(new StopFilter(
                        new LowerCaseTokenizer(reader),englishStopSet));
            else
                     return   new StopFilter(
                   new LowerCaseTokenizer(reader),javaStopSet);
     }
}


编写类JavaSourceCodeIndexer
第 二步生成索引。用来建立索引的非常重要的类有IndexWriter、Analyzer、Document和Field。对每一个源代码文件建立 Lucene的一个Document实例。解析源代码文件并且摘录出与代码相关的语法元素,主要包括:导入声明、类名称、所继承的类、实现的接口、实现的 方法、方法使用的参数和每个方法的代码等。然后把这些句法元素添加到Document实例中每个独立的Field实例中。然后使用存储索引的 IndexWriter实例将Document实例添加到索引中。

下面列出了JavaSourceCodeIndexer类的源代码。该 类使用了JavaParser类解析Java文件和摘录语法元素,也可以使用Eclipse3.0 ASTParser。这里就不探究JavaParser类的细节了,因为其它解析器也可以用于提取相关源码元素。在源代码文件提取元素的过程中,创建 Filed实例并添加到Document实例中。
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import com.infosys.lucene.code.JavaParser.*;

public class JavaSourceCodeIndexer {
    private static JavaParser parser = new JavaParser();
        private static final String IMPLEMENTS = "implements";
        private static final String IMPORT = "import";
        ...
        public static void main(String[] args) {
                File indexDir = new File("C:\\Lucene\\Java");
                File dataDir = new File("C:\\JavaSourceCode ");
                IndexWriter writer = new IndexWriter(indexDir,
                    new JavaSourceCodeAnalyzer(), true);
                indexDirectory(writer, dataDir);
                writer.close();
        }
        public static void indexDirectory(IndexWriter writer, File dir){
            File[] files = dir.listFiles();
            for (int i = 0; i < files.length; i++) {
                    File f = files[i];
                // Create a Lucene Document
                Document doc = new Document();
                //  Use JavaParser to parse file
                parser.setSource(f);
                addImportDeclarations(doc, parser);
                        addComments(doc, parser);
                 // Extract Class elements Using Parser
                JClass cls = parser.getDeclaredClass();
                addClass(doc, cls);
                 // Add field to the Lucene Document
                       doc.add(Field.UnIndexed(FILENAME, f.getName()));
                writer.addDocument(doc);
                    }
        }
        private static void addClass(Document doc, JClass cls) {
                   //For each class add Class Name field
            doc.add(Field.Text(CLASS, cls.className));
            String superCls = cls.superClass;
            if (superCls != null)
                   //Add the class it extends as extends field
        doc.add(Field.Text(EXTENDS, superCls));
            // Add interfaces it implements
            ArrayList interfaces = cls.interfaces;
            for (int i = 0; i < interfaces.size(); i++)
                doc.add(Field.Text(IMPLEMENTS, (String) interfaces.get(i)));
                    //Add details  on methods declared
            addMethods(cls, doc);
            ArrayList innerCls = cls.innerClasses;
                   for (int i = 0; i < innerCls.size(); i++)
                addClass(doc, (JClass) innerCls.get(i));
        }
        private static void addMethods(JClass cls, Document doc) {
            ArrayList methods = cls.methodDeclarations;
            for (int i = 0; i < methods.size(); i++) {
                       JMethod method = (JMethod) methods.get(i);
                // Add method name field
                doc.add(Field.Text(METHOD, method.methodName));
                // Add return type field
                doc.add(Field.Text(RETURN, method.returnType));
                ArrayList params = method.parameters;
                for (int k = 0; k < params.size(); k++)
                // For each method add parameter types
                    doc.add(Field.Text(PARAMETER, (String)params.get(k)));
                String code = method.codeBlock;
                if (code != null)
                //add the method code block
                    doc.add(Field.UnStored(CODE, code));
            }
        }
        private static void addImportDeclarations(Document doc, JavaParser parser) {
                   ArrayList imports = parser.getImportDeclarations();
            if (imports == null)     return;
            for (int i = 0; i < imports.size(); i++)
                    //add import declarations as keyword
                doc.add(Field.Keyword(IMPORT, (String) imports.get(i)));
        }
}


Lucene有四种不同的字段类型:Keyword,UnIndexed,UnStored和Text,用于指定建立最佳索引。
&#61548;        Keyword字段是指不需要分析器解析但需要被编入索引并保存到索引中的部分。JavaSourceCodeIndexer类使用该字段来保存导入类的声明。
&#61548;        UnIndexed字段是既不被分析也不被索引,但是要被逐字逐句的将其值保存到索引中。由于我们一般要存储文件的位置但又很少用文件名作为关键字来搜索,所以用该字段来索引Java文件名。
& #61548;        UnStored字段和UnIndexed字段相反。该类型的Field要被分析并编入索引,但其值不会被保存到索引中。 由于存储方法的全部源代码需要大量的空间。所以用UnStored字段来存储被索引的方法源代码。可以直接从Java源文件中取出方法的源代码,这样作可 以控制我们的索引的大小。
&#61548;        Text字段在索引过程中是要被分析、索引并保存的。类名是作为Text字段来保存。下表展示了JavaSourceCodeIndexer类使用Field字段的一般情况。

image

1.
   用Lucene建立的索引可以用Luke预览和修改,Luke是用于理解索引很有用的一个开源工具。图1中是Luke工具的一张截图,它显示了JavaSourceCodeIndexer类建立的索引。

image
图1:在Luke中索引截图

如图所见,导入类的声明没有标记化或分析就被保存了。类名和方法名被转换为小写字母后,才保存的。

查询Java源代码
建 立多字段索引后,可以使用Lucene来查询这些索引。它提供了这两个重要类分别是IndexSearcher和QueryParser,用于搜索文件。 QueryParser类则用于解析由用户输入的查询表达式,同时IndexSearcher类在文件中搜索满足查询条件的结果。下列表格显示了一些可能 发生的查询及它的含义:
image

用户通过索引不同的语法元素组成有效的查询条件并搜索代码。下面列出了用于搜索的示例代码。
public class JavaCodeSearch {
public static void main(String[] args) throws Exception{
    File indexDir = new File(args[0]);
    String q =  args[1]; //parameter:JGraph code:insert
    Directory fsDir = FSDirectory.getDirectory(indexDir,false);
    IndexSearcher is = new IndexSearcher(fsDir);

    PerFieldAnalyzerWrapper analyzer = new
        PerFieldAnalyzerWrapper( new
                JavaSourceCodeAnalyzer());

    analyzer.addAnalyzer("import", new KeywordAnalyzer());
    Query query = QueryParser.parse(q, "code", analyzer);
    long start = System.currentTimeMillis();
    Hits hits = is.search(query);
    long end = System.currentTimeMillis();
    System.err.println("Found " + hits.length() +
                " docs in " + (end-start) + " millisec");
    for(int i = 0; i < hits.length(); i++){
    Document doc = hits.doc(i);
        System.out.println(doc.get("filename")
                + " with a score of " + hits.score(i));
    }
    is.close();
}
}


IndexSearcher 实例用FSDirectory来打开包含索引的目录。然后使用Analyzer实例分析搜索用的查询字符串,以确保它与索引(还原词根,转换小写字母,过 滤掉,等等)具有同样的形式。为了避免在查询时将Field作为一个关键字索引,Lucene做了一些限制。Lucene用Analyzer分析在 QueryParser实例里传给它的所有字段。为了解决这个问题,可以用Lucene提供的PerFieldAnalyzerWrapper类为查询中 的每个字段指定必要的分析。因此,查询字符串import:org.w3c.* AND code:Document将用KeywordAnalyzer来解析字符串org.w3c.*并且用JavaSourceCodeAnalyzer来解 析Document。QueryParser实例如果查询没有与之相符的字段,就使用默认的字段:code,使用 PerFieldAnalyzerWrapper来分析查询字符串,并返回分析后的Query实例。IndexSearcher实例使用Query实例并 返回一个Hits实例,它包含了满足查询条件的文件。

结束语

这 篇文章介绍了Lucene——文本搜索引擎,其可以通过加载分析器及多字段索引来实现源代码搜索。文章只介绍了代码搜索引擎的基本功能,同时在源码检索中 使用愈加完善的分析器可以提高检索性能并获得更好的查询结果。这种搜索引擎可以允许用户在软件开发社区搜索和共享源代码。

资源
这篇文章的示例Sample code

分享到:
评论

相关推荐

    .NET lucene 源代码

    标题中的".NET Lucene 源代码"表明我们将探讨的是如何在.NET环境下利用Lucene进行搜索引擎的开发,并且会涉及到源代码层面的解析。描述中提到的“简单易用”,揭示了Lucene的核心特性之一,即它对开发者友好,易于...

    lucene3.6 的源代码

    总的来说,通过对Lucene 3.6源代码的学习,我们可以深入理解全文检索的底层机制,掌握如何高效地构建、查询和优化搜索系统。这对于开发自己的搜索引擎或者在现有项目中集成搜索功能具有极高的价值。同时,这也有助于...

    ]java(结合lucene)版的公交搜索系统源码

    【标题】:基于Java与Lucene的...通过研究这个公交搜索系统源码,开发者不仅可以学习到Java编程的基本技巧,还能掌握如何利用Lucene实现高效的信息检索,这对于进一步开发类似的应用或者提升搜索引擎技能都大有裨益。

    lucene-5.3.1源代码

    本文将深入探讨Lucene 5.3.1版本的核心概念、架构以及如何利用其源代码进行二次开发。 一、Lucene基础 1. Lucene核心组件: - 文档(Document):存储用户数据的容器,可以包含多个字段(Field),如标题、内容等...

    lucene实现 源代码,里面还含有索引创建,搜索等功能

    在这个压缩包文件中,包含的源代码着重展示了如何利用Lucene进行索引创建和搜索操作,这些都是Lucene的核心功能。 首先,让我们了解一下Lucene的索引创建过程。在Lucene中,数据被转化为一种便于搜索的结构——倒排...

    基于LUCENE的搜索引擎的设计与实现源代码

    总之,利用LUCENE构建搜索引擎是一项技术性强、实用性高的任务。通过合理的架构设计和优化,我们可以创建出高效、灵活的搜索解决方案,满足各类信息检索需求。而提供的源代码正是实现这一目标的具体实践,对于学习和...

    Lucene源代码剖析

    《Lucene源代码剖析》是一本深度探讨Java版本Lucene搜索引擎库的专业书籍。Lucene是Apache软件基金会的一个开源项目,广泛应用于全文检索和信息检索领域。本书旨在通过深入解析其源代码,帮助开发者理解Lucene的工作...

    lucene部分案例的源代码

    《Lucene案例源代码解析》 Lucene是一个高性能、全文本搜索库,广泛应用于各种信息检索系统中。本文将深入探讨“lucene部分案例的源代码”,解析其中的关键技术和应用场景,帮助读者更好地理解和运用Lucene。 一、...

    Lucene+nutch搜索引擎开发(源代码)

    《Lucene+nutch搜索引擎开发(源代码)》是关于构建搜索引擎的一个重要资源,它结合了Apache Lucene和Nutch两大开源技术,旨在帮助开发者深入了解搜索引擎的工作原理并实践相关开发。Lucene是一个强大的全文检索库,...

    基于java开发的搜索引擎系统附源代码

    【标题】:“基于Java开发的搜索引擎系统附源代码”是一个以Java编程语言为基础构建的搜索引擎项目的实践案例。这个项目不仅提供了完整的源代码,而且适用于Java技术的学生或开发者作为毕业论文的研究对象,帮助他们...

    基于lucene的ORC 图像搜索源代码

    在压缩包"Search"中,很可能包含了实现这些功能的源代码,包括ORC的实现、Lucene索引的创建和查询、以及相关数据处理的类。通过深入研究这些代码,开发者可以了解如何将Lucene与ORC技术结合,实现一个功能完善的图像...

    多线程搜索引擎java实现源代码

    本项目以"多线程搜索引擎java实现源代码"为标题,旨在介绍如何使用Java编程语言构建一个具备多线程特性的搜索引擎。这个搜索引擎可以抓取网络上的信息,存储网页快照,并建立索引,以便用户快速查询所需内容。下面...

    lucene-3.0.1库及源代码

    《深入理解Lucene 3.0.1:库与源代码解析》 Lucene是一个开源全文搜索引擎库,由Apache软件基金会开发并维护。这个“lucene-3.0.1”版本代表了Lucene在2009年的一个重要里程碑,它提供了强大的文本检索功能,被广泛...

    java+lucene)1236.rar_Lucene 搜索_Luncene_lucene_lucene web

    5. **项目结构与源代码**:“java的公交系统”文件可能包含了项目的源代码,这有助于我们了解其内部工作原理,包括如何使用Lucene进行索引和搜索,以及如何处理和展示搜索结果。 6. **资源与文档**:...

    Lucene实战(第二版)源代码

    最后,通过对《Lucene实战(第二版)源代码》的学习,开发者可以深入理解Lucene的工作原理,掌握如何利用Lucene构建高性能的全文搜索引擎,以及如何优化搜索性能和用户体验。这不仅可以应用于企业级搜索引擎的开发,...

    基于JAVA的源代码搜索引擎架构实现.pdf

    ### 基于JAVA的源代码搜索引擎架构实现 #### 概述 随着互联网技术的快速发展,网络信息资源呈现爆炸性增长。人们越来越依赖互联网来满足信息需求,这使得高效地从海量信息中提取有价值的数据变得至关重要。在此...

    《ajax+Lucene构建搜索引擎》源代码 for lucene 2.x

    《Ajax+Lucene构建搜索引擎》源代码是针对Lucene 2.x版本的一个示例项目,旨在教用户如何结合Ajax技术和Lucene搜索引擎库...通过深入研究这个源代码,开发者可以掌握Lucene的核心概念,以及如何利用Ajax提升搜索体验。

    lucene 5.1.0 码源

    通过对Lucene 5.1.0源代码的阅读和学习,不仅可以了解搜索引擎的基本工作流程,还可以学习到如何利用这些组件来构建高效的文本处理和搜索应用。这对于开发网络爬虫、信息检索系统或者任何需要处理大量文本数据的项目...

Global site tag (gtag.js) - Google Analytics