lucene简单的学习(一)

今天小白刚学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("哈哈");
    }
}


lucenedoc.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就是这样,可以设置某些字段为不存储,但是可以用来检索。

终于在一篇文章里看到这几句话,突然间就明白了。

  1. //Store.YES 保存 可以查询 可以打印内容

  2. Field storeYes = new Field("storeyes","storeyes",Store.YES,Index.TOKENIZED);

  3. //Store.NO 不保存 可以查询 不可打印内容 由于不保存内容所以节省空间

  4. Field storeNo = new Field("storeno","storeno",Store.NO,Index.TOKENIZED);

  5. //Store.COMPRESS 压缩保存 可以查询 可以打印内容 可以节省生成索引文件的空间,

  6. Field storeCompress = new Field("storecompress","storecompress",Store.COMPRESS,Index.TOKENIZED); 

至此,对于理解Store.YES,Store.NO 就是不存储就不能直接获取此字段的内容,存储了就可以。但是两者都可以用于检索。

字段是否能被搜索,还与Index有关。



猜你喜欢

转载自blog.csdn.net/xiao__miao/article/details/68062067
今日推荐