`
lxwt909
  • 浏览: 573475 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Lucene5学习之SpellCheck拼写纠错

阅读更多

        最近有点累,让这篇又姗姗来迟了,各位不好意思,让你们久等了。趁着周末一个人没什么事,继续Lucene5系列的脚步,今天主题是Suggest模块下另一个功能:拼写纠错。什么叫拼写纠错?大家还是看图吧,这样会比较形象:

 

       看完上面两张图片,我想大家应该已经知道SpellCheck是用来解决问题的了吧。其实这个功能主要目的还是为了提升用户体验问题,当用户输入的搜索关键字里包含了错别字(对于英文来说,就是单词拼写不正确),我们的搜索程序能智能检测出来并给出与用户输入的搜索关键字最相似的一组建议关键字,不过Google和百度都选择了只返回匹配度最高的一个搜索建议关键字并没有返回多个。

      知道了SpellCheck是干什么的,那接下来我们需要了解SpellCheck内部是如何工作的。首先我们需要对SpellCheck有个整体的宏观的了解,所以首先还是先查阅下SpellCheck的API文档:

      

     API里对于SpellChecker类没有太多说明,只是说这个SpellCheck功能的主类,代码作者是xxxxxx谁,这些信息对于我们来说没什么太大的价值,后面给出了一个使用示例,我们暂时也不急怎么去使用它,我们学习一个新的类或接口,我们首先需要理解它内部有哪些成员变量以及定义了哪些方法,以及每个方法的含义,这些API里都有说明,看图:


 

    定义了两个publick公开的静态成员变量,DEFAULT_ACCURACY表示默认的最小分数,SpellCheck会对字典里的每个词与用户输入的搜索关键字进行一个相似度打分,默认该值是0.5,相似度分值范围是0到1之间,数字越大表示越相似。F_WORD是对于字典文件里每一行创建索引时使用的默认域名称,默认值为:word

     然后看看有哪些方法并了解每个方法的用途:

 

clearIndex:  移除拼写检查索引中的所有Term项;

close: 关闭IndexSearcher的reader;

exist:判断用户输入的关键字是否存在于拼写检查索引中

getAccuracyaccuracy是精确度的意思,这里表示最小评分,评分越大表示与用户输入的关键字越相似

suggestSimilar:这个方法就是用来决定哪些word会被判定为比较相似的然后以数组的形式返回,这是SpellChecker的核心;

setComparator:设置比较器,既然涉及到相似度问题,那肯定有相似度大小问题,有大小必然存在比较,有比较必然需要比较器,通过比较器决定返回的建议词的顺序,因为一般需要把最相关的显示在最前面,然后依次排序显示;

setSpellIndex:设置拼写检查索引目录的

setStringDistance:设置编辑距实现

当然内部还有一些private内置函数在API里并未体现。大概了解了SpellChecker的内部设计,如果更深入了解SpellChcker则需要通过阅读它的源码:

 

/**
 * <p>
 *   Spell Checker class  (Main class) <br/>
 *  (initially inspired by the David Spencer code).
 * </p>
 *
 * <p>Example Usage:
 * 
 * <pre class="prettyprint">
 *  SpellChecker spellchecker = new SpellChecker(spellIndexDirectory);
 *  // To index a field of a user index:
 *  spellchecker.indexDictionary(new LuceneDictionary(my_lucene_reader, a_field));
 *  // To index a file containing words:
 *  spellchecker.indexDictionary(new PlainTextDictionary(new File("myfile.txt")));
 *  String[] suggestions = spellchecker.suggestSimilar("misspelt", 5);
 * </pre>
 * 
 *
 */
public class SpellChecker implements java.io.Closeable {

  /**
   * The default minimum score to use, if not specified by calling {@link #setAccuracy(float)} .
   */
  public static final float DEFAULT_ACCURACY = 0.5f;

  /**
   * Field name for each word in the ngram index.
   */
  public static final String F_WORD = "word";

  /**
   * the spell index
   */
  // don't modify the directory directly - see #swapSearcher()
  // TODO: why is this package private?
  Directory spellIndex;
  /**
   * Boost value for start and end grams
   */
  private float bStart = 2.0f;

  private float bEnd = 1.0f;
  // don't use this searcher directly - see #swapSearcher()

  private IndexSearcher searcher;
  /*
   * this locks all modifications to the current searcher.
   */

  private final Object searcherLock = new Object();
  /*
   * this lock synchronizes all possible modifications to the
   * current index directory. It should not be possible to try modifying
   * the same index concurrently. Note: Do not acquire the searcher lock
   * before acquiring this lock!
   */
  private final Object modifyCurrentIndexLock = new Object();

  private volatile boolean closed = false;
  // minimum score for hits generated by the spell checker query

  private float accuracy = DEFAULT_ACCURACY;

  private StringDistance sd;
  private Comparator<SuggestWord> comparator;

    public static final float DEFAULT_ACCURACY = 0.5f;
    这里设置了默认的相似度值,小于这个值就不会被返回了即默认会认为相似度小于0.5的就是不相似的。

 

 

    public static final String F_WORD = "word";

    我们在创建拼写索引时默认使用的域名称

  

    Directory spellIndex:拼写索引目录

 

    private float bStart = 2.0f;

    private float bEnd = 1.0f;

    这两个分别是前缀ngram和后缀ngram的权重值,即默认认为前缀ngram权重比后缀ngram大,ngram就是按定长来分割字符串成多个Term,比如lucene,假如采用3gram,则最后返回的Term数组为:luc,uce,cen,ene,显然这里luc是前缀ngram,ene是后缀ngram,当然你也可以采用2gram,只不过这时返回的term个数更多,Term个数多自然查询更精确,但响应速度也会慢些。如果ngram这里的N取值大的话,那匹配粒度大,虽然查询精确度会下降,但响应速度也加快了。

    

    private IndexSearcher searcher; 索引查询器对象,这个没什么好说的;

    

    private final Object searcherLock = new Object();  查询锁,进行查询之间需要先占有这把锁

     

private final Object modifyCurrentIndexLock = new Object();

修改索引锁,在添加索引或更新索引时需要获取这把锁

 

private volatile boolean closed = false;

标识IndexReader是否已经关闭

 

private StringDistance sd;

编辑距实现

 

private Comparator<SuggestWord> comparator;建议词比较器,决定了最终返回的拼写建议词在SuggestWordQueue队列中的排序

 

 

setSpellIndex:

public void setSpellIndex(Directory spellIndexDir) throws IOException {
    // this could be the same directory as the current spellIndex
    // modifications to the directory should be synchronized 
    synchronized (modifyCurrentIndexLock) {
      ensureOpen();
      if (!DirectoryReader.indexExists(spellIndexDir)) {
          IndexWriter writer = new IndexWriter(spellIndexDir,
            new IndexWriterConfig(null));
          writer.close();
      }
      swapSearcher(spellIndexDir);
    }
  }

  先拿到modifyCurrentIndexLock索引修改锁,防止索引被修改,

 

     ensureOpen();确保索引目录能打开

     DirectoryReader.indexExists(spellIndexDir):判断指定索引目录下是否存在段文件,如果不存在则关闭IndexWrtier,然后通过swapSearcher重新打开IndexReader。

     

 

private static String[] formGrams(String text, int ng) {
    int len = text.length();
    String[] res = new String[len - ng + 1];
    for (int i = 0; i < len - ng + 1; i++) {
      res[i] = text.substring(i, i + ng);
    }
    return res;
  }

   formGrams:根据提供的ng长度对text字符串进行ngram分割,返回分割后得到的gram数组

 

 

 

/**
   * Removes all terms from the spell check index.
   * @throws IOException If there is a low-level I/O error.
   * @throws AlreadyClosedException if the Spellchecker is already closed
   */
  public void clearIndex() throws IOException {
    synchronized (modifyCurrentIndexLock) {
      ensureOpen();
      final Directory dir = this.spellIndex;
      final IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(null)
          .setOpenMode(OpenMode.CREATE));
      writer.close();
      swapSearcher(dir);
    }
  }

    clearIndex:首先获取修改索引锁,防止索引没修改,然后ensureOpen确保索引目录能被打开,然后重新new了一个IndexWriter且索引写入模式设置为create即先前的索引将会被覆盖,然后close掉IndexWrtier,最后打开Reader重新new一个IndexWriter.这样做是为了覆盖掉先前的索引(OpenMode.CREATE是关键),即clearIndex方法的目的。

 

     

 

/**
   * Indexes the data from the given {@link Dictionary}.
   * @param dict Dictionary to index
   * @param config {@link IndexWriterConfig} to use
   * @param fullMerge whether or not the spellcheck index should be fully merged
   * @throws AlreadyClosedException if the Spellchecker is already closed
   * @throws IOException If there is a low-level I/O error.
   */
  public final void indexDictionary(Dictionary dict, IndexWriterConfig config, boolean fullMerge) throws IOException {
    synchronized (modifyCurrentIndexLock) {
      ensureOpen();
      final Directory dir = this.spellIndex;
      final IndexWriter writer = new IndexWriter(dir, config);
      IndexSearcher indexSearcher = obtainSearcher();
      final List<TermsEnum> termsEnums = new ArrayList<>();

      final IndexReader reader = searcher.getIndexReader();
      if (reader.maxDoc() > 0) {
        for (final LeafReaderContext ctx : reader.leaves()) {
          Terms terms = ctx.reader().terms(F_WORD);
          if (terms != null)
            termsEnums.add(terms.iterator(null));
        }
      }
      
      boolean isEmpty = termsEnums.isEmpty();

      try { 
        BytesRefIterator iter = dict.getEntryIterator();
        BytesRef currentTerm;
        
        terms: while ((currentTerm = iter.next()) != null) {
  
          String word = currentTerm.utf8ToString();
          int len = word.length();
          if (len < 3) {
            continue; // too short we bail but "too long" is fine...
          }
  
          if (!isEmpty) {
            for (TermsEnum te : termsEnums) {
              if (te.seekExact(currentTerm)) {
                continue terms;
              }
            }
          }
  
          // ok index the word
          Document doc = createDocument(word, getMin(len), getMax(len));
          writer.addDocument(doc);
        }
      } finally {
        releaseSearcher(indexSearcher);
      }
      if (fullMerge) {
        writer.forceMerge(1);
      }
      // close writer
      writer.close();
      // TODO: this isn't that great, maybe in the future SpellChecker should take
      // IWC in its ctor / keep its writer open?
      
      // also re-open the spell index to see our own changes when the next suggestion
      // is fetched:
      swapSearcher(dir);
    }
  }

    先通过IndexReader读取索引目录,加载word域上的所有Term存入TermEnum集合中,然后加载字典文件(字典文件里一行一个词),遍历字典文件里的每个词,内层循环里遍历索引目录里word域上的每个Term,如果当前字典文件里的词在word域中存在(避免Term重复),则直接跳过,否则需要将字典文件里当前词写入索引,这里存入索引并不是直接把当前词存入索引,这里还有个ngram过程,通过ngram分成多个Term再写入索引的,即这句代码Document doc = createDocument(word, getMin(len), getMax(len));min和max即ngram的分割长度范围。然后writer.addDocument(doc);写入索引目录,然后释放IndexReader即releaseSearcher(indexSearcher);,如果设置了fullMerge则会强制将段文件合并为一个即writer.forceMerge(1);最后关闭IndexWriter,最后swapSearcher(dir);即IndexReader重新打开,new一个新的IndexSearcher,保证新加的document被被search到。一句话总结:indexDictionary就是将字典文件里的词进行ngram操作后得到多个词然后分别写入索引。

 

      

/**
   * Suggest similar words (optionally restricted to a field of an index).
   *
   * <p>As the Lucene similarity that is used to fetch the most relevant n-grammed terms
   * is not the same as the edit distance strategy used to calculate the best
   * matching spell-checked word from the hits that Lucene found, one usually has
   * to retrieve a couple of numSug's in order to get the true best match.
   *
   * <p>I.e. if numSug == 1, don't count on that suggestion being the best one.
   * Thus, you should set this value to <b>at least</b> 5 for a good suggestion.
   *
   * @param word the word you want a spell check done on
   * @param numSug the number of suggested words
   * @param ir the indexReader of the user index (can be null see field param)
   * @param field the field of the user index: if field is not null, the suggested
   * words are restricted to the words present in this field.
   * @param suggestMode 
   * (NOTE: if indexReader==null and/or field==null, then this is overridden with SuggestMode.SUGGEST_ALWAYS)
   * @param accuracy The minimum score a suggestion must have in order to qualify for inclusion in the results
   * @throws IOException if the underlying index throws an {@link IOException}
   * @throws AlreadyClosedException if the Spellchecker is already closed
   * @return String[] the sorted list of the suggest words with these 2 criteria:
   * first criteria: the edit distance, second criteria (only if restricted mode): the popularity
   * of the suggest words in the field of the user index
   * 
   */
  public String[] suggestSimilar(String word, int numSug, IndexReader ir,
      String field, SuggestMode suggestMode, float accuracy) throws IOException {
    // obtainSearcher calls ensureOpen
    final IndexSearcher indexSearcher = obtainSearcher();
    try {
      if (ir == null || field == null) {
        suggestMode = SuggestMode.SUGGEST_ALWAYS;
      }
      if (suggestMode == SuggestMode.SUGGEST_ALWAYS) {
        ir = null;
        field = null;
      }

      final int lengthWord = word.length();

      final int freq = (ir != null && field != null) ? ir.docFreq(new Term(field, word)) : 0;
      final int goalFreq = suggestMode==SuggestMode.SUGGEST_MORE_POPULAR ? freq : 0;
      // if the word exists in the real index and we don't care for word frequency, return the word itself
      if (suggestMode==SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX && freq > 0) {
        return new String[] { word };
      }

      BooleanQuery query = new BooleanQuery();
      String[] grams;
      String key;

      for (int ng = getMin(lengthWord); ng <= getMax(lengthWord); ng++) {

        key = "gram" + ng; // form key

        grams = formGrams(word, ng); // form word into ngrams (allow dups too)

        if (grams.length == 0) {
          continue; // hmm
        }

        if (bStart > 0) { // should we boost prefixes?
          add(query, "start" + ng, grams[0], bStart); // matches start of word

        }
        if (bEnd > 0) { // should we boost suffixes
          add(query, "end" + ng, grams[grams.length - 1], bEnd); // matches end of word

        }
        for (int i = 0; i < grams.length; i++) {
          add(query, key, grams[i]);
        }
      }

      int maxHits = 10 * numSug;

  //    System.out.println("Q: " + query);
      ScoreDoc[] hits = indexSearcher.search(query, null, maxHits).scoreDocs;
  //    System.out.println("HITS: " + hits.length());
      SuggestWordQueue sugQueue = new SuggestWordQueue(numSug, comparator);

      // go thru more than 'maxr' matches in case the distance filter triggers
      int stop = Math.min(hits.length, maxHits);
      SuggestWord sugWord = new SuggestWord();
      for (int i = 0; i < stop; i++) {

        sugWord.string = indexSearcher.doc(hits[i].doc).get(F_WORD); // get orig word

        // don't suggest a word for itself, that would be silly
        if (sugWord.string.equals(word)) {
          continue;
        }

        // edit distance
        sugWord.score = sd.getDistance(word,sugWord.string);
        if (sugWord.score < accuracy) {
          continue;
        }

        if (ir != null && field != null) { // use the user index
          sugWord.freq = ir.docFreq(new Term(field, sugWord.string)); // freq in the index
          // don't suggest a word that is not present in the field
          if ((suggestMode==SuggestMode.SUGGEST_MORE_POPULAR && goalFreq > sugWord.freq) || sugWord.freq < 1) {
            continue;
          }
        }
        sugQueue.insertWithOverflow(sugWord);
        if (sugQueue.size() == numSug) {
          // if queue full, maintain the minScore score
          accuracy = sugQueue.top().score;
        }
        sugWord = new SuggestWord();
      }

      // convert to array string
      String[] list = new String[sugQueue.size()];
      for (int i = sugQueue.size() - 1; i >= 0; i--) {
        list[i] = sugQueue.pop().string;
      }

      return list;
    } finally {
      releaseSearcher(indexSearcher);
    }
  }

    现在来说说核心函数suggestSimilar,用来计算最后返回的建议词。首先需要说下SuggestMode的建议模式:

 

    SUGGEST_WHEN_NOT_IN_INDEX:

    意思就是只有当用户提供的搜索关键字在索引Term中不存在我才提供建议,否则我会认为用户输入的搜索关键字是正确的不需要提供建议。

    SUGGEST_MORE_POPULAR:

    表示只返回频率较高的词组,用户输入的搜索关键字首先需要经过ngram分割,创建索引的时候也需要进行分词,如果用户输入的词分割后得到的word在索引中出现的频率比索引中实际存在的Term还要高,那说明不需要进行拼写建议了。

    SUGGEST_ALWAYS:

    永远进行建议,只是返回的建议结果受numSug数量限制即最多返回几条拼写建议。

    if (suggestMode==SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX && freq > 0) {

        return new String[] { word };

      }

     这里freq>0表明用户输入的搜索关键字在Term中出现了且你配置只对搜索关键字在索引中不存在才进行拼写建议,则直接返回用户数的原词即默认用户输入是正确的,不需要进行拼写建议

    for (int ng = getMin(lengthWord); ng <= getMax(lengthWord); ng++)

    然后根据用户输入的搜索关键字的长度确定进行ngram的分割长度范围,然后对用户输入的搜索关键字进行ngram操作并把分割出的每个词使用booleanQuery链接(默认用的or链接)起来,如果是前缀ngram和后缀ngram还需要设置不同的权重,然后根据创建的Query进行检索:

ScoreDoc[] hits = indexSearcher.search(query, null, maxHits).scoreDocs;

 然后遍历实际命中的结果集for (int i = 0; i < stop; i++),这里的stop即结果集的实际长度,取出每个Term与当前用户输入的关键字进行比对,如果equals则直接pass,因为需要排除自身,即这句代码:

    if (sugWord.string.equals(word)) {

          continue;

    }

sugWord.score = sd.getDistance(word,sugWord.string);

返回用户输入关键字和索引中当前Term的相似度,这个取决于你Distance实现,默认实现是LevensteinDistance即计算编辑距。

if (sugWord.score < accuracy) {

          continue;

}

如果相似度小于设置的默认值则也不返回

if ((suggestMode==SuggestMode.SUGGEST_MORE_POPULAR && goalFreq > sugWord.freq) || sugWord.freq < 1) {

            continue;

 }

在SUGGEST_MORE_POPULAR 模式下,如果用户输入的关键字都比索引中的Term的出现频率高,那也直接跳过不返回(用户输入的关键字出现频率高,说明该关键字匹配度高啊,通过该关键字搜索自然会有结果集返回,自然不需要进行拼写检查啊)

sugQueue.insertWithOverflow(sugWord);

        if (sugQueue.size() == numSug) {

          accuracy = sugQueue.top().score;

 

}

条件符合那就把当前索引中的Term存入拼写建议队列中,如果队列满了则把队列顶部的score(即相似度)缓存到accuracy即该值就表示了当前最小的相似度值,因为当队列满了,自然是要把相似度最小的给移除啊,处于顶部的自然是最小的,所以你懂的。

sugWord = new SuggestWord();然后把SuggestWord重置进入索引中下一个Term匹配过程如此循环。

for (int i = sugQueue.size() - 1; i >= 0; i--) {

        list[i] = sugQueue.pop().string;

}

最后把队列里的word一个个pop从队列中弹出,因为是倒着赋值的(从数组尾部赋值的),那自然最终返回的数组里相似度高的在前面,相似度低的处于数组尾部。

 

关键重点是sugWord.score = sd.getDistance(word,sugWord.string);

判断两个词的相似度,默认实现是LevensteinDistance,至于LevensteinDistance算法实现自己去看LevensteinDistance源码吧。

其实lucene还内置了另外几种相似度实现:JaroWinklerDistance和NGramDistance。

Distance实现可以在构造SpellChecker时通过其构造函数进行自定义,

/**
   * Use the given directory as a spell checker index. The directory
   * is created if it doesn't exist yet.
   * @param spellIndex the spell index directory
   * @param sd the {@link StringDistance} measurement to use 
   * @throws IOException if Spellchecker can not open the directory
   */
  public SpellChecker(Directory spellIndex, StringDistance sd) throws IOException {
    this(spellIndex, sd, SuggestWordQueue.DEFAULT_COMPARATOR);
  }

    另外一个重点是comparator,在构造SpellChecker时如果没有显式设置comparator,则默认构造的是SuggestWordQueue.DEFAULT_COMPARATOR即SuggestWordScoreComparator,SuggestWordScoreComparator的比较规则是:

 先比较socre值即相似度值(这个相似度值是有Distance类决定的),如果score值相等,则再来比较freq即索引中出现频率,频率高则相似度高,如果还是分不出高下,则直接比较两个字符串吧,字符串比较其实就是从左至右逐个字符比较他们的ASCII码。

 

其实Compartor比较器也是可以通过构造函数重载进行指定的,如果默认的比较器满足不了你的要求,请自定义你自己的比较器,你懂的。

 

      还有一个重点就是Dictionary实现,它有3个实现:

     1. PlainTextDictionary:即文本字典文件,即从文本文件中读取内容来构建索引,不过要求一行只能一个词,内部会对每一行的词进行ngram分割然后才写入索引中

   2.LuceneDictionary:即直接以已经存在的索引中的某个域的值作为字典

   3.HighFrequencyDictionary:跟LuceneDictionary类似,它跟LuceneDictionary区别就是它限制了只有出现在各document中的次数满足一定数量时才会被写入索引,这个数量通过构造函数的thresh参数决定。

   Dictionary实现类通过SpellChecker的indexDictionary方法指定。

      按照惯例下面还是来段示例代码吧:

      

package com.yida.framework.lucene5.spellcheck;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;

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.TextField;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.spell.PlainTextDictionary;
import org.apache.lucene.search.spell.SpellChecker;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;

/**
 * 拼写检查测试
 * @author Lanxiaowei
 *
 */
public class SpellCheckTest {
	private static String dicpath = "C:/dictionary.dic";
    private Document document;
    private Directory directory = new RAMDirectory();
    private IndexWriter indexWriter;
    /**拼写检查器*/
    private SpellChecker spellchecker;
    private IndexReader indexReader;
    private IndexSearcher indexSearcher;
    
    /**
     * 创建测试索引
     * @param content
     * @throws IOException
     */
	public void createIndex(String content) throws IOException {
		IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer());
        indexWriter = new IndexWriter(directory, config);
        document = new Document();
        document.add(new TextField("content", content,Store.YES));
        try {
            indexWriter.addDocument(document);
            indexWriter.commit();
            indexWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	
	public void search(String word, int numSug) {
        directory = new RAMDirectory();
        try {
    		IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer());
            spellchecker = new SpellChecker(directory);
            //初始化字典目录
            //最后一个fullMerge参数表示拼写检查索引是否需要全部合并
            spellchecker.indexDictionary(new PlainTextDictionary(Paths.get(dicpath)),config,true);
            //这里的参数numSug表示返回的建议个数
            String[] suggests = spellchecker.suggestSimilar(word, numSug);
            if (suggests != null && suggests.length > 0) {
                for (String suggest : suggests) {
                    System.out.println("您是不是想要找:" + suggest);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	public static void main(String[] args) throws IOException {
		SpellCheckTest spellCheckTest = new SpellCheckTest();
		spellCheckTest.createIndex("《屌丝男士》不是传统意义上的情景喜剧,有固定时长和单一场景,以及简单的生活细节。而是一部具有鲜明网络特点,舞台感十足,整体没有剧情衔接,固定的演员演绎着并不固定角色的笑话集。");
		spellCheckTest.createIndex("屌丝男士的拍摄构想,首先源于“屌丝文化”在中国的刮起的现象级春风,红透了整片天空,全中国上下可谓无人不屌丝,无人不爱屌丝。");
		spellCheckTest.createIndex("德国的一部由女演员玛蒂娜-希尔主演的系列短剧,凭借其疯癫荒诞、自high耍贱、三俗无下限的表演风格,在中国取得了巨大成功,红火程度远远超过了德国。不仅位居国内各个视频网站的下载榜和点播榜高位,且在微博和媒体间,引发了坊间热议和话题传播。网友们更是形象地将其翻译为《屌丝女士》,对其无比热衷。于是我们决定着手拍一部属于中国人,带强烈国人屌丝色彩的《屌丝男士》。");
		
		String word = "吊丝男士";
		spellCheckTest.search(word, 5);
	}
}

 

 

     在C盘新建一个dictionary.dic文件,编辑内容如图:

     

     OK,有关SpellCheck拼写纠错功能就说到这儿了,如果还有哪里不清楚的请联系我,demo源码请在最底下的附件里下载。

 

       如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!

    

   

 

 

     

     

  • 大小: 245.8 KB
  • 大小: 214.5 KB
  • 大小: 289.5 KB
  • 大小: 97.8 KB
  • 大小: 481.3 KB
  • 大小: 206.9 KB
  • 大小: 256.1 KB
  • 大小: 42.8 KB
2
1
分享到:
评论

相关推荐

    lucene拼写纠错代码didyoumean

    在这个“lucene拼写纠错代码didyoumean”主题中,我们将深入探讨如何利用Lucene实现拼写纠错功能。 首先,Lucene的`SpellChecker`类是其拼写纠错功能的核心。这个类允许开发者构建一个拼写字典,并对比用户输入的...

    Lucene5学习之Facet(续)

    《Lucene5学习之Facet(续)》 在深入探讨Lucene5的Facet功能之前,我们先来了解一下什么是Faceting。Faceting是搜索引擎提供的一种功能,它允许用户通过分类或属性对搜索结果进行细分,帮助用户更精确地探索和理解...

    Lucene5学习之Suggest关键字提示

    而DirectSpellChecker主要处理拼写纠错,检查用户输入是否正确,并提供纠正建议。 要使用这些Suggester,我们需要进行以下步骤: 1. **创建Suggest索引**:这通常涉及到对输入数据的预处理,包括分词、赋予权重等...

    Lucene5学习之拼音搜索

    本文将围绕“Lucene5学习之拼音搜索”这一主题,详细介绍其拼音搜索的实现原理和实际应用。 首先,我们需要理解拼音搜索的重要性。在中文环境中,由于汉字的复杂性,用户往往习惯于通过输入词语的拼音来寻找信息。...

    Lucene5学习之自定义Collector

    这篇博客“Lucene5学习之自定义Collector”显然聚焦于如何在Lucene 5版本中通过自定义Collector来优化搜索结果的收集过程。Collector是Lucene搜索框架中的一个重要组件,它负责在搜索过程中收集匹配的文档,并根据...

    Lucene5学习之排序-Sort

    “Lucene5学习之排序-Sort”这个标题表明了我们要探讨的是关于Apache Lucene 5版本中的排序功能。Lucene是一个高性能、全文检索库,它提供了强大的文本搜索能力。在这个主题中,我们将深入理解如何在Lucene 5中对...

    Lucene5学习之Highlighte关键字高亮

    《Lucene5学习之Highlighter关键字高亮》 在信息技术领域,搜索引擎的使用已经变得无处不在,而其中的关键技术之一就是如何有效地突出显示搜索结果中的关键字,这就是我们今天要探讨的主题——Lucene5中的...

    Lucene5学习之Group分组统计

    "Lucene5学习之Group分组统计" 这个标题指出我们要讨论的是关于Apache Lucene 5版本中的一个特定功能——Grouping。在信息检索领域,Lucene是一个高性能、全文搜索引擎库,而Grouping是它提供的一种功能,允许用户对...

    Lucene5学习之增量索引(Zoie)

    总结起来,Lucene5学习之增量索引(Zoie)涉及到的关键技术点包括: 1. 基于Lucene的增量索引解决方案:Zoie系统。 2. 主从复制架构:Index Provider和Index User的角色。 3. 数据变更追踪:通过变更日志实现增量索引...

    Lucene5学习之创建索引入门示例

    **Lucene5学习之创建索引入门示例** 在IT领域,搜索引擎的开发与优化是一项关键技术,而Apache Lucene作为一款高性能、全文本搜索库,是许多开发者进行文本检索的首选工具。本文将深入探讨如何使用Lucene5来创建一...

    Lucene5学习之FunctionQuery功能查询

    **标题解析:** "Lucene5学习之FunctionQuery功能查询" Lucene5是Apache Lucene的一个版本,这是一个高性能、全文本搜索库,广泛应用于搜索引擎和其他需要高效文本检索的系统。FunctionQuery是Lucene中的一种查询...

    Lucene5学习之自定义排序

    本文将深入探讨“Lucene5学习之自定义排序”这一主题,帮助你理解如何在Lucene5中实现自定义的排序规则。 首先,Lucene的核心功能之一就是提供高效的全文检索能力,但默认的搜索结果排序通常是基于相关度得分...

    Lucene5学习之分页查询

    本文将深入探讨"Lucene5学习之分页查询"这一主题,结合给定的标签"源码"和"工具",我们将讨论如何在Lucene5中实现高效的分页查询,并探讨其背后的源码实现。 首先,理解分页查询的重要性是必要的。在大型数据集的...

    Lucene5学习之多线程创建索引

    《Lucene5学习之多线程创建索引》 在深入了解Lucene5的多线程索引创建之前,我们先来了解一下Lucene的基本概念。Lucene是一个高性能、全文本搜索库,由Apache软件基金会开发。它提供了强大的文本分析、索引和搜索...

    Lucene5学习之Spatial地理位置搜索

    《深入探索Lucene5 Spatial:地理位置搜索》 在信息技术飞速发展的今天,地理位置搜索已经成为许多应用和服务不可或缺的一部分。Apache Lucene作为一个强大的全文搜索引擎库,其在5.x版本中引入了Spatial模块,使得...

    Lucene5学习之Filter过滤器

    《深入理解Lucene5:Filter过滤器的奥秘》 在全文搜索引擎的开发过程中,Lucene作为一款强大的开源搜索引擎库,扮演着至关重要的角色。它提供了丰富的功能,使得开发者能够快速构建高效的搜索系统。其中,Filter...

    Lucene5学习之TermVector项向量

    《Lucene5学习之TermVector项向量》 在深入理解Lucene5的搜索引擎功能时,TermVector(项向量)是一个关键的概念,它对于文本分析、信息检索和相关性计算等方面起着至关重要的作用。TermVector是Lucene提供的一种...

    Lucene5学习之自定义同义词分词器简单示例

    本篇将聚焦于"Lucene5学习之自定义同义词分词器简单示例",通过这个主题,我们将深入探讨如何在Lucene5中自定义分词器,特别是实现同义词扩展,以提升搜索质量和用户体验。 首先,理解分词器(Analyzer)在Lucene中...

    Lucene5学习之评分Scoring

    《Lucene5学习之评分Scoring》 在信息检索领域,Lucene是一个广泛使用的全文搜索引擎库,尤其在Java开发中应用颇广。在Lucene 5版本中,对于搜索结果的排序和评分机制进行了优化,使得搜索体验更加精准。本文将深入...

Global site tag (gtag.js) - Google Analytics