数据的分类和查询方法
数据的分类
结构化数据
例如数据库中的数据。
格式固定、长度固定、数据类型固定。
非结构化数据
word 文档、pdf 文档、邮件、html、txt
格式不固定、长度不固定、数据类型不固定。
数据的查询
结构化数据的查询
SQL 语句,查询结构化数据的方法。简单、速度快。
非结构化数据的查询
例:从文本文件中找出包含 Spring 单词的文件:
目测法。
使用程序把文档读取到内存中,然后匹配字符串,顺序扫描。
把非结构化数据变成结构化数据:
先根据空格进行字符串拆分,得到一个单词列表,基于单词列表创建一个索引,然后查询索引,根据单词和文档的对应关系找到文档列表。这个过程就叫做全文检索。
全文检索定义
先创建索引然后查询索引的过程就叫做全文检索。索引一次创建可以多次使用,表现为每次查询速度很快。
全文检索应用场景
搜索引擎
百度、360搜索、谷歌、搜狗。
站内搜索
论坛内搜索、微博搜索、文章搜索、淘宝搜索、京东搜索。
只要是有搜索的地方就可以使用全文检索技术。
Lucene
Lucene 是一个基于 Java 开发全文检索工具包。
索引和搜索流程图
获得文档
原始文档:要基于哪些数据来进行搜索,那么这些数据就是原始文档。
搜索引擎:使用爬虫获取原始文档。
站内搜索:数据库中的数据。
构建文档对象
对应每个原始文档创建一个 Document 对象,每个 Document 对象中包含多个域 (field),域中保存的就是原始文档数据。域的名称和域的值构成一对键值对。
每个文档都有一个唯一的编号,也就是文档的 id。
分析文档
就是分词过程
根据空格进行字符串拆分,得到一个单词列表。
把单词统一转换成小(或大)写。
去除标点符号。
去除停用词(也就是无意义的词)。
每个关键字都封装成一个 Term 对象。Term 对象中包含两部分内容:一是关键字所在的域、二是关键词本身,不同的域中拆分出来的相同的关键词是不同的 Term。
创建索引
基于关键词列表创建一个索引,保存到索引库中。
索引库中包含:
索引
Document 对象
关键词和文档的对应关系
通过词语找文档,这种索引的结构叫做倒排索引结构。
用户查询接口
用户输入查询条件的地方。例如:百度的搜索框。
把关键词封装成一个查询对象
查询对象包含:要查询的域,要搜索的关键字。
执行查询
根据要查询的关键词到对应的域上进行搜索。找到关键词,根据关键词找到对应的文档。
渲染结果
根据文档的 id 找到文档的对象,对关键词进行高亮显示、分页处理。
创建一个 Directory 对象;
基于 Directory 对象创建一个 IndexWriter 对象;
读取磁盘上的文件,对应每一个文件创建一个文档对象;
向文档对象中添加域;
把文档对象写入索引库;
关闭 IndexWriter 对象。
/** * 1. 建立索引库,并把文档映射到索引 * */ @Test public void buildIndexStore() throws IOException { // 1. 创建一个 Directory 对象,指定索引库的位置 Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath()); // 2. 创建 写索引 对象 IndexWriterConfig config = new IndexWriterConfig(); IndexWriter indexWriter = new IndexWriter(directory, config); // 3. 读取磁盘上的文件,对应每个文件创建一个文档对象。 File file = new File("E:\\java\\15_Lucene\\01_lucene_demo\\searchsource"); File[] files = file.listFiles(); for (File f: files) { // 获取文件名 String fileName = f.getName(); System.out.println(fileName); // 获取文件路径 String filePath = f.getPath(); // 获取文件内容 String fileContent = FileUtils.readFileToString(f, "utf-8"); // 获取文件大小 long fileSize = FileUtils.sizeOf(f); // 参数1:域的名称,参数2:域的内容,参数3:是否存储 Field fieldName = new TextField("fileName", fileName, Field.Store.YES); Field fieldPath = new TextField("filePath", filePath, Field.Store.YES); Field fieldContent = new TextField("fileContent", fileContent, Field.Store.YES); Field fieldSize = new TextField("fileSize", String.valueOf(fileSize), Field.Store.YES); // 4. 创建文档对象 Document doc = new Document(); // 向文档对象中添加索引库 doc.add(fieldName); doc.add(fieldPath); doc.add(fieldContent); doc.add(fieldSize); // 5. 把文档对象写入索引库 indexWriter.addDocument(doc); } // 6. 关闭 IndexWriter 对象 indexWriter.close(); }注:可以使用 luke 查看索引库中的内容。
默认情况下使用的是标准分析器 StandardAnalyzer,但是 StandardAnalyzer 对中文分词的支持不是很友好。为了实现对中文的友好支持,可以使用 IKAnalyzer 类。
查看标准分析器 StandardAnalyzer 的分析效果
使用 Analyzer 对象的 tokenStream 方法返回一个 tokenStream 对象。此对象中包含了最终的分词结果。
实现步骤:
创建一个 Analyzer 对象,StandardAnalyzer 对象;使用分析器对象的 tokenStream 方法获得一个 TokenStream 对象;向 TokenStream 对象中设置一个引用,相当于是一个指针;调用 TokenStream 对象的 reset 方法,如果不调用则会抛出异常;使用 while 循环遍历 TokenStream 对象;关闭 TokenStream 对象。代码演示:
/** * 3. 测试使用 Analyzer * */ @Test public void testIKAnalyzer() throws IOException { // 1. 创建一个Analyzer对象,StandardAnalyzer 对象,主要是对英文进行分析 // Analyzer analyzer = new StandardAnalyzer(); // IKAnalyzer 主要是对中文进行分析 IKAnalyzer analyzer = new IKAnalyzer(); // 2. 使用分析器对象的 tokenStream 方法获得一个 TokenStream 对象 // TokenStream tokenStream = analyzer.tokenStream("", "Learn how to create a web page with Spring MVC."); TokenStream tokenStream = analyzer.tokenStream("","今天是个好日子啊,真的是个好日子啊,白日依山尽"); // 3. 向 TokenStream 对象中设置一个引用,相当于是一个指针 CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); // 4. 调用 TokenStream 对象的 reset 方法。如果不调用抛异常 tokenStream.reset(); // 5. 使用while循环遍历TokenStream对象 while(tokenStream.incrementToken()){ System.out.println(charTermAttribute.toString()); } // 6. 关闭 tokenStream 对象 tokenStream.close(); }IKAnalyzer 的使用方法
把 IKAnalyzer 的 jar 包添加到工程中;
* IK-Analyzer-1.0-SNAPSHOT.jar把配置文件和扩展词典添加到工程的 classpath 下。
* hotword.dic * IKAnalyzer.cfg.xml * stopword.dic注意:扩展词严禁使用 windows 自带的记事本编辑,主要是为了保证编码格式为 “UTF-8”。
扩展词典:添加一些新词;停用词词典:无意义的词或者是敏感词汇。代码演示:
// 创建 写索引 对象时,把 IKAnalyzer 对象添加进去配置对象即可 IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());添加文档
/** * 1. 添加文档 * */ @Test public void addDocument() throws IOException { Document document = new Document(); document.add(new TextField("name", "文章标题", Field.Store.YES)); // 是否存储的标准:是否要将内容展示给用户 document.add(new TextField("content", "这是文章的内容哦,弟中弟", Field.Store.NO)); // 不分析,不索引,但要Field存储在文档中 document.add(new StoredField("path", "c:/temp/hello")); // 把 document 对象存储到索引域中 indexWriter.addDocument(document); indexWriter.close(); }删除文档
删除全部文档
/** * 2. 删除所有文档 * */ @Test public void deleteAllDocuments() throws IOException { indexWriter.deleteAll(); indexWriter.close(); }根据查询、关键词查询文档
/** * 3. 删除指定文档 * */ @Test public void deleteDocumentsByTerm() throws Exception{ // 删除域名为 name 域中 包含 “文章” 关键字的文档 indexWriter.deleteDocuments(new Term("name", "文章")); indexWriter.close(); }更新文档
/** * 4. 更新文档 * */ @Test public void updateDocument() throws Exception{ Document document = new Document(); document.add(new TextField("name", "更新后,这是第一个标题", Field.Store.YES)); document.add(new TextField("name1", "更新后,哈哈,介系第二个嘛", Field.Store.YES)); document.add(new TextField("name2", "这是更新后的第三个标题", Field.Store.YES)); indexWriter.updateDocument(new Term("name", "标题"), document); indexWriter.close(); }使用 Query 的子类
TermQuery
根据关键词进行查询,需要指定要查询的域以及关键词。
public void testTermQuery() throws IOException { // 1. 创建一个 Directory 对象,指定索引库的位置 Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath()); // 2. 创建一个 IndexReader 对象 IndexReader indexReader = DirectoryReader.open(directory); // 3. 创建一个 IndexSearcher 对象 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 4. 创建一个 Query 对象,TermQuery Query query = new TermQuery(new Term("fileContent", "spring")); // 5. 执行查询,得到一个TopDocs对象 // 传入查询条件并设置最多返回 10 条结果 TopDocs topDocs = indexSearcher.search(query, 10); System.out.println("总的记录条数为:" + topDocs.totalHits); // 6. 获取文档列表 ScoreDoc[] docs = topDocs.scoreDocs; // 7. 打印文档中的内容 for (ScoreDoc d : docs ) { // 获取文档的 id int doc = d.doc; Document document = indexSearcher.doc(doc); System.out.println(document.get("fileName")); System.out.println(document.get("filePath")); System.out.println(document.get("fileSize")); System.out.println("-------------------------------"); } // 8. 关闭 indexReader 对象 indexReader.close(); }RangeQuery
范围查询。
代码演示:
/** * 限定文件大小范围查找 * */ @Test public void testRangeQuery() throws Exception{ // 查找条件为:域名为 size,且文件大小在 1~1000 字节之间 Query query = LongPoint.newRangeQuery("fileSize", 1, 1000); // 自己写的查询方法,具体怎么写,自己去上面查琢磨琢磨 printQuery(query); }使用 QueryParser 进行查询
可以先对要查询的内容先分词,然后基于分词的结果进行查询。
需要添加如下 jar 包:
* lucene-queryparser-7.4.0.jar代码演示:
/** * 对搜索的内容进行分析,然后查找 * */ @Test public void testQueryParser () throws Exception{ // 创建 queryParser 对象:要搜索哪个域,要对搜索的词使用什么分析器 QueryParser queryParser = new QueryParser("fileName", new IKAnalyzer()); // 要搜索的内容,并且对内容进行分析 Query query = queryParser.parse("lucene是一个Java开发的全文检索工具包"); // 执行查询 printQuery(query); }