今天小白刚学lucene,记录下自己的学习。先上代码,然后再分析
package com.wm.util;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;
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.LongField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.document.Field.Store;
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;
import org.apache.lucene.util.Version;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
/**
* @author xinghl
*
*/
public class IndexManager{
private static IndexManager indexManager;
private static String content="";
private static String INDEX_DIR = "/Users/wangmiao/WebstormProjects/luceneIndex";
private static String DATA_DIR = "/Users/wangmiao/WebstormProjects/luceneData";
private static Analyzer analyzer = null;
private static Directory directory = null;
private static IndexWriter indexWriter = null;
/**
* 创建索引管理器
* @return 返回索引管理器对象
*/
public IndexManager getManager(){
if(indexManager == null){
this.indexManager = new IndexManager();
}
return indexManager;
}
/**
* 创建当前文件目录的索引
* @param path 当前文件目录
* @return 是否成功
*/
public static boolean createIndex(String path) throws Exception{
Date date1 = new Date();
List<File> fileList = getFileList(path);
//创建分词的器物
analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
//创建索引目录(FSDirectory 索引库的存放位置,在硬盘上)/(RAMDirectory 索引库在内存上)
directory = FSDirectory.open(new File(INDEX_DIR));
//索引的写入操作
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_CURRENT, analyzer);
indexWriter = new IndexWriter(directory, config);
int id=0;
for (File file : fileList) {
content = "";
//获取文件后缀
String type = file.getName().substring(file.getName().lastIndexOf(".")+1);
if("txt".equalsIgnoreCase(type)){
content += txt2String(file);
}else if("doc".equalsIgnoreCase(type)){
content += doc2String(file);
}else if("xls".equalsIgnoreCase(type)){
content += xls2String(file);
}
System.out.println("name :"+file.getName());
System.out.println("path :"+file.getPath());
System.out.println("content :"+content);
System.out.println();
try{
File indexFile = new File(INDEX_DIR);
if (!indexFile.exists()) {
indexFile.mkdirs();
}
//继承field的集合,然后添加到IndexWrite中,有点蕾丝表中的行的概念
Document document = new Document();
// 提供了对要分析的字段进行何种处理,以KV形式呈现。
// ①:Field.Store.YES, Field.Index.NOT_ANALYZED 表示对索引字段采取:原样保存并且不被StandardAnalyzer进行切分。
// ②: Field.Store.NO, Field.Index.ANALYZED 不保存但是要被StandardAnalyzer切分。
//Store.YES存在内存中,可以被搜索出来,Store.NO不存在内存中,但可以被搜索,但结果不会被搜索出来
document.add(new TextField("filename", file.getName(), Store.YES));
document.add(new Field("content", content, Store.YES,Field.Index.ANALYZED));
document.add(new TextField("path", file.getPath(), Store.YES));
document.add(new Field("id",id+"",Store.YES,Field.Index.NO));
indexWriter.addDocument(document);
indexWriter.commit();
id++;
}catch(Exception e){
e.printStackTrace();
}
content = "";
}
closeWriter();
Date date2 = new Date();
System.out.println("创建索引-----耗时:" + (date2.getTime() - date1.getTime()) + "ms\n");
return true;
}
/**
* 读取txt文件的内容
* @param file 想要读取的文件对象
* @return 返回文件内容
*/
public static String txt2String(File file){
String result = "";
try{
InputStreamReader in = new InputStreamReader(new FileInputStream(file),"UTF-8");
BufferedReader br = new BufferedReader(in);//构造一个BufferedReader类来读取文件
String s = null;
while((s = br.readLine())!=null){//使用readLine方法,一次读一行
result = result + "\n" +s;
}
br.close();
}catch(Exception e){
e.printStackTrace();
}
return result;
}
/**
* 读取doc文件内容
* @param file 想要读取的文件对象
* @return 返回文件内容
*/
public static String doc2String(File file){
String result = "";
try{
FileInputStream fis = new FileInputStream(file);
HWPFDocument doc = new HWPFDocument(fis);
Range rang = doc.getRange();
result += rang.text();
fis.close();
}catch(Exception e){
e.printStackTrace();
}
return result;
}
/**
* 读取xls文件内容
* @param file 想要读取的文件对象
* @return 返回文件内容
*/
public static String xls2String(File file){
String result = "";
try{
FileInputStream fis = new FileInputStream(file);
StringBuilder sb = new StringBuilder();
jxl.Workbook rwb = Workbook.getWorkbook(fis);
Sheet[] sheet = rwb.getSheets();
for (int i = 0; i < sheet.length; i++) {
Sheet rs = rwb.getSheet(i);
for (int j = 0; j < rs.getRows(); j++) {
Cell[] cells = rs.getRow(j);
for(int k=0;k<cells.length;k++)
sb.append(cells[k].getContents());
}
}
fis.close();
result += sb.toString();
}catch(Exception e){
e.printStackTrace();
}
return result;
}
/**
* 查找索引,返回符合条件的文件
* @param text 查找的字符串
* @return 符合条件的文件List
*/
public static void searchIndex(String text){
Date date1 = new Date();
try{
directory = FSDirectory.open(new File(INDEX_DIR));
analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
DirectoryReader ireader = DirectoryReader.open(directory);
//IndexSearcher:这个我们可以理解成以只读的形式打开由IndexWriter创建的索引库,search给QueryParser提供了查询的桥梁。
IndexSearcher isearcher = new IndexSearcher(ireader);
//QueryParser:这玩意提供了一个parse方法能够将我们要查找的词转化为lucene能够理解了查询表达式。
QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, "content", analyzer);
Query query = parser.parse(text);
//Hits:这个就是获取匹配结果的一个指针,优点类似C#中的延迟加载,目的都是一样,提高性能。
ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs;
for (int i = 0; i < hits.length; i++) {
Document hitDoc = isearcher.doc(hits[i].doc);
System.out.println("____________________________");
System.out.println(hitDoc.get("filename"));
System.out.println("content>>"+hitDoc.get("content"));
System.out.println(hitDoc.get("path"));
System.out.println(hitDoc.get("id"));
System.out.println("____________________________");
}
ireader.close();
directory.close();
}catch(Exception e){
e.printStackTrace();
}
Date date2 = new Date();
System.out.println("查看索引-----耗时:" + (date2.getTime() - date1.getTime()) + "ms\n");
}
/**
* 过滤目录下的文件
* @param dirPath 想要获取文件的目录
* @return 返回文件list
*/
public static List<File> getFileList(String dirPath) {
File[] files = new File(dirPath).listFiles();
List<File> fileList = new ArrayList<File>();
for (File file : files) {
if (isTxtFile(file.getName())) {
fileList.add(file);
}
}
return fileList;
}
/**
* 判断是否为目标文件,目前支持txt xls doc格式
* @param fileName 文件名称
* @return 如果是文件类型满足过滤条件,返回true;否则返回false
*/
public static boolean isTxtFile(String fileName) {
if (fileName.lastIndexOf(".txt") > 0) {
return true;
}else if (fileName.lastIndexOf(".xls") > 0) {
return true;
}else if (fileName.lastIndexOf(".doc") > 0) {
return true;
}
return false;
}
public static void closeWriter() throws Exception {
if (indexWriter != null) {
indexWriter.close();
}
}
/**
* 删除文件目录下的所有文件
* @param file 要删除的文件目录
* @return 如果成功,返回true.
*/
public static boolean deleteDir(File file){
if(file.isDirectory()){
File[] files = file.listFiles();
for(int i=0; i<files.length; i++){
deleteDir(files[i]);
}
}
file.delete();
return true;
}
public static void main(String[] args) throws Exception{
// File fileIndex = new File(INDEX_DIR);
// if(deleteDir(fileIndex)){
// fileIndex.mkdir();
// }else{
// fileIndex.mkdir();
// }
createIndex(DATA_DIR);
searchIndex("哈哈");
}
}
lucene在doc.add(new Field("content",curArt.getContent(),Field.Store.NO,Field.Index.TOKENIZED));
Field有两个属性可选:存储和索引。
通过存储属性你可以控制是否对这个Field进行存储;
通过索引属性你可以控制是否对该Field进行索引。
事实上对这两个属性的正确组合很重要。
Field.Index | Field.Store | 说明 |
TOKENIZED(分词) | YES | 被分词索引且存储 |
TOKENIZED | NO | 被分词索引但不存储 |
NO | YES | 这是不能被搜索的,它只是被搜索内容的附属物。如URL等 |
UN_TOKENIZED | YES/NO | 不被分词,它作为一个整体被搜索,搜一部分是搜不出来的 |
NO | NO | 没有这种用法 |
我们以文章表为例.articleinfo.有ID,title(标题),sumary(摘要),content(内容),userName(用户名)
其中title(标题),sumary(摘要)属于第一种情况,既要索引也要分词,也要存储.
content(内容)要分词,索引,但不存储.由于他太大了,而且界面也不用显示整个内容.
ID要存储,不用索引.因为没人用他来查询.但拼URL却很需要他.索引要存储.
userName(用户名)索引,但不分词.可用保存.为什么不分词?比如"成吉思汗",我不想被"成汉"搜索到.我希望要么"成吉思汗"或者"*吉思*"通配符搜到.
Field.Store.YES:存储字段值(未分词前的字段值)
Field.Store.NO:不存储,存储与索引没有关系
Field.Store.COMPRESS:压缩存储,用于长文本或二进制,但性能受损
Field.Index.ANALYZED:分词建索引
Field.Index.ANALYZED_NO_NORMS:分词建索引,但是Field的值不像通常那样被保存,而是只取一个byte,这样节约存储空间
Field.Index.NOT_ANALYZED:不分词且索引
Field.Index.NOT_ANALYZED_NO_NORMS:不分词建索引,Field的值去一个byte保存
TermVector表示文档的条目(由一个Document和Field定位)和它们在当前文档中所出现的次数
Field.TermVector.YES:为每个文档(Document)存储该字段的TermVector
Field.TermVector.NO:不存储TermVector
Field.TermVector.WITH_POSITIONS:存储位置
Field.TermVector.WITH_OFFSETS:存储偏移量
Field.TermVector.WITH_POSITIONS_OFFSETS:存储位置和偏移量
一:索引:
相信大家对索引还是比较熟悉的,lucene能够将我们内容切分成很多词,然后将词作为key,建立“倒排索引”,然后放到索引库中,在上面
的例子中,我们看到了索引过程中使用到了IndexWriter,FSDirectory,StandardAnalyzer,Document和Field这些类,下面简要分析下。
1:IndexWriter
我们看到该类有一个AddDocument方法,所以我们认为该类实现了索引的写入操作。
2:FSDirectory
这个就更简单了,提供了索引库的存放位置,比如我们这里的D:\Sample,或许有人问,能不能存放在内存中,在强大的lucene面前当然
可以做到,lucene中的RAMDirectory就可以实现,当然我们的内存足够大的话,还是可以用内存承载索引库,进而提高搜索的效率。
3:StandardAnalyzer
这个算是索引过程中最最关键的一步,也是我们使用lucene非常慎重考虑的东西,之所以我们能搜索秒杀,关键在于我们如何将输入的内容
进行何种形式的切分,当然不同的切分形式诞生了不同的分析器,StandardAnalyzer就是一个按照单字分词的一种分析器,详细的介绍后续文
章分享。
4:Document
在上面的例子可以看到,他是承载field的集合,然后添加到IndexWriter中,有点类似表中的行的概念。
5: Field
提供了对要分析的字段进行何种处理,以KV形式呈现。
①:Field.Store.YES, Field.Index.NOT_ANALYZED 表示对索引字段采取:原样保存并且不被StandardAnalyzer进行切分。
②: Field.Store.NO, Field.Index.ANALYZED 不保存但是要被StandardAnalyzer切分。
二:搜索
这个比较容易,根据我们输入的词lucene能够在索引库中快速定位到我们要找的词,同样我们可以看到IndexSearcher,QueryParser,Hits。
1:IndexSearcher
这个我们可以理解成以只读的形式打开由IndexWriter创建的索引库,search给QueryParser提供了查询的桥梁。
2:QueryParser
这玩意提供了一个parse方法能够将我们要查找的词转化为lucene能够理解了查询表达式。
3:Hits
这个就是获取匹配结果的一个指针,优点类似C#中的延迟加载,目的都是一样,提高性能。
连接补充
http://blog.csdn.net/chenyi0834/article/details/7846868
http://www.cnblogs.com/ShaYeBlog/archive/2012/09/04/2670432.html
http://www.codeweblog.com/%E6%80%BB%E7%AE%97%E6%89%BE%E5%88%B0lucene-%E5%85%B3%E4%BA%8Estore-yes%E7%9A%84%E8%A7%A3%E9%87%8A%E4%BA%86/
最后ps:
一直对Lucene Store.YES不太理解,网上多数的说法是存储字段,NO为不存储。
这样的解释有点郁闷:字面意思一看就明白,但是不解。
之前我的理解是:如果字段可以不存储,那要怎么搜索这个不存储的字段呢?
原来Lucene就是这样,可以设置某些字段为不存储,但是可以用来检索。
终于在一篇文章里看到这几句话,突然间就明白了。
- //Store.YES 保存 可以查询 可以打印内容
Field storeYes = new Field("storeyes","storeyes",Store.YES,Index.TOKENIZED);
- //Store.NO 不保存 可以查询 不可打印内容 由于不保存内容所以节省空间
Field storeNo = new Field("storeno","storeno",Store.NO,Index.TOKENIZED);
- //Store.COMPRESS 压缩保存 可以查询 可以打印内容 可以节省生成索引文件的空间,
Field storeCompress = new Field("storecompress","storecompress",Store.COMPRESS,Index.TOKENIZED);
至此,对于理解Store.YES,Store.NO 就是不存储就不能直接获取此字段的内容,存储了就可以。但是两者都可以用于检索。
字段是否能被搜索,还与Index有关。