Group即分组,类似SQL里的group by功能,Lucene中分组是通过内置的几种Collector结果集收集器实现的,有关group的结果集收集器都在org.apache.lucene.search.grouping包及其子包下,
包含group关键字的Collector都是有关Group分组的结果收集器,如果你只需要统计如下这些分组信息:
/** 所有组的数量 */ int totalGroupCount = 0; /** 所有满足条件的记录数 */ int totalHitCount = 0; /** 所有组内的满足条件的记录数(通常该值与totalHitCount是一致的) */ int totalGroupedHitCount = -1;
则直接使用FirstPassGroupingCollector收集器即可,如果你需要统计每个分组内部的命中总数以及命中索引文档的评分等信息,则需要使用SecondPassGroupingCollector,为了提高第二次查询的效率,你可以使用CacheCollector来缓存第一次查询结果,这样第二次就直接从缓存中获取第一次查询结果,为了统计总的分组数量,你可能还需要使用AllGroupsCollector结果收集器。常用的结果收集器就这几个。
下面是一个Group分组使用示例,具体详细说明请看代码里面的注释:
package com.yida.framework.lucene5.group; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Random; 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.Field.Index; import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.SortedDocValuesField; 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.index.IndexWriterConfig.OpenMode; import org.apache.lucene.index.Term; import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.queries.function.valuesource.BytesRefFieldSource; import org.apache.lucene.search.CachingCollector; import org.apache.lucene.search.Collector; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MultiCollector; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.SimpleCollector; import org.apache.lucene.search.Sort; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.grouping.AbstractAllGroupsCollector; import org.apache.lucene.search.grouping.AbstractFirstPassGroupingCollector; import org.apache.lucene.search.grouping.AbstractSecondPassGroupingCollector; import org.apache.lucene.search.grouping.GroupDocs; import org.apache.lucene.search.grouping.SearchGroup; import org.apache.lucene.search.grouping.TopGroups; import org.apache.lucene.search.grouping.function.FunctionAllGroupsCollector; import org.apache.lucene.search.grouping.function.FunctionFirstPassGroupingCollector; import org.apache.lucene.search.grouping.function.FunctionSecondPassGroupingCollector; import org.apache.lucene.search.grouping.term.TermAllGroupsCollector; import org.apache.lucene.search.grouping.term.TermFirstPassGroupingCollector; import org.apache.lucene.search.grouping.term.TermSecondPassGroupingCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.mutable.MutableValue; import org.apache.lucene.util.mutable.MutableValueStr; import com.yida.framework.lucene5.util.Tools; /** * Lucene分组测试 * @author Lanxiaowei * */ public class GroupTest { /** 索引目录 */ private static final String indexDir = "C:/group-index"; /** 分词器 */ private static Analyzer analyzer = new StandardAnalyzer(); /** 分组域 */ private static String groupField = "author"; public static void main(String[] args) throws Exception { // 创建测试索引 // createIndex(); Directory directory = FSDirectory.open(Paths.get(indexDir)); IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); Query query = new TermQuery(new Term("content", "random")); /**每个分组内部的排序规则*/ Sort groupSort = Sort.RELEVANCE; groupBy(searcher, query, groupSort); //groupSearch(searcher); } public static void groupBy(IndexSearcher searcher, Query query, Sort groupSort) throws IOException { /** 前N条中分组 */ int topNGroups = 10; /** 分组起始偏移量 */ int groupOffset = 0; /** 是否填充SearchGroup的sortValues */ boolean fillFields = true; /** groupSort用于对组进行排序,docSort用于对组内记录进行排序,多数情况下两者是相同的,但也可不同 */ Sort docSort = groupSort; /** 用于组内分页,起始偏移量 */ int docOffset = 0; /** 每组返回多少条结果 */ int docsPerGroup = 2; /** 是否需要计算总的分组数量 */ boolean requiredTotalGroupCount = true; /** 是否需要缓存评分 */ boolean cacheScores = true; TermFirstPassGroupingCollector c1 = new TermFirstPassGroupingCollector( "author", groupSort, groupOffset + topNGroups); //第一次查询缓存容量的大小:设置为16M double maxCacheRAMMB = 16.0; /** 将TermFirstPassGroupingCollector包装成CachingCollector,为第一次查询加缓存,避免重复评分 * CachingCollector就是用来为结果收集器添加缓存功能的 */ CachingCollector cachedCollector = CachingCollector.create(c1, cacheScores, maxCacheRAMMB); // 开始第一次分组统计 searcher.search(query, cachedCollector); /**第一次查询返回的结果集TopGroups中只有分组域值以及每组总的评分,至于每个分组里有几条,分别哪些索引文档,则需要进行第二次查询获取*/ Collection<SearchGroup<BytesRef>> topGroups = c1.getTopGroups( groupOffset, fillFields); if (topGroups == null) { System.out.println("No groups matched "); return; } Collector secondPassCollector = null; // 是否获取每个分组内部每个索引的评分 boolean getScores = true; // 是否计算最大评分 boolean getMaxScores = true; // 如果需要对Lucene的score进行修正,则需要重载TermSecondPassGroupingCollector TermSecondPassGroupingCollector c2 = new TermSecondPassGroupingCollector( "author", topGroups, groupSort, docSort, docOffset + docsPerGroup, getScores, getMaxScores, fillFields); // 如果需要计算总的分组数量,则需要把TermSecondPassGroupingCollector包装成TermAllGroupsCollector // TermAllGroupsCollector就是用来收集总分组数量的 TermAllGroupsCollector allGroupsCollector = null; //若需要统计总的分组数量 if (requiredTotalGroupCount) { allGroupsCollector = new TermAllGroupsCollector("author"); secondPassCollector = MultiCollector.wrap(c2, allGroupsCollector); } else { secondPassCollector = c2; } /**如果第一次查询已经加了缓存,则直接从缓存中取*/ if (cachedCollector.isCached()) { // 第二次查询直接从缓存中取 cachedCollector.replay(secondPassCollector); } else { // 开始第二次分组查询 searcher.search(query, secondPassCollector); } /** 所有组的数量 */ int totalGroupCount = 0; /** 所有满足条件的记录数 */ int totalHitCount = 0; /** 所有组内的满足条件的记录数(通常该值与totalHitCount是一致的) */ int totalGroupedHitCount = -1; if (requiredTotalGroupCount) { totalGroupCount = allGroupsCollector.getGroupCount(); } //打印总的分组数量 System.out.println("groupCount: " + totalGroupCount); TopGroups<BytesRef> groupsResult = c2.getTopGroups(docOffset); //这里打印的3项信息就是第一次查询的统计结果 totalHitCount = groupsResult.totalHitCount; totalGroupedHitCount = groupsResult.totalGroupedHitCount; System.out.println("groupsResult.totalHitCount:" + totalHitCount); System.out.println("groupsResult.totalGroupedHitCount:" + totalGroupedHitCount); System.out.println("///////////////////////////////////////////////"); int groupIdx = 0; //下面打印的是第二次查询的统计结果,如果你仅仅值需要第一次查询的统计结果信息,不需要每个分组内部的详细信息,则不需要进行第二次查询,请知晓 // 迭代组 for (GroupDocs<BytesRef> groupDocs : groupsResult.groups) { groupIdx++; String groupVL = groupDocs.groupValue == null ? "分组域的域值为空" : new String(groupDocs.groupValue.bytes); // 分组域的域值,groupIdx表示组的索引即第几组 System.out.println("group[" + groupIdx + "].groupFieldValue:" + groupVL); // 当前分组内命中的总记录数 System.out .println("group[" + groupIdx + "].totalHits:" + groupDocs.totalHits); int docIdx = 0; // 迭代组内的记录 for (ScoreDoc scoreDoc : groupDocs.scoreDocs) { docIdx++; // 打印分组内部每条记录的索引文档ID及其评分 System.out.println("group[" + groupIdx + "][" + docIdx + "]{docID:Score}:" + scoreDoc.doc + "/" + scoreDoc.score); //根据docID可以获取到整个Document对象,通过doc.get(fieldName)可以获取某个存储域的域值 //注意searcher.doc根据docID返回的document对象中不包含docValuesField域的域值,只包含非docValuesField域的域值,请知晓 Document doc = searcher.doc(scoreDoc.doc); System.out.println("group[" + groupIdx + "][" + docIdx + "]{docID:author}:" + doc.get("id") + ":" + doc.get("content")); } System.out.println("******************华丽且拉轰的分割线***********************"); } } public static void groupSearch(IndexSearcher indexSearcher) throws IOException { Sort groupSort = Sort.RELEVANCE; /** 第一次查询只有Top N条记录进行分组统计 */ final AbstractFirstPassGroupingCollector<?> c1 = createRandomFirstPassCollector( groupField, groupSort, 10); indexSearcher.search(new TermQuery(new Term("content", "random")), c1); /* * final AbstractSecondPassGroupingCollector<?> c2 = * createSecondPassCollector( c1, groupField, groupSort, null, 0, 5, * true, true, true); indexSearcher.search(new TermQuery(new * Term("content", "random")), c2); */ /** 第一个参数表示截取偏移量offset,截取[offset, offset+topN]范围内的组 */ Collection<?> groups = c1.getTopGroups(0, true); System.out.println("group.size:" + groups.size()); for (Object object : groups) { SearchGroup searchGroup = (SearchGroup) object; if (searchGroup.groupValue != null) { if (searchGroup.groupValue.getClass().isAssignableFrom( BytesRef.class)) { String groupVL = new String( (((BytesRef) searchGroup.groupValue)).bytes); if (groupVL.equals("")) { System.out.println("该分组不包含分组域"); } else { System.out.println(groupVL); } } else if (searchGroup.groupValue.getClass().isAssignableFrom( MutableValueStr.class)) { if (searchGroup.groupValue.toString().endsWith("(null)")) { System.out.println("该分组不包含分组域"); } else { System.out .println(new String( (((MutableValueStr) searchGroup.groupValue)).value .bytes())); } } } else { System.out.println("该分组不包含分组域"); } for (int i = 0; i < searchGroup.sortValues.length; i++) { System.out.println("searchGroup.sortValues:" + searchGroup.sortValues[i]); } } /* * System.out.println("groups.maxScore:" + groups.maxScore); * System.out.println("groups.totalHitCount:" + groups.totalHitCount); * System.out.println("groups.totalGroupedHitCount:" + * groups.totalGroupedHitCount); System.out.println("groups.length:" + * groups.groups.length); System.out.println(""); * * GroupDocs<?> group = groups.groups[0]; compareGroupValue("author3", * group); System.out.println(group.scoreDocs.length); */ } /** * 创建测试用的索引文档 * * @throws IOException */ public static void createIndex() throws IOException { Directory dir = FSDirectory.open(Paths.get(indexDir)); IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer); indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND); IndexWriter writer = new IndexWriter(dir, indexWriterConfig); addDocuments(groupField, writer); } /** * 添加索引文档 * * @param groupField * @param writer * @throws IOException */ public static void addDocuments(String groupField, IndexWriter writer) throws IOException { // 0 Document doc = new Document(); addGroupField(doc, groupField, "author1"); doc.add(new TextField("content", "random text", Field.Store.YES)); doc.add(new Field("id", "1", Store.YES, Index.NOT_ANALYZED)); writer.addDocument(doc); // 1 doc = new Document(); addGroupField(doc, groupField, "author1"); doc.add(new TextField("content", "some more random text", Field.Store.YES)); doc.add(new Field("id", "2", Store.YES, Index.NOT_ANALYZED)); writer.addDocument(doc); // 2 doc = new Document(); addGroupField(doc, groupField, "author1"); doc.add(new TextField("content", "some more random textual data", Field.Store.YES)); doc.add(new Field("id", "3", Store.YES, Index.NOT_ANALYZED)); writer.addDocument(doc); // 3 doc = new Document(); addGroupField(doc, groupField, "author2"); doc.add(new TextField("content", "some random text", Field.Store.YES)); doc.add(new Field("id", "4", Store.YES, Index.NOT_ANALYZED)); writer.addDocument(doc); // 4 doc = new Document(); addGroupField(doc, groupField, "author3"); doc.add(new TextField("content", "some more random text", Field.Store.YES)); doc.add(new Field("id", "5", Store.YES, Index.NOT_ANALYZED)); writer.addDocument(doc); // 5 doc = new Document(); addGroupField(doc, groupField, "author3"); doc.add(new TextField("content", "random", Field.Store.YES)); doc.add(new Field("id", "6", Store.YES, Index.NOT_ANALYZED)); writer.addDocument(doc); // 6 -- no author field doc = new Document(); doc.add(new TextField("content", "random word stuck in alot of other text", Field.Store.YES)); doc.add(new Field("id", "6", Store.YES, Index.NOT_ANALYZED)); writer.addDocument(doc); writer.commit(); writer.close(); } /** * 判断域值是否与分组域值相等 * * @param expected * @param group */ private static void compareGroupValue(String expected, GroupDocs<?> group) { if (expected == null) { if (group.groupValue == null) { return; } else if (group.groupValue.getClass().isAssignableFrom( MutableValueStr.class)) { return; } else if (((BytesRef) group.groupValue).length == 0) { return; } } if (group.groupValue.getClass().isAssignableFrom(BytesRef.class)) { System.out.println("expected == groupValue?" + new BytesRef(expected) == group.groupValue); } else if (group.groupValue.getClass().isAssignableFrom( MutableValueStr.class)) { MutableValueStr v = new MutableValueStr(); v.value.copyChars(expected); System.out .println("expected == groupValue?" + v == group.groupValue); } else { } } /** * 创建FirstPassCollector首次检索 * * @param groupField * @param groupSort * @param topDocs * @param firstPassGroupingCollector * @return * @throws IOException */ private AbstractFirstPassGroupingCollector<?> createFirstPassCollector( String groupField, Sort groupSort, int topDocs, AbstractFirstPassGroupingCollector<?> firstPassGroupingCollector) throws IOException { if (TermFirstPassGroupingCollector.class .isAssignableFrom(firstPassGroupingCollector.getClass())) { ValueSource vs = new BytesRefFieldSource(groupField); return new FunctionFirstPassGroupingCollector(vs, new HashMap(), groupSort, topDocs); } return new TermFirstPassGroupingCollector(groupField, groupSort, topDocs); } private static AbstractFirstPassGroupingCollector<?> createRandomFirstPassCollector( String groupField, Sort groupSort, int topDocs) throws IOException { AbstractFirstPassGroupingCollector<?> selected; // boolean flag = new Random().nextBoolean(); if (false) { ValueSource vs = new BytesRefFieldSource(groupField); // FunctionFirstPassGroupingCollector区别是对于分组域的值采用MutableValueStr进行存储, // MutableValueStr内部维护的是一个BytesRefBuilder,BytesRefBuilder内部有一个grow函数,会自动 // 扩充内部byte[]容量,而BytesRef是定长的buffer selected = new FunctionFirstPassGroupingCollector(vs, new HashMap(), groupSort, topDocs); } else { // TermFirstPassGroupingCollector适用于你的分组域是一个非DocValuesField selected = new TermFirstPassGroupingCollector(groupField, groupSort, topDocs); } return selected; } private static <T> AbstractSecondPassGroupingCollector<T> createSecondPassCollector( AbstractFirstPassGroupingCollector firstPassGroupingCollector, String groupField, Sort groupSort, Sort sortWithinGroup, int groupOffset, int maxDocsPerGroup, boolean getScores, boolean getMaxScores, boolean fillSortFields) throws IOException { if (TermFirstPassGroupingCollector.class .isAssignableFrom(firstPassGroupingCollector.getClass())) { Collection<SearchGroup<BytesRef>> searchGroups = firstPassGroupingCollector .getTopGroups(groupOffset, fillSortFields); return (AbstractSecondPassGroupingCollector) new TermSecondPassGroupingCollector( groupField, searchGroups, groupSort, sortWithinGroup, maxDocsPerGroup, getScores, getMaxScores, fillSortFields); } else { ValueSource vs = new BytesRefFieldSource(groupField); Collection<SearchGroup<MutableValue>> searchGroups = firstPassGroupingCollector .getTopGroups(groupOffset, fillSortFields); return (AbstractSecondPassGroupingCollector) new FunctionSecondPassGroupingCollector( searchGroups, groupSort, sortWithinGroup, maxDocsPerGroup, getScores, getMaxScores, fillSortFields, vs, new HashMap()); } } // Basically converts searchGroups from MutableValue to BytesRef if grouping // by ValueSource @SuppressWarnings("unchecked") private AbstractSecondPassGroupingCollector<?> createSecondPassCollector( AbstractFirstPassGroupingCollector<?> firstPassGroupingCollector, String groupField, Collection<SearchGroup<BytesRef>> searchGroups, Sort groupSort, Sort sortWithinGroup, int maxDocsPerGroup, boolean getScores, boolean getMaxScores, boolean fillSortFields) throws IOException { if (firstPassGroupingCollector.getClass().isAssignableFrom( TermFirstPassGroupingCollector.class)) { return new TermSecondPassGroupingCollector(groupField, searchGroups, groupSort, sortWithinGroup, maxDocsPerGroup, getScores, getMaxScores, fillSortFields); } else { ValueSource vs = new BytesRefFieldSource(groupField); List<SearchGroup<MutableValue>> mvalSearchGroups = new ArrayList<SearchGroup<MutableValue>>( searchGroups.size()); for (SearchGroup<BytesRef> mergedTopGroup : searchGroups) { SearchGroup<MutableValue> sg = new SearchGroup(); MutableValueStr groupValue = new MutableValueStr(); if (mergedTopGroup.groupValue != null) { groupValue.value.copyBytes(mergedTopGroup.groupValue); } else { groupValue.exists = false; } sg.groupValue = groupValue; sg.sortValues = mergedTopGroup.sortValues; mvalSearchGroups.add(sg); } return new FunctionSecondPassGroupingCollector(mvalSearchGroups, groupSort, sortWithinGroup, maxDocsPerGroup, getScores, getMaxScores, fillSortFields, vs, new HashMap()); } } private AbstractAllGroupsCollector<?> createAllGroupsCollector( AbstractFirstPassGroupingCollector<?> firstPassGroupingCollector, String groupField) { if (firstPassGroupingCollector.getClass().isAssignableFrom( TermFirstPassGroupingCollector.class)) { return new TermAllGroupsCollector(groupField); } else { ValueSource vs = new BytesRefFieldSource(groupField); return new FunctionAllGroupsCollector(vs, new HashMap()); } } /** * 添加分组域 * * @param doc * 索引文档 * @param groupField * 需要分组的域名称 * @param value * 域值 */ private static void addGroupField(Document doc, String groupField, String value) { doc.add(new SortedDocValuesField(groupField, new BytesRef(value))); } }
最近本人身体出了点小状况,人不太舒服,就不多说了,大家看看示例代码自己理解理解,里面注释我写的很详细了,如果你们有哪里看不懂,QQ上联系我。Demo源码在底下的附件里,请知晓!
若你还有什么疑问,请加我Q-Q:7-3-6-0-3-1-3-0-5,或者加裙:
,欢迎你加入一起交流学习。
相关推荐
"Lucene group by" 指的就是在 Lucene 中实现基于特定字段的分组操作,类似于 SQL 中的 GROUP BY 子句。这使得用户能够按类别聚合文档,例如,根据作者、日期或其他分类标准来查看搜索结果。 在 Lucene 中,分组...
在"一步一步跟我学习lucene(12)---lucene搜索之分组处理group查询"中,我们将重点关注如何利用Lucene实现这一高级搜索功能。 首先,Lucene是一个开源全文搜索引擎库,它为Java开发者提供了构建高效、可扩展的搜索...
《Lucene.Net搜索引擎技术及其在锤子搜索引擎分组统计中的应用》 Lucene.Net是一个开源全文检索库,它是Apache Lucene项目的一个.NET平台实现。这个强大的搜索引擎库为开发者提供了高效、可扩展的文本搜索功能,...
《Lucene5学习之Facet(续)》 在深入探讨Lucene5的Facet功能之前,我们先来了解一下什么是Faceting。Faceting是搜索引擎提供的一种功能,它允许用户通过分类或属性对搜索结果进行细分,帮助用户更精确地探索和理解...
### Lucene 分组统计 #### 一、Lucene 分组统计概述 在 Lucene 的应用场景中,分组统计是一项非常重要的功能。它可以帮助用户快速获取文档集合中的统计数据,例如按类别进行分组并统计每组的数量等。在 Lucene 中...
本文将围绕“Lucene5学习之拼音搜索”这一主题,详细介绍其拼音搜索的实现原理和实际应用。 首先,我们需要理解拼音搜索的重要性。在中文环境中,由于汉字的复杂性,用户往往习惯于通过输入词语的拼音来寻找信息。...
lucene-grouping-3.5.0.jar分组统计+分类统计插件 分组统计+分类统计
这篇博客“Lucene5学习之自定义Collector”显然聚焦于如何在Lucene 5版本中通过自定义Collector来优化搜索结果的收集过程。Collector是Lucene搜索框架中的一个重要组件,它负责在搜索过程中收集匹配的文档,并根据...
“Lucene5学习之排序-Sort”这个标题表明了我们要探讨的是关于Apache Lucene 5版本中的排序功能。Lucene是一个高性能、全文检索库,它提供了强大的文本搜索能力。在这个主题中,我们将深入理解如何在Lucene 5中对...
《Lucene5学习之Highlighter关键字高亮》 在信息技术领域,搜索引擎的使用已经变得无处不在,而其中的关键技术之一就是如何有效地突出显示搜索结果中的关键字,这就是我们今天要探讨的主题——Lucene5中的...
**标题:“Lucene5学习之SpellCheck拼写纠错”** 在深入探讨Lucene5的SpellCheck功能之前,首先需要理解Lucene是什么。Lucene是一个开源的全文检索库,由Apache软件基金会开发,它提供了高性能、可扩展的文本搜索...
总结起来,Lucene5学习之增量索引(Zoie)涉及到的关键技术点包括: 1. 基于Lucene的增量索引解决方案:Zoie系统。 2. 主从复制架构:Index Provider和Index User的角色。 3. 数据变更追踪:通过变更日志实现增量索引...
本文将深入探讨“Lucene5学习之自定义排序”这一主题,帮助你理解如何在Lucene5中实现自定义的排序规则。 首先,Lucene的核心功能之一就是提供高效的全文检索能力,但默认的搜索结果排序通常是基于相关度得分...
本文将深入探讨"Lucene5学习之分页查询"这一主题,结合给定的标签"源码"和"工具",我们将讨论如何在Lucene5中实现高效的分页查询,并探讨其背后的源码实现。 首先,理解分页查询的重要性是必要的。在大型数据集的...
**标题解析:** "Lucene5学习之FunctionQuery功能查询" Lucene5是Apache Lucene的一个版本,这是一个高性能、全文本搜索库,广泛应用于搜索引擎和其他需要高效文本检索的系统。FunctionQuery是Lucene中的一种查询...
《深入探索Lucene5:Suggest关键字提示技术》 在信息检索领域,用户输入查询时,提供快速、准确的关键字提示能显著提升用户体验。Lucene,作为Java领域最流行的全文检索库,其5.x版本引入了Suggest组件,用于实现...
**Lucene5学习之创建索引入门示例** 在IT领域,搜索引擎的开发与优化是一项关键技术,而Apache Lucene作为一款高性能、全文本搜索库,是许多开发者进行文本检索的首选工具。本文将深入探讨如何使用Lucene5来创建一...
可以配置采集网站的图片,包含分组统计,相同数据合并功能,主要是给群内成员来个demo,让大家有个学习的demo 小试牛刀、临时写的,莫吐槽 需要用到mysql数据库,项目里有个image.sql文件,请先执行一下,然后...
《深入探索Lucene5 Spatial:地理位置搜索》 在信息技术飞速发展的今天,地理位置搜索已经成为许多应用和服务不可或缺的一部分。Apache Lucene作为一个强大的全文搜索引擎库,其在5.x版本中引入了Spatial模块,使得...