全文检索:Apache Lucene框架入门实例

  1. 云栖社区>
  2. 博客>
  3. 正文

全文检索:Apache Lucene框架入门实例

技术小阿哥 2017-11-27 13:19:00 浏览1120
展开阅读全文

一 简介

Lucene属于Apache开源项目的一部分,是一个开源的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)

Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆

注:以上介绍参考至百度百科

在使用Lucene建立索引时,可以选择将索引文件存储在内存中或者磁盘里。下面我将分别介绍基于这两种存储方式的全文索引的创建与检索

二 基于内存的索引创建与检索

首先需要做的是下载相关的jar包,下载地址是:http://lucene.apache.org/core/downloads.html

其次,在正式介绍下面的内容之前,至少需要导入以下三个jar包:

  • lucene-analyzers-common-6.2.1.jar

  • lucene-core-6.2.1.jar

  • lucene-queryparser-6.2.1.jar

基于内存的全文索引示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package cn.zifangsky.lucene;
 
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
 
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
 
public class Demo1 {
 
    /**
     * 创建索引
     
     * @param sourceMap
     *            待索引的内容
     * @return
     */
    public static Directory createIndex(Map<String, String> sourceMap) {
        //1 创建一个默认的词法分析器
        Analyzer analyzer = new StandardAnalyzer();
 
        //2 设置索引文件存储位置,可以存储到磁盘和内存中,这里设置为存储到内存
        Directory directory = new RAMDirectory(); // 存储到内存
 
        //3 索引的写入
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        try {
            IndexWriter indexWriter = new IndexWriter(directory, config);
 
            //将内容添加到索引中,每本书表示一个“文档”,并将每个文档进行存储
            if (!sourceMap.isEmpty()) {
                for (Entry<String, String> source : sourceMap.entrySet()) {
                    Document document = new Document();
                    //标题需要分词,使用TextField
                    document.add(new TextField("title", source.getKey(), Field.Store.YES));
                    //作者不需要分词,使用StringField
                    document.add(new StringField("author", source.getValue(), Field.Store.YES));
                    indexWriter.addDocument(document);
                }
            }
            indexWriter.close();
        catch (IOException e) {
            e.printStackTrace();
        }
        return directory;
    }
 
    /**
     * 搜索
     
     * @param directory
     * @param searchWord
     *            搜索关键词
     */
    public static void readIndex(Directory directory, String searchWord) {
        int preHits = 10//获取前面多少个结果
 
        try {
            //1 打开一个文档
            IndexReader indexReader = DirectoryReader.open(directory);
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            Analyzer analyzer = new StandardAnalyzer();
 
            //2 设置使用关键字检索,这里是检索标题
            QueryParser parser = new QueryParser("title", analyzer);
            Query query = parser.parse(searchWord);
 
            //3 获取检索到的结果
            System.out.println("总共有 " + indexSearcher.count(query) + " 个结果");
            ScoreDoc[] hits = indexSearcher.search(query, preHits).scoreDocs;
 
            System.out.println("当前有 " + hits.length + " 个结果,内容分别如下:");
            //遍历检索到的“文档”
            for (int i = 0; i < hits.length; i++) {
                int docId = hits[i].doc;
                Document hitDoc = indexSearcher.doc(docId);
                System.out.println("《 " + hitDoc.get("title") + "》    作者: " + hitDoc.get("author"));
            }
            indexReader.close();
        catch (IOException | ParseException e) {
            e.printStackTrace();
        }
    }
 
    public static void main(String[] args) {
        Map<String, String> books = new HashMap<>();
        books.put("Java编程思想""Bruce Eckel");
        books.put("Java8实战""Raoul-Gabriel Urma");
        books.put("Spring入门经典""Mert Caliskan");
        books.put("Spring实战""Craig Walls");
        books.put("Spring Boot实战""汪云飞");
        books.put("Redis实战""Josiah L. Carlson");
 
        Directory directory = Demo1.createIndex(books);
        Demo1.readIndex(directory, "Spring");
//      Demo1.readIndex(directory, "实战");
    }
}

上面代码并不复杂,而且还有详细的注释,因此一些细节流程我就不多做介绍了。只是需要强调的一点是:

在创建索引时使用了这样的代码:

1
document.add(new TextField("title", source.getKey(), Field.Store.YES));

其中,上面的Field.Store.YES表示在创建索引的同时将内容的原文(也就是:source.getKey())也存储到内存中。如果我们选择了Field.Store.NO,在创建索引和检索的时候都是没问题的,但是在最后是没法提取检索关键字所在的原文内容的

最后的输出结果如下:

1
2
3
4
5
总共有 3 个结果
当前有 3 个结果,内容分别如下:
《 Spring Boot实战》    作者: 汪云飞
《 Spring实战》    作者: Craig Walls
《 Spring入门经典》    作者: Mert Caliskan

三 基于磁盘的索引创建与检索

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package cn.zifangsky.lucene;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
 
public class Demo2 {
 
    public static void main(String[] args) {
        Demo2.createIndex("D:/test/source""D:/test/index");
        Demo2.searchIndex("D:/test/index""apache");
    }
 
    /**
     * 给一个目录下的所有文本文件创建索引
     
     * @param sourceDir
     *            待索引的文件目录
     * @param indexDir
     *            索引文件存储目录
     * @return
     */
    public static void createIndex(String sourceDir, String indexDir) {
        List<File> fileList = getFileList(sourceDir);
 
        if (fileList.size() > 0) {
            // 遍历文件并分别创建索引
            for (File file : fileList) {
                StringBuffer stringBuffer = new StringBuffer();
                stringBuffer.append(getFileContent(file));
 
//              System.out.println("fileName: " + file.getName() + "    filePath: " + file.getPath());
 
                Analyzer analyzer = new StandardAnalyzer();
                try {
                    File indexFile = new File(indexDir);
                    if (!indexFile.exists()) {
                        indexFile.mkdirs();
                    }
 
                    //存储到文件中
                    Directory directory = FSDirectory.open(new File(indexDir).toPath());
                    IndexWriterConfig config = new IndexWriterConfig(analyzer);
                    IndexWriter indexWriter = new IndexWriter(directory, config);
 
                    Document document = new Document();
                    document.add(new TextField("fileName", file.getName(), Store.YES));
                    document.add(new TextField("content", stringBuffer.toString(), Store.YES));
                    document.add(new StringField("path", file.getPath(), Store.YES));
 
                    indexWriter.addDocument(document);
                    indexWriter.commit();
 
                    indexWriter.close();
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 
    /**
     * 在索引目录下检索关键字
     
     * @param indexDir
     *            索引文件存储目录
     * @param searchWord
     *            搜索的关键字
     */
    public static void searchIndex(String indexDir, String searchWord) {
        Analyzer analyzer = new StandardAnalyzer();
 
        try {
            //从一个磁盘目录中检索
            Directory directory = FSDirectory.open(new File(indexDir).toPath());
            DirectoryReader directoryReader = DirectoryReader.open(directory);
            IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
 
            // 检索正文
            QueryParser queryParser = new QueryParser("content", analyzer);
            Query query = queryParser.parse(searchWord);
 
            // 检索前1000个结果
            System.out.println("总共有 " + indexSearcher.count(query) + " 个结果");
            ScoreDoc[] hits = indexSearcher.search(query, 1000).scoreDocs;
 
            System.out.println("当前有 " + hits.length + " 个结果,分别如下:");
            for (int i = 0; i < hits.length; i++) {
                int docId = hits[i].doc;
                Document hitDoc = indexSearcher.doc(docId);
                System.out.println("文件名: " + hitDoc.get("fileName") + "    路径: " + hitDoc.get("path"));
                // System.out.println(hitDoc.get("content"));
            }
            directoryReader.close();
            directory.close();
        catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /***
     * 获取一个目录下的所有文件
     
     * @param sourceDir
     *            文件目录
     * @return 所有文件的集合
     */
    private static List<File> getFileList(String sourceDir) {
        File dir = new File(sourceDir);
        if (dir.isDirectory()) {
            // 返回指定格式的文本文件
            File[] files = dir.listFiles(new FilenameFilter() {
 
                public boolean accept(File dir, String name) {
                    return name.endsWith(".txt") || name.endsWith(".log") || name.endsWith(".xml");
                }
            });
 
            List<File> fileList = new ArrayList<File>();
            if (files.length > 0) {
                for (File tmpFile : files) {
                    fileList.add(tmpFile);
                }
            }
            return fileList;
        }
        return null;
    }
 
    /**
     * 获取一个文本文件的所有内容
     
     * @param file
     * @return
     */
    private static StringBuffer getFileContent(File file) {
        try {
//          BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line = null;
            StringBuffer stringBuffer = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                stringBuffer.append(line + "\n");
            }
 
            reader.close();
            return stringBuffer;
        catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

从上面的代码可以看出,索引存储到内存或者磁盘上,其基本步骤是差不多的,只是使用的类稍有区别。同时多了一些基本的IO操作代码,包括文件的读写等操作

运行上面的索引创建方法之后,打开磁盘上的“D:/test/index”,可以发现多了很多这样的文件:

wKioL1hHiFShS6vOAACsEwCrR8w273.png

最后,检索关键字“apache”,检索到的结果如下:

1
2
3
4
5
总共有 3 个结果
当前有 3 个结果,分别如下:
文件名: Lucene简介.txt    路径: D:\test\source\Lucene简介.txt
文件名: H5_ws.log    路径: D:\test\source\H5_ws.log
文件名: Shiro简介.txt    路径: D:\test\source\Shiro简介.txt

可以发现,有三个文件包含“apache”这个关键字

至此,Lucene的入门介绍到此结束




本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1880295,如需转载请自行联系原作者

网友评论

登录后评论
0/500
评论
技术小阿哥
+ 关注