龙空技术网

Lucene分词器详解

代码接盘侠 219

前言:

眼前兄弟们对“分词搜索商品代码”大体比较注重,小伙伴们都需要剖析一些“分词搜索商品代码”的相关资讯。那么小编在网摘上收集了一些对于“分词搜索商品代码””的相关知识,希望我们能喜欢,小伙伴们一起来学习一下吧!

前面介绍了Lucene,这里看下Lucene分词器的介绍。

org.apache.lucene.analysi.Analyzer,抽象类:Analyzer(分析器)提供了两个方法:

    public final TokenStream tokenStream(String fieldName, Reader reader) {        Analyzer.TokenStreamComponents components = this.reuseStrategy.getReusableComponents(this, fieldName);        Reader r = this.initReader(fieldName, reader);        if (components == null) {            components = this.createComponents(fieldName);            this.reuseStrategy.setReusableComponents(this, fieldName, components);        }        components.setReader(r);        return components.getTokenStream();    }
    public final TokenStream tokenStream(String fieldName, String text) {        Analyzer.TokenStreamComponents components = this.reuseStrategy.getReusableComponents(this, fieldName);        ReusableStringReader strReader = components != null && components.reusableStringReader != null ? components.reusableStringReader : new ReusableStringReader();        strReader.setValue(text);        Reader r = this.initReader(fieldName, strReader);        if (components == null) {            components = this.createComponents(fieldName);            this.reuseStrategy.setReusableComponents(this, fieldName, components);        }        components.setReader(r);        components.reusableStringReader = strReader;        return components.getTokenStream();    }

两个都为fianl方法说明方法不能扩展

第一个方法:fieldName:字段的名字,reader:字符输入流

第二个方法:fieldName:字段的名字,text:字符串

方法返回的是一个TokenStream。TokenStream:分词处理器

Analyzer.TokenStreamComponents components = this.reuseStrategy.getReusableComponents(this, fieldName);

首先会从重用策略(reuseStrategy)中去获取components ,如果没有则回去创建一个components 然后将其放入到 reuseStrategy 中,其实是一个Map。

Reader r = this.initReader(fieldName, reader);

获取reader,此方法什么也没做,返回还是原来的reader。

components = this.createComponents(fieldName);

没有从重用策略拿到components,重新构建,此方法是抽象方法,需要使用者自己实现。

components.setReader(r);

将reader传给components中的Tokenizer。

return components.getTokenStream();

从分词处理器组件获取到tokenStream,并返回。

看下Analyzer.TokenStreamComponents components的属性方法:

    public static class TokenStreamComponents {        protected final Tokenizer source;        protected final TokenStream sink;        transient ReusableStringReader reusableStringReader;        public TokenStreamComponents(Tokenizer source, TokenStream result) {            this.source = source;            this.sink = result;        }        public TokenStreamComponents(Tokenizer source) {            this.source = source;            this.sink = source;        }      //省去get/set方法

TonkenStreamComponents 分词处理器组件:这个类中封装有供外部使用的TonkenStream分词处理器。提供了对Tokenizer source和TokenStream sink两个属性的访问方法。

从上面的分析可以看出,Analyzer抽象类,提供了可以扩展方法createComponents,通过实现这个方法,传入实现自己的Tokenizer和TokenStream,reader构建出分词处理器TokenStream。

那么具体看TokenStream和Tokenizer:

TokenStream是AttributeSource的子类,有一个抽象的方法:

public abstract boolean incrementToken() throws IOException;

这个方法主要是用于不断的迭代,取得下一个分项(Token)的,如果有则返回true没有则返回false。每次调用一次返回一个分项。

TokenStream有两个子类,Tokenizer和TokenFilter。

Tokenizer:分词器,输入是Reader字符流的TokenStream,完成从流中分出分项

TokenFilter:分项过滤器,它的输入是另一个TokenStream,完成对从上一个TokenStream中流出的token的特殊处理。

字符串-->字符流--->Tokenizer(分词)-->分项--->TokenFilter(过滤,修饰)

可能不太准确,有很多的组合嵌套。

这个我们可能可以看出createComponents的sink,source了,传入Tokenizer,TokenFilter进行一番操作,完成分词处理工作。TokenFilter采用装饰着模式。如果我们需要对分词进行各种处理,只需要按我们的处理顺序一层层包裹即可(每一层完成特定的处理)。不同的处理需要,只用不同的包裹顺序、层数。

那通过不同的Tokenizer /TokenFilter处理后的属性信息怎么保存呢,比如位置,次数,偏移量等。

主要通过以下几个类:

AttributeSource、Attribute、AttributeImpl、AttributeFactory

AttributeSource 负责存放Attribute对象,他提供对应的存、取方法Attribute 对象中则可以存储一个或多个属性信息AttributeFactory 则是负责创建Attribute对象的工厂,在TokenStream中默认使用了 AttributeFactory.getStaticImplementation 我们不需要提供,遵守它的规则即可。

TokenStream继承AttributeSource,提供了添加属性,获取属性的方法,

某个TokenStream实现中如要存储分项属性,通过AttributeSource的两个add方法之一,往AttributeSource中加入属性对象,如:

MyCharAttribute charAttr = this.addAttribute(MyCharAttribute.class);

在TokenStream中,我们用自己实现的Attribute,默认的工厂。当我们调用这个add方法时,它怎么知道实现类是哪个?这里有一定规则要遵守:

1、自定义的属性接口 MyAttribute 继承 Attribute

2、自定义的属性实现类必须继承AttributeImpl,实现自定义的接口MyAttribute

3、自定义的属性实现类必须提供无参构造方法

4、为了让默认工厂能根据自定义接口找到实现类,实现类名需为 接口名+Impl 。

我们在应用中并不直接使用分词器,只需为索引引擎和搜索引擎创建我们想要的分词器对象。但我们在选择分词器时,会需要测试分词器的效果,就需要知道如何使用得到的分词处理器TokenStream,使用步骤:

1、从tokenStream获得你想要获得分项属性对象(信息是存放在属性对象中的)

2、调用 tokenStream 的 reset() 方法,进行重置。因为tokenStream是重复利用的。

3、循环调用tokenStream的incrementToken(),一个一个分词,直到它返回false

4、在循环中取出每个分项你想要的属性值。

5、调用tokenStream的end(),执行任务需要的结束处理。

6、调用tokenStream的close()方法,释放占有的资源。

String text = "An AttributeSource contains a list of different AttributeImpls, and methods to add and get them. ";   try (Analyzer ana = new MyWhitespaceAnalyzer();         TokenStream ts = ana.tokenStream("aa", text);) {      MyCharAttribute ca = ts.getAttribute(MyCharAttribute.class);      ts.reset();      while (ts.incrementToken()) {         System.out.print(ca.getString() + "|");      }      ts.end();      System.out.println();   } catch (IOException e) {      e.printStackTrace();   }}

简单实现一个我们自己的Analyzer:

Tokenizer:

实现对英文按空白字符进行分词。

需要记录的属性信息有: 词

TokenFilter:

要进行的处理:转为小写

public class MyWhitespaceAnalyzer extends Analyzer {	@Override	protected TokenStreamComponents createComponents(String fieldName) {		Tokenizer source = new MyWhitespaceTokenizer();		TokenStream filter = new MyLowerCaseTokenFilter(source);		return new TokenStreamComponents(source, filter);	}	static class MyWhitespaceTokenizer extends Tokenizer {		// 需要记录的属性		// 词		MyCharAttribute charAttr = this.addAttribute(MyCharAttribute.class);		char[] buffer = new char[255];		int length = 0;		int c;		@Override		public boolean incrementToken() throws IOException {			// 清除所有的词项属性			clearAttributes();			length = 0;			while (true) {				c = this.input.read();				if (c == -1) {					if (length > 0) {						// 复制到charAttr						this.charAttr.setChars(buffer, length);						return true;					} else {						return false;					}				}				if (Character.isWhitespace(c)) {					if (length > 0) {						// 复制到charAttr						this.charAttr.setChars(buffer, length);						return true;					}				}				buffer[length++] = (char) c;			}		}	}	public static class MyLowerCaseTokenFilter extends TokenFilter {		public MyLowerCaseTokenFilter(TokenStream input) {			super(input);		}		MyCharAttribute charAttr = this.addAttribute(MyCharAttribute.class);		@Override		public boolean incrementToken() throws IOException {			boolean res = this.input.incrementToken();			if (res) {				char[] chars = charAttr.getChars();				int length = charAttr.getLength();				if (length > 0) {					for (int i = 0; i < length; i++) {						chars[i] = Character.toLowerCase(chars[i]);					}				}			}			return res;		}	}	public static interface MyCharAttribute extends Attribute {		void setChars(char[] buffer, int length);		char[] getChars();		int getLength();		String getString();	}	public static class MyCharAttributeImpl extends AttributeImpl			implements MyCharAttribute {		private char[] chatTerm = new char[255];		private int length = 0;		@Override		public void setChars(char[] buffer, int length) {			this.length = length;			if (length > 0) {				System.arraycopy(buffer, 0, this.chatTerm, 0, length);			}		}		public char[] getChars() {			return this.chatTerm;		}		public int getLength() {			return this.length;		}		@Override		public String getString() {			if (this.length > 0) {				return new String(this.chatTerm, 0, length);			}			return null;		}		@Override		public void clear() {			this.length = 0;		}		@Override		public void reflectWith(AttributeReflector reflector) {		}		@Override		public void copyTo(AttributeImpl target) {		}	}	public static void main(String[] args) {		String text = "An AttributeSource contains a list of different AttributeImpls, and methods to add and get them. ";		try (Analyzer ana = new MyWhitespaceAnalyzer();				TokenStream ts = ana.tokenStream("aa", text);) {			MyCharAttribute ca = ts.getAttribute(MyCharAttribute.class);			ts.reset();			while (ts.incrementToken()) {				System.out.print(ca.getString() + "|");			}			ts.end();			System.out.println();		} catch (IOException e) {			e.printStackTrace();		}	}}

标签: #分词搜索商品代码