java中如何实现全文搜索

浅谈java中如何实现全文搜索

什么是全文搜索

  • 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程.

  • 那么实现全文搜索的主要2个方向

    • 索引的建立

    • 索引的查询 如何创建索引,肯定跟业务息息相关. 不同业务数据存在不同的维度, 那么索引创建的关键则是, 如何合理创建索引维度.

常见的系统全文搜索软件

AstroGrep

  • java中如何实现全文搜索_第1张图片

PowerGREP

  • java中如何实现全文搜索_第2张图片

Everything等等

  • java中如何实现全文搜索_第3张图片

JAVA中常用的全文搜索框架

Java 全文搜索引擎框架 Lucene

  • 毫无疑问,Lucene是目前最受欢迎的Java全文搜索框架,准确地说,它是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene为开发人员提供了相当完整的工具包,可以非常方便地实现强大的全文检索功能。下面有几款搜索引擎框架也是基于Lucene实现的。

  • 官方网站:Apache Lucene - Welcome to Apache Lucene

开源Java搜索引擎Nutch

  • Nutch 是一个开源Java实现的搜索引擎。它提供了我们运行自己的搜索引擎所需的全部工具。包括全文搜索和Web爬虫。

  • 利用Nutch,你可以做到以下这些功能:

    • 每个月取几十亿网页

    • 为这些网页维护一个索引

    • 对索引文件进行每秒上千次的搜索

    • 提供高质量的搜索结果

    • 以最小的成本运作

    • 官方网站:Apache Nutch™

分布式搜索引擎 ElasticSearch

  • ElasticSearch就是一款基于Lucene框架的分布式搜索引擎,并且也是一款为数不多的基于JSON进行索引的搜索引擎。ElasticSearch特别适合在云计算平台上使用。

  • 官方网站:Elastic Observability and Security — built on Elasticsearch | Elastic

实时分布式搜索引擎 Solandra

  • Solandra 是一个实时的分布式搜索引擎,基于 Apache Solr 和 Apache Cassandra 构建。

  • 其特性如下:

    • 支持Solr的大多数默认特性 (search, faceting, highlights)

    • 数据复制,分片,缓存及压缩这些都由Cassandra来进行

    • Multi-master (任意结点都可供读写)

    • 实时性高,写操作完成即可读到

    • Easily add new SolrCores w/o restart across the cluster 轻松添加及重启结点

    • 官方网站:GitHub - tjake/Solandra: Solandra = Solr + Cassandra

IndexTank

  • IndexTank是一套基于Java的索引-实时全文搜索引擎实现,IndexTank有以下几个特点:

    • 索引更新实时生效

    • 地理位置搜索

    • 支持多种客户端语言

    • Ruby, Rails, Python, Java, PHP, .NET & more!

    • 支持灵活的排序与评分控制

    • 支持自动完成

    • 支持面搜索(facet search)

    • 支持匹配高亮

    • 支持海量数据扩展(Scalable from a personal blog to hundreds of millions of documents! )

    • 支持动态数据

    • 官方网站:GitHub - LinkedInAttic/indextank-engine: Indexing engine for IndexTank

搜索引擎 Compass

  • Compass是一个强大的,事务的,高性能的对象/搜索引擎映射(OSEM:object/search engine mapping)与一个Java持久层框架.Compass包括:

    • 搜索引擎抽象层(使用Lucene搜索引荐)

    • OSEM (Object/Search Engine Mapping) 支持

    • 事务管理

    • 类似于Google的简单关键字查询语言

    • 可扩展与模块化的框架

    • 简单的API

    • 官方网站:http://www.compass-project.org/

Java全文搜索服务器 Solr

  • Solr也是基于Java实现的,并且是基于Lucene实现的,Solr的主要特性包括:高效、灵活的缓存功能,垂直搜索功能,高亮显示搜索结果。值得注意的是,Solr还提供一款很棒的Web界面来管理索引的数据。

  • 官方网站:Welcome to Apache Solr - Apache Solr

Lucene图片搜索 LIRE

  • LIRE是一款基于Java的图片搜索框架,其核心也是基于Lucene的,利用该索引就能够构建一个基于内容的图像检索(content- based image retrieval,CBIR)系统,来搜索相似的图像。

  • 官方网站:http://www.semanticmetadata.NET/lire/

全文本搜索引擎 Egothor

  • Egothor是一个用Java编写的开源而高效的全文本搜索引擎。借助Java的跨平台特性,Egothor能应用于任何环境的应用,既可配置为单独的搜索引擎,又能用于你的应用作为全文检索之用。

全文搜索实践

JAVA开发中常用的Eclipse (CTRL+SHIFT+R) 和IDEA(SHIFT+SHIFT) 进行文件搜索时,同样也可以算做一个全文搜索. 如果你也有一个全文搜索的功能需求, 那么怎么实现呢?

索引分析和创建

  • 在特定的文件存储目录中 ,建立关于文件的索引信息

  • 遍历该文件夹的所有文件, 创建文件的索引信息 , 可以使用 Files.walkFileTree

  • 文件的删除, 重命名,目录变更需要更改索引信息

  • 监听该文件夹,对于文件发生的变更即时更新索引信息 , 可以使用 apache commons-io包的 FileAlterationObserver

FileWatchService

  • import cn.hutool.log.StaticLog;
    import org.apache.commons.io.filefilter.FileFilterUtils;
    import org.apache.commons.io.filefilter.IOFileFilter;
    import org.apache.commons.io.monitor.FileAlterationListener;
    import org.apache.commons.io.monitor.FileAlterationMonitor;
    import org.apache.commons.io.monitor.FileAlterationObserver;
    ​
    import javax.annotation.Nonnull;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    ​
    /**
     * @author jee
     * @description: 文件观察服务
     * 

    * + 定义轮询时间 和文件过滤器 * + 指定监听目录 * + 自定义文件监听动作 *

     * // 自定义文件监听事件
     * FileWatchAdapter fileWatchAdapter = new FileWatchAdapter(true);
     * List listeners = CollUtil.newArrayList(fileWatchAdapter);
     *
     * // 创建文件监听
     * FileWatchService monitorFile = new FileWatchService.WatchBuilder().dirPath(dirPath)
     * .listeners(listeners).build();
     *
     * monitorFile.start();
     *
     * // 自定义频率和过滤器
     * * FileWatchService monitorFile = new FileWatchService.WatchBuilder(3,FileFilterUtils.suffixFileFilter(".txt")).dirPath(dirPath)
     *  * .listeners(listeners).build();
     *
     *
     * 
    *

    * 指定文件过滤监听示例 *

     *
     *         // txt过滤器
     *         IOFileFilter filefilter = FileFilterUtils.suffixFileFilter(".txt");
     *         // 子目录的txt后缀
     *         IOFileFilter subFilefilter = FileFilterUtils.or(FileFilterUtils.directoryFileFilter(), filefilter);
     *         //根目录和子目录变化
     *         IOFileFilter filter = FileFilterUtils.or(filefilter, subFilefilter);
     *
     *         // 创建文件监听
     *         FileWatchService monitorFile = new FileWatchService.WatchBuilder(5, filter).dirPath(dirPath)
     *                 .listeners(listeners).build();
     *         monitorFile.start();
     *
     * 
    */ public class FileWatchService { ​   /*监听目录*/   private final File fileDir; ​   /*循环周期*/   private long cycleTime; ​   /*监听操作*/   private final List listeners; ​   /*文件过滤器*/   private final IOFileFilter fileFilter; ​   /*监听器*/   private final FileAlterationMonitor monitor; ​   /*监听适配器*/   private final FileAlterationObserver observer; ​ ​   public FileWatchService(WatchBuilder builder) {       this.fileDir = builder.fileDir;       this.cycleTime = builder.cycleTime;       StaticLog.debug("监听目录: {}, 轮询时间: {}ms", fileDir, cycleTime);       this.listeners = builder.listeners;       this.fileFilter = builder.fileFilter;       observer = new FileAlterationObserver(fileDir, fileFilter);       listeners.forEach(observer::addListener); ​       monitor = new FileAlterationMonitor(cycleTime, observer);   } ​   /**     * 启动目录观察器     *     * @throws Exception 实例化observer失败异常     */   public void start() throws Exception {       StaticLog.debug("启动目录:{} 观察器", fileDir);       monitor.start();   } ​   /**     * 停止监控     */   public void destroy() {       try {           if (monitor != null) {               monitor.stop();               StaticLog.debug("停止目录:{} 观察器", fileDir);           }       } catch (Exception e) {           StaticLog.error(e, "文件停止监控失败");       } ​   } ​   /**     * @author jee     * @description: builder模式     */   public static class WatchBuilder { ​       /*轮询时间*/       private final long cycleTime; ​       /*文件拦截器*/       private final IOFileFilter fileFilter; ​       /*文件目录*/       private File fileDir; ​       /*自定义文件监听器*/       private List listeners; ​       /**         * 默认3s轮询一次 ,监听整个目录         */       public WatchBuilder() {           this(3);       } ​       /**         * 自定义轮询时间         *         * @param cycleTime 轮询时间         */       public WatchBuilder(long cycleTime) {           this(cycleTime, FileFilterUtils.trueFileFilter());       } ​ ​       /**         * 自定义轮询时间和文件过滤器         *         * @param cycleTime 默认轮询时间,单位秒         * @param fileFilter 轮询文件过滤器         */       public WatchBuilder(long cycleTime, IOFileFilter fileFilter) {           this.cycleTime = TimeUnit.SECONDS.toMillis(cycleTime);           this.fileFilter = fileFilter;           this.listeners = new ArrayList<>();       } ​       /**         * 设置监听文件目录         *         * @param dirPath 目录绝对路径         * @return WatchBuilder         * @throws IOException 非目录或目录不存在异常!         */       public WatchBuilder dirPath(String dirPath) throws IOException {           this.fileDir = new File(dirPath);           if (!fileDir.exists()) {               throw new FileNotFoundException("not found: " + fileDir.getAbsolutePath());           }           if (!fileDir.isDirectory()) {               throw new IOException("not a directory: " + fileDir.getAbsolutePath());           }           return this;       } ​       /**         * 设置监听器         *         * @param listeners 文件监听器集合         * @return WatchBuilder         */       public WatchBuilder listeners(@Nonnull List listeners) {           this.listeners.addAll(listeners);           return this;       } ​       public FileWatchService build() {           return new FileWatchService(this);       }   } }

建立抽象的文件索引管理器

  • 实现了索引的初始化 ⇒ 所有文件信息的集合

  • 实现了索引的自动维护 => 监听文件变化更新索引信息

  • 未实现的3个方法

  • import cn.hutool.core.collection.CollUtil;
    import cn.note.service.toolkit.filestore.FileStore;
    import cn.note.service.toolkit.filestore.RelativeFileStore;
    import cn.note.service.toolkit.filewatch.FileWatchService;
    import lombok.Getter;
    import lombok.Setter;
    import org.apache.commons.io.filefilter.FileFilterUtils;
    import org.apache.commons.io.filefilter.IOFileFilter;
    import org.apache.commons.io.monitor.FileAlterationListener;
    import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
    ​
    import java.io.File;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    ​
    /**
     * 文件索引管理器
     * 可以用来管理文件基本信息的索引
     *
     * @param  某种文件性质类型的存储管理器
     * @see FileIndexManager
     */
    @Getter
    @Setter
    public abstract class AbstractIndexManager {
    ​
        /*文件存储对象*/
        private FileStore fileStore;
    ​
        /*文件索引*/
        private Map indexes;
    ​
        /*是否初始化完成*/
        private boolean initCompleted;
    ​
    ​
        public AbstractIndexManager(String homeDir) {
            this(new RelativeFileStore(homeDir));
        }
    ​
    ​
        public AbstractIndexManager(FileStore fileStore) {
            this.fileStore = fileStore;
            this.indexes = new ConcurrentHashMap<>();
        }
    ​
    ​
        /**
         * 移除文件
         *
         * @param file 文件
         */
        protected void deleteIndex(File file) {
            indexes.remove(file.getAbsolutePath());
        }
    ​
    ​
        /**
         * 添加文件
         *
         * @param file 文件
         */
        protected void addIndex(File file) {
            String filePath = file.getAbsolutePath();
            indexes.put(filePath, fileToIndexObject(file));
        }
    ​
    ​
        /**
         * 递归遍历文件和子目录生成索引文件 ,默认所有文件和目录
         */
        public void initialize() throws Exception {
            initialize(FileFilterUtils.trueFileFilter(), null);
        }
    ​
        /**
         * 指定文件过滤器,递归遍历文件和子目录生成索引文件
         *
         * @param fileFilter 指定文件过滤器
         */
        public void initialize(IOFileFilter fileFilter) throws Exception {
            initialize(fileFilter, null);
        }
    ​
    ​
        /**
         * 遍历文件至目录
         *
         * @param fileFilter 文件过滤器
         * @param ignoreDirs 忽略目录
         * @throws Exception 遍历文件时发生IO异常, 创建监听器时发生异常
         */
        public void initialize(IOFileFilter fileFilter, List ignoreDirs) throws Exception {
            AbstractTreeFileVisitor fileVisitor = new AbstractTreeFileVisitor() {
                @Override
                public void addNode(Path path, boolean isDir) {
                    File file = path.toFile();
    //                if (!file.equals(fileStore.homeDir())) {
                    if (!isDir) {
                        addIndex(file);
                    }
                }
            };
            fileVisitor.setFileFilter(fileFilter);
            if (ignoreDirs != null) {
                fileVisitor.setIgnoreDirs(ignoreDirs);
            }
            Files.walkFileTree(fileStore.homeDir().toPath(), fileVisitor);
    ​
            createFileWatch(fileFilter);
        }
    ​
    ​
        /**
         * 创建文件监听
         */
        protected void createFileWatch(IOFileFilter fileFilter) throws Exception {
            // 定义拦截动作
            FileAlterationListenerAdaptor fileAlterationListenerAdaptor = new FileAlterationListenerAdaptor() {
    ​
                @Override
                public void onFileCreate(final File file) {
                    addIndex(file);
                }
    ​
                @Override
                public void onFileDelete(final File file) {
                    deleteIndex(file);
                }
            };
    ​
            List listeners = CollUtil.newArrayList(fileAlterationListenerAdaptor);
            // 文件过滤器
            IOFileFilter subFileFilter = FileFilterUtils.or(FileFilterUtils.directoryFileFilter(), fileFilter);
            //根目录和子目录变化
            IOFileFilter filter = FileFilterUtils.or(fileFilter, subFileFilter);
    ​
            // 创建文件监听
            FileWatchService monitorFile = new FileWatchService.WatchBuilder(3, filter).dirPath(fileStore.homeDir().getAbsolutePath())
                    .listeners(listeners).build();
            monitorFile.start();
    ​
            this.initCompleted = true;
    ​
        }
    ​
    ​
        /**
         * @param indexContext 索引内容
         * @return 索引集合
         */
        public abstract List searchIndex(String indexContext);
    ​
    ​
        /**
         * 将文件转文件索引信息
         *
         * @param file 文件
         * @return 文件索引信息
         */
        public abstract T fileToIndexObject(File file);
    ​
    ​
        /**
         * 索引对象反向转文件对象
         *
         * @param index 索引对象
         * @return 文件对象
         */
        public abstract File indexToFileObject(T index);
    ​
    }

文件基本信息的索引

  • 包含文件名称,路径,时间, 是否目录等信息

  • /**
     * 文件基本信息类
     */
    @Setter
    @Getter
    public class FileIndex {
    ​
        /* 文件名称*/
        private String fileName;
    ​
        /* 文件路径*/
        private String relativePath;
    ​
        /* 修改时间 */
        private String modifiedDate;
    ​
        /*是否目录*/
        private boolean dir;
        /**
         * 对fileName 进行忽略大小写
         */
        private String searchName;
    ​
        @Override
        public String toString() {
            String fmt = StrUtil.format("{}( {})", fileName, relativePath);
            return fmt;
        }
    }

  • 扩展文件索引管理, 实现文件名称不区分大小写搜索

  • import cn.hutool.core.util.StrUtil;
    import cn.note.service.toolkit.filestore.FileStore;
    ​
    import java.io.File;
    import java.util.Collections;
    import java.util.List;
    import java.util.stream.Collectors;
    ​
    /**
     * @description: 目录索引管理器
     * @author: jee
     */
    public class FileIndexManager extends AbstractIndexManager {
    ​
    ​
        public FileIndexManager(String homeDir) {
            super(homeDir);
        }
    ​
        public FileIndexManager(FileStore fileStore) {
            super(fileStore);
        }
    ​
        @Override
        public List searchIndex(String indexContext) {
            if (!isInitCompleted()) {
                throw new IllegalStateException("调用是否isInitCompleted 检查初始化是否完成!!!");
            }
    ​
            if (StrUtil.isBlank(indexContext)) {
                return Collections.emptyList();
            }
            final String matchName = indexContext.toLowerCase();
            return getIndexes().values().stream().filter(index -> !index.isDir() && index.getSearchName().contains(matchName)).collect(Collectors.toList());
    ​
        }
    ​
        @Override
        protected FileIndex fileToIndexObject(File file) {
            String fileName = file.getName();
            String relativePath = getFileStore().getRelativePath(file);
            String modifiedDate = getFileStore().getModifiedDate(file);
            FileIndex fileIndex = new FileIndex();
            fileIndex.setFileName(fileName);
            fileIndex.setRelativePath(relativePath);
            fileIndex.setModifiedDate(modifiedDate);
            fileIndex.setDir(file.isDirectory());
            fileIndex.setSearchName(fileName.toLowerCase());
            return fileIndex;
        }
    ​
        @Override
        public File indexToFileObject(FileIndex index) {
            return getFileStore().getFile(index.getRelativePath());
        }
    }

  • 测试

/**
 * 目录文件扫描测试
 */
public class FileIndexManagerTest {
​
​
    public static void main(String[] args) throws Exception {
​
        String home = SystemUtil.getUserInfo().getCurrentDir() + "/note-service-toolkit";
        FileIndexManager fileIndexManager = new FileIndexManager(home);
        fileIndexManager.initialize(FileFilterUtils.suffixFileFilter(".java"), CollUtil.newArrayList("target"));
        CmdWindow.show((cmd) -> {
            List noteFileIndices = fileIndexManager.searchIndex(cmd);
            if (noteFileIndices.size() == 0) {
                Console.log("未匹配到结果!");
            }
            int i = 1;
            for (FileIndex fileIndex : noteFileIndices) {
                Console.log("r: {}, relativePath: {},fileName:{}", i, fileIndex.getRelativePath(), fileIndex.getFileName());
                i++;
            }
        });
​
    }
}
  • java中如何实现全文搜索_第4张图片

so… 如何在swing中使用实现一个文件搜索

  • java中如何实现全文搜索_第5张图片

你可能感兴趣的:(搜索引擎,lucene,elasticsearch,全文检索,java)