标签:offset amd reader wildcard 正则表达 its 多个 standard token
在学习Lucene的查询方法前,先了解一下下面几个类:
封装某种查询类型的具体子类,配置查询的查询条件。Query实例将被传递给IndexSearcher的search方法。下面是常用的Query子类:
l 通过项进行搜索 TermQuery类
l 在指定的项范围内搜索 TermRangeQuery类
l 通过字符串搜索 PrefixQuery类
l 组合查询 BooleanQuery类
l 通过短语搜索 PhraseQuery类
l 通配符查询 WildcardQuery类
l 搜索类似项 FuzzyQuery类
l 匹配所有文档 MatchAllDocsQuery类
l 不匹配文档 MatchNoDocsQuery类
l 解析查询表达式 QueryParser类
l 多短语查询 MultiPhraseQuery类
l 查询所有 MatchAllDocsQuery类
l 不匹配所有文档 MatchNoDocsQuery类
将用户输入的可读的查询表达式处理成具体的Query对象
示例:
1 @Test 2 3 public void testQueryParser() throws ParseException, IOException { 4 5 //使用WhitespaceAnalyzer分析器不会忽略大小写,也就是说大小写敏感 6 7 QueryParser queryParser = new QueryParser("bookcontent", new IKAnalyzer()); 8 9 Query query = queryParser.parse("2018"); 10 11 IndexSearcher searcher = getIndexSearcher(); 12 13 TopDocs topDocs = searcher.search(query, 10); 14 15 16 17 // 返回查询结果。遍历查询结果并输出。 18 19 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 20 21 for (ScoreDoc scoreDoc : scoreDocs) { 22 23 int doc = scoreDoc.doc; 24 25 Document document = searcher.doc(doc); 26 27 // 打印content字段的值 28 29 System.out.println("bookid: "+document.get("bookid")); 30 31 System.out.println("bookname: "+document.get("bookname")); 32 33 System.out.println("booktype: "+document.get("booktype")); 34 35 System.out.println("bookcontent: "+document.get("bookcontent")); 36 37 } 38 39 }
1 /** 2 3 * 传统解析器-多默认字段 4 5 * B. 基于新的 flexible 框架的解析器:StandardQueryParser 6 7 */ 8 9 @Test 10 11 public void MultiFieldQueryParser() { 12 13 try { 14 15 String[] multiDefaultFields = { "bookname", "booktype", "bookcontent" }; 16 17 org.apache.lucene.queryparser.classic.MultiFieldQueryParser multiFieldQueryParser = new org.apache.lucene.queryparser.classic.MultiFieldQueryParser( 18 19 multiDefaultFields, new IKAnalyzer()); 20 21 // 设置默认的组合操作,默认是 OR 22 23 multiFieldQueryParser.setDefaultOperator(Operator.OR); 24 25 Query query = multiFieldQueryParser.parse("西游记》又称央视86版《西游记》,改编自明12345678901"); 26 27 doSearch(query); 28 29 } catch (Exception e) { 30 31 e.printStackTrace(); 32 33 } 34 35 }
1 /** 2 3 * 词项查询 4 5 * 最基本、最常用的查询。用来查询指定字段包含指定词项的文档。 6 7 */ 8 9 @Test 10 11 public void termQuery() { 12 13 try { 14 15 IndexSearcher searcher = getIndexSearcher(); 16 17 18 19 TermQuery tq = new TermQuery(new Term("bookname", "西游记")); 20 21 TopDocs topDocs = searcher.search(tq, 10); 22 23 printTopDocs(topDocs); 24 25 } catch (Exception e) { 26 27 e.printStackTrace(); 28 29 } 30 31 }
1 /** 2 3 * 布尔查询 4 5 * 搜索的条件往往是多个的,如要查询名称包含“电脑” 或 “thinkpad”的商品,就需要两个词项查询做或合并。布尔查询就是用来组合多个子查询的。 6 7 * 每个子查询称为布尔字句 BooleanClause,布尔字句自身也可以是组合的。 组合关系支持如下四种: 8 9 * Occur.SHOULD 或 10 11 * Occur.MUST 且 12 13 * Occur.MUST_NOT 且非 14 15 * Occur.FILTER 同 MUST,但该字句不参与评分 16 17 * 布尔查询默认的最大字句数为1024,在将通配符查询这样的查询rewriter为布尔查询时,往往会产生很多的字句,可能抛出TooManyClauses 异常。 18 19 * 可通过BooleanQuery.setMaxClauseCount(int)设置最大字句数。 20 21 */ 22 23 @Test 24 25 public void booleanQuery() { 26 27 try { 28 29 // 创建一个indexsearcher对象 30 31 IndexSearcher searcher = getIndexSearcher(); 32 33 34 35 BooleanQuery.Builder builder = new BooleanQuery.Builder(); 36 37 // 书内容 38 39 QueryParser queryParser = new QueryParser("bookcontent", new IKAnalyzer()); 40 41 Query query1 = queryParser.parse("西游记"); 42 43 builder.add(query1, Occur.MUST); 44 45 46 47 // 书名称 48 49 Query qymc = new FuzzyQuery(new Term("bookname", "西游记")); 50 51 builder.add(qymc, Occur.MUST); 52 53 54 55 // 书类型 56 57 Query gmsfhm = new TermQuery(new Term("booktype", "小说")); 58 59 builder.add(gmsfhm, Occur.MUST); 60 61 62 63 BooleanQuery booleanQuery = builder.build(); 64 65 66 67 TopDocs topDocs = searcher.search(booleanQuery, 10); 68 69 printTopDocs(topDocs); 70 71 } catch (Exception e) { 72 73 e.printStackTrace(); 74 75 } 76 77 }
Occur是用来决定各个条件的逻辑关系,具体如下:
l Occur.SHOULD 或
l Occur.MUST 且
l Occur.MUST_NOT 且非
l Occur.FILTER 同 MUST,但该字句不参与评分
而这些逻辑组合之间也有要关注的地方(下面几点关注一下即可,跟版本有关,在8.1.0的版本中是直接意思,并没有出现下面的情况。应该是新版已经更新,6版本之前的可以关注下面的信息。):
1 /** 2 3 * 短语查询 4 5 * 最常用的查询,匹配特定序列的多个词项。PhraserQuery使用一个位置移动因子(slop)来决定任意两个词项的位置可最大移动多少个位置来进行匹配,默认为0。 6 7 * 有两种方式来构建对象: 8 9 * 注意:所有加入的词项都匹配才算匹配(即使是你在同一位置加入多个词项)。如果需要在同一位置匹配多个同义词中的一个,适合用MultiPhraseQuery 10 11 */ 12 13 @Test 14 15 public void phraseQuery() { 16 17 try { 18 19 PhraseQuery phraseQuery1 = new PhraseQuery("bookcontent", "根据", "明代"); 20 21 22 23 PhraseQuery phraseQuery2 = new PhraseQuery(0, "bookcontent", "根据", "明代"); 24 25 26 27 PhraseQuery phraseQuery3 = new PhraseQuery("bookcontent", "笔记本电脑", "联想"); 28 29 30 31 PhraseQuery phraseQuery4 = new PhraseQuery.Builder() 32 33 .add(new Term("bookcontent", "根据"), 4) 34 35 .add(new Term("bookcontent", "施耐"), 5).build(); 36 37 // 这两句等同 38 39 PhraseQuery phraseQuery5 = new PhraseQuery.Builder() 40 41 .add(new Term("bookcontent", "笔记本电脑"), 0) 42 43 .add(new Term("bookcontent", "联想"), 1).build(); 44 45 46 47 doSearch(phraseQuery2); 48 49 } catch (Exception e) { 50 51 e.printStackTrace(); 52 53 } 54 55 }
1 /** 2 3 * 多重短语查询 4 5 * 短语查询的一种更通用的用法,支持同位置多个词的OR匹配。通过里面的Builder来构建MultiPhraseQuery: 6 7 */ 8 9 @Test 10 11 public void multiPhraseQuery() { 12 13 try { 14 15 // 4 MultiPhraseQuery 多重短语查询 16 17 Term[] terms = new Term[2]; 18 19 terms[0] = new Term("bookcontent", "根据"); 20 21 terms[1] = new Term("bookcontent", "根据明代"); 22 23 Term t = new Term("bookcontent", "施耐"); 24 25 MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder() 26 27 .add(terms).add(t).build(); 28 29 30 31 // 对比 PhraseQuery在同位置加入多个词 ,同位置的多个词都需匹配,所以查不出。 32 33 PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0) 34 35 .add(terms[1], 0).add(t, 1).build(); 36 37 38 39 doSearch(multiPhraseQuery); 40 41 } catch (Exception e) { 42 43 e.printStackTrace(); 44 45 } 46 47 }
1 /** 2 3 * 临近查询(跨度查询) 4 5 * 用于更复杂的短语查询,可以指定词间位置的最大间隔跨度。通过组合一系列的SpanQuery 实例来进行查询,可以指定是否按顺序匹配、slop、gap 6 7 */ 8 9 @Test 10 11 public void spanNearQuery() { 12 13 try { 14 15 // SpanNearQuery 临近查询 16 17 SpanTermQuery tq1 = new SpanTermQuery(new Term("bookcontent", "中央电视台")); 18 19 SpanTermQuery tq2 = new SpanTermQuery(new Term("bookcontent", "无锡")); 20 21 SpanNearQuery spanNearQuery = new SpanNearQuery( 22 23 new SpanQuery[] { tq1, tq2 }, 0, true); 24 25 26 27 // SpanNearQuery 临近查询 gap slop 使用 28 29 SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery 30 31 .newOrderedNearQuery("bookcontent"); 32 33 spanNearQueryBuilder.addClause(tq1).addGap(0).setSlop(1) 34 35 .addClause(tq2); 36 37 SpanNearQuery spanNearQuery5 = spanNearQueryBuilder.build(); 38 39 // IndexSearcher searcher = getIndexSearcher(); 40 41 // TopDocs topDocs = searcher.search(spanNearQueryBuilder, 10); 42 43 doSearch(spanNearQuery); 44 45 } catch (Exception e) { 46 47 e.printStackTrace(); 48 49 } 50 51 }
1 /** 2 3 * 词项范围查询 4 5 * 用于查询包含某个范围内的词项的文档,如以字母开头a到c的词项。词项在反向索引中是排序的,只需指定的开始词项、结束词项,就可以查询该范围的词项。 6 7 * 如果是做数值的范围查询则用 PointRangeQuery 8 9 * 参数说明: 10 11 * 第1个参数:要查询的字段-field 12 13 * 第2个参数::下边界词-lowerTerm 14 15 * 第3个参数:上边界词-upperTerm 16 17 * 第4个参数:是否包含下边界-includeLower 18 19 * 第5个参数:是否包含上边界 includeUpper 20 21 */ 22 23 @Test 24 25 public void termRangeQuery() { 26 27 try { 28 29 // TermRangeQuery 词项范围查询 30 31 TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("bookcontent", 32 33 "中央电视台", "同名小说改编", false, true); 34 35 doSearch(termRangeQuery); 36 37 } catch (Exception e) { 38 39 e.printStackTrace(); 40 41 } 42 43 }
1 /** 2 3 * 前缀查询 4 5 * PrefixQuery:前缀查询,查询包含以xxx为前缀的词项的文档,是通配符查询,如 app,实际是 app* 6 7 */ 8 9 @Test 10 11 public void prefixQuery() { 12 13 try { 14 15 // PrefixQuery 前缀查询 16 17 PrefixQuery prefixQuery = new PrefixQuery(new Term("bookcontent", "中国")); 18 19 doSearch(prefixQuery); 20 21 } catch (Exception e) { 22 23 e.printStackTrace(); 24 25 } 26 27 }
1 /** 2 3 * 通配符查询 4 5 * WildcardQuery:通配符查询, *表示0个或多个字符,?表示1个字符,\是转义符。通配符查询可能会比较慢,不可以通配符开头(那样就是所有词项了) 6 7 */ 8 9 @Test 10 11 public void wildcardQuery() { 12 13 try { 14 15 // WildcardQuery 通配符查询 16 17 WildcardQuery wildcardQuery = new WildcardQuery( 18 19 new Term("bookcontent", "中国*")); 20 21 doSearch(wildcardQuery); 22 23 } catch (Exception e) { 24 25 e.printStackTrace(); 26 27 } 28 29 }
1 /** 2 3 * 正则表达式查询 4 5 * RegexpQuery:正则表达式查询,词项符合某正则表达式 6 7 */ 8 9 @Test 10 11 public void regexpQuery() { 12 13 try { 14 15 // RegexpQuery 正则表达式查询 16 17 RegexpQuery regexpQuery = new RegexpQuery(new Term("bookcontent", "厉害.{4}")); 18 19 doSearch(regexpQuery); 20 21 } catch (Exception e) { 22 23 e.printStackTrace(); 24 25 } 26 27 }
1 /** 2 3 * 模糊查询 4 5 * 简单地与索引词项进行相近匹配,允许最大2个不同字符。常用于拼写错误的容错:如把 “thinkpad” 拼成 “thinkppd”或 “thinkd”,使用FuzzyQuery 仍可搜索到正确的结果。 6 7 */ 8 9 @Test 10 11 public void fuzzyQuery() { 12 13 try { 14 15 // FuzzyQuery 模糊查询 16 17 FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("bookcontent", "猪八戒")); 18 19 20 21 FuzzyQuery fuzzyQuery2 = new FuzzyQuery(new Term("bookcontent", "thinkd"), 2); 22 23 24 25 FuzzyQuery fuzzyQuery3 = new FuzzyQuery(new Term("bookcontent", "thinkpaddd")); 26 27 28 29 FuzzyQuery fuzzyQuery4 = new FuzzyQuery(new Term("bookcontent", "thinkdaddd")); 30 31 doSearch(fuzzyQuery); 32 33 } catch (Exception e) { 34 35 e.printStackTrace(); 36 37 } 38 39 }
1 /** 2 3 * 高亮排序查询 4 5 * @throws InvalidTokenOffsetsException 6 7 */ 8 9 private void highLightSearch(Query query) throws InvalidTokenOffsetsException { 10 11 // 获取一个indexReader对象 12 13 try { 14 15 Analyzer ikanalyzer = new IKAnalyzer(); 16 17 IndexSearcher searcher = getIndexSearcher(); 18 19 //true表示降序 20 21 //SortField.Type.SCORE 根据相关度进行排序(默认) 22 23 //SortField.Type.DOC 根据文档编号或者说是索引顺序 24 25 //SortField.Type.FLOAT(Long等),根据fieldName的数值类型进行排序 26 27 SortField sortField = new SortField("bookprice",SortField.Type.INT,false); 28 29 Sort sort = new Sort(sortField); 30 31 32 33 TopDocs topDocs = searcher.search(query, 10, sort); 34 35 System.out.println("数字查询"); 36 37 System.out.println("命中结果数为: "+ topDocs.totalHits); 38 39 // 返回查询结果。遍历查询结果并输出。 40 41 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 42 43 for (ScoreDoc scoreDoc : scoreDocs) { 44 45 int doc = scoreDoc.doc; 46 47 Document document = searcher.doc(doc); 48 49 String text = document.get("bookcontent"); 50 51 SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color=‘red‘>", "</font>"); 52 53 Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query)); 54 55 highlighter.setTextFragmenter(new SimpleFragmenter(text.length())); 56 57 if (text != null) { 58 59 TokenStream tokenStream = ikanalyzer.tokenStream("bookcontent", new StringReader(text)); 60 61 String highLightText = highlighter.getBestFragment(tokenStream,text); 62 63 System.out.println("高亮显示第 " + (doc + 1) + " 条检索结果如下所示:"); 64 65 System.out.println("bookcontent: "+highLightText); 66 67 } 68 69 70 71 // 打印content字段的值 72 73 System.out.println("bookid: "+document.get("bookid")); 74 75 System.out.println("bookname: "+document.get("bookname")); 76 77 System.out.println("booktype: "+document.get("booktype")); 78 79 System.out.println("bookprice: "+document.get("bookprice")); 80 81 System.out.println("bookdate: "+document.get("bookdate")); 82 83 // System.out.println("bookcontent: "+document.get("bookcontent")); 84 85 System.out.println("查询得分是: "+scoreDoc.score); 86 87 System.out.println("--------------我是分割线------------------"); 88 89 } 90 91 } catch (IOException e) { 92 93 e.printStackTrace(); 94 95 } 96 97 }
1 /** 2 3 * 排序查询 4 5 */ 6 7 private void sortSearch(Query query) { 8 9 // 获取一个indexReader对象 10 11 try { 12 13 IndexSearcher searcher = getIndexSearcher(); 14 15 //true表示降序 16 17 //SortField.Type.SCORE 根据相关度进行排序(默认) 18 19 //SortField.Type.DOC 根据文档编号或者说是索引顺序 20 21 //SortField.Type.FLOAT(Long等),根据fieldName的数值类型进行排序 22 23 SortField sortField = new SortField("bookcontent",SortField.Type.SCORE,false); 24 25 Sort sort = new Sort(sortField); 26 27 28 29 TopDocs topDocs = searcher.search(query, 10, sort); 30 31 System.out.println("数字查询"); 32 33 System.out.println("命中结果数为: "+ topDocs.totalHits); 34 35 // 返回查询结果。遍历查询结果并输出。 36 37 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 38 39 for (ScoreDoc scoreDoc : scoreDocs) { 40 41 int doc = scoreDoc.doc; 42 43 Document document = searcher.doc(doc); 44 45 // 打印content字段的值 46 47 System.out.println("bookid: "+document.get("bookid")); 48 49 System.out.println("bookname: "+document.get("bookname")); 50 51 System.out.println("booktype: "+document.get("booktype")); 52 53 System.out.println("bookprice: "+document.get("bookprice")); 54 55 System.out.println("bookcontent: "+document.get("bookcontent")); 56 57 System.out.println("查询得分是: "+scoreDoc.score); 58 59 System.out.println("--------------我是分割线------------------"); 60 61 } 62 63 } catch (IOException e) { 64 65 e.printStackTrace(); 66 67 } 68 69 }
l SortField.Type.SCORE 根据相关度进行排序(默认)
l SortField.Type.DOC 根据文档编号或者说是索引顺序
l SortField.Type.FLOAT(Long等),根据fieldName的数值类型进行排序
1 import java.io.IOException; 2 import java.io.StringReader; 3 import org.apache.lucene.analysis.Analyzer; 4 import org.apache.lucene.analysis.TokenStream; 5 import org.apache.lucene.analysis.core.WhitespaceAnalyzer; 6 import org.apache.lucene.analysis.standard.StandardAnalyzer; 7 import org.apache.lucene.document.Document; 8 import org.apache.lucene.document.Field; 9 import org.apache.lucene.document.IntPoint; 10 import org.apache.lucene.document.NumericDocValuesField; 11 import org.apache.lucene.document.StoredField; 12 import org.apache.lucene.document.StringField; 13 import org.apache.lucene.document.TextField; 14 import org.apache.lucene.index.DirectoryReader; 15 import org.apache.lucene.index.IndexWriter; 16 import org.apache.lucene.index.IndexWriterConfig; 17 import org.apache.lucene.index.Term; 18 import org.apache.lucene.queryparser.classic.ParseException; 19 import org.apache.lucene.queryparser.classic.QueryParser; 20 import org.apache.lucene.queryparser.classic.QueryParser.Operator; 21 import org.apache.lucene.queryparser.simple.SimpleQueryParser; 22 import org.apache.lucene.search.BooleanQuery; 23 import org.apache.lucene.search.FuzzyQuery; 24 import org.apache.lucene.search.IndexSearcher; 25 import org.apache.lucene.search.MultiPhraseQuery; 26 import org.apache.lucene.search.PhraseQuery; 27 import org.apache.lucene.search.PrefixQuery; 28 import org.apache.lucene.search.Query; 29 import org.apache.lucene.search.RegexpQuery; 30 import org.apache.lucene.search.ScoreDoc; 31 import org.apache.lucene.search.Sort; 32 import org.apache.lucene.search.SortField; 33 import org.apache.lucene.search.TermQuery; 34 import org.apache.lucene.search.TermRangeQuery; 35 import org.apache.lucene.search.TopDocs; 36 import org.apache.lucene.search.WildcardQuery; 37 import org.apache.lucene.search.highlight.Highlighter; 38 import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; 39 import org.apache.lucene.search.highlight.QueryScorer; 40 import org.apache.lucene.search.highlight.SimpleFragmenter; 41 import org.apache.lucene.search.highlight.SimpleHTMLFormatter; 42 import org.apache.lucene.search.spans.SpanNearQuery; 43 import org.apache.lucene.search.spans.SpanQuery; 44 import org.apache.lucene.search.spans.SpanTermQuery; 45 import org.apache.lucene.search.BooleanClause.Occur; 46 import org.apache.lucene.store.Directory; 47 import org.apache.lucene.store.RAMDirectory; 48 import org.junit.Before; 49 import org.junit.Test; 50 import org.wltea.analyzer.lucene.IKAnalyzer; 51 public class FullTextRetrieval { 52 private Directory directory = new RAMDirectory(); 53 private IndexWriterConfig indexWriterConfig = new IndexWriterConfig(new IKAnalyzer()); 54 private IndexWriter indexWriter; 55 @Before 56 public void createIndex() { 57 try { 58 indexWriter = new IndexWriter(directory, indexWriterConfig); 59 Document doc = new Document(); 60 // 书主键 61 doc = new Document(); 62 doc.add(new StringField("bookid", "1345678", Field.Store.YES)); 63 // 书名 64 doc.add(new StringField("bookname", "西游记", Field.Store.YES)); 65 // 书的类型 66 doc.add(new StringField("booktype", "小说", Field.Store.YES)); 67 // 书的价格 68 doc.add(new NumericDocValuesField("bookprice", 123)); 69 // 书的日期年份 70 Field intPoint = new IntPoint("bookdate", 1066); 71 doc.add(intPoint); 72 intPoint = new StoredField("bookdate", 1066); 73 doc.add(intPoint); 74 // doc.add(new NumericDocValuesField("bookdate", 123)); 75 // 书的内容 76 doc.add(new TextField("bookcontent", "《西游记》又称央视86版《西游记》,改编自明代小说家吴承恩同名文学古典名著。是由中央电视台、中国电视剧制作中心出品的一部25集古装神话剧。由杨洁执导,戴英禄,杨洁,邹忆青共同编剧,六小龄童、徐少华、迟重瑞、汪粤、马德华、闫怀礼等主演,李世宏、李扬、张云明、里坡等担任主要配音。 [1] \r\n" + 77 78 "该剧讲述的是孙悟空、猪八戒、沙僧辅保大唐高僧玄奘去西天取经,师徒四人一路抢滩涉险,降妖伏怪,历经八十一难,取回真经,终修正果的故事。\r\n" + 79 80 "《西游记》于1982年7月3日开机,同年10月1日首播试集《除妖乌鸡国》。1986年春节在央视首播前11集,1988年25集播出。\r\n" + 81 82 "1986年春节一经播出,轰动全国,老少皆宜,获得了极高评价,造就了89.4%的收视率神话,至今仍是寒暑假被重播最多的电视剧,重播次数超过3000次,依然百看不厌,成为一部公认的无法超越的经典。", Field.Store.YES)); 83 84 // 添加文档 85 indexWriter.addDocument(doc); 86 // 书主键 87 doc = new Document(); 88 doc.add(new StringField("bookid", "12345678", Field.Store.YES)); 89 // 书名 90 doc.add(new StringField("bookname", "水浒传", Field.Store.YES)); 91 // 书的类型 92 doc.add(new StringField("booktype", "小说", Field.Store.YES)); 93 // 书的价格 94 doc.add(new NumericDocValuesField("bookprice", 432)); 95 // 书的日期年份 96 Field intPoint1 = new IntPoint("bookdate", 1666); 97 doc.add(intPoint1); 98 intPoint = new StoredField("bookdate", 1666); 99 doc.add(intPoint1); 100 // 书的内容 101 doc.add(new TextField("bookcontent", "中国大陆,中央电视台无锡太湖影视城 43集\r\n" + 102 "《水浒传》是由中央电视台与中国电视剧制作中心联合出品的43集电视连续剧,根据明代施耐庵的同名小说改编。 [1] 由张绍林执导,杨争光 、冉平改编,李雪健、周野芒、臧金生、丁海峰、赵小锐领衔主演。\r\n" + 103 104 "该剧讲述的是宋朝徽宗时皇帝昏庸、奸臣当道、官府腐败、贪官污吏陷害忠良,弄得民不聊生,许多正直善良的人被官府逼得无路可走,被迫奋起反抗,最终108条好汉聚义梁山泊,但随后宋江对朝廷的投降使得一场轰轰烈烈的农民起义最后走向失败的故事。 [2] \r\n" + 105 "《水浒传》于1998年1月8日在中央电视台一套首播。 [3] \r\n" + 106 "2018年9月8日,9月15日,9月22日,央视四台《中国文艺》“向经典致敬”栏目播出《水浒传》20周年聚首专题节目", Field.Store.YES)); 107 indexWriter.addDocument(doc); 108 indexWriter.close(); 109 } catch (IOException e) { 110 e.printStackTrace(); 111 } 112 } 113 private IndexSearcher getIndexSearcher() throws IOException { 114 return new IndexSearcher(DirectoryReader.open(directory)); 115 } 116 117 /** 118 * 全文检索 119 * @throws ParseException 120 * @throws IOException 121 */ 122 @Test 123 public void testQueryParser() throws ParseException, IOException { 124 //使用WhitespaceAnalyzer分析器不会忽略大小写,也就是说大小写敏感 125 QueryParser queryParser = new QueryParser("bookcontent", new IKAnalyzer()); 126 Query query = queryParser.parse("2018"); 127 IndexSearcher searcher = getIndexSearcher(); 128 TopDocs topDocs = searcher.search(query, 10); 129 // 返回查询结果。遍历查询结果并输出。 130 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 131 132 for (ScoreDoc scoreDoc : scoreDocs) { 133 134 int doc = scoreDoc.doc; 135 136 Document document = searcher.doc(doc); 137 138 // 打印content字段的值 139 140 System.out.println("bookid: "+document.get("bookid")); 141 142 System.out.println("bookname: "+document.get("bookname")); 143 144 System.out.println("booktype: "+document.get("booktype")); 145 146 System.out.println("bookcontent: "+document.get("bookcontent")); 147 148 } 149 } 150 /** 151 * 词项查询 152 * 最基本、最常用的查询。用来查询指定字段包含指定词项的文档。 153 */ 154 @Test 155 public void termQuery() { 156 try { 157 IndexSearcher searcher = getIndexSearcher(); 158 TermQuery tq = new TermQuery(new Term("bookname", "西游记")); 159 TopDocs topDocs = searcher.search(tq, 10); 160 printTopDocs(topDocs); 161 } catch (Exception e) { 162 e.printStackTrace(); 163 } 164 } 165 /** 166 * 布尔查询 167 * 搜索的条件往往是多个的,如要查询名称包含“电脑” 或 “thinkpad”的商品,就需要两个词项查询做或合并。布尔查询就是用来组合多个子查询的。 168 * 每个子查询称为布尔字句 BooleanClause,布尔字句自身也可以是组合的。 组合关系支持如下四种: 169 * Occur.SHOULD 或 170 * Occur.MUST 且 171 * Occur.MUST_NOT 且非 172 * Occur.FILTER 同 MUST,但该字句不参与评分 173 * 布尔查询默认的最大字句数为1024,在将通配符查询这样的查询rewriter为布尔查询时,往往会产生很多的字句,可能抛出TooManyClauses 异常。 174 * 可通过BooleanQuery.setMaxClauseCount(int)设置最大字句数。 175 */ 176 @Test 177 public void booleanQuery() { 178 try { 179 // 创建一个indexsearcher对象 180 IndexSearcher searcher = getIndexSearcher(); 181 BooleanQuery.Builder builder = new BooleanQuery.Builder(); 182 // 书内容 183 QueryParser queryParser = new QueryParser("bookcontent", new IKAnalyzer()); 184 Query query1 = queryParser.parse("西游记"); 185 builder.add(query1, Occur.MUST); 186 // 书名称 187 Query qymc = new FuzzyQuery(new Term("bookname", "西游记")); 188 builder.add(qymc, Occur.MUST); 189 // 书类型 190 Query gmsfhm = new TermQuery(new Term("booktype", "小说")); 191 builder.add(gmsfhm, Occur.MUST); 192 BooleanQuery booleanQuery = builder.build(); 193 TopDocs topDocs = searcher.search(booleanQuery, 10); 194 printTopDocs(topDocs); 195 } catch (Exception e) { 196 e.printStackTrace(); 197 } 198 } 199 200 /** 201 * 短语查询 202 * 最常用的查询,匹配特定序列的多个词项。PhraserQuery使用一个位置移动因子(slop)来决定任意两个词项的位置可最大移动多少个位置来进行匹配,默认为0。 203 * 有两种方式来构建对象: 204 * 注意:所有加入的词项都匹配才算匹配(即使是你在同一位置加入多个词项)。如果需要在同一位置匹配多个同义词中的一个,适合用MultiPhraseQuery 205 */ 206 @Test 207 public void phraseQuery() { 208 try { 209 PhraseQuery phraseQuery1 = new PhraseQuery("bookcontent", "根据", "明代"); 210 PhraseQuery phraseQuery2 = new PhraseQuery(0, "bookcontent", "根据", "明代"); 211 PhraseQuery phraseQuery3 = new PhraseQuery("bookcontent", "笔记本电脑", "联想"); 212 PhraseQuery phraseQuery4 = new PhraseQuery.Builder() 213 214 .add(new Term("bookcontent", "根据"), 4) 215 216 .add(new Term("bookcontent", "施耐"), 5).build(); 217 // 这两句等同 218 PhraseQuery phraseQuery5 = new PhraseQuery.Builder() 219 220 .add(new Term("bookcontent", "笔记本电脑"), 0) 221 222 .add(new Term("bookcontent", "联想"), 1).build(); 223 224 225 226 doSearch(phraseQuery2); 227 } catch (Exception e) { 228 e.printStackTrace(); 229 } 230 } 231 /** 232 * 多重短语查询 233 * 短语查询的一种更通用的用法,支持同位置多个词的OR匹配。通过里面的Builder来构建MultiPhraseQuery: 234 */ 235 @Test 236 public void multiPhraseQuery() { 237 try { 238 // 4 MultiPhraseQuery 多重短语查询 239 240 Term[] terms = new Term[2]; 241 242 terms[0] = new Term("bookcontent", "根据"); 243 244 terms[1] = new Term("bookcontent", "根据明代"); 245 246 Term t = new Term("bookcontent", "施耐"); 247 248 MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder() 249 250 .add(terms).add(t).build(); 251 252 // 对比 PhraseQuery在同位置加入多个词 ,同位置的多个词都需匹配,所以查不出。 253 254 PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0) 255 256 .add(terms[1], 0).add(t, 1).build(); 257 258 doSearch(multiPhraseQuery); 259 260 } catch (Exception e) { 261 262 e.printStackTrace(); 263 } 264 } 265 /** 266 267 * 临近查询(跨度查询) 268 269 * 用于更复杂的短语查询,可以指定词间位置的最大间隔跨度。通过组合一系列的SpanQuery 实例来进行查询,可以指定是否按顺序匹配、slop、gap 270 271 */ 272 @Test 273 public void spanNearQuery() { 274 try { 275 // SpanNearQuery 临近查询 276 SpanTermQuery tq1 = new SpanTermQuery(new Term("bookcontent", "中央电视台")); 277 SpanTermQuery tq2 = new SpanTermQuery(new Term("bookcontent", "无锡")); 278 SpanNearQuery spanNearQuery = new SpanNearQuery( 279 new SpanQuery[] { tq1, tq2 }, 0, true); 280 // SpanNearQuery 临近查询 gap slop 使用 281 SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery 282 .newOrderedNearQuery("bookcontent"); 283 spanNearQueryBuilder.addClause(tq1).addGap(0).setSlop(1) 284 .addClause(tq2); 285 SpanNearQuery spanNearQuery5 = spanNearQueryBuilder.build(); 286 // IndexSearcher searcher = getIndexSearcher(); 287 // TopDocs topDocs = searcher.search(spanNearQueryBuilder, 10); 288 doSearch(spanNearQuery); 289 } catch (Exception e) { 290 e.printStackTrace(); 291 } 292 } 293 294 /** 295 * 词项范围查询 296 * 用于查询包含某个范围内的词项的文档,如以字母开头a到c的词项。词项在反向索引中是排序的,只需指定的开始词项、结束词项,就可以查询该范围的词项。 297 298 * 如果是做数值的范围查询则用 PointRangeQuery 299 300 * 参数说明: 301 302 * 第1个参数:要查询的字段-field 303 304 * 第2个参数::下边界词-lowerTerm 305 306 * 第3个参数:上边界词-upperTerm 307 308 * 第4个参数:是否包含下边界-includeLower 309 310 * 第5个参数:是否包含上边界 includeUpper 311 312 */ 313 314 @Test 315 316 public void termRangeQuery() { 317 318 try { 319 320 // TermRangeQuery 词项范围查询 321 322 TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("bookcontent", 323 324 "中央电视台", "同名小说改编", false, true); 325 326 doSearch(termRangeQuery); 327 328 } catch (Exception e) { 329 330 e.printStackTrace(); 331 332 } 333 334 } 335 336 337 338 /** 339 340 * 前缀查询 341 342 * PrefixQuery:前缀查询,查询包含以xxx为前缀的词项的文档,是通配符查询,如 app,实际是 app* 343 344 */ 345 346 @Test 347 348 public void prefixQuery() { 349 350 try { 351 352 // PrefixQuery 前缀查询 353 354 PrefixQuery prefixQuery = new PrefixQuery(new Term("bookcontent", "中国")); 355 356 doSearch(prefixQuery); 357 358 } catch (Exception e) { 359 360 e.printStackTrace(); 361 362 } 363 364 } 365 366 367 368 /** 369 370 * 通配符查询 371 372 * WildcardQuery:通配符查询, *表示0个或多个字符,?表示1个字符,\是转义符。通配符查询可能会比较慢,不可以通配符开头(那样就是所有词项了) 373 374 */ 375 376 @Test 377 378 public void wildcardQuery() { 379 380 try { 381 382 // WildcardQuery 通配符查询 383 384 WildcardQuery wildcardQuery = new WildcardQuery( 385 386 new Term("bookcontent", "中国*")); 387 388 doSearch(wildcardQuery); 389 390 } catch (Exception e) { 391 392 e.printStackTrace(); 393 394 } 395 396 } 397 398 399 400 /** 401 402 * 正则表达式查询 403 404 * RegexpQuery:正则表达式查询,词项符合某正则表达式 405 406 */ 407 408 @Test 409 410 public void regexpQuery() { 411 412 try { 413 414 // RegexpQuery 正则表达式查询 415 416 RegexpQuery regexpQuery = new RegexpQuery(new Term("bookcontent", "厉害.{4}")); 417 418 doSearch(regexpQuery); 419 420 } catch (Exception e) { 421 422 e.printStackTrace(); 423 424 } 425 426 } 427 428 429 430 431 432 /** 433 434 * 模糊查询 435 436 * 简单地与索引词项进行相近匹配,允许最大2个不同字符。常用于拼写错误的容错:如把 “thinkpad” 拼成 “thinkppd”或 “thinkd”,使用FuzzyQuery 仍可搜索到正确的结果。 437 438 */ 439 440 @Test 441 442 public void fuzzyQuery() { 443 444 try { 445 446 // FuzzyQuery 模糊查询 447 448 FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("bookcontent", "猪八戒")); 449 450 451 452 FuzzyQuery fuzzyQuery2 = new FuzzyQuery(new Term("bookcontent", "thinkd"), 2); 453 454 455 456 FuzzyQuery fuzzyQuery3 = new FuzzyQuery(new Term("bookcontent", "thinkpaddd")); 457 458 459 460 FuzzyQuery fuzzyQuery4 = new FuzzyQuery(new Term("bookcontent", "thinkdaddd")); 461 462 doSearch(fuzzyQuery); 463 464 } catch (Exception e) { 465 466 e.printStackTrace(); 467 468 } 469 470 } 471 472 473 474 475 476 /** 477 478 * 数值查询 479 480 * 前提:查询的数值字段必须索引。通过 IntPoint, LongPoint, FloatPoint, or DoublePoint 中的方法构建对应的查询。以IntPoint为例: 481 482 */ 483 484 @Test 485 486 public void pointQuery() { 487 488 try { 489 490 // 精确值查询 491 492 Query exactQuery = IntPoint.newExactQuery("bookprice", 123); 493 494 495 496 // 数值范围查询 497 498 Query pointRangeQuery = IntPoint.newRangeQuery("bookprice", 111,134); 499 500 501 502 // 集合查询 503 504 Query setQuery = IntPoint.newSetQuery("bookprice", 1999900, 1000000,2000000); 505 506 doSearch(exactQuery); 507 508 } catch (Exception e) { 509 510 e.printStackTrace(); 511 512 } 513 514 } 515 516 517 518 /** 519 520 * 查询解析生成器 521 522 * QueryParser 查询解析生成器 523 524 * Lucene QueryPaser包中提供了两类查询解析器: 525 526 * A. 传统的解析器:QueryParser和MultiFieldQueryParser 527 528 * B. 基于新的 flexible 框架的解析器:StandardQueryParser 529 530 */ 531 532 @Test 533 534 public void QueryParser() { 535 536 try { 537 538 QueryParser parser = new QueryParser("bookcontent", new IKAnalyzer()); 539 540 //parser.setPhraseSlop(2); 541 542 Query query = parser.parse("中国文艺央视"); 543 544 // sortSearch(query); 545 546 highLightSearch(query); 547 548 } catch (Exception e) { 549 550 e.printStackTrace(); 551 552 } 553 554 } 555 556 557 558 /** 559 560 * 传统解析器-多默认字段 561 562 * B. 基于新的 flexible 框架的解析器:StandardQueryParser 563 564 */ 565 566 @Test 567 568 public void MultiFieldQueryParser() { 569 570 try { 571 572 String[] multiDefaultFields = { "bookname", "booktype", "bookcontent" }; 573 574 org.apache.lucene.queryparser.classic.MultiFieldQueryParser multiFieldQueryParser = new org.apache.lucene.queryparser.classic.MultiFieldQueryParser( 575 576 multiDefaultFields, new IKAnalyzer()); 577 578 // 设置默认的组合操作,默认是 OR 579 580 multiFieldQueryParser.setDefaultOperator(Operator.OR); 581 582 Query query = multiFieldQueryParser.parse("西游记》又称央视86版《西游记》,改编自明12345678901"); 583 584 doSearch(query); 585 586 } catch (Exception e) { 587 588 e.printStackTrace(); 589 590 } 591 592 } 593 594 595 596 /** 597 598 * 新解析框架的标准解析器 599 600 * B. 基于新的 flexible 框架的解析器:StandardQueryParser 601 602 */ 603 604 @Test 605 606 public void StandardQueryParser() { 607 608 try { 609 610 SimpleQueryParser queryParserHelper = new SimpleQueryParser(new IKAnalyzer(),"bookcontent"); 611 612 // 设置默认字段 613 614 // queryParserHelper.setMultiFields(CharSequence[] fields); 615 616 // queryParserHelper.setPhraseSlop(8); 617 618 // Query query = queryParserHelper.parse("a AND b", "defaultField"); 619 620 Query query5 = queryParserHelper.parse("央视"); 621 622 sortSearch(query5); 623 624 } catch (Exception e) { 625 626 e.printStackTrace(); 627 628 } 629 630 } 631 632 633 634 /** 635 636 * 高亮排序查询 637 638 * @throws InvalidTokenOffsetsException 639 640 */ 641 642 private void highLightSearch(Query query) throws InvalidTokenOffsetsException { 643 644 // 获取一个indexReader对象 645 646 try { 647 648 Analyzer ikanalyzer = new IKAnalyzer(); 649 650 IndexSearcher searcher = getIndexSearcher(); 651 652 //true表示降序 653 654 //SortField.Type.SCORE 根据相关度进行排序(默认) 655 656 //SortField.Type.DOC 根据文档编号或者说是索引顺序 657 658 //SortField.Type.FLOAT(Long等),根据fieldName的数值类型进行排序 659 660 SortField sortField = new SortField("bookprice",SortField.Type.INT,false); 661 662 Sort sort = new Sort(sortField); 663 664 665 666 TopDocs topDocs = searcher.search(query, 10, sort); 667 668 System.out.println("数字查询"); 669 670 System.out.println("命中结果数为: "+ topDocs.totalHits); 671 672 // 返回查询结果。遍历查询结果并输出。 673 674 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 675 676 for (ScoreDoc scoreDoc : scoreDocs) { 677 678 int doc = scoreDoc.doc; 679 680 Document document = searcher.doc(doc); 681 682 String text = document.get("bookcontent"); 683 684 SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color=‘red‘>", "</font>"); 685 686 Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query)); 687 688 highlighter.setTextFragmenter(new SimpleFragmenter(text.length())); 689 690 if (text != null) { 691 692 TokenStream tokenStream = ikanalyzer.tokenStream("bookcontent", new StringReader(text)); 693 694 String highLightText = highlighter.getBestFragment(tokenStream,text); 695 696 System.out.println("高亮显示第 " + (doc + 1) + " 条检索结果如下所示:"); 697 698 System.out.println("bookcontent: "+highLightText); 699 700 } 701 702 703 704 // 打印content字段的值 705 706 System.out.println("bookid: "+document.get("bookid")); 707 708 System.out.println("bookname: "+document.get("bookname")); 709 710 System.out.println("booktype: "+document.get("booktype")); 711 712 System.out.println("bookprice: "+document.get("bookprice")); 713 714 System.out.println("bookdate: "+document.get("bookdate")); 715 716 // System.out.println("bookcontent: "+document.get("bookcontent")); 717 718 System.out.println("查询得分是: "+scoreDoc.score); 719 720 System.out.println("--------------我是分割线------------------"); 721 722 } 723 724 } catch (IOException e) { 725 726 e.printStackTrace(); 727 728 } 729 730 } 731 732 733 734 /** 735 736 * 排序查询 737 738 */ 739 740 private void sortSearch(Query query) { 741 742 // 获取一个indexReader对象 743 744 try { 745 746 IndexSearcher searcher = getIndexSearcher(); 747 748 //true表示降序 749 750 //SortField.Type.SCORE 根据相关度进行排序(默认) 751 752 //SortField.Type.DOC 根据文档编号或者说是索引顺序 753 754 //SortField.Type.FLOAT(Long等),根据fieldName的数值类型进行排序 755 756 SortField sortField = new SortField("bookcontent",SortField.Type.SCORE,false); 757 758 Sort sort = new Sort(sortField); 759 760 761 762 TopDocs topDocs = searcher.search(query, 10, sort); 763 764 System.out.println("数字查询"); 765 766 System.out.println("命中结果数为: "+ topDocs.totalHits); 767 768 // 返回查询结果。遍历查询结果并输出。 769 770 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 771 772 for (ScoreDoc scoreDoc : scoreDocs) { 773 774 int doc = scoreDoc.doc; 775 776 Document document = searcher.doc(doc); 777 778 // 打印content字段的值 779 780 System.out.println("bookid: "+document.get("bookid")); 781 782 System.out.println("bookname: "+document.get("bookname")); 783 784 System.out.println("booktype: "+document.get("booktype")); 785 786 System.out.println("bookprice: "+document.get("bookprice")); 787 788 System.out.println("bookcontent: "+document.get("bookcontent")); 789 790 System.out.println("查询得分是: "+scoreDoc.score); 791 792 System.out.println("--------------我是分割线------------------"); 793 794 } 795 796 } catch (IOException e) { 797 798 e.printStackTrace(); 799 800 } 801 802 } 803 804 805 806 /** 807 808 * 查询打印结果 809 810 */ 811 812 private void doSearch(Query query) { 813 814 // 获取一个indexReader对象 815 816 try { 817 818 IndexSearcher searcher = getIndexSearcher(); 819 820 TopDocs topDocs = searcher.search(query, 10); 821 822 System.out.println("数字查询"); 823 824 System.out.println("命中结果数为: "+ topDocs.totalHits); 825 826 // 返回查询结果。遍历查询结果并输出。 827 828 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 829 830 for (ScoreDoc scoreDoc : scoreDocs) { 831 832 int doc = scoreDoc.doc; 833 834 Document document = searcher.doc(doc); 835 836 // 打印content字段的值 837 838 System.out.println("bookid: "+document.get("bookid")); 839 840 System.out.println("bookname: "+document.get("bookname")); 841 842 System.out.println("booktype: "+document.get("booktype")); 843 844 System.out.println("bookcontent: "+document.get("bookcontent")); 845 846 System.out.println("查询得分是: "+scoreDoc.score); 847 848 System.out.println("--------------我是分割线------------------"); 849 850 } 851 852 } catch (IOException e) { 853 854 e.printStackTrace(); 855 856 } 857 858 } 859 860 861 862 /** 863 864 * 打印查询结果 865 866 */ 867 868 private void printTopDocs(TopDocs topDocs) { 869 870 // 获取一个indexReader对象 871 872 try { 873 874 IndexSearcher searcher = getIndexSearcher(); 875 876 System.out.println("数字查询"); 877 878 System.out.println("命中结果数为: "+ topDocs.totalHits); 879 880 // 返回查询结果。遍历查询结果并输出。 881 882 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 883 884 for (ScoreDoc scoreDoc : scoreDocs) { 885 886 int doc = scoreDoc.doc; 887 888 Document document = searcher.doc(doc); 889 890 // 打印content字段的值 891 892 System.out.println("bookid: "+document.get("bookid")); 893 894 System.out.println("bookname: "+document.get("bookname")); 895 896 System.out.println("booktype: "+document.get("booktype")); 897 898 System.out.println("bookcontent: "+document.get("bookcontent")); 899 900 } 901 902 } catch (IOException e) { 903 904 e.printStackTrace(); 905 906 } 907 908 } 909 910 911 912 }
1 /** 2 3 * 查询所有文档 4 5 * @throws ParseException 6 7 * @throws IOException 8 9 */ 10 11 @Test 12 13 public void testMatchAllDocsQuery() throws ParseException, IOException { 14 15 Query query = new MatchAllDocsQuery(); 16 17 IndexSearcher searcher = getIndexSearcher(); 18 19 TopDocs topDocs = searcher.search(query, 10); 20 21 22 23 // 返回查询结果。遍历查询结果并输出。 24 25 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 26 27 for (ScoreDoc scoreDoc : scoreDocs) { 28 29 int doc = scoreDoc.doc; 30 31 Document document = searcher.doc(doc); 32 33 // 打印content字段的值 34 35 System.out.println("bookid: "+document.get("bookid")); 36 37 System.out.println("bookname: "+document.get("bookname")); 38 39 System.out.println("booktype: "+document.get("booktype")); 40 41 System.out.println("bookcontent: "+document.get("bookcontent")); 42 43 } 44 45 }
1 /** 2 3 * 不匹配任何文档(好像没什么用) 4 5 * @throws ParseException 6 7 * @throws IOException 8 9 */ 10 11 @Test 12 13 public void testMatchNoDocsQuery() throws ParseException, IOException { 14 15 Query query = new MatchNoDocsQuery(); 16 17 IndexSearcher searcher = getIndexSearcher(); 18 19 TopDocs topDocs = searcher.search(query, 10); 20 21 22 23 // 返回查询结果。遍历查询结果并输出。 24 25 ScoreDoc[] scoreDocs = topDocs.scoreDocs; 26 27 for (ScoreDoc scoreDoc : scoreDocs) { 28 29 int doc = scoreDoc.doc; 30 31 Document document = searcher.doc(doc); 32 33 // 打印content字段的值 34 35 System.out.println("bookid: "+document.get("bookid")); 36 37 System.out.println("bookname: "+document.get("bookname")); 38 39 System.out.println("booktype: "+document.get("booktype")); 40 41 System.out.println("bookcontent: "+document.get("bookcontent")); 42 43 } 44 45 }
标签:offset amd reader wildcard 正则表达 its 多个 standard token
原文地址:https://www.cnblogs.com/bestlmc/p/11865725.html