#
final class SegmentInfos extends Vector
可以看出该类实际上是一个Vector 以及封装了对该Vevtor的一些操作
实际上封装的是对segments文件的一些读写操作
先来看下segments文件的格式
segments文件的格式:
int: =-1 文件是否是Lucene合法的文件格式正常情况下为 -1
long: 版本号,每更新一次该文件将会将版本号加1
int: 用来命名新段
int: 段的数目
String + int 段的信息 String是段的名称 int是段中所含的doc数目
String + int 同上
所以用Lucene的API,我们可以简单的打印出其segments的所有信息
try {
//DataInputStream fis = new DataInputStream(new FileInputStream("C:\\sf\\snow\\segments"));
FSDirectory dir=FSDirectory.getDirectory("C:/sf/snow", false);
InputStream input = dir.openFile("segments");
System.out.println("Format:"+input.readInt()); //得到文件标志,是否为正常的segments文件
System.out.println("version:"+input.readLong()); //得到版本号
System.out.println("name:"+input.readInt()); //得到用来重命名新段的int,暂时不知道有什么用
int n=input.readInt(); //段的数目
System.out.println("SegmentNum:"+n);
for(int i=0;i<n;i++) { //用循环打印出所有段的信息 名称和长度
System.out.println("segment "+i+" - name:"+input.readString()+" num:"+input.readInt());
}
} catch (Exception e) {
}
当然,该类提供了更为复杂的访问和更新segments文件的方法
final void read(Directory directory) 将所有的段信息保存在本vector中
final void write(Directory directory) 跟新该segment文件的内容,主要是为了添加段,
主要是更新 版本号 段的数目,跟新完这些后即可往segment文件后添加新段的信息。
segment(段)的信息
该类比较简单,贴出其全部代码
import org.apache.lucene.store.Directory;
final class SegmentInfo {
public String name; //在索引目录中唯一的名称
public int docCount; // 该段中doc的数目
public Directory dir; // 该段所存在的Dirrectory
public SegmentInfo(String name, int docCount, Directory dir) {
this.name = name;
this.docCount = docCount;
this.dir = dir;
}
}
该类是从RAMFile中读数据用的
最重要的一个方法:
该方法存在着从RAMFile的多个byte[1024]中读取数据的情况,所以应该在循环中进行处理
public void readInternal(byte[] dest, int destOffset, int len) {
int remainder = len;
int start = pointer;
while (remainder != 0) {
int bufferNumber = start/BUFFER_SIZE; // buffer的序号
int bufferOffset = start%BUFFER_SIZE; // buffer偏移量
int bytesInBuffer = BUFFER_SIZE - bufferOffset;// 在当前buffer中剩下的字节数
//如果缓冲区中剩余的字节大于len,则读出len长度的字节,如果不够则读出剩余的字节数
// bytesToCopy表示实际读出的字节数
int bytesToCopy = bytesInBuffer >= remainder ? remainder : bytesInBuffer;
byte[] buffer = (byte[])file.buffers.elementAt(bufferNumber);
System.arraycopy(buffer, bufferOffset, dest, destOffset, bytesToCopy);
destOffset += bytesToCopy; //增加已经复制的byte数据长度 到 dest中的偏移量
start += bytesToCopy; //RAMFile文件指针,用来确定bufferNumber 和bytesInBuffer 相当于内存中的分页
remainder -= bytesToCopy; //剩余的还未复制的字节数
}
pointer += len;//文件指针位置
}
这是OutputStream的一个子类,其输出设备是内存,准确来说是RAMFile,即将数据写入到RAMFile的Vector中去。
该类有一个最重要的方法,现在把它整个贴出来
public void flushBuffer(byte[] src, int len) {
int bufferNumber = pointer/BUFFER_SIZE; //buffer序列,即当前所写Buffer在RAMFile中的Vector中的序列号
int bufferOffset = pointer%BUFFER_SIZE; //偏移量,即当前所写字节在当前Buffer中的偏移量。
int bytesInBuffer = BUFFER_SIZE - bufferOffset; //当前Buffer的剩余可写字节数
//bytesToCopy是实际写入的字节数,如果当前Bufer的剩余字节数大于需要写的字节的总数则写入所有字节
//否则,将当前Buffer写满即可,剩余的字节将写入下一个Buffer
int bytesToCopy = bytesInBuffer >= len ? len : bytesInBuffer;
if (bufferNumber == file.buffers.size())
file.buffers.addElement(new byte[BUFFER_SIZE]); //在RAMFile中添加新的byte[1024]元素
byte[] buffer = (byte[])file.buffers.elementAt(bufferNumber);
System.arraycopy(src, 0, buffer, bufferOffset, bytesToCopy);
if (bytesToCopy < len) { // not all in one buffer,
int srcOffset = bytesToCopy;
bytesToCopy = len - bytesToCopy; // remaining bytes 剩余的未写入的字节数
bufferNumber++; //将buffer数增加1
if (bufferNumber == file.buffers.size())
file.buffers.addElement(new byte[BUFFER_SIZE]);
buffer = (byte[])file.buffers.elementAt(bufferNumber); //剩余字节写入下一个Buffer
System.arraycopy(src, srcOffset, buffer, 0, bytesToCopy);
}
pointer += len;
if (pointer > file.length)
file.length = pointer; //移位文件指针 在原有的基础上加上实际写入的字节总数
file.lastModified = System.currentTimeMillis(); //修改文件的最后修改时间为当前时间
}
从指定的字节数组复制指定长度的字节到RAMFile中去。由于RAMFile中Vector的元素是byte[1024]所以可能存在做一次该操作
要操作两个Vector元素的情况。即先将当前byte[1024]数组填满,再新建一个元素装载剩余的字节。
另外还有一个writeTo(OutputStream out)方法,将RAMFile中的数据输出到另一个输出流
这个类比较简单
import java.util.Vector;
class RAMFile {
Vector buffers = new Vector();
long length;
long lastModified = System.currentTimeMillis();
}
可以理解为一个存储在内存中的文件,buffers是存储数据的容器,length是容器中数据的总的字节数
lastModified 是最后修改时间。
在实际使用过程中容器buffers存放的对象是一个byte[1024]数组。
OutputStream
这是一个Abstract类,是Lucene自己的一个文件输出流的基类
BUFFER_SIZE = 1024 缓冲区 大小为 1024bit
bufferStart = 0 文件位置指针
bufferPosition = 0 内存缓冲区指针
public final void writeByte(byte b) throws IOException {
if (bufferPosition >= BUFFER_SIZE)
flush();
buffer[bufferPosition++] = b;
}
几乎所有的写入函数都要调用这个函数,如果缓冲区的当前容量已经等于他的最大容量,则将缓冲区中的数据写入文件。
public final void writeBytes(byte[] b, int length) throws IOException
批量写byte进入内存缓冲
public final void writeInt(int i) throws IOException
写入整形数据
public final void writeLong(long i) throws IOException
写入长整型数据,即结合移位运算调用两次writeInt(int i)
另外,最值得注意的是在该类中有两个最特殊的函数
writeVInt(int i) / writeVLong(long i),
先说
writeVInt(int i ) {
while ((i & ~0x7F) != 0) {
writeByte((byte)((i & 0x7f) | 0x80));
i >>>= 7;
}
writeByte((byte)i);
}
~0x7F==~(0111 1111)==(1000 0000)==0x80
((i & ~0x7F) != 0) 这一句判断i是否大于0x80,如果不是则说明该int只有一个字节的有效数据,其他字节都是0,直接转化为Byte写入。
如果大于0x80则
(i & 0x7f) | 0x80
i&0x7f 只对后7位进行处理,|0x80将第8位置1,与前面的7个bit构成一个字节,置1的原因是说明该字节并不是一个完整的整形数,需要与其他的字节合起来才能构成一个整形数字。
这个算法相当于将一个32bit的整形数字按照每7位编码成一个字节进行存储,将按照整形数的大小存储1-5个字节。
writeVLong(long i)方法大致与其相同。
final void writeChars(String s, int start, int length)
将字符串转化成UTF-8编码的格式进行存储。
附:
UNICODE值 UTF-8编码
U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
可见对于在 0x00-0x7F范围内的UNICODE值(最大有效数位:7位),将会编码成单字节的,会大大节约存储空间。
对于在 0x80-0x7FF范围内的UNICODE(最大有效数位:11位),会编码成双字节的。先存储原字节低5位的数位,且将最高位和次高位都置1,再次高位置0(writeByte((byte)(0xC0 | (code >> 6)));)。然后存储后6位的字节,将前两位置10(writeByte((byte)(0x80 | (code & 0x3F)));)
对于其他的UNICODE值则
writeByte((byte)(0xE0 | (code >>> 12))); 4位
writeByte((byte)(0x80 | ((code >> 6) & 0x3F))); 5位
writeByte((byte)(0x80 | (code & 0x3F))); 3- 5位
final void writeString(String s) throws IOException
该函数首先用s.length()判断该String总共有多少个字符
然后首先调用writeVInt写入这个字符长度
再调用writeChars(s,s.length())写入字符
在inputStream中的readString()方法则与其相反,首先用readVInt()方法读取字符长度len 然后读取len长度的字符
protected final void flush() throws IOException
该方法调用另外一个方法flushBuffer将缓冲区中的数据输出,然后清空缓冲区;
abstract void flushBuffer(byte[] b, int len) throws IOException
可见flushBuffer方法是abstract的,即需要其子类对该方法进行覆写,以定位该输出流的输出方式。
final long getFilePointer() throws IOException
得到文件指针的位置,即得到输出流已经输出的字节数。
public void seek(long pos) throws IOException
输出缓冲区的内容,然后将文件指针定位到long所指示的文件位置。
abstract long length() throws IOException
返回文件中已有的字节数。需要子类实现。
FSDirectory继承了abstract类Directory
在该类中既有该类的一些初始化操作,又有对FSDirectory对象本身的一些操作,这是为什么把其构造函数设置为私有的一部分原因
static final Hashtable DIRECTORIES = new Hashtable();
每新建一个FSDirectory都会将其加入到该Hashtable中来。名称是FSDirectory对应的File 值是该FSDirectory。
注意:final对象并非是不可更改的
static final String LOCK_DIR =
System.getProperty("org.apache.lucene.lockdir",
System.getProperty("java.io.tmpdir"));
首先看用户是否注册了"org.apache.lucene.lockdir"属性,如果没有则用JAVA虚拟机固有的"java.io.tmpdir"属性
这个属性是一个路径,代表lucene的锁文件锁放的位置。
static final boolean DISABLE_LOCKS =
Boolean.getBoolean("disableLuceneLocks") || Constants.JAVA_1_1;
如果用户注册了"disableLuceneLocks"属性且为false,或者JAVA的版本是1.1则无法使用锁。
static FSDirectory getDirectory(String path, boolean create)
static FSDirectory getDirectory(File file, boolean create)
从得到一个指定路径或者文件的FSDirectory如果在则取出,如果不存在则用其私有的构造函数构造一个
该类还有3个非static的类变量
private File directory = null; 索引目录
private int refCount; 锁目录
private File lockDir; 索引目录数目
实际上,初始化一个FSDirectory只需要初始化这3个变量即可
如果create的值为true 则:如果索引目录是已经存在的目录,则会遍历该目录然后删除每一个文件,如果锁目录是已存在的也会用list返回所有的文件然后调用file.delete() 删除。 如果目录不存在则创建一个新的。
注意:list()方法 会先用文件名进行排序然后返回(a.txt会比b.txt先返回) 且delete方法删除文件夹时,只能删除空文件夹。如果失败则跳出程序,不会删除在该文件夹之后返回的文件。(如果有aa.txt , ab/b.txt , b.txt , 则删除时候由于a文件夹非空删除失败,则b.txt由于前面删除失败跳出程序,也不会被删除,但是aa.txt被正常删除)
private FSDirectory(File path, boolean create) throws IOException
私有的构造函数
private synchronized void create() throws IOException
创建新的directory /lockDir目录,当目录已存在时即清空该目录,不存在即创建新的目录。
final String[] list() throws IOException
以字符串文件名的形式返回索引目录的所有文件
final boolean fileExists(String name) throws IOException
在索引目录是否存在指定文件名的文件
final long fileModified(String name) throws IOException
static final long fileModified(File directory, String name)
返回该文件的最后修改时间,directory参数为相对路径,第一个函数的相对路径为索引目录
void touchFile(String name) throws IOException
将该文件的最后修改时间设置为当前时间
final long fileLength(String name) throws IOException
返回该文件的长度
final void deleteFile(String name) throws IOException
删除该文件
final synchronized void renameFile(String from, String to) throws IOException
重命名该文件
该方法会首先检测新的文件名命名的文件是否已经存在如果存在即删除该文件,然后再将文件重新命名为新的文件名。
doug cutting在该方法的注释上写到:
1.删除操作和重命名的操作不是原子的。
2.重命名操作在有些虚拟机上面不能正确的工作,如果重命名失败则会采用手动copy的方法。使用输入输出流将旧的文件的内容写入到新的文件中去,然后删除旧的文件。
注意:该方法必须是同步的。
final OutputStream createFile(String name) throws IOException
用指定的文件名创建一个新的可写的空文件 实际上返回的是FSOutputStream,注意这里的OutputStream并不是java的基础类。而是doug cutting自己写的一个文件随即访问类。同理FSInputStream和InputStream也是Lucene自己的类。
final InputStream openFile(String name) throws IOException
从一个存在的文件打开一个输入流
getLockPrefix()
在FSDirectory中还有
private static MessageDigest DIGESTER;这个静态变量是提供加密功能的
DIGESTER=MessageDigest.getInstance("MD5"),-----MD5加密算法
或者可以DIGESTER=MessageDigest.getInstance("SHA"),-----SHA加密算法
用于对锁目录的 文件名的加密
用法如下:
digest = DIGESTER.digest(dirName.getBytes()); dirName是需要被加密的字符串,这里是索引文件的目录名,
在FSContext中,其应用在 getLockPrefix() 该方法是为某个索引目录创建其对应的锁目录文件名。
首先返回经过加密后的byte[] 数组digest,然后将digest按照每4个bit转化为一个16进制的字符,存进一个StringBuffer中
其转化类似与Base64编码方式,不过要简单得多。
方法
Lockl makeLock(String name)
是从Directory中扩展而来的,该方法返回一个Lock对象,该对象将会在介绍完Lucene的输入输出流之后介绍。
该方法比较简单,首先是调用了getLockPrefix() 方法,返回文件锁的部分对象名,然后在该名称后面加上锁的特征名
譬如说读写锁 事务锁
其名称类似于下:
lucene-12c90c2c381bc7acbc4846b4ce97593b-write.lock
lucene-12c90c2c381bc7acbc4846b4ce97593b-commit.lock
这两种锁机制将会在后面介绍
最后通过一个匿名的内部类返回一个经过重载的Lock对象,该内部类中的方法有锁的创建,得到,释放,以及检测,另外还有一个toString()方法返回锁的名称。
在FSDirectory类中有OutputStream和InputStream的实现类,这两个虚类只是定义了一些操作,并没有定义输入或者输出的设备。
Lucene在输入输出的设计上,将会由子类定义输入输出的设备。
FSOutputStream
在FSOutputStream中有一个 RandomAccess File=new RandomAccessFile(path, "rw");
在对该输出流的操作将用调用该file的相应方法实现
最重要的
public final void flushBuffer(byte[] b, int size) throws IOException {
file.write(b, 0, size);
}
flushBuffer的调用将会将byte中的0--size范围的数据写入到文件path中去。
FSInputStream
最重要的
protected final void readInternal(byte[] b, int offset, int len)
throws IOException {
synchronized (file) {
long position = getFilePointer(); //得到该当前文件指针
if (position != file.position) {
file.seek(position);
file.position = position;
}
int total = 0;
do {
//从文件中读取指定长度的字节到字节数组
// 在其基类InputStream中的refill()方法 将会调用 readInternal(buffer, 0, bufferLength);首先从文件中读取字节到缓冲数组。
// 在InputStream中每次读取操作都会调用readInternal方法,或者通过refill()方法间接调用该方法。
int i = file.read(b, offset+total, len-total); //将文件中的数据读到缓冲区
if (i == -1)
throw new IOException("read past EOF");
file.position += i;
total += i;
} while (total < len);
}
}
该类提供了日期和字符串之间的相互转化,实际上是 long型和String型的相互转化,转化时用到了一个不常用的
Long.toString(long,int);方法。是按指定的方式对long型进行转化
第一个参数是要转化的long,第二个参数是转化时候的基数,如果基数是10就相当于方法Long.toString(long);
这里使用的参数是最大值,即36== 10个数字+26个英文字母。这样转化出来的字符串长度比较短,占用比较少的空间,
另外,在转化时,统一了转化后的字符串长度,如果不足9位(日期的long转化后最高为9位,1970之后的日期可正确转换),
统一长度后的字符串可以通过比较字符串来比较日期的大小。
日期转化成的字符串类似于
0fev8eza3
本来应该是fev8eza3 采取了不足9位补0的方法。
private static int DATE_LEN = Long.toString(1000L*365*24*60*60*1000,
Character.MAX_RADIX).length();
计算出从1970年开始后1000年的时间转化为字符串后的长度,所有转化后的时间都不应超过这个长度,如果不足则在前面补0
可以通过字符串转化为日期的函数计算出能表示的最大日期为
stringToTime("zzzzzzzzz");
打印出来是 Fri Apr 22 19:04:28 CST 5188
所以该函数能转化的日期范围为 1970-1-1~~5188-4-22
日期转化为字符串
public static String timeToString(long time)
字符串转化为日期
public static long stringToTime(String s)
实际上 函数 LongToString(long i,int radix) 相当于 先将i转化为radix进制的整数,然后再用函数
LongToString(i)转化为字符串。所以radix的值应该在2--36之间如果不是 则按照10进制计算。
Document是一些Field的集合,每个Field有一个名字和文本值,当中的某些Field可能会随着Documnet被存储。这样,每个Document应该至少包含一个可以唯一标示它的被存储的Field
//Field集合
List fields = new Vector();
//增强因子,作用于该Document的所有Field
private float boost = 1.0f;
//向Document中添加Field
public final void add(Field field) {
fields.add(field);
}
//删除指定名称的第一个Field
public final void removeField(String name)
//删除所有拥有指定名称的Field
public final void removeFields(String name)
//得到指定名称的第一个Field
public final Field getField(String name)
//以数组的形式返回指定名称的所有Field
public final Field[] getFields(String name)
//得到所有Field的一个枚举
public final Enumeration fields()
该类也重载了toString()方法
打印出所有Field的信息
package org.apache.lucene.document;
Field
是Document的一部分,每个Field有两个部分组成 名字-值 对 名字是String 值 可以是String 和 Reader,如果是KeyWord类型的Field,那么值将不会被进一步处理,像URL,Date等等。Field被存储在Index中,以便于能以Hits的形式返回原有的Document
Field有3 个Boolean形的标识
private boolean isStored = false; 被存储
private boolean isIndexed = true; 被索引
private boolean isTokenized = true 被分割
通过调整这3个boolean的值,可以确定该Field的类型
Keyword true, true, false 一般存储 URL DATE 等关键字
UnIndexed true, false, false 一般是随HITS查询结果一起返回的信息
Text true, true, true
UnStored false, true, true
另外,还有一个重载的toString方法 可以打印出该Field的类型
float boost = 1.0f; 增强因子,用于排序的评分,作用于拥有该域(field)的所有文档(document)