lucene之document

来源:互联网 发布:c语言总结 编辑:程序博客网 时间:2024/05/06 03:54
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的实现比较简单,就不说了。

 

 

Lucene add、updateDocument添加、更新与search查询

 

package com.lucene;
002 
003import java.io.IOException;
004 
005import org.apache.lucene.analysis.standard.StandardAnalyzer;
006import org.apache.lucene.document.Document;
007import org.apache.lucene.document.Field;
008import org.apache.lucene.index.IndexWriter;
009import org.apache.lucene.index.Term;
010import org.apache.lucene.queryParser.QueryParser;
011import org.apache.lucene.search.Hits;
012import org.apache.lucene.search.IndexSearcher;
013import org.apache.lucene.search.Query;
014 
015public class UpdateDocument {
016   
017    privatestatic String path = "d:/index";
018   
019   
020    publicstatic void main(String[] args){
021//        addIndex();
022        updateIndex();
023        search("李四");
024        search("王五");
025    }
026   
027    publicstatic void addIndex(){
028        try{
029            IndexWriter write =new IndexWriter(path,newStandardAnalyzer(),true);
030           
031            Document doc =new Document();
032            doc.add(newField("id","123456",Field.Store.YES,Field.Index.UN_TOKENIZED));
033            doc.add(newField("userName","张三",Field.Store.YES,Field.Index.TOKENIZED));
034            doc.add(newField("comefrom","北京",Field.Store.YES,Field.Index.TOKENIZED));
035           
036            write.addDocument(doc);
037           
038            write.close();
039           
040        }catch (IOException e) {
041            e.printStackTrace();
042        }
043    }
044   
045   
046    publicstatic void updateIndex(){
047        try{
048           
049            IndexWriter write =new IndexWriter(path,newStandardAnalyzer(),false);
050            Document docNew =new Document();
051            docNew.add(newField("id","123456",Field.Store.YES,Field.Index.UN_TOKENIZED));
052            docNew.add(newField("userName","王五",Field.Store.YES,Field.Index.TOKENIZED));
053            Term term =new Term("id","123456");
054            /**
055              调用updateDocument的方法,传给它一个新的doc来更新数据,
056              Term term = new Term("id","1234567");
057              先去索引文件里查找id为1234567的Doc,如果有就更新它(如果有多条,最后更新后只有一条)。如果没有就新增.
058           
059              数据库更新的时候,我们可以只针对某个列来更新,而lucene只能针对一行数据更新。
060             */
061            write.updateDocument(term, docNew);
062           
063            write.close();
064           
065        }catch (IOException e) {
066            e.printStackTrace();
067        }
068    }
069 
070    publicstatic Query queryParser(String str){
071        QueryParser queryParser =new QueryParser("userName",new StandardAnalyzer());
072        try{
073            Query query =  queryParser.parse(str);
074            returnquery;
075        }catch (Exception e) {
076            e.printStackTrace();
077        }
078        returnnull;
079    }
080   
081    publicstatic void search(String str){
082        try{
083            IndexSearcher search =new IndexSearcher(path);
084           
085            Query query = queryParser(str);
086           
087            Hits hits = search.search(query);
088            if(hits==null){
089                return;
090            }
091            if(hits.length() ==0){
092                System.out.println(" 没有搜索到'"+ str+"'");
093                return;
094            }
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("");
102            }
103           
104        }catch (Exception e) {
105            e.printStackTrace();
106        }
107    }
108 
109}

 

------华------丽------的------分------隔------线------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

原创粉丝点击