需求的提出
现在的公司数据集群中,已经存在约8亿的数据,现在有一个业务的要求如下,
1. 比如搜索"广东",则需要把包含广东以下市,区,镇,街道等的所有的关键字都给匹配出来.
2. 同时,搜索"天河",则需要返回一个"广东 广州 天河" 这样子详细的路径出来.意思不能是简单的关键字匹配,因为它有地区的层次归属.
3. 比如文章中含有"朝阳",则全国地区中只含有"朝阳"的,: 吉林 长春 朝阳, 辽宁 朝阳, 北京 朝阳,都要全部返回.
4. 如果文章中,是 广州,天河区,则匹配出来的,只需要 "广东 广州 天河" 就可以了.
如何做?
其实从上面的需求来说,要实现起来其实并不困难,因为只要找一个这样子地区对应表回来,判断文章中是否包含了这些地区关键字,然后组织成分层次的地区就可以了.
但现在的情况是数据量有8亿,在匹配这些关键字过程中,是否性能跟得上?
另外一种,我通过类似分词的方式去匹配对比地区,类似于IK分词,但针对地区作了改进.
方法中,getAllArea 是使用了分词的方式, getAllArea2 是使用了字符匹配的方式.
大家可以直接执行 main 方法,可以对比出两者之间的性能差别,分词的结果是一样的.
对比性能结果图如下:
如果对源码感兴趣,可以查看下面:
private static String[] areaList[] = new String[100*1000][]; private static String[] areaNameList = new String[100*1000] ; private static HashMap<String, Object> hMap = new HashMap<String,Object>(); private static HashMap<String, Set<String>> mappingSet = new HashMap<String, Set<String>>(); static{ // 会完善好这些地区列表 InputStream in = null; BufferedReader reader = null; try { in = new FileInputStream("conf/all_area.txt"); reader = new BufferedReader(new InputStreamReader(in, "utf-8")); String line = null; int index = 0; while ((line = reader.readLine()) != null) { line = line.trim(); if (isEmptyString(line)){ continue; } String areas[] = line.split("\\s+"); // String lastArea = areas[areas.length-1]; areaList[index] = areas; areaNameList[index] = line; index++; } for(String[] areas:areaList){ // 每个区域从大到小开始匹配 if(areas == null){ // 数据原生数组,效率比 ArrayList 要快不少 break; } // String areas[] = area.split("\\s+"); String lastArea = areas[areas.length-1]; loadMap(lastArea , hMap ); // 加载分词的结构 map } loadMapping(mappingSet); // 加载分词后,对应的地区 mapping } catch (Exception e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * * @param input * @return * 采用关键字包含形式, * 现在已经不再使用这种方式 * */ public static String getAllArea2(String input){ // input = input + ""; // in LinkedList<String> allLinkedArea = new LinkedList<String>(); // 为了性能,使用了ArrayList, 初始化 100 int areaIndexs = 0; for(String[] areas:areaList){ // 每个区域从大到小开始匹配 if(areas == null){ // 数据原生数组,效率比 ArrayList 要快不少 break; } // String areas[] = area.split("\\s+"); String lastArea = areas[areas.length-1]; // int lastArea = areas.length-1; // String lastArea = ""; // ; String area = null; area = areaNameList[areaIndexs++]; if ( input.contains(lastArea) ){ int listIndex = -1; boolean notMatch = true; for(int s=0;s<allLinkedArea.size();s++){ // for(String tmp:allLinkedArea){ String tmp = allLinkedArea.get(s).trim(); int t1 = tmp.split("\\s+").length; int t2 = area.split("\\s+").length; if((tmp.contains(area) || area.contains(tmp))){ notMatch = false; if( t1 < t2){ //比如三级包含了二级,则不需要加入 listIndex = s; break; } // 如果是三级包含了二级,则忽略 } } if(allLinkedArea.isEmpty() || notMatch){ allLinkedArea.add(area); continue; } if(listIndex > -1){ allLinkedArea.remove(listIndex); allLinkedArea.add(area); } } } return allLinkedArea.toString().replaceAll("\\]|\\[", "").trim(); } /** * * @param input * @return * * 基于自己实现的地区分词, * 性能: * 未去从前: * 普通PC 10W遍 515 个字的文本,用时 6.8 秒. * 折合:速度为 757352 字/s * 去从后: * 普通PC 10W遍 515 个字的文本,用时 8.7 秒. * 折合:速度为 591954 字/s */ public static String getAllArea(String input){ if(input == null){ return ""; } char[] chars = input.toCharArray(); int c = 0; HashSet<String> matchedArea = new HashSet<String>(); HashMap<String, Object> firstMap = hMap; // 初始化 // 已经重新开始匹配了 StringBuffer word = new StringBuffer(); HashMap<String, Object> tMap = null; LinkedList<String> allLinkedArea = new LinkedList<String>(); while ( c < chars.length ){ String nowChar = String.valueOf(chars[c]); if ( (tMap=((HashMap<String, Object>)firstMap.get(nowChar ))) == null ){ // 中间断开了 firstMap = hMap; //把 Map 退回最原始的, 本层已经断开了 word = new StringBuffer(); if ((tMap=((HashMap<String, Object>)firstMap.get(nowChar ))) == null ){ // 中间断开了 // 在最 top 层查找 c++; word = new StringBuffer(); continue; } } word.append(nowChar); if(word.length() > 1){ // 如果匹配了2个字以上 // 找回对应的一个地区对应多个地方的情况 Set<String> areaSet = (Set<String>)mappingSet.get(word.toString()); if(areaSet != null){ // System.out.println(word.toString() + " -> " + areaSet); // debug 时用 matchedArea.addAll(areaSet); } } c++; if( c > chars.length ){ break; } if ( tMap.isEmpty() ){ // 最尽头了 continue; } firstMap = tMap; } for(String area:matchedArea){ int listIndex = -1; boolean notMatch = true; for(int s=0;s<allLinkedArea.size();s++){ // for(String tmp:allLinkedArea){ String tmp = allLinkedArea.get(s).trim(); int t1 = tmp.split("\\s+").length; int t2 = area.split("\\s+").length; if((tmp.contains(area) || area.contains(tmp))){ notMatch = false; if( t1 < t2){ //比如三级包含了二级,则不需要加入 listIndex = s; break; } // 如果是三级包含了二级,则忽略 } } if(allLinkedArea.isEmpty() || notMatch){ allLinkedArea.add(area); continue; } if(listIndex > -1){ allLinkedArea.remove(listIndex); allLinkedArea.add(area); } } // allLinkedArea.toString().replaceAll("\\]|\\[", "").trim() return allLinkedArea.toString().replaceAll("\\[|\\]", "").replace("\\s+", " "); // return ""; } private static void loadMap(String values ,HashMap<String, Object> tmpMap){ if ( values.trim().isEmpty()){ return ; } String nowChar = values.substring(0, 1); Object valObject = tmpMap.get( nowChar ); String nextStr = values.substring(1, values.length()); if(valObject == null){ HashMap<String, Object> tMap = new HashMap<String, Object>(); tmpMap.put(nowChar+"" , tMap ); loadMap(nextStr , tMap); } if(valObject instanceof HashMap){ HashMap<String, Object> exitsMap = (HashMap<String, Object>)valObject; loadMap(nextStr , exitsMap); } } private static void loadMapping(HashMap<String, Set<String>> mappingSet){ int aid = 0; for(String[] areas:areaList){ // 每个区域从大到小开始匹配 if(areas == null){ // 数据原生数组,效率比 ArrayList 要快不少 break; } // String areas[] = area.split("\\s+"); String lastArea = areas[areas.length-1]; Set<String> areaSet = (Set<String>)mappingSet.get(lastArea); String fullAreaName = areaNameList[aid++]; if(areaSet == null){ areaSet = new HashSet<String>(); mappingSet.put(lastArea, areaSet); } areaSet.add(fullAreaName.trim()); } } private static boolean isEmptyString(String input){ return input == null || input.trim().isEmpty(); } public static void main(String[] args) { String str = "【首款涡轮增压车型 静态评测雷克萨斯NX】在本届北京车展上,雷克萨斯带来了全新NX。雷克萨斯NX的设计灵感来源于雷克萨斯去年法兰克福车展发布的LF-NX概念车,基于RAV4平台打造的,定位低于雷克萨斯RX,以奔驰GLA、宝马X1、奥迪Q3为竞争对手。更多详情:"; str = str + "广东新闻【雷克萨斯NX系列】4月20日,雷克萨斯全新SUV NX系列全球首发,其设计灵感来自LF-NX概念车。搭载2.0T发动机,最大功率达到243马力。雷克萨斯NX定位于紧凑型SUV,未来将与奥迪Q3、奔驰GLA等车型展开竞争"; str = str + "【莲花SUV车型T5亮相 预计四季度上市】莲花在北京车展发布中型SUV莲花T5,莲花T5的设计同样来自于英国莲花独特的“边缘激流动力美学”设计理念,赋予了莲花T5纯正的莲花车动力美学。该车安全配置丰富,为行车安全增添了保障,预计将于今年第四季度正式上市"; str = str + "本报记者从现场传来消息:火是从五六楼空调外挂机附近引发,目前现场已封锁,尚无人员伤亡消息。消防出动云梯车投入灭火,现在出顶层附近仍烟雾缭绕外,已看不到明火。一些附近居民纷纷拿出水盆、水桶,想参与救火,已被隔离在封锁线外。"; str = str + "北京市车展, 明年会在广州琶洲开幕,然后向二级城市,如江门镇,佛山地区"; System.out.println("匹配的内容字数:" + str.length()); // str = "朝阳"; long l = System.currentTimeMillis(); for(int i = 0;i<100;i++){ // String s = getAllArea2(str);// 旧的字符包含方式 String s = getAllArea(str); // 现在的分词方式 // System.out.println(s); } l = System.currentTimeMillis() - l; System.out.println(l + " ms"); System.exit(0); }
欢迎转载,请注明出处及原作者 kernaling.wong
http://kernaling-wong.iteye.com/blog/2054626
相关推荐
盘古分词器的设计理念是基于统计和规则相结合的方法,它结合了词典匹配和上下文信息,以提高分词的准确性。词典通常包含了大量的常用词汇和专有名词,而上下文信息则帮助识别一些在特定语境下才能确定的词汇边界。...
全国地区数据的使用方法多种多样。例如,你可以将其用于电商平台,实现精确的配送地址管理;在地图应用中,可以提供更加精细化的定位服务;在政府公共服务系统中,可以用于统计分析,辅助政策制定;在物流行业中,...
地址收录程序V1.0通过正向最大匹配算法,有效地解决了这一问题,使得程序能够适应不同地区和场合的地址格式,提高信息处理的准确性。 六、应用场景及价值 该程序广泛适用于各种需要处理大量地址信息的场景,如地图...
同时,宝贝标题、创意标题与关键词的相关性也是关键,需确保它们紧密匹配。 2. **相关性调整**:关键词与宝贝类目、属性的一致性至关重要。选择正确的关键词并确保其与宝贝信息相符,能够有效提升质量分。此外,...
通过使用地理编码技术,可以精确获取发货地和目的地的经纬度坐标,进而通过GIS系统计算出最优运输路径,不仅节省了运输成本,还提高了配送效率。 #### 七、技术发展趋势 随着人工智能和大数据技术的发展,未来的...
**正文** 《Lucene全文搜索:分组、精确查找与...同时,配合使用像IKAnalyzer这样的分词工具,可以进一步提升对中文文本的处理能力。无论是开发搜索引擎,还是在数据分析、信息检索等领域,Lucene都是不可或缺的工具。
- **分词迭代器 `regex_token_iterator`**:说明了如何使用`regex_token_iterator`对字符串进行分词操作。 - **异常类 `bad_expression`**:解释了`bad_expression`异常类,该类用于表示无效的正则表达式。 - **语法...
9. **高亮显示(Highlighting)**: 搜索结果通常会高亮显示匹配的关键词,以帮助用户快速定位到相关部分。Lucene提供了高亮工具,可以根据搜索结果生成高亮后的文本。 以上就是关于“Lucene中文搜索”的核心知识点...
1. **索引**: 全文检索的核心是建立索引,Lucene.Net通过分析文档内容,将其转换为倒排索引结构,使得快速查找匹配关键词的文档成为可能。 2. **分词器**: 分词器是Lucene.Net中的关键组件,它将文本分解为可搜索的...
如根据网站中分析的地区信息\\行业分析信息等各类分类信息匹配成为系统需要的信息,如从网站获得的地区信息:浙江省>>杭州市>>将这一内容自动匹配为浙江省或杭州市,也可以根据企业名自动匹配地区信息,如:杭州市欧派...
常见的分词方法有基于词典的精确匹配法、统计模型如隐马尔可夫模型(HMM)、最大熵模型等。这些方法的运用使得搜索软件能够理解并解析用户的中文查询。 拼音转换则对于输入不规范或使用拼音输入法的用户来说至关...
这需要系统具备对用户输入的关键词进行分词和匹配的能力,可以采用二分查找、哈希表或者Trie树等数据结构来优化搜索效率。同时,考虑到中文名字的特性,可能需要支持简体、繁体甚至不同地区的拼音或注音检索。 接着...
至于电话号码和邮政编码的提取,工具可能有预设的规则库,针对中国地区的电话号码格式(如11位数字)和邮政编码(6位数字)进行匹配。这要求工具具备良好的本地化能力,以适应不同国家和地区的数据标准。 最后,...
3. **多语言支持**:包括繁体中文在内的多种语言支持,确保不同地区的用户都能顺利使用。 4. **自定义配置**:允许管理员根据网站特性和需求调整搜索参数,如搜索范围、排序方式等。 5. **搜索结果分页**:通过分页...
4. **查询处理**:用户输入查询后,搜索引擎会解析查询语句,使用倒排索引快速找出匹配的网页,并根据相关性算法(如TF-IDF、PageRank等)对结果进行排序。 二、搜索引擎优化 1. **内容质量**:搜索引擎优先展示高...
然后,【数据分析处理】是核心部分,系统采用文本信息提取、分词(jieba分词系统)、关键词提取和词频统计等方法,对抓取的职位信息进行深度分析,以揭示行业招聘趋势。这有助于理解不同地区的薪酬分布、行业动态,...
通常会使用jieba分词库或其他成熟的中文分词工具。 3. **查询分析**:对用户的输入进行智能处理,如纠错、同义词扩展、短语查询等,以提高搜索精度和召回率。例如,用户可能输入错别字,系统需要能够识别并纠正。 ...
这部分源码可能包含对拼音的匹配算法,如基于字典的分词策略,以及如何快速查找和排序候选词。 3. **智能预测与联想**:谷歌拼音输入法的一大特色是其智能预测功能,能够根据上下文预测用户可能要输入的词语。这...