2006年5月29日
反向索引:
正向索引(草稿,不完全,因为收到field info的影响,不同的field存储内容不同,且fieldInfo的有些信息,TOKENIZED BINARY COMPRESSED也是保存在.fdt的每个document相关段的bits中,而不是.fnm中):
Lucene在1.9版本的时候就已经加入了对GCJ的支持,利用GCJ编译Lucene,并且使用新的GCJIndexInput.java读写文件系统,
直接调用操作系统级别的native方法,相信读写性能能够极大得提高啊。
具体代码可见Lucene的gcj目录,编译使用ant gcj
说明见Similarity.java的javadoc信息:
算法请参考javadoc的,它使用的是Vector Space Model (VSM) of Information Retrieval。
针对一条查询语句q(query),一个d(document)的得分公式
其中,
tf(t in d) 表示某个term的出现频率,定义了term t出现在当前地document d的次数。 那些query中给定地term,如果出现越多次的,得分越高 。它在默认实现DefaultSimilarity的公式为
idf(t) 表示反向文档频率。这个参数表示docFreq(term t一共在多少个文档中出现)的反向影响值。它意味着在越少文档中出现的terms贡献越高地分数。它在默认实现DefaultSimilarity的公式为:
idf(t) = |
1 + log ( |
numDocs |
––––––––– |
docFreq+1 |
|
) |
coord(q,d) 是一个基于在该文档中出现了多少个query中的terms的得分 因素。文档中出现的query中的terms数量/query总共多少个query数量。典型的,一个文档包含越多地query中的terms会得到更高地分。This is a search time factor computed in coord(q,d) by the Similarity in effect at search time.
queryNorm(q) 是一个标准化参数,它是用来区分比较不同queries时的因素,这个因素不影响document ranking (因为所有的ranked document都会乘以相同的值),但是不同地queries(或这不同地indexes中)它会得到不同的可用于比较的值。This is a search time factor computed by the Similarity in effect at search time. 它在默认实现DefaultSimilarity的公式为:
其中的sumOfSquaredWeights(of the query terms)是根据the query Weight object计算出来的. For example, a boolean query computes this value as:
t.getBoost() 是一个term t在query q中的search time boost, 它是在the query text (see query syntax)中指定的, 或者被应用程序直接调用 setBoost() 设置的. 注意,这儿没有直接的API去访问在 a multi term query的一个term的boost值,但是multi terms会以multi TermQuery objects在一个query中被表示,因此the boost of a term in the query可以使用子query的 getBoost() 反问到.
norm(t,d) 封装(encapsulates)了一些(indexing time)的boost和length factors: ???这个参数之和field中tokens的数量有关,和term本身无关???
Document boost - set by calling doc.setBoost() before adding the document to the index.
Field boost - set by calling field.setBoost() before adding the field to a document.
lengthNorm(field) -。当文档被加入到索引时计算,,和document的field 中的tokens的数量有关,因此,一个比较短的fields贡献更高的分数。LengthNorm is computed by the Similarity class in effect at indexing. DefaultSimilarity中的实现为(float)(1.0 / Math.sqrt(numTerms));
当一个文档被加入索引时,上述因素会被相乘。如果文档有多个fields同名,他们的boosts数值会被多次相乘。
但是 ,计算出的norm数值在存储时是使用一个a single byte编码的。search时,这个norm byte从index directory读取,并且被解码回float。这个编码/解码算法会产生精度丢失。 - it is not guaranteed that decode(encode(x)) = x. For instance, decode(encode(0.89)) = 0.75. Also notice that search time is too late to modify this norm part of scoring, e.g. by using a different Similarity for search.
Lucene的索引文件,会包含很多个segments文件,每个segment中包含多个documents文件,一个segment中会有完整的正向索引和反向索引。
在搜索时,Lucene会遍历这些segments,以segments为基本单位独立搜索每个segments文件,而后再把搜索结果合并。
建立索引文件的过程,实际就是把documents文件一个个加入索引中,Lucene的做法是最开始为每个新加入的document独立生成一个segment,放在内存中。而后,当内存中segments数量到达一个阙值时,合并这些segments,新生成一个segment加入文件系统的segments列表中。
而当文件系统的segments数量过多时,势必影响搜索效率,因此需要不断合并这些segments文件。
那么Lucene的合并策略是什么?如何保证合适的segments数量呢?
其实Lucene有两套基本的策略:
第一种策略实现代码位于IndexWriter#optimize()函数,其实就是把所有segments文件合并成一个文件。合并的算法是递归合并列表最后的mergeFactor个segments文件直到合并成一个文件。这儿mergeFactor是Lucene的一个参数。
btw: 从算法细节上看,其实我不是喜欢这段实现,因为列表的最后mergeFactor个文件内容实际被扫描了segmens_count/mergeFactor次。如果分段归纳合并的方式不知道是否更好,每个segment文件内容都将被扫描 ceil(Log_mergeFactor(segmens_count)) 或ceil(Log_mergeFactor(segmens_count)) +1次,是否更好?
第二种策略实现代码位于IndexWriter#maybeMergeSegments()函数中,这个代码就复杂了,它的基本策略就是要求确保合并后两个公式的成立:
假定segments是个有序列表,B表示maxBufferedDocs,f(n)=ceil(log_M(ceil(n/B))),M表示mergeFactor,这儿maxBufferedDocs和mergeFactor是两个参数
1. 如果第i个segment的documents数量为x,第i+1个segment的documents数量为y,那么f(x)>f(y)一定成立
2. f(n)值相同的segments的数量不得超过M。
那么maybeMergeSegments()函数是如何确保这两个公式成立的呢?
1.首先,从代码:
protected final void maybeFlushRamSegments() throws IOException {
// A flush is triggered if enough new documents are buffered or
// if enough delete terms are buffered
if (ramSegmentInfos.size() >= minMergeDocs
|| numBufferedDeleteTerms >= maxBufferedDeleteTerms) {
flushRamSegments();
}
}
这儿minMergeDocs=maxBufferedDocs, 因此可以看出,当内存中缓存的segments被合并写回磁盘时生成的segment的document count等于或小于maxBufferedDocs(即minMergeDocs)。
补充:因为maybeMergeSegments()运行在同步代码中,因此只要ramSegmentInfos.size==minMergerDocs(即maxBufferedDocs)就会写回磁盘,因此应该不存在ramSegmentInfos.size>maxBufferedDocs才写回的情况。而且,但如果是这种情况,因为合并后的segment文件的document count过大,后面的maybeMergeSegments()将不做合并处理直接退出,上述公式就可能不成立,那么算法将有错。
----
2.
2.1 因此maybeMergeSegments()第一次执行时,所有segments的document count都小于等于maxBufferedDocs。此时,从i=0开始,合并i~i+mergeFactor-1个文件,如果合并后的doc count>maxBufferedDocs时,保留第i个segment,否则继续合并改变后的i~i+mergeFactor-1,直到doc count>maxBufferedDocs或所有segments文件个数已经<mergeFactor了。经过这样一轮的合并,除了最后<mergeFactor个的doc counts<=maxBufferedDocs文件外,其它文件的doc counts一定都>maxBufferedDocs并<maxBufferedDocs*mergeFactor了。
2.2 这时,不理会最后<mergeFactor个doc count<maxBufferedDocs的文件,而后按2.1的同理规则,合并之前的文件,让这些文件的最后<mergerFactor个segment符合 maxBufferedDocs<doc counts<=maxBufferedDocs*mergeFactor,之前的segment文件都符合maxBufferedDocs*mergeFactor<doc counts<=maxBufferedDocs*mergeFactor^2
2.3 重复2.2,最后得到的列表就会满足上述两等式的成立
---
3
之后,每次从内存缓存中flush出一个新的segment时,也就是往这个segments列表的最后增加一个doc_count<=maxBufferedDocs的文件,同样执行上述步骤2进行合并,能够始终保证上述两公式的成立。
----
4
4.1
IndexWriter#addIndexesNoOptimize同样借鉴使用了maybeMergeSegments()算法,区别此时,实际是已有一个符合两公式的segments序列T,在T之后追加上随意顺序的segments序列S。这时,我们先找到S中doc count值最大的那个segment,计算出它属于的区间f(x),使得maxBufferedDocs*mergeFactor^x<doc counts<=maxBufferedDocs*mergeFactor^(x+1),而后按2.2的算法合并出除了最后<mergerFactor个segments外, 之前所有segments都符合 a. doc count>maxBufferedDocs*mergeFactor^(x+1) b.符合上述2等式。
btw: 因为这儿调用IndexWriter#addIndexesNoOptimize传入的参数是maxBufferedDocs*mergeFactor^(x+1),因为S所有segment的doc count都一定小于maxBufferedDocs*mergeFactor^(x+1),因此S的所有元素都会参与收缩合并。
4.2 将最后<mergerFactor个doc count<maxBufferedDocs*mergeFactor^(x+1)的segments合并,如果合并后的segment的doc count大于maxBufferedDocs*mergeFactor^(x+1),就继续2.2步骤,使得整个队列符合上述两公式
-----
上述两种策略,最终确保了Lucene中的segments不会太多,确保效率。
BTW:实际上,如果documents太多时,Lucene还支持把docuements分成几个组,每个组用独立的进程或电脑进行索引,而后再多个目录的索引合并起来,具体可参考IndexWriter#addIndexesNoOptimize和IndexWriter#addIndexes函数
FieldSelectorResult:枚举,分别为
LOAD Document#getFieldable和Document#getField不会返回null
LAZY_LOAD :Lazy的Field意味着在搜索结果里这个Field的值缺省是不读取的,只有当你真正对这个Field取值的时候才会去取。所以如果你要对它取值,你得保证IndexReader还没有close。 Document#getField不能使用,只能使用Document#getFieldable
NO_LOAD Document#getField和Document#getFieldable都返回null,Document#add不被调用。
LOAD_AND_BREAK 类似LOAD,Document#getField和Document#getFieldable都可用,但返回后就结束,Document可能没有完整的field的Set,参考LoadFirstFieldSelector 。
LOAD_FOR_MERGE 类似LOAD,但不压缩任何数据。只被SegmentMerger的一个FieldSelector匿名内嵌实现类使用。Document#getField和Document#getFieldable可返回null.
SIZE 返回Field的size而不是value. Size表示存储这个field需要的bytes數,string数值使用2*chars。size被存储为a binary value,表现为as an int in a byte[],with the higher order byte first in [0]。
SIZE_AND_BREAK 类似SIZE,但立刻break from the field loading loop, i.e. stop loading further fields, after the size is loaded
======================================
Field中三大enum: Store Index和TermVector:
------------------------------------
Store.COMPRESS Store the original field value in the index in a compressed form. This is useful for long documents and for binary valued fields.压缩存储;
Store.YES Store the original field value in the index. This is useful for short texts like a document's title which should be displayed with the results. The value is stored in its original form, i.e. no analyzer is used before it is stored. 索引文件本来只存储索引数据, 此设计将原文内容直接也存储在索引文件中,如文档的标题。
Store.NO Do not store the field value in the index. 原文不存储在索引文件中,搜索结果命中后,再根据其他附加属性如文件的Path,数据库的主键等,重新连接打开原文,适合原文内容较大的情况。
决定了Field对象的 this.isStored 和 this.isCompressed
------------------------------------
Index.NO Do not index the field value. This field can thus not be searched, but one can still access its contents provided it is Field.Store stored. 不进行索引,存放不能被搜索的内容如文档的一些附加属性如文档类型, URL等。
Index.TOKENIZED Index the field's value so it can be searched. An Analyzer will be used to tokenize and possibly further normalize the text before its terms will be stored in the index. This is useful for common text. 分词索引
Index.UN_TOKENIZED Index the field's value without using an Analyzer, so it can be searched. As no analyzer is used the value will be stored as a single term. This is useful for unique Ids like product numbers. 不分词进行索引,如作者名,日期等,Rod Johnson本身为一单词,不再需要分词。
Index.NO_NORMS 不分词,建索引。norms是什么???字段值???。但是Field的值不像通常那样被保存,而是只取一个byte,这样节约存储空间???? Index the field's value without an Analyzer, and disable the storing of norms. No norms means that index-time boosting and field length normalization will be disabled. The benefit is less memory usage as norms take up one byte per indexed field for every document in the index.Note that once you index a given field <i>with</i> norms enabled, disabling norms will have no effect. In other words, for NO_NORMS to have the above described effect on a field, all instances of that field must be indexed with NO_NORMS from the beginning.
决定了Field对象的 this.isIndexed this.isTokenized this.omitNorms
------------------------------------
Lucene 1.4.3新增的:
TermVector.NO Do not store term vectors. 不保存term vectors
TermVector.YES Store the term vectors of each document. A term vector is a list of the document's terms and their number of occurences in that document. 保存term vectors。
TermVector.WITH_POSITIONS Store the term vector + token position information 保存term vectors。(保存值和token位置信息)
TermVector.WITH_OFFSETS Store the term vector + Token offset information
TermVector.WITH_POSITIONS_OFFSETS Store the term vector + Token position and offset information 保存term vectors。(保存值和Token的offset)
决定了Field对象的this.storeTermVector this.storePositionWithTermVector this.storeOffsetWithTermVector
以下内容均为转载,url见具体链接:
最常见的四个Analyzer,说明: http://windshowzbf.bokee.com/3016397.html
WhitespaceAnalyzer 仅仅是去除空格,对字符没有lowcase化,不支持中文
SimpleAnalyzer :功能强于WhitespaceAnalyzer,将除去letter之外的符号全部过滤掉,并且将所有的字符lowcase化,不支持中文
StopAnalyzer: StopAnalyzer的功能超越了SimpleAnalyzer,在SimpleAnalyzer的基础上.增加了去除StopWords的功能,不支持中文.类中使用一个static数组保存了ENGLISH_STOP_WORDS, 太常见不index的words
StandardAnalyzer: 用Javacc定义的一套EBNF,严禁的语法。有人说英文的处理能力同于StopAnalyzer.支持中文采用的方法为单字切分。未仔细比较,不敢确定。
其他的扩展:
ChineseAnalyzer:来自于Lucene的sand box.性能类似于StandardAnalyzer,缺点是不支持中英文混和分词.
CJKAnalyzer:chedong写的CJKAnalyzer的功能在英文处理上的功能和StandardAnalyzer相同.但是在汉语的分词上,不能过滤掉标点符号,即使用二元切分
TjuChineseAnalyzer: http://windshowzbf.bokee.com/3016397.html写的,功能最为强大.TjuChineseAnlyzer的功能相当强大,在中文分词方面由于其调用的为ICTCLAS的java接口.所以其在中文方面性能上同与ICTCLAS.其在英文分词上采用了Lucene的StopAnalyzer,可以去除 stopWords,而且可以不区分大小写,过滤掉各类标点符号.
例子:
http://www.langtech.org.cn/index.php/uid-5080-action-viewspace-itemid-68, 还有简单的代码分析
Analyzing "The quick brown fox jumped over the lazy dogs"
WhitespaceAnalyzer:
[The] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
SimpleAnalyzer:
[the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
StopAnalyzer:
[quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
StandardAnalyzer:
[quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
Analyzing "XY&Z Corporation - xyz@example.com"
WhitespaceAnalyzer:
[XY&Z] [Corporation] [-] [xyz@example.com]
SimpleAnalyzer:
[xy] [z] [corporation] [xyz] [example] [com]
StopAnalyzer:
[xy] [z] [corporation] [xyz] [example] [com]
StandardAnalyzer:
[xy&z] [corporation] [xyz@example.com]
参考连接:
http://macrochen.blogdriver.com/macrochen/1167942.html
http://macrochen.blogdriver.com/macrochen/1153507.html
http://my.dmresearch.net/bbs/viewthread.php?tid=8318
http://windshowzbf.bokee.com/3016397.html
推荐
http://gceclub.sun.com.cn/developer/technicalArticles/Intl/Supplementary/index_zh_CN.html
http://www.linuxpk.com/3821.html
=======================================
BMP的解释:
http://zh.wikipedia.org/w/index.php?title=%E5%9F%BA%E6%9C%AC%E5%A4%9A%E6%96%87%E7%A8%AE%E5%B9%B3%E9%9D%A2&variant=zh-cn
http://zh.wikipedia.org/w/index.php?title=%E8%BE%85%E5%8A%A9%E5%B9%B3%E9%9D%A2&variant=zh-cn#.E7.AC.AC.E4.B8.80.E8.BC.94.E5.8A.A9.E5.B9.B3.E9.9D.A2
1个BMP和16个辅助plane,需要21个bits.
======================================
ISO-10646与Unicode关系
http://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%AD%97%E7%AC%A6%E9%9B%86
ISO-10646术语
|
Unicode术语
|
UCS-2 |
BMP UTF-16 |
UCS-4 |
UTF-32 |
|
|
注意:UTF-16可看成是UCS-2的 父集。在沒有辅助平面字符前,UTF-16與UCS-2所指的是同一的意思。但當引入辅助平面字符後,就只稱為UTF-16了,因为我们会使用2个UTF-16,也就似乎4bytes保存一个辅助平面字符。現在若有軟件聲稱自己支援UCS-2編碼,那其實是暗指它不能支援辅助平面字符的委婉語。
======================================
UTF-8要完整表达unicode需要4bytes,表达BMP需要3bytes,见 http://en.wikipedia.org/wiki/UTF-8,注意“The range D800-DFFF is disallowed by Unicode. The encoding scheme reliably transforms values in that range, but they are not valid scalar values in Unicode. See Table 3-7 in the Unicode 5.0 standard. ”
======================================
BOM Byte Order Mark,在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。
字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"(也就是U+FEFF)的UTF-8编码是EF BB BF(就是11101111,10111011,10111111)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。
除了FEFF,英文wiki http://en.wikipedia.org/wiki/UTF-8还解释说明了一些目前不会出现在utf-8字节流中的byte值。
=========================================
Java
http://www.jorendorff.com/articles/unicode/java.html
http://gceclub.sun.com.cn/developer/technicalArticles/Intl/Supplementary/index_zh_CN.html 完美解释java中的unicode。另外提到java中utf-8其实有两种格式,分别是标准utf-8和改良utf-8。对于文本输入, Java 2 SDK 提供用于接受“\Uxxxxxx”格式字符串的代码点输入方法,这里大写的“U”表示转义序列包含六个十六进制数字,因此允许使用增补字符。小写的“u”表示转义序列“\uxxxx”的原始格式。
http://dlog.cn/html/diary/showlog.vm?sid=2&cat_id=-1&log_id=557 介绍了String的JDK5新增方法
http://blog.csdn.net/qinysong/archive/2006/09/05/1179480.aspx 连着三篇用实例说明,语言比较乱,说的也不尽正确,但他用了做试验的java代码有点意思,能帮助思考代码中一些tricky的现象。
http://topic.csdn.net/u/20070928/22/5207088c-c47d-43ed-8416-26f850631cff.html 有一些回答,
http://topic.csdn.net/u/20070515/14/57af3319-28de-4851-b4cf-db65b2ead01c.html 有些试验代码,价值不大
http://www.w3china.org/blog/more.asp?name=hongrui&id=24817 有些java实例代码,没细看。
另:
Java 1.0 supports Unicode version 1.1.
Java 1.1 onwards supports Unicode version 2.0.
J2SE 1.4中的字符处理是基于Unicode 3.0标准的。
J2SE v 1.5 supports Unicode 4.0 character set.
而:
Unicode 3.0:1999年九月;涵蓋了來自ISO 10646-1的十六位元通用字元集(UCS)基本多文種平面(Basic Multilingual Plane)
Unicode 3.1:2001年三月;新增從ISO 10646-2定義的輔助平面(Supplementary Planes)
所以:
代码点在U+0000到U+FFFF之间的就用\u0000到\uffff表示
U+10000到U+1FFFF之间的用 \ud800到\udbff中的作为第一个单元, 用\udc00到\udfff作为第二单元,组合起来表示
char这个概念就是指\u0000到\uffff,这是占两个字节
其余的用code point这个概念
JDK 1.5 以上支持 Unicode 4.0,也就是 Unicode 的范围是 U+0000~U+10FFFF,
超过 U+FFFF 的字符采用代码点(也就是 int 类型的数据)来表示,具体的可以
参考一下下面这个链接的文章《Java 平台中的增补字符》,对此作了很详细的介
绍。 http://gceclub.sun.com.cn/developer/technicalArticles/Intl/Supplementary/index_zh_CN.html
================================
http://www.blogjava.net/tim-wu/archive/2007/09/12/144550.html
================================
U-00000000 - U-0000007F: |
0xxxxxxx |
U-00000080 - U-000007FF: |
110xxxxx 10xxxxxx |
U-00000800 - U-0000FFFF: |
1110xxxx 10xxxxxx 10xxxxxx |
U-00010000 - U-001FFFFF: |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-00200000 - U-03FFFFFF: |
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-04000000 - U-7FFFFFFF: |
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
但目前ISO和Unicode组织都不会规定10FFFF以上的字符
代碼範圍
十六進制 |
標量值(scalar value
二進制 |
UTF-8
二進制 / 十六進制 |
註釋 |
000000 - 00007F
128個代碼 |
00000000 00000000 0zzzzzzz |
0zzzzzzz(00-7F) |
ASCII等值範圍,位元組由零開始 |
七個z |
七個z |
000080 - 0007FF
1920個代碼 |
00000000 00000yyy yyzzzzzz |
110yyyyy(C2-DF) 10zzzzzz(80-BF) |
第一個位元組由110開始,接著的位元組由10開始 |
三個y;二個y;六個z |
五個y;六個z |
000800 - 00FFFF
63488個代碼 |
00000000 xxxxyyyy yyzzzzzz |
1110xxxx(E0-EF) 10yyyyyy 10zzzzzz |
第一個位元組由1110開始,接著的位元組由10開始 |
四個x;四個y;二個y;六個z |
四個x;六個y;六個z |
010000 - 10FFFF
1048576個代碼 |
000wwwxx xxxxyyyy yyzzzzzz |
11110www(F0-F4) 10xxxxxx 10yyyyyy 10zzzzzz |
由11110開始,接著的位元組由10開始 |
三個w;二個x;四個x;四個y;二個y;六個z |
三個w;六個x;六個y;六個z |
================================
参考:http://blog.csdn.net/qinysong/archive/2006/09/05/1179480.aspx,但该文对unicode版本说明有误,说明见上
在大约 1993 年之后开发的大多数现代编程语言都有一个特别的数据类型, 叫做 Unicode/ISO 10646-1 字符. 在 Ada95 中叫 Wide_Character, 在 Java 中叫 char.
ISO C 也详细说明了处理多字节编码和宽字符 (wide characters) 的机制, 1994 年 9 月 Amendment 1 to ISO C 发表时又加入了更多. 这些机制主要是为各类东亚编码而设计的, 它们比处理 UCS 所需的要健壮得多. UTF-8 是 ISO C 标准调用多字节字符串的编码的一个例子, wchar_t 类型可以用来存放 Unicode 字符.
代码为QueryParser.jj,语法为JavaCC实现的LL():
完整文档:http://lucene.apache.org/java/2_0_0/queryparsersyntax.html
和正则一样:
?表示0个或1个
+表示一个或多个
*表示0个或多个
以下是Token部分:
_NUM_CHAR::=["0"-"9"] //数字
_ESCAPED_CHAR::= "\\" [ "\\", "+", "-", "!", "(", ")", ":", "^", "[", "]", "\"", "{", "}", "~", "*", "?" ] > //特殊字符,
_TERM_START_CHAR ::=( ~[ " ", "\t", "\n", "\r", "+", "-", "!", "(", ")", ":", "^","[", "]", "\"", "{", "}", "~", "*", "?" ] //TERM的起始字符,除了列出的其它字符都可以
_TERM_CHAR::=( <_TERM_START_CHAR> | <_ESCAPED_CHAR> | "-" | "+" ) > //TERM可使用字符
_WHITESPACE::= ( " " | "\t" | "\n" | "\r") //空格和回车,
<DEFAULT> TOKEN:
AND::=("AND" | "&&")
OR::=("OR" | "||")
NOT::=("NOT" | "!")
PLUS::="+"
MINUS::="-"
LPAREN::="("
RPAREN::=")"
COLON::=":"
STAR::="*"
CARAT::="^" //后接Boost,原文<CARAT: "^" > : Boost,后面Boost说明什么没明白
QUOTED::="\"" (~["\""] | "\\\"")+ "\"" // 表示用"包起来的字符串,字符"开始,中间由不是"的符号或者连着的这两个符号\"组成,字符"结束,
TERM::=<_TERM_START_CHAR> (<_TERM_CHAR>)*
FUZZY_SLOP::="~" ( (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? )? //字符~开始,而后是数字.Lucene支持模糊查询,例如"roam~"或"roam~0.8",The value is between 0 and 1,算法为the Levenshtein Distance, or Edit Distance algorithm
PREFIXTERM::=(<_TERM_START_CHAR> | "*") (<_TERM_CHAR>)* "*" > //模糊查找,表示以某某开头的查询, 字符表示为"something*",前缀允许模糊符号*,中间可有字符也可没有, 结尾必须是*
WILDTERM::=(<_TERM_START_CHAR> | [ "*", "?" ]) (<_TERM_CHAR> | ( [ "*", "?" ] ))* > //类似上面,但同时支持?字符,结尾可以是字符也可以是* ?。使用[]表示or关系时,不需要使用|,只要,号分割即可
RANGEIN_START::="[" //在RangeQuery中,[或{表示了是否包含边界条件本身, 用字符表示为"[begin TO end]" 或者"{begin TO end}",后接RangeIn
RANGEEX_START::="{" //同上,后接RangeEx
<Boost> TOKEN:
NUMBER::=(<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? //后接DEFAULT, 整数或小数
<RangeIn> TOKEN:
RANGEIN_TO::="TO"
RANGEIN_END::="]" //后接DEFAULT, RangIn的结束
RANGEIN_QUOTED::= "\"" (~["\""] | "\\\"")+ "\"" //同上述QUOTED,表示用"包起来的字符串,
RANGEIN_GOOP::= (~[ " ", "]" ])+ //1个或多个不是空格和]的符号,这样就能提取出[]中的内容
<RangeEx> TOKEN :
RANGEEX_TO::="TO">
RANGEEX_END::="}" //后接DEFAULT, RangeEx的结束
RANGEEX_QUOTED::="\"" (~["\""] | "\\\"")+ "\"" //同上述QUOTED,表示用"包起来的字符串,
RANGEEX_GOOP::=(~[ " ", "}" ])+ //1个或多个不是空格和]的符号,这样就能提取出[]中的内容
<DEFAULT, RangeIn, RangeEx> SKIP : {
< <_WHITESPACE>>
} //所有空格和回车被忽略
以下为解析部分
Conjunction::=[ <AND> { ret = CONJ_AND; } | <OR> { ret = CONJ_OR; } ] //连接
Modifiers::=[ <PLUS> { ret = MOD_REQ; } | <MINUS> { ret = MOD_NOT; } | <NOT> { ret = MOD_NOT; } ] //+ - !符号
Query::=Modifiers Clause (Conjunction Modifiers Clause)*
Clause::=[(<TERM> <COLON>|<STAR> <COLON>)] //btw:代码中LOOKAHEAD[2]表示使用LL(2)
(Term|<LPAREN> Query <RPAREN> (<CARAT> <NUMBER>)?) //子句. ???????这儿语法有点,仿佛允许 *:(*:dog)这样的语法,很奇怪
Term::=(
(<TERM>|<STAR>|<PREFIXTERM>|<WILDTERM>|<NUMBER>) [<FUZZY_SLOP>] [<CARAT><NUMBER>[<FUZZY_SLOP>]}
| ( <RANGEIN_START> (<RANGEIN_GOOP>|<RANGEIN_QUOTED>) [ <RANGEIN_TO> ] (<RANGEIN_GOOP>|<RANGEIN_QUOTED> <RANGEIN_END> ) [ <CARAT> boost=<NUMBER> ] //这儿看出range必须同时有两端,不能只有有一端
| ( <RANGEEX_START> <RANGEEX_GOOP>|<RANGEEX_QUOTED> [ <RANGEEX_TO> ] <RANGEEX_GOOP>|<RANGEEX_QUOTED> <RANGEEX_END> )[ <CARAT> boost=<NUMBER> ] //在RangeQuery中,[或{表示了是否包含边界条件本身, 用字符表示为"[begin TO end]" 或者"{begin TO end}",后接RangeIn
| <QUOTED> [ <FUZZY_SLOP> ] [ <CARAT> boost=<NUMBER> ] //被""包含的内容
btw: 猜测: javacc中,如果使用[],则允许出现0次或1次
今天读了lucent中的PriorityQueue.java, 一个很巧妙的复杂度为log(n)的排序堆栈.
始终确保数组A[1...n]中:
A[i]<A[2*i] & A[i] < A[2*i +1]
很容易推论出A[1]一定是最小数值, 并且每次put()和pop()至多移动log(n)个数值
真是好久没接触算法了:)
ruby.exe提供了一个参数-r, 允许ruby在允许之前加载你指定的库
1 如果你安装了gem,那么环境变量RUBYOPT将为-rubygems,这个参数说明了ruby将提前加载ubygem.rb(注意,没有r,不是rubygem.rb,而是ubygem:)
2 这时,如果你运行 ruby -e "puts $:",可以查看到ruby查询lib库的目录顺序,其中第一个就是类似"..\ruby\site_ruby\1.8"目录
3 因此,ubygem.rb将在ruby\site_ruby\1.8\ubygems.rb位置中被ruby定位到,而ubygem.rb只有一句话require 'rubygems',这次才真正调用了rubygems.rb
4 接着rubygems.rb的最后一句require 'rubygems/custom_require'将加载custom_require.rb
5 最后custom_require.rb中替换了原先的require()函数的实现,这之后,库的加载,将遵循gem的目录约定。
记得上学时学数据库,书中说过:“数据库建模一般实现到3NF和BCNF,4NF 5NF基本没用”,造成多年对4NF和5NF置之不理。
离开学校7年后的今天,无事有把数据库的范式定义拿起来翻翻,发觉好象我对4NF和5NF的理解一直有误?
在做业务建模时,不少情况我们会尽可能地完全反映显示业务关系,以 厂 采购员 和 订单 为例:
如果业务认为订单不属于特定采购员,也即关系如下:
厂和订单为1:n的关系;
厂和采购员也为1:n的关系;
采购员与订单无关。
此时,那么我们肯定ORM简单得处理为两个关联表,这时,不正是符合了4NF么?(如果只建立一个关联表,表中三个字段厂id,订单id,采购员id,而后三个id组成联合主键,那就是符合了BCNF但不符合4NF了)
而如果关系更复杂,一个订单可以被多个采购员处理,一个订单还可以同属于多个厂共享,那么我们一定是建立三个关联表,独立记录三者间互相的关系,这不就是遵循了5NF么?
今天注册了个Google Code的项目,还是google code爽啊,够简单,
http://code.google.com/hosting/createProject 注册一下就成功,不用得着什么审核
svn速度爆快,相比sf.net就是个牛车。
其它没试,不评论,这两点就足够了。
不知道rubyforge怎么样。
final byte[] addBuffer(int size) {
byte[] buffer = new byte[size];
if (directory!=null)
synchronized (directory) { // Ensure addition of buffer and adjustment to directory size are atomic wrt directory
buffers.add(buffer);
directory.sizeInBytes += size;
sizeInBytes += size;
}
else
buffers.add(buffer);
return buffer;
}
今天读了 http://infolab.stanford.edu/~backrub/google.html 一文,发现我毕业那年(2000年)google已有如此成果。
PageRank or PR(A) can be calculated using a simple iterative algorithm, and corresponds to the principal eigenvector of the normalized link matrix of the web. Also, a PageRank for 26 million web pages can be computed in a few hours on a medium size workstation.
最酷的就是这句话了,真酷的algorithm啊,我想破头想不出头绪来达到这个效率。
呵呵,从wikipedia开始,发现很有意思的文章越来越多:)
http://en.wikipedia.org/wiki/HITS_algorithm
今天翻了util包,发现自己对float的匮乏知识,好在有伟大的wiki百科:
http://en.wikipedia.org/wiki/IEEE_754
很清楚得解释了IEEE 754规范
可惜看不懂lucene对small float的结构定义。
如果按我的理解,似乎类SmallFloat的函数byteToFloat()和byte315ToFloat()有bug,通过编写测试代码:
SmallFloat.byte315ToFloat(Byte.parseByte("01111000", 2)得到的float值为0.5f,
但按照byte315ToFloat()函数的说明,"01111000"的mantissaBits尾数长度为3, zeroExponent为15,无负数,
而01111按IEEE 754规范,应该理解为1,所以01111000表达的数值应该为1.0×2^0=1,而不是0.5f
非常之tricky。
通过读byte315ToFloat()函数实现,发现"01111000"转换为32位值0,01111110,00000000000000000000000,
该值按IEEE 754规范的确为0.5f, 其中正负位为1位,指数(exponent)位为8位,尾数(mantissa)位为23位。
而在byteToFloat()和byte315ToFloat()的实现中,我们可以常看到作者将尾数位偏移24-mantissa位,
也就是说,他理解的IEEE 754规范中尾数位不是23位而是24位。
以上仅为我的猜测,因为还没有看到byteToFloat()的具体使用环境。
而且通过测试代码,发现byte和float对应关系:
10000,000==2.0f= 1*2^1
10001,000==8.0f=1*2^3
10010,000==32.0f=1*2^5
01111,000 = 0.5f = 1*2^-1
01110,000==0.125f= 1* 2^-3
完全看不出合理的small float的结构,tricky!!!
因为工作需要,今天开始学习lucene,
因为有可能以后要深入研究full text的搜索,决定暂时不看in action,
采用最愚笨的方法,直接爬代码。凭借以前的门外汉理解一步一步爬吧,
不知道还能衍生出多少知识。
swt中,默认只有用户线程被允许访问UI图形控件和一些图形API,其他非用户线程如果直接访问这类资源时,SWTException直接被抛出。 如果真有这种需求,必须使用*.widget.Display类中的两个线程同步方法:syncExec(Runnable)和asyncExec(Runnable)。方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回,而后者则无论指定的线程是否执行都会立即返回到当前线程。
例子: Display.getCurrent().asyncExec(new Runnable() { public void run() { butt.setText("Push"); } });
以下载自某论坛(sorry,忘记了出处),关于内存释放:
Rule 1: If you created it, you dispose it. Rule 2: Disposing the parent disposes the children. (from http://www.eclipse.org/articles/swt-design-2/swt-design-2.html)
当使用构造函数来创建小窗口或图形对象时,使用完时必须用手工来除掉它。 如果没有使用构造函数就获取小窗口或图形对象,则一定不能用手工来除掉它,因为您未分配它。 如果将对小窗口或图形对象的引用传送至另一个对象,则一定要小心,仍在使用它时一定不要除掉它。与在使用图像的插件模式中所描述的规则相似。) 当用户关闭Shell时,将递归地销毁 shell 及其所有子代小窗口。在此情况下,不需要除掉小窗口本身。然而,必须释放与那些小窗口一起分配的所有图形资源。 如果创建图形对象以便在其中一个小窗口的生命周期内使用它,则在除掉小窗口时必须除掉图形对象。这可以通过向小窗口注册销毁侦听器,并在接收到销毁事件时释放图形对象来实现。
这些规则有一个例外。简单的数据对象(例如,矩形和点)不使用操作系统资源。它们没有 dispose() 方法,您也不需要释放它们。如果有疑问,则检查特定类的 javadoc。
第一种方式,zkjbeyond的文章中有很详细的说明http://www.blogjava.net/zkjbeyond/archive/2006/05/05/44676.html 简单说,就是利用Ajax读取包的.js文件后,直接使用eval()运行代码。
除此外,dojo还有另一种加载方式,用在调试阶段(例如使用firebug活Venkman调试dojo时都需要用到,不过我没试过使用MSE7或Visual Studio时是否需要使用这种加载方式)。
你在加载包前,将djConfig.debugAtAllCosts设置为true,那么包的加载方式会有所变化,采用第二种方式。例如:
djConfig.debugAtAllCosts=true; dojo.require("dojo.widget.TimBar"); dojo.hostenv.writeIncludes();
此时,dojo会使用加载brower_debug.js文件, brower_debug.js重载了函数dojo.hostenv.loadUri,这儿,不再直接使用eval()运行,而只是把需要的包push到dojo.hostenv.loadedUris数组中。实现代码:
var text = this.getText(uri, null, true); var requires = dojo.hostenv.getRequiresAndProvides(text); eval(requires.join(";")); dojo.hostenv.loadedUris.push(uri); dojo.hostenv.loadedUris[uri] = true; var delayRequires = dojo.hostenv.getDelayRequiresAndProvides(text); eval(delayRequires.join(";"));
从代码可以看出,dojo为了保证加载的顺序,使用dojo.hostenv.getRequiresAndProvides函数将提前依赖的包抠出来,用递归提前push到dojo.hostenv.loadedUris中。 而后,在代码最后,我们只要主动使用dojo.hostenv.writeIncludes();函数,就可一次性将被依赖的包利用document.write包含进来。
btw: 当然,这种方法有不少缺陷: 1、使用document.write方式加载包,会有延迟效应,会等待本页面中代码都执行完才执行包中的代码,自己编码时有点绕,影响可读性; 2、从上面代码中看出,dojo会有更多的IO操作,第一dojo已经完整读取了包代码,但是不马上执行,只是用来分析依赖关系;之后,浏览器又要加载一次包代码。 3、包绝对不允许循环引用。当然,从OO角度,不循环引用是好的方式
所以,这种方式只能用在调试期间
摘要: 常规循环引用内存泄漏和Closure内存泄漏
要了解javascript的内存泄漏问题,首先要了解的就是javascript的GC原理。
我记得原来在犀牛书《JavaScript: The Definitive
Guide》中看到过,IE使用的GC算法是计数器,因此只碰到循环 引用就会造成memory
leakage。后来一直觉得和观察到的现象很不一致,直到看到Eric的文... 阅读全文
|
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
导航
统计
- 随笔: 28
- 文章: 0
- 评论: 38
- 引用: 1
常用链接
留言簿(4)
我参与的团队
随笔档案
搜索
最新评论
阅读排行榜
评论排行榜
|
|