Lucene4.3开发之插曲之包容万物

2024-05-15 04:58

本文主要是介绍Lucene4.3开发之插曲之包容万物,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[b][color=red][size=x-large]允许转载,转载请注明原创地址:
[url]http://qindongliang1922.iteye.com/blog/1927605[/url]
谢谢配合
[/size][/color][/b]

[b][size=x-large][color=green]最近在群里面(324714439)遇到几位朋友提出了一些特殊的分词需求,在此做个总结。本来的Lucene的内置的分词器,差不多可以完成我们的大部分分词工作了,如果是英文文章那么可以使用StandardAnalyzer标准分词器,WhitespaceAnalyzer空格分词器,对于中文我们则可以选择IK分词器,Messeg4j,庖丁等分词器。[/color][/size][/b]

[b][color=green][size=x-large]我们先来看看下面的几个需求

[table]
|编号|需求分析
|1|按单个字符进行分词无论是数字,字母还是特殊符号
|2|按特定的字符进行分词,类似String中spilt()方法
|3|按照某个字符或字符串进行分词
[/table][/size][/color][/b]
[b][size=x-large]仔细分析下上面的需求,会觉得上面的需求很没意思,但是在特定的场合下确实是存在这样的需求的,看起来上面的需求很简单,但是lucene里面内置的分析器却没有一个支持这种变态的"无聊的"分词需求,如果想要满足上面的需求,可能就需要我们自己定制自己的分词器了。[/size][/b]


[b][color=violet][size=x-large]先来看第一个需求,单个字符切分,这就要不管你是the一个单词还是一个电话号码还是一段话还是其他各种特殊符号都要保留下来,进行单字切分,这种特细粒度的分词,有两种需求情况,可能适应这两种场景
(-)100%的实现数据库模糊匹配
(=)对于某个电商网站笔记本的型号Y490,要求用户无论输入Y还是4,9,0都可以找到这款笔记本
[/size][/color][/b]

[b][size=x-large]这种单字切分确实可以实现数据库的百分百模糊检索,但是同时也带来了一些问题,如果这个域中是存电话号码,或者身份证之类的与数字的相关的信息,那么这种分词法,会造成这个域的倒排链表非常之长,反映到搜索上,就会出现中文检索很快,而数字的检索确实非常之慢的问题。原因是因为数字只有0-9个字符,而汉字则远远比这个数量要大的多,所以在选用这种分词时,还是要慎重的考虑下自己的业务场景到底适不适合这种分词,否则就会可能出一些问题。[/size][/b]

[b][size=x-large][color=green]再来分析下2和3的需求,这种需求可能存在这么一种情况,就是某个字段里存的内容是按照逗号或者空格,#号,或者是自己定义的一个字符串进行分割存储的,而这种时候我们可能就会想到一些非常简单的做法,直接调用String类的spilt方法进行打散,确实,这种方式是可行的,但是lucene里面的结构某些情况下,就可能不适合用字符串拆分的方法,而是要求我们必须定义一个自己的分词器来完成这种功能,因为涉及到一些参数需要传一个分词器或者索引和检索时都要使用分词器来构造解析,所以有时候就必须得自己定义个专门处理这种情况的分词器了。[/color][/size][/b]

[b][color=green][size=x-large]好了,散仙不在唠叨了,下面开始给出代码,首先针对第一个需求,单字切分,其实这个需求没什么难的,只要熟悉lucene的Tokenizer就可以轻松解决,我们改写ChineseTokenizer来满足我们的需求.[/size][/color][/b]
package com.piaoxuexianjing.cn;

import java.io.IOException;
import java.io.Reader;

import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.util.AttributeSource.AttributeFactory;

public class China extends Tokenizer {

public China(Reader in) {
super(in);
}

public China(AttributeFactory factory, Reader in) {
super(factory, in);
}

private int offset = 0, bufferIndex=0, dataLen=0;
private final static int MAX_WORD_LEN = 255;
private final static int IO_BUFFER_SIZE = 1024;
private final char[] buffer = new char[MAX_WORD_LEN];
private final char[] ioBuffer = new char[IO_BUFFER_SIZE];


private int length;
private int start;

private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);

private final void push(char c) {

if (length == 0) start = offset-1; // start of token
buffer[length++] = Character.toLowerCase(c); // buffer it

}

private final boolean flush() {

if (length>0) {
//System.out.println(new String(buffer, 0,
//length));
termAtt.copyBuffer(buffer, 0, length);
offsetAtt.setOffset(correctOffset(start), correctOffset(start+length));
return true;
}
else
return false;
}

@Override
public boolean incrementToken() throws IOException {
clearAttributes();

length = 0;
start = offset;


while (true) {

final char c;
offset++;

if (bufferIndex >= dataLen) {
dataLen = input.read(ioBuffer);
bufferIndex = 0;
}

if (dataLen == -1) {
offset--;
return flush();
} else
c = ioBuffer[bufferIndex++];


switch(Character.getType(c)) {

case Character.DECIMAL_DIGIT_NUMBER://注意此部分不过滤一些熟悉或者字母
case Character.LOWERCASE_LETTER://注意此部分
case Character.UPPERCASE_LETTER://注意此部分
// push(c);
// if (length == MAX_WORD_LEN) return flush();
// break;

case Character.OTHER_LETTER:
if (length>0) {
bufferIndex--;
offset--;
return flush();
}
push(c);
return flush();

default:
if (length>0) return flush();

break;

}
}
}

@Override
public final void end() {
// set final offset
final int finalOffset = correctOffset(offset);
this.offsetAtt.setOffset(finalOffset, finalOffset);
}

@Override
public void reset() throws IOException {
super.reset();
offset = bufferIndex = dataLen = 0;
}

}


[b][size=x-large]然后定义个自己的分词器[/size][/b]

package com.piaoxuexianjing.cn;

import java.io.Reader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;

/**
* @author 三劫散仙
* 单字切分
*
* **/
public class MyChineseAnalyzer extends Analyzer {

@Override
protected TokenStreamComponents createComponents(String arg0, Reader arg1) {

Tokenizer token=new China(arg1);

return new TokenStreamComponents(token);
}





}



[b][size=x-large]下面我们来看单字切词效果,对于字符串
String text="天气不错132abc@#$+-)(*&^.,/";[/size][/b]





1
3
2
a
b
c
@
#
$
+
-
)
(
*
&
^
.
,
/



[b][size=x-large]对于第二种需求我们要模仿空格分词器的的原理,代码如下[/size][/b]
package com.splitanalyzer;

import java.io.Reader;

import org.apache.lucene.analysis.util.CharTokenizer;
import org.apache.lucene.util.Version;

/***
*
*@author 三劫散仙
*拆分char Tokenizer
*
* */
public class SpiltTokenizer extends CharTokenizer {

char c;
public SpiltTokenizer(Version matchVersion, Reader input,char c) {
super(matchVersion, input);
// TODO Auto-generated constructor stub
this.c=c;
}

@Override
protected boolean isTokenChar(int arg0) {
return arg0==c?false:true ;
}




}

[b][color=olive][size=x-large]然后在定义自己的分词器[/size][/color][/b]
package com.splitanalyzer;

import java.io.Reader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.util.Version;

/**
* @author 三劫散仙
* 自定义单个char字符分词器
* **/
public class SplitAnalyzer extends Analyzer{
char c;//按特定符号进行拆分

public SplitAnalyzer(char c) {
this.c=c;
}

@Override
protected TokenStreamComponents createComponents(String arg0, Reader arg1) {
// TODO Auto-generated method stub
return new TokenStreamComponents(new SpiltTokenizer(Version.LUCENE_43, arg1,c));
}


}

[b][size=x-large]下面看一些测试效果[/size][/b]
package com.splitanalyzer;

import java.io.StringReader;

import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;

/**
* 测试的demo
*
* **/
public class Test {

public static void main(String[] args)throws Exception {
SplitAnalyzer analyzer=new SplitAnalyzer('#');
//SplitAnalyzer analyzer=new SplitAnalyzer('+');
//PatternAnalyzer analyzer=new PatternAnalyzer("abc");
TokenStream ts= analyzer.tokenStream("field", new StringReader("我#你#他"));
// TokenStream ts= analyzer.tokenStream("field", new StringReader("我+你+他"));
CharTermAttribute term=ts.addAttribute(CharTermAttribute.class);
ts.reset();
while(ts.incrementToken()){
System.out.println(term.toString());
}
ts.end();
ts.close();

}

}





[b][size=x-large][color=blue]到这里,可能一些朋友已经看不下去了,代码太多太臃肿了,有没有一种通用的办法,解决此类问题,散仙的回答是肯定的,如果某些朋友,连看到这部分的耐心都没有的话,那么,不好意思,你只能看到比较低级的解决办法了,当然能看到这部分的道友们,散仙带着大家来看一下比较通用解决办法,这个原理其实是基于正则表达式的,所以由此看来,正则表达式在处理文本字符串上面有其独特的优势。下面我们要做的就是改写自己的正则解析器,代码非常精简,功能却是很强大的,上面的3个需求都可以解决,只需要传入不用的参数即可。[/color][/size][/b]



package com.splitanalyzer;

import java.io.Reader;
import java.util.regex.Pattern;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.pattern.PatternTokenizer;

/**
* @author 三劫散仙
* 自定义分词器
* 针对单字切
* 单个符号切分
* 多个符号组合切分
*
* **/
public class PatternAnalyzer extends Analyzer {

String regex;//使用的正则拆分式
public PatternAnalyzer(String regex) {
this.regex=regex;
}

@Override
protected TokenStreamComponents createComponents(String arg0, Reader arg1) {
return new TokenStreamComponents(new PatternTokenizer(arg1, Pattern.compile(regex),-1));
}



}



[b][size=x-large]我们来看下运行效果:[/size][/b]
package com.splitanalyzer;

import java.io.StringReader;

import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;

/**
* 测试的demo
*
* **/
public class Test {

public static void main(String[] args)throws Exception {
// SplitAnalyzer analyzer=new SplitAnalyzer('#');
PatternAnalyzer analyzer=new PatternAnalyzer("");
//空字符串代表单字切分
TokenStream ts= analyzer.tokenStream("field", new StringReader("我#你#他"));
CharTermAttribute term=ts.addAttribute(CharTermAttribute.class);
ts.reset();
while(ts.incrementToken()){
System.out.println(term.toString());
}
ts.end();
ts.close();

}

}

[b][size=x-large]输出效果:[/size][/b]

#

#


[b][size=x-large]传入#号参数[/size][/b]
PatternAnalyzer analyzer=new PatternAnalyzer("#");

[b][size=x-large]输出效果:[/size][/b]





[b][size=x-large]传入任意长度的字符串参数[/size][/b]
 PatternAnalyzer analyzer=new PatternAnalyzer("分割");
TokenStream ts= analyzer.tokenStream("field", new StringReader("我分割你分割他分割"));

[b][size=x-large]输出效果:[/size][/b]







[b][color=red][size=x-large]允许转载,转载请注明原创地址:
[url]http://qindongliang1922.iteye.com/blog/1927605[/url]
谢谢配合
[/size][/color][/b]


[b][color=red][size=x-large]至此,我们可以完成一些比较特殊的分词,当然,大千世界无奇不有,无论什么时候,适合自己业务就是最好的,我们要根据自己的需求来灵活变通。[/size][/color][/b]

这篇关于Lucene4.3开发之插曲之包容万物的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/990870

相关文章

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

基于Java开发一个极简版敏感词检测工具

《基于Java开发一个极简版敏感词检测工具》这篇文章主要为大家详细介绍了如何基于Java开发一个极简版敏感词检测工具,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录你是否还在为敏感词检测头疼一、极简版Java敏感词检测工具的3大核心优势1.1 优势1:DFA算法驱动,效率提升10

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

Java 与 LibreOffice 集成开发指南(环境搭建及代码示例)

《Java与LibreOffice集成开发指南(环境搭建及代码示例)》本文介绍Java与LibreOffice的集成方法,涵盖环境配置、API调用、文档转换、UNO桥接及REST接口等技术,提供... 目录1. 引言2. 环境搭建2.1 安装 LibreOffice2.2 配置 Java 开发环境2.3 配

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用