FSDirectory介绍

本文深入解析了Lucene中的FSDirectory类,详细介绍了其构造函数、静态方法及关键功能,包括锁管理、文件操作等。

其中常用的就是FSDirectory:表示对文件系统目录的操作。RAMDirectory :内存中的目录操作。

首先我们看看类FSDirectory的源代码

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.util.HashMap;

import java.util.Map;

import org.apache.lucene.index.IndexFileNameFilter;

// Used only for WRITE_LOCK_NAME in deprecated create=true case:

import org.apache.lucene.index.IndexWriter;

public class FSDirectory extends Directory {

  //主要是用来缓存FilePath与FSDirectory,主要是保证针对统一目录,只能有一个FSDirectory实例

  private static final Map DIRECTORIES = new HashMap();

//主要判断是否给目录上锁

  private static boolean disableLocks = false;

//设置是否给目录上锁,一般只能用户只读目录。

  //They should only be disabled if the index

  // is on a read-only medium like a CD-ROM

  public static void setDisableLocks(boolean doDisableLocks) {

    FSDirectory.disableLocks = doDisableLocks;

  }

//f返回是否给目录上锁

  public static boolean getDisableLocks() {

    return FSDirectory.disableLocks;

  }

  public static final String LOCK_DIR = System.getProperty("org.apache.lucene.lockDir",

                                                           System.getProperty("java.io.tmpdir"));

  private static Class IMPL; //主要是获取FSDirectory的实例

  static {

    try {

      String name =System.getProperty("org.apache.lucene.FSDirectory.class",

                           FSDirectory.class.getName());

      IMPL = Class.forName(name); //首先获取该类的名称,然后在获取该类的Class实例

    } catch (ClassNotFoundException e) {

      throw new RuntimeException("cannot load FSDirectory class: " + e.toString(), e);

    } catch (SecurityException se) {

      try {

        IMPL = Class.forName(FSDirectory.class.getName()); //直接使用JAVA反射获取该类Class实例

      } catch (ClassNotFoundException e) {

        throw new RuntimeException("cannot load default FSDirectory class: " + e.toString(), e);

      }

    }

  }

  private static MessageDigest DIGESTER; //获取加密

  static {

    try {

      DIGESTER = MessageDigest.getInstance("MD5"); //使用MD5加密

    } catch (NoSuchAlgorithmException e) {

        throw new RuntimeException(e.toString(), e);

    }

  }

  private byte[] buffer = null;

  /**静态方法,根据基于指定目录,返回该目录中对应的一个FSDirectory实例 */

  public static FSDirectory getDirectory(String path) throws IOException {

    return getDirectory(new File(path), null);

  }

/** 静态方法,根据指定的路径path以及锁工厂LockFactory参数,返回该路径的一个FSDirectiry实例 */

  public static FSDirectory getDirectory(String path, LockFactory lockFactory) throws IOException {

    return getDirectory(new File(path), lockFactory);

  }

  /**静态方法,根据指定的File对象,返回该路径的一个FSDirectiry实例 */

  public static FSDirectory getDirectory(File file) throws IOException {

    return getDirectory(file, null);

  }

/**静态方法,根据指定File对象以及锁工厂LockFactory参数,返回该路径的一个FSDirectiry实例

其他方法,都最终转化为该方法来实现 */

  public static FSDirectory getDirectory(File file, LockFactory lockFactory)  throws IOException

  {

    file = new File(file.getCanonicalPath()); //定义一个File对象,以传递的参数作为目录

    if (file.exists() && !file.isDirectory()) //首先判断文件存在时,是否是目录

      throw new IOException(file + " not a directory");

    if (!file.exists()) //如果文件不存在。则创建

      if (!file.mkdirs())  throw new IOException("Cannot create directory: " + file);  //创建所有目录

    FSDirectory dir; //定义一个类 FSDirectory

    synchronized (DIRECTORIES) {

      dir = (FSDirectory)DIRECTORIES.get(file); //首先从Map查找是否存在该实例,

      if (dir == null) {

        try {

          dir = (FSDirectory)IMPL.newInstance(); //调用静态内部类IMPL获取一个与文件系统目录有关的Directory类,并加载该类

        } catch (Exception e) {

          throw new RuntimeException("cannot load FSDirectory class: " + e.toString(), e);

        }

        dir.init(file, lockFactory); // 根据指定的file和lockFactory,调用该类Directory的init方法,进行FSDirectory的初始化初始化工作

        DIRECTORIES.put(file, dir); //将该实例放置到HashMap中

      } else {

//如果该目录dir管理所用的锁工厂实例为空,或者不是同一个锁工厂实例,抛出异常

        if (lockFactory != null && lockFactory != dir.getLockFactory()) {

          throw new IOException("Directory was previously created with a different LockFactory instance; please pass null as the lockFactory instance and use setLockFactory to change it");

        }

      }

    }

    synchronized (dir) {

      dir.refCount++; //用于记录该目录dir被引用的计数增加1

    }

    return dir;

  }

/** 废弃的方法  */

  public static FSDirectory getDirectory(String path, boolean create) throws IOException {

    return getDirectory(new File(path), create);

  }

/** 废弃的方法  */

  public static FSDirectory getDirectory(File file, boolean create)  throws IOException

  {

    FSDirectory dir = getDirectory(file, null);

    if (create) {

      dir.create();

    }

    return dir;

  }

  private void create() throws IOException {

    if (directory.exists()) {

      String[] files = directory.list(IndexFileNameFilter.getFilter());            // clear old files

      if (files == null)

        throw new IOException("cannot read directory " + directory.getAbsolutePath() + ": list() returned null");

      for (int i = 0; i < files.length; i++) {

        File file = new File(directory, files[i]);

        if (!file.delete())

          throw new IOException("Cannot delete " + file);

      }

    }

    lockFactory.clearLock(IndexWriter.WRITE_LOCK_NAME);

  }

  private File directory = null; //File directory是FSDirectory类的一个成员

  private int refCount; //用于记录该目录dir被引用的计数增加1

  protected FSDirectory() {};                     // permit subclassing

  private void init(File path, LockFactory lockFactory) throws IOException {

//根据指定的file和lockFactory,调用该类Directory的init方法,进行FSDirectory的初始化初始化工作

    directory = path;

    boolean doClearLockID = false;

    if (lockFactory == null) { //锁工厂实例为null

      if (disableLocks) { //如果锁不可以使用

        lockFactory = NoLockFactory.getNoLockFactory(); //调用NoLockFactory类,获取NoLockFactory实例,为当前的锁工厂实例。其实NoLockFactory是一个单态(singleton)模式的工厂类,应用中只能有一个锁实例,不需要进行同步

      } else {                //如果锁可以使用,获取锁工厂类名称的字符串描述

        String lockClassName = System.getProperty("org.apache.lucene.store.FSDirectoryLockFactoryClass");

        if (lockClassName != null && !lockClassName.equals("")) {//如果获取的锁工厂类名称的字符串描述不为null,而且者不为空

          Class c;

          try {

            c = Class.forName(lockClassName); //创建一个Class对象,加载该锁工厂类

          } catch (ClassNotFoundException e) {

            throw new IOException("unable to find LockClass " + lockClassName);

          }

          try {

            lockFactory = (LockFactory) c.newInstance();   //获取一个锁工厂的实例

          } catch (IllegalAccessException e) {

            throw new IOException("IllegalAccessException when instantiating LockClass " + lockClassName);

          } catch (InstantiationException e) {

            throw new IOException("InstantiationException when instantiating LockClass " + lockClassName);

          } catch (ClassCastException e) {

            throw new IOException("unable to cast LockClass " + lockClassName + " instance to a LockFactory");

          }

// 根据获取的锁工厂实例的类型来设置对文件File path加锁的方式

          if (lockFactory instanceof NativeFSLockFactory) {

            ((NativeFSLockFactory) lockFactory).setLockDir(path);

          } else if (lockFactory instanceof SimpleFSLockFactory) {

            ((SimpleFSLockFactory) lockFactory).setLockDir(path);

          }

        } else {  

// 没有其他的锁工厂类可用,则使用默认的锁工厂类创建一个锁工厂实例 

lockFactory = new SimpleFSLockFactory(path);

          doClearLockID = true;

        }

      }

    }

// 设置当前FSDirectory相关锁工厂实例

    setLockFactory(lockFactory);

    if (doClearLockID) {

      // Clear the prefix because write.lock will be

      // stored in our directory:

      lockFactory.setLockPrefix(null);

    }

  }

  /** 返回所有的在当前目录下的Lucene索引文件名,并且保存在数组中*/

  public String[] list() {

    ensureOpen();

    return directory.list(IndexFileNameFilter.getFilter());

  }

/**检查指定名称的文件是否存在 */

  public boolean fileExists(String name) {

    ensureOpen();

    File file = new File(directory, name);

    return file.exists();

  }

  /**  返回指定文件最后修改的时间*/

  public long fileModified(String name) {

    ensureOpen();

    File file = new File(directory, name);

    return file.lastModified();

  }

/** 返回指定目录和文件名的文件最后修改的时间*/

  public static long fileModified(File directory, String name) {

    File file = new File(directory, name);

    return file.lastModified();

  }

/**设置指定文件最后修改的时间为当前时间*/

  public void touchFile(String name) {

    ensureOpen();

    File file = new File(directory, name);

    file.setLastModified(System.currentTimeMillis());

  }

/**返回指定文件的长度*/

  public long fileLength(String name) {

    ensureOpen();

    File file = new File(directory, name);

    return file.length();

  }

/**删除当前目录中指定文件名的文件*/

  public void deleteFile(String name) throws IOException {

    ensureOpen();

    File file = new File(directory, name);

    if (!file.delete())

      throw new IOException("Cannot delete " + file);

  }

/** 创建一个名称为name的文件,返回一个输出流,以便对该文件进行写入操作 */

  public IndexOutput createOutput(String name) throws IOException {

    ensureOpen();

    File file = new File(directory, name);

    if (file.exists() && !file.delete())          //如果文件存在,则将文件删除

      throw new IOException("Cannot overwrite: " + file);

    return new FSIndexOutput(file);  // 返回文件File file的一个输出流

  }

// Lucene使用该方法确保所有的针对该文件的写操作都会存储到Index。并且阻止machine/OS发生故障 破坏该index。

  public void sync(String name) throws IOException {

    ensureOpen();

    File fullFile = new File(directory, name);

    boolean success = false;

    int retryCount = 0;

    IOException exc = null;

    while(!success && retryCount < 5) {

      retryCount++;

      RandomAccessFile file = null;

      try {

        try {

          file = new RandomAccessFile(fullFile, "rw");

          file.getFD().sync();

          success = true;

        } finally {

          if (file != null)

            file.close();

        }

      } catch (IOException ioe) {

        if (exc == null)

          exc = ioe;

        try {

          // Pause 5 msec

          Thread.sleep(5);

        } catch (InterruptedException ie) {

          Thread.currentThread().interrupt();

        }

      }

    }

    if (!success)

      // Throw original exception

      throw exc;

  }

  //读取名称为name的文件,返回一个输出流,以便定义对读取出来的内容进行操作

  public IndexInput openInput(String name) throws IOException {

    ensureOpen();

    return openInput(name, BufferedIndexInput.BUFFER_SIZE);

  }

  // 打开指定名称为name的文件,指定大小为缓冲区大小bufferSize,返回一个输入流

  public IndexInput openInput(String name, int bufferSize) throws IOException {

    ensureOpen();

    return new FSIndexInput(new File(directory, name), bufferSize);

  }

// 一个字符缓冲区,将字节转换为十六进制

  private static final char[] HEX_DIGITS =

  {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

//获取锁的标识ID

  public String getLockID() {

    ensureOpen();

    String dirName;                               // name to be hashed

    try {

      dirName = directory.getCanonicalPath();

    } catch (IOException e) {

      throw new RuntimeException(e.toString(), e);

    }

    byte digest[];

    synchronized (DIGESTER) {

      digest = DIGESTER.digest(dirName.getBytes());

    }

    StringBuffer buf = new StringBuffer();

    buf.append("lucene-");

    for (int i = 0; i < digest.length; i++) {

      int b = digest[i];

      buf.append(HEX_DIGITS[(b >> 4) & 0xf]);

      buf.append(HEX_DIGITS[b & 0xf]);

    }

    return buf.toString();

  }

  /** 关闭当前目录. */

  public synchronized void close() {

    if (isOpen && --refCount <= 0) {

      isOpen = false;

      synchronized (DIRECTORIES) {

        DIRECTORIES.remove(directory);

      }

    }

  }

//获取当前目录File类

  public File getFile() {

    ensureOpen();

    return directory;

  }

  /** For debug output. */

  public String toString() {

    return this.getClass().getName() + "@" + directory;

  }

//FSIndexInput是一个静态内部类,用于管理文件系统中的索引文件输入流

  protected static class FSIndexInput extends BufferedIndexInput {

    protected static class Descriptor extends RandomAccessFile {

// 用于记录文件打开状态的boolean型变量isOpen

      protected volatile boolean isOpen;

      long position;

      final long length; // 文件长度

//构造函数for类Descriptor

      public Descriptor(File file, String mode) throws IOException {

        super(file, mode);

        isOpen=true;

        length=length();

      }

  //关闭

      public void close() throws IOException {

        if (isOpen) {

          isOpen=false;

          super.close();

        }

      }

      protected void finalize() throws Throwable {

        try {

          close();

        } finally {

          super.finalize();

        }

      }

    }

    protected final Descriptor file;

    boolean isClone;

    public FSIndexInput(File path) throws IOException {

      this(path, BufferedIndexInput.BUFFER_SIZE);

    }

    public FSIndexInput(File path, int bufferSize) throws IOException {

      super(bufferSize);

      file = new Descriptor(path, "r");

    }

/** 比较底层的读取操作,主要是对字节操作 */

    protected 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 {

          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);

      }

    }

//仅当不是拷贝的时间关闭

    public void close() throws IOException {

      // only close the file if this is not a clone

      if (!isClone) file.close();

    }

    protected void seekInternal(long position) {

    }

//返回文件的长度

    public long length() {

      return file.length;

    }

//创建一个拷贝

    public Object clone() {

      FSIndexInput clone = (FSIndexInput)super.clone();

      clone.isClone = true;

      return clone;

    }

    /** Method used for testing. Returns true if the underlying

     *  file descriptor is valid.

     */

    boolean isFDValid() throws IOException {

      return file.getFD().valid();

    }

  }

// FSIndexOutput是一个静态内部类,用于管理文件系统中的索引文件输出流,与FSIndexInput实现类似

  protected static class FSIndexOutput extends BufferedIndexOutput {

    RandomAccessFile file = null; //定一个随机读写文件

    private volatile boolean isOpen;

//构造函数,根据文件定一个一个可以读写

    public FSIndexOutput(File path) throws IOException {

      file = new RandomAccessFile(path, "rw");

      isOpen = true;

    }

//输入内容

    public void flushBuffer(byte[] b, int offset, int size) throws IOException {

      file.write(b, offset, size);

}

//关闭该对象。仅当该对象被打开,从来没有被关闭过

    public void close() throws IOException {

      if (isOpen) {

        boolean success = false;

        try {

          super.close();

          success = true;

        } finally {

          isOpen = false;

          if (!success) {

            try {

              file.close();

            } catch (Throwable t) {

              // Suppress so we don't mask original exception

            }

          } else

            file.close();

        }

      }

    }

// 随机访问的方法实现

    public void seek(long pos) throws IOException {

      super.seek(pos);

      file.seek(pos);

    }

    public long length() throws IOException {

      return file.length();

    }

    public void setLength(long length) throws IOException {

      file.setLength(length);

    }

  }

}

由具体构造函数实现方法以及dir.init()方法,可以看出来,针对每一个目录只能有一个FSDirectory实例,

由此我们可以看看FSDirectory的主要功能:

1、针对同一个目录进行维护

2、对于锁工厂的获取以及管理

3、对文件系统目录下索引文件的输入流和输出流的管理

4、FSDirectory类中实现了很多静态内部类,这使得只能在FSDirectory类内部访问这些静态类,对外部透明。

因此该类主要Directory抽象类的操作本地文件的实现。

转自:

http://sunjun041640.blog.163.com/blog/static/256268322009428112129497/

<think>嗯,用户的问题是关于如何在Lucene中使用FSDirectory和QueryParser进行分页查询的Java示例。我需要先回忆一下Lucene的基本组件和分页的实现方法。 首先,FSDirectory是Lucene中用于访问文件系统目录的,通常用来存储索引。QueryParser则用于将用户输入的查询字符串解析成Lucene可执行的查询对象。分页查询的话,关键点在于控制搜索结果的起始位置和返回数量,也就是利用TopScoreDocCollector或者TopDocs来实现分页参数。 记得分页需要计算起始的文档位置,比如每页显示10条,第二页就是从第10条开始。Lucene的IndexSearcher.search方法可以接受一个参数来指定返回的最大文档数,但分页可能需要结合ScoreDoc来实现,比如使用searchAfter方法从上一页的最后一个结果开始获取下一页的数据。 然后,用户可能需要一个完整的代码示例,包括创建查询、执行分页、处理结果等步骤。还需要注意分页时可能需要保存上一页的最后一个ScoreDoc,以便获取下一页的数据。比如,每页显示pageSize条,第page页的起始位置是(page-1)*pageSize,但Lucene的TopDocs.scoreDocs数组可能不够长,需要处理边界情况。 另外,分页查询时的高效性也很重要,避免每次都从头开始遍历所有结果。使用searchAfter方法可以利用上一次的最后一个文档来快速定位下一页的起始点,这样效率更高,尤其是在处理大量数据的时候。 还需要考虑QueryParser的创建,需要指定分析器和默认字段。分析器通常和索引时使用的一致,比如StandardAnalyzer。默认字段则根据具体的应用场景来定,比如用户可能搜索的是“title”或者“content”字段。 然后,处理搜索结果时,需要遍历TopDocs中的ScoreDoc,提取文档的各个字段,并转换为适合展示的数据结构。分页参数如当前页码和总页数也需要计算,总页数可以通过totalHits除以pageSize向上取整得到。 可能还需要处理一些异常情况,比如用户输入的查询字符串有语法错误,这时候QueryParser可能会抛出ParseException,需要在代码中进行捕获和处理。 总结一下,回答的结构应该是先介绍分页的关键点,然后给出分步骤的代码示例,包括初始化Directory和IndexSearcher,创建QueryParser,构建分页参数,执行查询,处理结果,最后优化分页效率的方法。同时,可能还需要提醒用户关于分析器的选择和分页参数的边界处理。 用户提供的引用中提到IndexSearcher和查询对象的创建,这些在示例代码中需要体现。分页处理部分可能涉及TopDocs和ScoreDoc的使用,确保代码示例中详细展示如何计算起始位置和获取当前页的数据。此外,处理分页时的总页数计算和页码的有效性检查也是需要注意的点。 最后,确保代码语法正确,使用正确的Lucene版本对应的API,比如在较新的版本中,某些方法可能有变化,但用户可能需要一个通用的示例,所以使用常见的和方法,比如IndexSearcher、QueryParser、TopScoreDocCollector等。 现在需要将这些思考整理成一个结构清晰、步骤分明的回答,包含代码示例和相关解释,同时生成相关问题供用户参考。</think>### 分步解释:使用Lucene实现分页查询 #### 关键点说明 1. **分页核心参数**:通过`start`(起始位置)和`pageSize`(每页数量)控制分页逻辑 2. **查询结果截取**:利用`TopDocs.scoreDocs`数组的索引范围获取当前页数据 3. **高效分页优化**:使用`searchAfter()`方法避免全量遍历 #### 完整代码示例 ```java import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.FSDirectory; public class LucenePaginationExample { public static void main(String[] args) throws Exception { // 1. 初始化索引目录 FSDirectory directory = FSDirectory.open(Paths.get("/path/to/index")); DirectoryReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); // 2. 创建查询解析器(与索引时相同的分析器) QueryParser parser = new QueryParser("content", new StandardAnalyzer()); Query query = parser.parse("search keywords"); // 3. 分页参数设置 int pageSize = 10; // 每页显示数量 int currentPage = 2; // 当前页码(从1开始) int start = (currentPage - 1) * pageSize; // 4. 执行分页查询 TopDocs results = searcher.search(query, start + pageSize); ScoreDoc[] hits = results.scoreDocs; // 5. 处理当前页数据 int end = Math.min(hits.length, start + pageSize); for (int i = start; i < end; i++) { Document doc = searcher.doc(hits[i].doc); System.out.println("结果" + (i + 1) + ": " + doc.get("title")); } // 6. 分页元数据 int totalHits = results.totalHits.value; int totalPages = (int) Math.ceil((double) totalHits / pageSize); System.out.printf("共 %d 条结果,当前第 %d/%d 页", totalHits, currentPage, totalPages); reader.close(); directory.close(); } } ``` #### 优化分页实现(推荐方式) ```java // 使用searchAfter优化大数据量分页 ScoreDoc lastScoreDoc = null; if (currentPage > 1) { TopDocs prevResults = searcher.search(query, (currentPage - 1) * pageSize); lastScoreDoc = prevResults.scoreDocs[prevResults.scoreDocs.length - 1]; } TopDocs currentResults = searcher.searchAfter(lastScoreDoc, query, pageSize); ``` #### 重要注意事项 1. **索引一致性**:确保查询使用的分析器与建索引时一致[^2] 2. **边界处理**:当`start`超过总结果数时返回空集 3. **性能优化**:大数据量分页优先使用`searchAfter`代替传统分页 4. **资源释放**:查询完成后必须关闭`DirectoryReader`和`FSDirectory`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值