org.apache.lucene.document这个包当然是关于lucene索引的数据结构单元——document。这个包里面的类不多,就是围绕Document和Field两个概念展开的。这两个东东我在前一篇贴过定义了,就不多说。 Document:Document类看着方法挺多,说到底就是干了一件事:存储Field。Document其实就是一个Field的List,大部分方法只不过是为了方便地存取Field而设的。这从Document的成员变量就可以看出来了,一共就俩:List fields = new Vector();
private float boost = 1.0f;这 里的fields是一个Vector,应该是为了同步的需要。Vector的代码里充斥了synchronized。。。其实要同步的话还有一个办法,就 是Collections.sychronizedList方法。不过本质是一样的,还是synchronized。这个synchronized一来, 不论读写统统给我锁住,实在是太霸道。现在还不清楚这个fields的读写操作比例如何,如果读多并且多线程的话,搞个读写锁应该好点吧?不知道为啥在 java的concurrent包里面没有一个这样的list实现,只找到一个CopyOnWriteList。这个可以研究一下,如果没问题Doug Lea大牛应该不会漏了这个东东吧。。。 我刚才之所以说“大部分方法”就是因为还有 boost 的set/get方法。boost在搜索中最后反映出来的应该是结果的优先级,它在其它的地方有更多的设置。在Document类里的这个boost其实是对这个Document里面所有的Field的一个缺省设置。
要注意的一点是:这个值不是直接存储在索引的Document里面的,而是以乘积的形式对这个Document里面的每个Field作出贡献。所以这个值只在把Document写入索引以前有效。假如你建好索引以后,通过搜索搜出一个Document,然后getBoost()得到它的boost值,这个值已经不是前面你写入索引时候的那个值了。 Field:Field由三个类主打:Fieldable,AbstractField,Field
当然了,有AbstractField当然还会有别的Field实现,不过不在这个包里面,先不管。Fieldable定义了Field的所有行为。有一大陀的属性,大多挺直观的。isLazy()值得说一下。Lazy的Field意味着在搜索结果里这个Field的值缺省是不读取的,只有当你真正对这个Field取值的时候才会去取。所以如果你要对它取值,你得保证IndexReader还没有close。get/set Boost和Document里的意思差不多,无非是for field的,它也是不在索引里直接存储的。不太确定的是get/set OmitNorms。注释上面这么写的:/** Expert:
*
* If set, omit normalization factors associated with this indexed field.
* This effectively disables indexing boosts and length normalization for this field.
*/在Code 里一顿乱搜以后,指向了org.apache.lucene.search.Similarity#lengthNorm(String,int)这个方 法。Similarity类ms代表搜索结果相关性。lengthNorm的作用是根据一个Field里面term的总数来计算这个Field的 normalization value。这个值用来对Field的boost进行修正:DocumentWriter#writeNorms(String segment){...if(fi.isIndexed &&!fi.omitNorms){
float norm = fieldBoosts[n] * similarity.lengthNorm(fi.name, fieldLengths[n]);...}...lucene认为较长的Field里面的匹配相对精度较低。相反假如说短短几个词里面就有匹配,那说明相关度较高。所以缺省的DefaultSimilarity是这样实现的://numTerms代表这个Field里面的term总数public float lengthNorm(String fieldName, int numTerms) {
return (float)(1.0 / Math.sqrt(numTerms));
}Similarity里面还有几个xxxNorm的方法,应该是类似作用。不过这个扯远了,扯到后面的东东了。回到get/set OmitNorms,可以看出这个参数可以控制这个参数修正的停用或启用。后面应该还会读到关于Norm的内容,到时再仔细分析。 Fieldable定义了四种可能的取值方法:string,binary,reader,tokenStream。通过它们你可以得到这个Field所代表的真正数据的值。这四个取值方法里面
有且只有一个 的返回值不是null。 AbstractField 实现了Fieldable接口,提供了一些缺省的实现代码。而Field类又继承了AbstractField。在Field类里,主要的代码是构造函数 和三个public static final class:Store,Index,TermVector。这三个类都是继承自org.apache.lucene.util.Parameter。 Parameter类其实是一个枚举(Enum),不过它实现了Serializable这个接口,因此是可以序列化的。所以继承了它的这三个静态类其实 代表了Field的三个参数。它们可能的值分别是:Store(Compress/Yes/No)——表示怎样存储这个Field,Index (No/Tokenized/UN_Tokenized/No_Norms)——表示怎样建索引,TermVector (No/Yes/With_Positions/With_Offsets/With_Positions_Offsets)——表示怎样存储Term。 在Field的构造函数里会用到这几个参数来进行初始化,不同的组合会给Field里面的属性设置不同的值。由于索引主要是处理字符串用的,lucene还为Field提供了两个工具类,DateTools和NumberTools,用来进行date和long型数据与string之间的互转。(互转的时候还有一些讲究,比如转换后要保持排序不变,具体可以看看code)。 FieldSelector:这是这个包里面最后的一组类的。简明的接口定义告诉我们了一切:public interface FieldSelector extends Serializable {FieldSelectorResult accept(String fieldName);
}
FieldSelectorResult又是一个可序列化的枚举类。我注意到这里transient关键字的用法。
public final class FieldSelectorResult implements Serializable {
public transient static final FieldSelectorResult LOAD = new FieldSelectorResult(0);
public transient static final FieldSelectorResult LAZY_LOAD = new FieldSelectorResult(1);
...
当 我们在序列化的时候,可能有某些成员我们不希望自动被序列化,transient可以把它们排除在序列化之外。奇怪的是static成员应当本身就不在自 动序列化之列,这里的transient好像有点多余了,难道是个双保险?前面同是可序列化枚举的Parameter类的子类们就没有使用 transient关键字。
FieldSelector的实现比较简单,就不说了。
003 | import java.io.IOException; |
005 | import org.apache.lucene.analysis.standard.StandardAnalyzer; |
006 | import org.apache.lucene.document.Document; |
007 | import org.apache.lucene.document.Field; |
008 | import org.apache.lucene.index.IndexWriter; |
009 | import org.apache.lucene.index.Term; |
010 | import org.apache.lucene.queryParser.QueryParser; |
011 | import org.apache.lucene.search.Hits; |
012 | import org.apache.lucene.search.IndexSearcher; |
013 | import org.apache.lucene.search.Query; |
015 | public class UpdateDocument { |
017 | private static String path = "d:/index" ; |
020 | public static void main(String[] args){ |
027 | public static void addIndex(){ |
029 | IndexWriter write = new IndexWriter(path, new StandardAnalyzer(), true ); |
031 | Document doc = new Document(); |
032 | doc.add( new Field( "id" , "123456" ,Field.Store.YES,Field.Index.UN_TOKENIZED)); |
033 | doc.add( new Field( "userName" , "张三" ,Field.Store.YES,Field.Index.TOKENIZED)); |
034 | doc.add( new Field( "comefrom" , "北京" ,Field.Store.YES,Field.Index.TOKENIZED)); |
036 | write.addDocument(doc); |
040 | } catch (IOException e) { |
046 | public static void updateIndex(){ |
049 | IndexWriter write = new IndexWriter(path, new StandardAnalyzer(), false ); |
050 | Document docNew = new Document(); |
051 | docNew.add( new Field( "id" , "123456" ,Field.Store.YES,Field.Index.UN_TOKENIZED)); |
052 | docNew.add( new Field( "userName" , "王五" ,Field.Store.YES,Field.Index.TOKENIZED)); |
053 | Term term = new Term( "id" , "123456" ); |
055 | 调用updateDocument的方法,传给它一个新的doc来更新数据, |
056 | Term term = new Term("id","1234567"); |
057 | 先去索引文件里查找id为1234567的Doc,如果有就更新它(如果有多条,最后更新后只有一条)。如果没有就新增. |
059 | 数据库更新的时候,我们可以只针对某个列来更新,而lucene只能针对一行数据更新。 |
061 | write.updateDocument(term, docNew); |
065 | } catch (IOException e) { |
070 | public static Query queryParser(String str){ |
071 | QueryParser queryParser = new QueryParser( "userName" , new StandardAnalyzer()); |
073 | Query query = queryParser.parse(str); |
075 | } catch (Exception e) { |
081 | public static void search(String str){ |
083 | IndexSearcher search = new IndexSearcher(path); |
085 | Query query = queryParser(str); |
087 | Hits hits = search.search(query); |
091 | if (hits.length() == 0 ){ |
092 | System.out.println( " 没有搜索到'" + str+ "'" ); |
095 | for ( int i = 0 ; i < hits.length(); i++) { |
096 | Document doc = hits.doc(i); |
097 | System.out.println( "id = " +hits.id(i)); |
098 | System.out.println( "own id = " + doc.get( "id" )); |
099 | System.out.println( "userName = " +doc.get( "userName" )); |
100 | System.out.println( "come from = " +doc.get( "comefrom" )); |
101 | System.out.println( "" ); |
104 | } catch (Exception e) { |
------华------丽------的------分------隔------线------start------
关于java中的Serializable,其实还有一些讲究的,算作今天的课外知识,贴在这里(找不到出处了,作者包涵)。
1、实现Serializable回导致发布的API难以更改,并且使得package-private和private这两个本来封装的较好的咚咚也不能得到保障了
2、Serializable 会为每个类生成一个序列号,生成依据是类名、类实现的接口名、public和protected方法,所以只要你一不小心改了一个已经publish的 API,并且没有自己定义一个long类型的叫做serialVersionUID的field,哪怕只是添加一个getXX,就会让你读原来的序列化到 文件中的东西读不出来(不知道为什么要把方法名算进去?)
3、不用构造函数用Serializable就可以构造对象,看起来不大合理,这被称为extralinguistic mechanism,所以当实现Serializable时应该注意维持构造函数中所维持的那些不变状态
4、增加了发布新版本的类时的测试负担
5、1.4版本后,JavaBeans的持久化采用基于XML的机制,不再需要Serializable
6、 设计用来被继承的类时,尽量不实现Serializable,用来被继承的interface也不要继承Serializable。但是如果父类不实现 Serializable接口,子类很难实现它,特别是对于父类没有可以访问的不含参数的构造函数的时候。所以,一旦你决定不实现 Serializable接口并且类被用来继承的时候记得提供一个无参数的构造函数
7、内部类还是不要实现Serializable好了,除非是static的,(偶也觉得内部类不适合用来干这类活的)
8、使用一个自定义的序列化方法
看看下面这个保存一个双向链表的例子:
publicclass StringList implements Serializable
{
privateint size = 0;
private Entry head = null;
privatestaticclass Entry implements Serializable{
String data;
Entry next;
Entry previous;
}
//Remainder ommitted
}
这样会导致链表的每个元素以及元素之间的关系(双向链表之间的连接)都保存下来,更好的方法是提供一个自定义的序列化如下:
//String List with a resonable custom serialized form
class StringList implements Serializable
{
privatetransientint size = 0;//!transient
privatetransient Entry head = null;//!transient
//no longer serializable!
privatestaticclass Entry{
String data;
Entry next;
Entry previous;
}
//Appends the specified string to the list
publicvoid add(String s) {/*...*/};
/**
* Serialize this StringList
instance
* @author yuchifang
* @serialData The size of the list (the number of strings
* it contains) is emitted(int), in the proper sequence
*/
privatevoid writeObject(ObjectOutputStream s) throws IOException
{
s.defaultWriteObject();
s.writeInt(size);
//Write out all elements in the proper order
for (Entry e = head; e != null; e = e.next)
s.writeObject(e.data);
}
privatevoid readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
int numElements = s.readInt();
//Read in all elements andd insert them in list
for (int i = 0; i <numElements; i++)
add((String)s.readObject());
}
//...remainder omitted
}
9、不管你选择什么序列化形式,声明一个显式的UID:
private static final long serialVersionUID = randomLongValue;
10、不需要序列化的东西使用transient注掉它吧,别什么都留着
11、writeObject/readObject重载以完成更好的序列化
readResolve 与 writeReplace重载以完成更好的维护invariant controllers
------华------丽------的------分------隔------线------End------
今天的所得:
概念:Document,Field,FieldSelector
初步认识:用normalize value修正boost
温故知新:Serializable transient
遗留问题:读写锁的concurrent list