分词

转载自:http://blog.itpub.net/28624388/viewspace-765691/

   Analyzer通过对文本的分析来建立TokenStreams(分词数据流)。TokenStream是由一个个Token(分词组成的数据流)。所以说Analyzer就代表着一个从文本数据中抽取索引词(Term)的一种策略。TokenStream即是从Document的域(field)中或者查询条件中抽取一个个分词而组成的一个数据流。TokenSteam中是一个个的分词,而每个分词又是由一个个的属性(Attribute)组成。对于所有的分词来说,每个属性只有一个实例。这些属性都保存在AttributeSource中,而AttributeSource正是TokenStream的父类。


TokenStream的工作流程:
   1.实例化TokenStream, 添加属性到AttributeSource,或从AttributeSource中获取属性.
   2.调用reset()方法,设置stream的初始状态
   3.调用increamStoken()方法,来获取下一个分词。这个方法会被docuemnt中的每一个分词调用。所以一个有效的实现对于好的性能来说是至关重要的。
   4.调用end()方法来完成一些收尾工作
   5.调用close()方法来释放stream关联的一些资源。


AttributeSource:
   一个AttributeSource中包含着一个由不同AttributeImpl组成的列表,以及添加和获取它们的一些方法。在同一个AttributeSource实例中 每个属性只有一个单实例。AttributeSource通过AttributeFactory来创建AttributeImpl的实例。通过State来标示每个AttributeImpl的状态。
AttributeImpl类介绍
   1. AttributeImpl:一个可以往attributeSource中添加属性的基类,属性通常用来以动态的,线程安全的方式往一个流的数据源中添加数据,如tokenStream。
   2.CharTermAttributeImpl:保存Token对应的term文本
   3.FlagsAttributeImpl:在Tokenizer链中,用以在不同的节点之间传递标识信息。该类同TypeAttribute有着相似的目录但他们之间还是有所不同的,Flags可以用于不同TokenFilter之间分词(Token)信息的加密。
   4.TypeAttributeImpl:分词的词汇类型,默认值为“word”
   5.KeywordAttributeImpl:该属性用于标识一个分词(token)为关键字。对于TokenStream来说可以用此属性判断分词(Token)是否为关键字来决定是否进行修改,对于TokenFilter来说可以根据分词是否为关键字来进行跳过(skip)处理。
   6.OffsetAttributeImpl:Token分词的起始字符,结束字符偏移量
   7.PositionIncrementAttribute:它表示tokenStream中的当前token与前一个token在实际的原文本中相隔的词语数量

   8.PositionLengthAttributeImpl:Token所占用的位置个数


举例:原文本:I'm a student. these are apples     
TokenSteam: [1:I'm ] [2:a] [3:student] [4:these] [5:are ] [6:apples]
(1) TermAttribute: 表示token的字符串信息。比如"I'm"
(2) TypeAttribute: 表示token的类别信息(在上面讲到)。比如 I'm 就属于<APOSTROPHE>,有撇号的类型
(3) OffsetAttribute:表示token的首字母和尾字母在原文本中的位置。比如 I'm 的位置信息就是(0,3)
(4) PositionIncrementAttribute:这个有点特殊,它表示tokenStream中的当前token与前一个token在实际的原文本中相隔的词语数量。比如: 在tokenStream中[2:a] 的前一个token是[1: I'm ] ,它们在原文本中相隔的词语数是1,则token="a"的PositionIncrementAttribute值为1。如果token是原文本中的第一个词,则默认值为1。因此上面例子的PositionIncrementAttribute结果就全是1了。
   如果我们使用停用词表来进行过滤之后的话:TokenSteam就会变成: [1:I'm ] [2:student] [3:apples]这时student的PositionIncrementAttribute值就不会再是1,而是与[1: I'm ]在原文本中相隔词语数量=2。而apples则变成了3。

   那么这个属性有什么用呢,用处很大的。加入我们想搜索一个短语student apples(假如有这个短语)。很显然,用户是要搜索出student apples紧挨着出现的文档。这个时候我们找到了某一篇文档(比如上面例子的字符串)都含有student apples。但是由于apples的PositionIncrementAttribute值是3,说明肯定没有紧挨着。


介绍几个类:

   Tokenizer:接受Reader字符流,将Reader进行分词操作, extends TokenStream。
   TokenFilter:将分词的语汇单元进行过滤, extends TokenStream。
   TokenStream:分词器处理完毕后得到的一个流,存储了分词的各种信息。
   分词流程:Read -----> Tokenizer -----> TokenFilter.0 -----> … -----> TokenFilter.n -----> TokenStream


几种分词器介绍
   StandardAnalyzer:标准分词器,如果用来处理中文,只是将其分成单个汉字,并不存在任何语义或词性。
   StopAnalyzer:被忽略的词分词器,被忽略的词就是在分词结果中,被丢弃的字符串,如标点、空格等。
   SimpleAnalyzer:简单分词器,一句话就是一个词,遇到标点、空格等,就将其之前的内容当作一个词。
   WhitespaceAnalyzer:空格分词,这个分词技术就相当于按照空格简单的切分字符串。
   mmseg4j: 用Chih-Hao Tsai 的MMSeg算法实现的中文分词器,并实现lucene的analyzer和solr的TokenizerFactory以方便在Lucene和Solr中使用。


由于mmseg4j暂不支持lucene5.0。以下示例均为lucene3.5版本。

public class AnalyzerUtils {
	public static void displayToken(String str, Analyzer analyzer) {
		try {
			TokenStream tokenStream = analyzer.tokenStream("---",
					new StringReader(str));
			// 创建一个属性,这个属性会添加流中,随着这个TokenStream增加
			CharTermAttribute charTerm = tokenStream
					.addAttribute(CharTermAttribute.class);
			tokenStream.reset();// 必须先调用reset方法,否则会报java.lang.IllegalStateException
			while (tokenStream.incrementToken()) {
				System.out.print("" + charTerm + "|");
			}
			tokenStream.end();
			tokenStream.close();
			System.out.println();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void displayAllTokenInfo(String str, Analyzer analyzer) {
		try {
			TokenStream tokenStream = analyzer.tokenStream("content",
					new StringReader(str));
			// 位置增量的属性,存储语汇单元之间的距离,它表示tokenStream中的当前token与前一个token在实际的原文本中相隔的词语数量
			PositionIncrementAttribute pos = tokenStream
					.addAttribute(PositionIncrementAttribute.class);
			// 每个语汇单元的位置偏移量
			OffsetAttribute offset = tokenStream
					.addAttribute(OffsetAttribute.class);
			// 存储每一个语汇单元的信息(分词单元信息),保存Token对应的term文本
			CharTermAttribute charTerm = tokenStream
					.addAttribute(CharTermAttribute.class);
			// 使用的分词器的类型信息,分词的词汇类型,默认值为“word”
			TypeAttribute type = tokenStream.addAttribute(TypeAttribute.class);
			tokenStream.reset();
			for (; tokenStream.incrementToken();) {
				System.out.print(pos.getPositionIncrement() + ": ");
				System.out.print("|" + charTerm + "|" + "---offset["
						+ offset.startOffset() + "-" + offset.endOffset()
						+ "]---type:" + type.type() + "\n");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

/**
 * 自定义停用词过滤分词器
 */
public class MyStopAnalyzer extends Analyzer {
	private Set stops;

	public MyStopAnalyzer(String[] sws) {
		// 会自动将字符串数组转换为Set
		stops = StopFilter.makeStopSet(Version.LUCENE_35, sws, true);
		// 将原有的停用词加入到现在的停用词
		stops.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
	}

	public TokenStream tokenStream(String str, Reader reader) {
		// 为这个分词器设定过滤链和Tokenizer
		return new StopFilter(Version.LUCENE_35, new LowerCaseFilter(
				Version.LUCENE_35, new LetterTokenizer(Version.LUCENE_35,
						reader)), stops);
	}

}

/**
 * 自定义同义词过滤分词器
 */
public class MySameAnalyzer extends Analyzer {

	@Override
	public TokenStream tokenStream(String arg0, Reader reader) {
		// TODO Auto-generated method stub
		Dictionary dic = Dictionary.getInstance("data/");
		return new MySameTokenFilter(new MMSegTokenizer(new MaxWordSeg(dic),
				reader));
	}

	class MySameTokenFilter extends TokenFilter {
		private CharTermAttribute charTerm = null;
		private PositionIncrementAttribute pos = null;
		private AttributeSource.State current;
		private Stack<String> sames = null;
		private Map<String, String[]> map = null;

		protected MySameTokenFilter(TokenStream input) {
			super(input);
			charTerm = this.addAttribute(CharTermAttribute.class);
			pos = this.addAttribute(PositionIncrementAttribute.class);
			sames = new Stack<String>();
			map = new HashMap<String, String[]>();
			map.put("中国", new String[] { "天朝", "大陆" });
		}

		@Override
		public boolean incrementToken() throws IOException {
			if (sames.size() > 0) {
				// 将元素出栈,并且获取这个同义词
				String str = sames.pop();
				// 还原状态
				restoreState(current);
				charTerm.setEmpty();
				charTerm.append(str);
				// 设置位置0
				pos.setPositionIncrement(0);
				return true;
			}
			
			if (!this.input.incrementToken())
				return false;

			if (addSames(charTerm.toString())) {
				// 如果有同义词将当前状态先保存
				current = captureState();
			}
			return true;
		}

		private boolean addSames(String string) {
			String[] sws = map.get(string);
			if (sws != null) {
				for (String str : sws) {
					sames.push(str);
				}
				return true;
			}
			return false;
		}
	}
}

@Test
	public void test01() {
		String txt = "this is my blog,我来自湖北,武汉";
		Analyzer a1 = new StandardAnalyzer(Version.LUCENE_35);
		Analyzer a2 = new StopAnalyzer(Version.LUCENE_35);
		Analyzer a3 = new SimpleAnalyzer(Version.LUCENE_35);
		Analyzer a4 = new WhitespaceAnalyzer(Version.LUCENE_35);
		System.out.println(AnalyzerUtils.class.getResource("/"));
		Analyzer a5 = new MMSegAnalyzer(new File("E:/sina_workspace/Lucene/data"));
		AnalyzerUtils.displayToken(txt, a1);
		AnalyzerUtils.displayToken(txt, a2);
		AnalyzerUtils.displayToken(txt, a3);
		AnalyzerUtils.displayToken(txt, a4);
		AnalyzerUtils.displayToken(txt, a5);
	}
结果:

my|blog|我|来|自|湖|北|武|汉|
my|blog|我来自湖北|武汉|
this|is|my|blog|我来自湖北|武汉|
this|is|my|blog,我来自湖北,武汉|
this|is|my|blog|我|来自|湖北|武汉|

@Test
	public void test02() {
		String txt = "this is my blog,我来自湖北,武汉";
		Analyzer a1 = new StandardAnalyzer(Version.LUCENE_35);
		Analyzer a2 = new StopAnalyzer(Version.LUCENE_35);
		Analyzer a3 = new SimpleAnalyzer(Version.LUCENE_35);
		Analyzer a4 = new WhitespaceAnalyzer(Version.LUCENE_35);
		System.out.println(AnalyzerUtils.class.getResource("/"));
		Analyzer a5 = new MMSegAnalyzer(new File("data/"));
		AnalyzerUtils.displayAllTokenInfo(txt, a1);
		System.out.println("***************************");
		AnalyzerUtils.displayAllTokenInfo(txt, a2);
		System.out.println("***************************");
		AnalyzerUtils.displayAllTokenInfo(txt, a3);
		System.out.println("***************************");
		AnalyzerUtils.displayAllTokenInfo(txt, a4);
		System.out.println("***************************");
		AnalyzerUtils.displayAllTokenInfo(txt, a5);
	}
结果:

3: |my|---offset[8-10]---type:<ALPHANUM>
1: |blog|---offset[11-15]---type:<ALPHANUM>
1: |我|---offset[16-17]---type:<IDEOGRAPHIC>
1: |来|---offset[17-18]---type:<IDEOGRAPHIC>
1: |自|---offset[18-19]---type:<IDEOGRAPHIC>
1: |湖|---offset[19-20]---type:<IDEOGRAPHIC>
1: |北|---offset[20-21]---type:<IDEOGRAPHIC>
1: |武|---offset[22-23]---type:<IDEOGRAPHIC>
1: |汉|---offset[23-24]---type:<IDEOGRAPHIC>
***************************
3: |my|---offset[8-10]---type:word
1: |blog|---offset[11-15]---type:word
1: |我来自湖北|---offset[16-21]---type:word
1: |武汉|---offset[22-24]---type:word
***************************
1: |this|---offset[0-4]---type:word
1: |is|---offset[5-7]---type:word
1: |my|---offset[8-10]---type:word
1: |blog|---offset[11-15]---type:word
1: |我来自湖北|---offset[16-21]---type:word
1: |武汉|---offset[22-24]---type:word
***************************
1: |this|---offset[0-4]---type:word
1: |is|---offset[5-7]---type:word
1: |my|---offset[8-10]---type:word
1: |blog,我来自湖北,武汉|---offset[11-24]---type:word
***************************
1: |this|---offset[0-4]---type:letter
1: |is|---offset[5-7]---type:letter
1: |my|---offset[8-10]---type:letter
1: |blog|---offset[11-15]---type:letter
1: |我|---offset[16-17]---type:word
1: |来自|---offset[17-19]---type:word
1: |湖北|---offset[19-21]---type:word
1: |武汉|---offset[22-24]---type:word

@Test
	public void test03() {
		Analyzer a1 = new MyStopAnalyzer(new String[] { "I", "you" });
		System.out.println(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
		// 原停用词
		// [but, be, with, such, then, for, no, will, not, are, and, their, if,
		// this, on, into, a, or, there, in, that, they,
		// was, is, it, an, the, as, at, these, by, to, of]
		String txt = "how are you thank you, I hate you";
		AnalyzerUtils.displayToken(txt, a1);
	}
结果:

[but, be, with, such, then, for, no, will, not, are, and, their, if, this, on, into, a, or, there, i
n, that, they, was, is, it, an, the, as, at, these, by, to, of]
how|thank|hate|


@Test
	public void test04() {
		try {
			Analyzer a2 = new MySameAnalyzer();
			String txt = "this is my blog,我来自中国湖北,武汉";
			Directory dir = new RAMDirectory();
			IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(
					Version.LUCENE_35, a2));
			Document doc = new Document();
			doc.add(new Field("content", txt, Field.Store.YES,
					Field.Index.ANALYZED));
			writer.addDocument(doc);
			writer.close();
			IndexSearcher searcher = new IndexSearcher(IndexReader.open(dir));
			TopDocs tds = searcher.search(new TermQuery(
					new Term("content", "天朝")), 10);
			Document d = searcher.doc(tds.scoreDocs[0].doc);
			System.out.println(d.get("content"));
			AnalyzerUtils.displayAllTokenInfo(txt, a2);
		} catch (CorruptIndexException e) {
			e.printStackTrace();
		} catch (LockObtainFailedException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
结果:

this is my blog,我来自中国湖北,武汉
----------1
1: |this|---offset[0-4]---type:letter
this----------1
1: |is|---offset[5-7]---type:letter
is----------1
1: |my|---offset[8-10]---type:letter
my----------1
1: |blog|---offset[11-15]---type:letter
blog----------1
1: |我|---offset[16-17]---type:word
我----------1
1: |来自|---offset[17-19]---type:word
来自----------1
1: |中国|---offset[19-21]---type:word
0: |大陆|---offset[19-21]---type:word
0: |天朝|---offset[19-21]---type:word
天朝----------0
1: |湖北|---offset[21-23]---type:word
湖北----------1
1: |武汉|---offset[24-26]---type:word
武汉----------1











你可能感兴趣的:(分词)