纵横小说分布式采集

最后更新于:2022-04-01 19:49:05

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/46812645](http://blog.csdn.net/xiaojimanman/article/details/46812645) [http://www.llwjy.com/blogdetail/9df464b20cca5405c7ce07e2fb2d768f.html](http://www.llwjy.com/blogdetail/9df464b20cca5405c7ce07e2fb2d768f.html) 个人博客站已经上线了,网址 [www.llwjy.com ](http://www.llwjy.com)~欢迎各位吐槽~ ------------------------------------------------------------------------------------------------- 在前面的几篇博客中,我们已经介绍了如何采集纵横小说网站上的信息以及如何把这些信息持久化到数据库中,现在我们就开始介绍如何做分布式采集,让各个模块之间可以完美的配合。 **采集类修改** 在开始介绍分布式采集之前,我们需要对之前介绍的采集类添加一些方法,也就是返回上一篇博客中介绍的小说javabean,具体源码还请参照个人网站上的[博客源码](http://www.llwjy.com/source.html)。 1.简介页 简介页需呀添加一个方法,让它返回简介页的数据信息,具体如下: ~~~ /** * @return * @Author:lulei * @Description: 分析简介页,获取简介页数据 */ public NovelIntroModel getNovelIntro() { NovelIntroModel bean = new NovelIntroModel(); bean.setMd5Id(ParseMD5.parseStrToMd5L32(this.pageUrl)); bean.setName(getName()); bean.setAuthor(getAuthor()); bean.setDescription(getDesc()); bean.setType(getType()); bean.setLastChapter(getLatestChapter()); bean.setChapterlisturl(getChapterListUrl()); bean.setWordCount(getWordCount()); bean.setKeyWords(keyWords()); return bean; } ~~~ 2.阅读页 阅读页内同样需要添加一个方法,让它返回阅读页内的数据信息,具体如下: ~~~ /** * @return * @Author:lulei * @Description: 分析阅读页,获取阅读页数据 */ public NovelReadModel getNovelRead(){ NovelReadModel novel = new NovelReadModel(); novel.setTitle(getTitle()); novel.setWordCount(getWordCount()); novel.setContent(getContent()); return novel; } ~~~ 这些方法都是对之前类中的方法做一个整合,将之前分析到的数据组装成一个javabean返回,方便后面的操作。 **各页采集线程类** 在实现分布式采集的时候,就需要编写各个页面的采集线程类,让他来控制各页面的采集业务,下面我们就一一介绍: 1.更新列表页线程 这个线程的主要功能就是监控更新列表页的数据,提取页面上的简介页URL,认为它们是有更新的页面,将对应的信息持久化到数据库中,具体实现如下: ~~~ /** *@Description: 更新列表页线程 */ package com.lulei.crawl.novel.zongheng; import java.util.List; import java.util.concurrent.TimeUnit; import com.lulei.db.novel.zongheng.ZonghengDb; public class UpdateListThread extends Thread{ private boolean flag = false; private String url;//抓取的更新列表页URL private int frequency;//采集频率 public UpdateListThread(String name, String url, int frequency){ super(name); this.url = url; this.frequency = frequency; } @Override public void run() { flag = true; ZonghengDb db = new ZonghengDb(); while (flag){ try { UpdateList updateList = new UpdateList(url); List urls = updateList.getPageUrls(true); db.saveInfoUrls(urls); TimeUnit.SECONDS.sleep(frequency); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } super.run(); } public static void main(String[] args) { // TODO Auto-generated method stub UpdateListThread thread = new UpdateListThread("llist", "http://book.zongheng.com/store/c0/c0/b9/u0/p1/v0/s9/t0/ALL.html", 60); thread.start(); } } ~~~ 2.简介页&章节列表页线程类 由于一个简介页就对应一个章节列表页,所以我们就把这两个线程合为一个线程,让其实现小说简介信息的采集以及小说章节列表信息的采集,具体实现如下: ~~~ /** *@Description: 小说简介信息线程 */ package com.lulei.crawl.novel.zongheng; import java.util.List; import java.util.concurrent.TimeUnit; import com.lulei.crawl.novel.zongheng.model.NovelIntroModel; import com.lulei.db.novel.zongheng.ZonghengDb; public class IntroPageThread extends Thread { private boolean flag = false; public IntroPageThread(String name) { super(name); } @Override public void run() { flag = true; try { ZonghengDb db = new ZonghengDb(); while (flag) { //随机获取一个待采集的简介页url String url = db.getRandIntroPageUrl(1); if (url != null) { IntroPage intro = new IntroPage(url); NovelIntroModel bean = intro.getNovelIntro(); //采集小说章节列表页信息 ChapterPage chapterPage = new ChapterPage(bean.getChapterlisturl()); List chapters = chapterPage.getChaptersInfo(); bean.setChapterCount(chapters == null ? 0 : chapters.size()); //更新小说简介信息 db.updateInfo(bean); //插入待采集的章节列表 db.saveChapters(chapters); //如果本次有待采集的资源,睡眠一个时间,没有待采集的资源,睡眠另一个时间 TimeUnit.MILLISECONDS.sleep(500); }else { TimeUnit.MILLISECONDS.sleep(1000); } } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { // TODO Auto-generated method stub IntroPageThread thread = new IntroPageThread("novelinfo"); thread.start(); } } ~~~ 3.阅读页线程 这个线程的主要功能就是将小说阅读页的信息采集并持久化到数据库中,具体如下: ~~~ /** *@Description: 小说阅读页线程 */ package com.lulei.crawl.novel.zongheng; import java.util.concurrent.TimeUnit; import com.lulei.crawl.novel.zongheng.model.NovelChapterModel; import com.lulei.crawl.novel.zongheng.model.NovelReadModel; import com.lulei.db.novel.zongheng.ZonghengDb; import com.lulei.util.ParseMD5; public class ReadPageThread extends Thread { private boolean flag = false; public ReadPageThread(String name) { super(name); } @Override public void run() { flag = true; ZonghengDb db = new ZonghengDb(); while (flag) { try { //随机获取待采集的阅读页 NovelChapterModel chapter = db.getRandReadPageUrl(1); if (chapter != null) { ReadPage read = new ReadPage(chapter.getUrl()); NovelReadModel novel = read.getNovelRead(); if (novel == null) { continue; } novel.setChapterId(chapter.getChapterId()); novel.setTime(chapter.getTime()); novel.setUrl(chapter.getUrl()); //保存阅读页信息 db.saveNovelRead(novel); //将状态修改为不需要采集 db.updateChapterState(ParseMD5.parseStrToMd5L32(novel.getUrl()), 0); //如果本次有待采集的资源,睡眠一个时间,没有待采集的资源,睡眠另一个时间 TimeUnit.MILLISECONDS.sleep(500); } else { TimeUnit.MILLISECONDS.sleep(1000); } } catch(Exception e){ e.printStackTrace(); } } } public static void main(String[] args) { ReadPageThread thread = new ReadPageThread("novel read page"); thread.start(); } } ~~~ **分布式采集** 上面已经介绍完了各个线程完成的工作,下面就需要一个类来控制管理这些线程,让其运行起来,具体代码如下: ~~~ /** *@Description: */ package com.lulei.crawl.novel.zongheng; import java.util.List; import com.lulei.crawl.novel.zongheng.model.CrawlListInfo; import com.lulei.db.novel.zongheng.ZonghengDb; public class CrawStart { private static boolean booleanCrawlList = false; private static boolean booleanCrawlIntro = false; //简介页采集线程数目 private static int crawlIntroThreadNum = 2; private static boolean booleanCrawlRead = false; //阅读页采集线程数目 private static int crawlReadThreadNum = 10; /** * @Author:lulei * @Description: 更新列表页采集 */ public void startCrawlList(){ if (booleanCrawlList) { return; } booleanCrawlList = true; ZonghengDb db = new ZonghengDb(); List infos = db.getCrawlListInfos(); if (infos == null) { return; } for (CrawlListInfo info : infos) { if (info.getUrl() == null || "".equals(info.getUrl())) { continue; } UpdateListThread thread = new UpdateListThread(info.getInfo(), info.getUrl(), info.getFrequency()); thread.start(); } } /** * @Author:lulei * @Description: 小说简介页和章节列表页 */ public void startCrawlIntro() { if (booleanCrawlIntro) { return; } booleanCrawlIntro = true; for (int i = 0; i < crawlIntroThreadNum; i++) { IntroPageThread thread = new IntroPageThread("novel info thread" + i); thread.start(); } } /** * @Author:lulei * @Description: 小说阅读页 */ public void startCrawlRead() { if (booleanCrawlRead) { return; } booleanCrawlRead = true; for (int i = 0; i < crawlReadThreadNum; i++) { ReadPageThread thread = new ReadPageThread("novel read page" + i); thread.start(); } } public static void main(String[] args) { CrawStart start = new CrawStart(); start.startCrawlList(); start.startCrawlIntro(); start.startCrawlRead(); } } ~~~ **运行结果** 通过上面的这几个步骤,纵横小说的分布式采集程序已经完成,下面就为大家展示一下采集后的数据库截图 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf2a3397.jpg) **写在最后** 在上面的线程实现中,有很多的配置信息,比如说线程中的两个请求之间的间隔时间以及各类线程的数量,像这些信息我们都可以将其写到配置文件中,方便之后的修改(这里写到程序中是方便大家的理解,还请见谅)。 ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 基于[lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://www.llwjy.com/blogtype/lucene.html)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/blogtype/lucene.html ------------------------------------------------------------------------------------------------- 小福利 ------------------------------------------------------------------------------------------------- 个人在极客学院上《Lucene案例开发》课程已经上线了(目前上线到第二课),欢迎大家吐槽~ [第一课:Lucene概述](http://www.jikexueyuan.com/course/937.html) [第二课:Lucene 常用功能介绍](http://www.jikexueyuan.com/course/1292.html)
';

纵横小说数据库操作

最后更新于:2022-04-01 19:49:03

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/46785223](http://blog.csdn.net/xiaojimanman/article/details/46785223) [http://www.llwjy.com/blogdetail/efda32f346445dd8423a942aa4c8c2cd.html](http://www.llwjy.com/blogdetail/efda32f346445dd8423a942aa4c8c2cd.html) 个人博客站已经上线了,网址 [www.llwjy.com](http://www.llwjy.com) ~欢迎各位吐槽~ ------------------------------------------------------------------------------------------------- 首先和大家说一生抱歉,由于最近经常在外面出差,博客断更了很长时间,后面不出意外的话,博客会恢复更新。 在上次的博客中已经介绍了纵横小说的数据库表结构,这里需要说明的是,我在设计数据表的时候,取消了数据表之间的外键,至于为什么这样做这里就不再多说,感兴趣的可以自行百度下。下面我们就开始今天的介绍: **模版类** 在介绍数据库的操作之前,我们首先看一下定义的模版(javabean),这里定义了四个模版分别为抓取入口信息模版、小说简介页模版、小说章节列表模版、小说阅读页模版,类中只有一些简单的set和put方法,下面就看下具体的代码实现: 1.CrawlListInfo ~~~ /** *@Description: */ package com.lulei.crawl.novel.zongheng.model; public class CrawlListInfo { private String url; private String info; private int frequency; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } public int getFrequency() { return frequency; } public void setFrequency(int frequency) { this.frequency = frequency; } } ~~~ 2.NovelIntroModel ~~~ /** *@Description: */ package com.lulei.crawl.novel.zongheng.model; public class NovelIntroModel { private String md5Id; private String name; private String author; private String description; private String type; private String lastChapter; private String chapterlisturl; private int wordCount; private String keyWords; private int chapterCount; public String getMd5Id() { return md5Id; } public void setMd5Id(String md5Id) { this.md5Id = md5Id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getLastChapter() { return lastChapter; } public void setLastChapter(String lastChapter) { this.lastChapter = lastChapter; } public String getChapterlisturl() { return chapterlisturl; } public void setChapterlisturl(String chapterlisturl) { this.chapterlisturl = chapterlisturl; } public int getWordCount() { return wordCount; } public void setWordCount(int wordCount) { this.wordCount = wordCount; } public String getKeyWords() { return keyWords; } public void setKeyWords(String keyWords) { this.keyWords = keyWords; } public int getChapterCount() { return chapterCount; } public void setChapterCount(int chapterCount) { this.chapterCount = chapterCount; } } ~~~ 3.NovelChapterModel ~~~ /** *@Description: */ package com.lulei.crawl.novel.zongheng.model; public class NovelChapterModel { private String url; private int chapterId; private long time; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getChapterId() { return chapterId; } public void setChapterId(int chapterId) { this.chapterId = chapterId; } public long getTime() { return time; } public void setTime(long time) { this.time = time; } } ~~~ 4.NovelReadModel ~~~ /** *@Description: */ package com.lulei.crawl.novel.zongheng.model; public class NovelReadModel extends NovelChapterModel { private String title; private int wordCount; private String content; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getWordCount() { return wordCount; } public void setWordCount(int wordCount) { this.wordCount = wordCount; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } ~~~ **数据库操作** 这里的数据库操作我们使用博客[《基于lucene的案例开发:数据库连接池》](http://www.llwjy.com/blogdetail/9f4d773be6ae1408b4b70ddd789360f4.html)介绍的数据库连接池,在采集这个业务过程中,主要是插入和查询操作,当然还有记录的状态值的更新操作,下面我们就每一个操作介绍一个方法,方面大家理解如何使用我们自己的数据库连接池操作来完成数据库的增删改查操作。 1.数据表查询:随机获取一条记录 我们之后的爬虫希望可以做成分布式的采集,因此这里我们在获取简介页的URL时候,我们可以每次获取一个随机值,这样在线程之间出现同时采集一个URL的情况就会大大降低,至于Mysql中的随机我们可以使用 order by rand() limit n 来获取前n条记录,其他的数据库实现方式稍微有点差异。 ~~~ /** * @param state * @return * @Author:lulei * @Description: 随机获取一个简介url */ public String getRandIntroPageUrl(int state) { DBServer dbServer = new DBServer(POOLNAME); try { String sql = "select * from novelinfo where state = '" + state + "' order by rand() limit 1"; ResultSet rs = dbServer.select(sql); while (rs.next()) { return rs.getString("url"); } } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } return null; } ~~~ 在这个方法中,我们直接使用DBServer中的select(String sql)方法即可执行对应的sql语句,他的返回值就是查询的结果集。 2.数据表更新:修改简介页的抓取状态 在简介页一次采集完成之后或者更新列表页检测到该简介页有更新的时候,我们需要对小说的简介页的抓取状态进行修改,标识这个简介页已经完成采集或需要采集,我们直接使用DBServer中的update(String sql)方法即可执行对应的sql语句。 ~~~ /** * @param md5Id * @param state * @Author:lulei * @Description: 修改简介页的抓取状态 */ public void updateInfoState(String md5Id, int state) { DBServer dbServer = new DBServer(POOLNAME); try { String sql = "update novelinfo set state = '" + state + "' where id = '" + md5Id + "'"; dbServer.update(sql); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } ~~~ 3.数据表插入:保存小说阅读页信息 在完成小说阅读页数据解析之后,我们需要将解析后的数据持久化到数据库中,这里我们可以使用DBServer中的insert(String table, String columns, HashMap params)方法即可执行相关的插入操作。 ~~~ /** * @param novel * @Author:lulei * @Description: 保存小说阅读页信息 */ public void saveNovelRead(NovelReadModel novel) { if (novel == null) { return; } DBServer dbServer = new DBServer(POOLNAME); try { HashMap params = new HashMap(); int i = 1; String md5Id = ParseMD5.parseStrToMd5L32(novel.getUrl()); //如果已经存在,则直接返回 if (haveReadUrl(md5Id)) { return; } long now = System.currentTimeMillis(); params.put(i++, md5Id); params.put(i++, novel.getUrl()); params.put(i++, novel.getTitle()); params.put(i++, novel.getWordCount()); params.put(i++, novel.getChapterId()); params.put(i++, novel.getContent()); params.put(i++, novel.getTime()); params.put(i++, now); params.put(i++, now); dbServer.insert("novelchapterdetail", "id,url,title,wordcount,chapterid,content,chaptertime,createtime,updatetime", params); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } ~~~ **庐山真面目** 完整的纵横小说数据库操作类代码如下: ~~~ /** *@Description: 纵横中文小说数据库操作 */ package com.lulei.db.novel.zongheng; import java.sql.ResultSet; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.lulei.crawl.novel.zongheng.model.CrawlListInfo; import com.lulei.crawl.novel.zongheng.model.NovelChapterModel; import com.lulei.crawl.novel.zongheng.model.NovelIntroModel; import com.lulei.crawl.novel.zongheng.model.NovelReadModel; import com.lulei.db.manager.DBServer; import com.lulei.util.ParseMD5; public class ZonghengDb { private static final String POOLNAME = "proxool.test"; /** * @param urls * @Author:lulei * @Description: 保存更新列表采集到的URL */ public void saveInfoUrls(List urls) { if (urls == null || urls.size() < 1) { return; } for (String url : urls) { String md5Id = ParseMD5.parseStrToMd5L32(url); if (haveInfoUrl(md5Id)) { updateInfoState(md5Id, 1); } else { insertInfoUrl(md5Id, url); } } } /** * @param state * @return * @Author:lulei * @Description: 随机获取一个简介url */ public String getRandIntroPageUrl(int state) { DBServer dbServer = new DBServer(POOLNAME); try { String sql = "select * from novelinfo where state = '" + state + "' order by rand() limit 1"; ResultSet rs = dbServer.select(sql); while (rs.next()) { return rs.getString("url"); } } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } return null; } /** * @param state * @return * @Author:lulei * @Description: 随机获取一个章节信息 */ public NovelChapterModel getRandReadPageUrl(int state) { DBServer dbServer = new DBServer(POOLNAME); try { String sql = "select * from novelchapter where state = '" + state + "' order by rand() limit 1"; ResultSet rs = dbServer.select(sql); while (rs.next()) { NovelChapterModel chapter = new NovelChapterModel(); chapter.setChapterId(rs.getInt("chapterid")); chapter.setTime(rs.getLong("chaptertime")); chapter.setUrl(rs.getString("url")); return chapter; } } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } return null; } /** * @param novel * @Author:lulei * @Description: 保存小说阅读页信息 */ public void saveNovelRead(NovelReadModel novel) { if (novel == null) { return; } DBServer dbServer = new DBServer(POOLNAME); try { HashMap params = new HashMap(); int i = 1; String md5Id = ParseMD5.parseStrToMd5L32(novel.getUrl()); //如果已经存在,则直接返回 if (haveReadUrl(md5Id)) { return; } long now = System.currentTimeMillis(); params.put(i++, md5Id); params.put(i++, novel.getUrl()); params.put(i++, novel.getTitle()); params.put(i++, novel.getWordCount()); params.put(i++, novel.getChapterId()); params.put(i++, novel.getContent()); params.put(i++, novel.getTime()); params.put(i++, now); params.put(i++, now); dbServer.insert("novelchapterdetail", "id,url,title,wordcount,chapterid,content,chaptertime,createtime,updatetime", params); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } /** * @return * @Author:lulei * @Description: 获取监控的更新列表页 */ public List getCrawlListInfos(){ List infos = new ArrayList(); DBServer dbServer = new DBServer(POOLNAME); try { String sql = "select * from crawllist where state = '1'"; ResultSet rs = dbServer.select(sql); while (rs.next()) { CrawlListInfo info = new CrawlListInfo(); infos.add(info); info.setFrequency(rs.getInt("frequency")); info.setInfo(rs.getString("info")); info.setUrl(rs.getString("url")); } } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } return infos; } /** * @param bean * @Author:lulei * @Description: 更新简介页记录 */ public void updateInfo(NovelIntroModel bean) { if (bean == null) { return; } DBServer dbServer = new DBServer(POOLNAME); try { HashMap params = new HashMap(); int i = 1; params.put(i++, bean.getName()); params.put(i++, bean.getAuthor()); params.put(i++, bean.getDescription()); params.put(i++, bean.getType()); params.put(i++, bean.getLastChapter()); params.put(i++, bean.getChapterCount()); params.put(i++, bean.getChapterlisturl()); params.put(i++, bean.getWordCount()); params.put(i++, bean.getKeyWords()); long now = System.currentTimeMillis(); params.put(i++, now); params.put(i++, "0"); String columns = "name, author, description, type, lastchapter, chaptercount, chapterlisturl, wordcount, keywords, updatetime, state"; String condition = "where id = '" + bean.getMd5Id() + "'"; dbServer.update("novelinfo", columns, condition, params); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } /** * @param chapters * @Author:lulei * @Description: 保存章节列表信息 */ public void saveChapters(List chapters) { if (chapters == null) { return; } DBServer dbServer = new DBServer(POOLNAME); try { for (int i = 0; i < chapters.size(); i++) { String[] chapter = chapters.get(i); if (chapter.length != 4) { continue; } //name、wordcount、time、url String md5Id = ParseMD5.parseStrToMd5L32(chapter[3]); if (!haveChapterUrl(md5Id)) { insertChapterUrl(chapter, i); } } } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } /** * @param md5Id * @param state * @Author:lulei * @Description: 修改简介页的抓取状态 */ public void updateInfoState(String md5Id, int state) { DBServer dbServer = new DBServer(POOLNAME); try { String sql = "update novelinfo set state = '" + state + "' where id = '" + md5Id + "'"; dbServer.update(sql); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } /** * @param md5Id * @param state * @Author:lulei * @Description: 更新章节列表采集状态 */ public void updateChapterState(String md5Id, int state) { DBServer dbServer = new DBServer(POOLNAME); try { String sql = "update novelchapter set state = '" + state + "' where id = '" + md5Id + "'"; dbServer.update(sql); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } /** * @param md5Id * @param url * @Author:lulei * @Description: 新增一个抓取简介页 */ private void insertInfoUrl(String md5Id, String url) { DBServer dbServer = new DBServer(POOLNAME); try { HashMap params = new HashMap(); int i = 1; params.put(i++, md5Id); params.put(i++, url); long now = System.currentTimeMillis(); params.put(i++, now); params.put(i++, now); params.put(i++, "1"); dbServer.insert("novelinfo", "id, url, createtime, updatetime, state", params); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } /** * @param md5Id * @return * @Author:lulei * @Description: 判断简介页是否存在 */ private boolean haveInfoUrl(String md5Id) { DBServer dbServer = new DBServer(POOLNAME); try { ResultSet rs = dbServer.select("select sum(1) as count from novelinfo where id = '" + md5Id + "'"); if (rs.next()) { int count = rs.getInt("count"); return count > 0; } return false; } catch (Exception e) { e.printStackTrace(); return true; } finally{ dbServer.close(); } } /** * @param md5Id * @return * @Author:lulei * @Description: 判断阅读页信息是否存在 */ private boolean haveReadUrl(String md5Id) { DBServer dbServer = new DBServer(POOLNAME); try { ResultSet rs = dbServer.select("select sum(1) as count from novelchapterdetail where id = '" + md5Id + "'"); if (rs.next()) { int count = rs.getInt("count"); return count > 0; } return false; } catch (Exception e) { e.printStackTrace(); return true; } finally{ dbServer.close(); } } /** * @param chapter * @param chapterId * @Author:lulei * @Description: 插入章节列表页信息 */ private void insertChapterUrl(String[] chapter, int chapterId) { //name、wordcount、time、url DBServer dbServer = new DBServer(POOLNAME); try { HashMap params = new HashMap(); int i = 1; params.put(i++, ParseMD5.parseStrToMd5L32(chapter[3])); params.put(i++, chapter[3]); params.put(i++, chapter[0]); params.put(i++, chapter[1]); params.put(i++, chapterId); params.put(i++, chapter[2]); long now = System.currentTimeMillis(); params.put(i++, now); params.put(i++, "1"); dbServer.insert("novelchapter", "id, url, title, wordcount, chapterid, chaptertime, createtime, state", params); } catch (Exception e) { e.printStackTrace(); } finally{ dbServer.close(); } } /** * @param md5Id * @return * @Author:lulei * @Description: 是否存在章节信息 */ private boolean haveChapterUrl(String md5Id) { DBServer dbServer = new DBServer(POOLNAME); try { ResultSet rs = dbServer.select("select sum(1) as count from novelchapter where id = '" + md5Id + "'"); if (rs.next()) { int count = rs.getInt("count"); return count > 0; } return false; } catch (Exception e) { e.printStackTrace(); return true; } finally{ dbServer.close(); } } public static void main(String[] args) { // TODO Auto-generated method stub } } ~~~ 对于上面的代码还希望大家可以认真的阅读下,里面有一些简单的去重操作;在下一篇博客中我们将会介绍如何基于这写数据库操作来实现分布式采集。 ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于[ 基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://www.llwjy.com/blogtype/lucene.html)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/blogtype/lucene.html ------------------------------------------------------------------------------------------------- 小福利 ------------------------------------------------------------------------------------------------- 个人在极客学院上《Lucene案例开发》课程已经上线了(目前上线到第二课),欢迎大家吐槽~ [第一课:Lucene概述](http://www.jikexueyuan.com/course/937.html) [第二课:Lucene 常用功能介绍](http://www.jikexueyuan.com/course/1292.html)
';

纵横小说数据库设计

最后更新于:2022-04-01 19:49:00

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/45694049](http://blog.csdn.net/xiaojimanman/article/details/45694049) [http://www.llwjy.com/blogdetail/fa404163e42295646ab6e36e1ddb1037.html](http://www.llwjy.com/blogdetail/fa404163e42295646ab6e36e1ddb1037.html) 个人博客站已经上线了,网址 [www.llwjy.com](http://www.llwjy.com) ~欢迎各位吐槽~ ------------------------------------------------------------------------------------------------- 首先和大家说一声抱歉,由于最近公司和个人的原因,博客停更已经有一个月了,最近自己会慢慢的恢复博客的更新。 在前面的几篇中已经介绍了纵横中文小说网的采集,这篇博客就介绍下数据库的设计。 **设计思路** 对于纵横中文小说网的四个采集类,我们将设计四张表来存储相关的信息。表**crawllist**主要存储采集的入口,存储着采集的地址、采集状态和采集频率;表**novelinfo**存储由更新列表页采集程序得到的url,并通过简介页采集程序将其他的信息更新完整;表**novelchapter**存储由章节列表采集程序得到的信息;表**novelchapterdetail**存储由小说阅读页采集程序得到的信息。表novelchapterdetail的信息可以合并到表novelchapter中,但这里为了以后的拓展需要,特意将其分开。 在这些表中,添加一个**state**字段,该字段标识这该项目下的url是否需要采集,这个字段是实现分布式采集的关键所在。 **表设计图** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf28b959.jpg) **sql文** ~~~ /* Navicat MySQL Data Transfer Source Server : 本机数据库 Source Server Version : 50151 Source Host : localhost:3306 Source Database : novel Target Server Type : MYSQL Target Server Version : 50151 File Encoding : 65001 Date: 2015-05-13 15:37:35 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `crawllist` 小说采集入口 -- ---------------------------- DROP TABLE IF EXISTS `crawllist`; CREATE TABLE `crawllist` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `url` varchar(100) NOT NULL,##采集url `state` enum('1','0') NOT NULL,##采集状态 `info` varchar(100) DEFAULT NULL,##描述 `frequency` int(11) DEFAULT '60',##采集频率 PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for `novelchapter` 小说章节信息 -- ---------------------------- DROP TABLE IF EXISTS `novelchapter`; CREATE TABLE `novelchapter` ( `id` varchar(32) NOT NULL, `url` varchar(100) NOT NULL,##阅读页URL `title` varchar(50) DEFAULT NULL,##章节名 `wordcount` int(11) DEFAULT NULL,##字数 `chapterid` int(11) DEFAULT NULL,##章节排序 `chaptertime` bigint(20) DEFAULT NULL,##章节时间 `createtime` bigint(20) DEFAULT NULL,##创建时间 `state` enum('1','0') NOT NULL,##采集状态 PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for `novelchapterdetail` 小说章节详细信息 -- ---------------------------- DROP TABLE IF EXISTS `novelchapterdetail`; CREATE TABLE `novelchapterdetail` ( `id` varchar(32) NOT NULL, `url` varchar(100) NOT NULL,##阅读页url `title` varchar(50) DEFAULT NULL,##章节标题 `wordcount` int(11) DEFAULT NULL,##字数 `chapterid` int(11) DEFAULT NULL,##章节排序 `content` text,##正文 `chaptertime` bigint(20) DEFAULT NULL,##章节时间 `createtime` bigint(20) DEFAULT NULL,##创建时间 `updatetime` bigint(20) DEFAULT NULL,##最后更新时间 PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for `novelinfo` 小说简介信息 -- ---------------------------- DROP TABLE IF EXISTS `novelinfo`; CREATE TABLE `novelinfo` ( `id` varchar(32) NOT NULL, `url` varchar(100) NOT NULL,##简介页url `name` varchar(50) DEFAULT NULL,##小说名 `author` varchar(30) DEFAULT NULL,##作者名 `description` text,##小说简介 `type` varchar(20) DEFAULT NULL,##分类 `lastchapter` varchar(100) DEFAULT NULL,##最新章节名 `chaptercount` int(11) DEFAULT NULL,##章节数 `chapterlisturl` varchar(100) DEFAULT NULL,##章节列表页url `wordcount` int(11) DEFAULT NULL,##字数 `keywords` varchar(100) DEFAULT NULL,##关键字 `createtime` bigint(20) DEFAULT NULL,##创建时间 `updatetime` bigint(20) DEFAULT NULL,##最后更新时间 `state` enum('1','0') NOT NULL,##采集状态 PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ~~~ ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/blogtype/lucene.html
';

纵横小说阅读页采集

最后更新于:2022-04-01 19:48:58

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/44937073](http://blog.csdn.net/xiaojimanman/article/details/44937073) [http://www.llwjy.com/blogdetail/29bd8de30e8d17871c707b76ec3212b0.html](http://www.llwjy.com/blogdetail/29bd8de30e8d17871c707b76ec3212b0.html) 个人博客站已经上线了,网址 [www.llwjy.com](http://www.llwjy.com) ~欢迎各位吐槽~ ------------------------------------------------------------------------------------------------- 在之前的三篇博客中,我们已经介绍了关于纵横小说的更新列表页、简介页、章节列表页的相关信息采集,今天这篇博客就重点介绍一下最重要的阅读页的信息采集。本文还是以一个简单的URL为例,网址如下:http://book.zongheng.com/chapter/362857/6001264.html 。 页面分析 上述url网址下的下面样式如下: ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf1d830c.jpg) 阅读页和章节列表页一样,都无法通过简单的鼠标右键-->查看网页源代码 这个操作,所以还是通过**F12-->NetWork-->Ctrl+F5**这个操作找到页面的源代码,结果截图如下: ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf217a75.jpg) 对页面源代码做简单的查找,即可找到标题、字数和章节内容这些属性值所在的位置分别是 **47行、141行和145行**(页面不同,可能所在的行数也略微有点差别,具体的行数请个人根据实际情况来确定)。 对于这三部分的正则,因为和之前的大同小异,使用的方法之前也已经介绍了,所以这里就只给出最终的结果: ~~~ \\章节内容正则 private static final String CONTENT = "
(.*?)
"; \\标题正则 private static final String TITLE = "chapterName=\"(.*?)\""; \\字数正则 private static final String WORDCOUNT = "itemprop=\"wordCount\">(\\d*)"; ~~~ **运行结果** ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf25c180.jpg) 看到运行结果的截图,你也许会发现一个问题,就是章节内容中含有一些html标签,这里是因为我们的案例最终的展示是网页展示,所以这里就偷个懒,如果需要去掉这些标签的,可以直接通过String的repalceAll方法对其替换。 **源代码** 查看最新源代码请访问:http://www.llwjy.com/source/com.lulei.crawl.novel.zongheng.ReadPage.html ~~~ /** *@Description: 阅读页 */ package com.lulei.crawl.novel.zongheng; import java.io.IOException; import java.util.HashMap; import com.lulei.crawl.CrawlBase; import com.lulei.util.DoRegex; import com.lulei.util.ParseUtil; public class ReadPage extends CrawlBase { private static final String CONTENT = "
(.*?)
"; private static final String TITLE = "chapterName=\"(.*?)\""; private static final String WORDCOUNT = "itemprop=\"wordCount\">(\\d*)"; private String pageUrl; private static HashMap params; /** * 添加相关头信息,对请求进行伪装 */ static { params = new HashMap(); params.put("Referer", "http://book.zongheng.com"); params.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36"); } public ReadPage(String url) throws IOException { readPageByGet(url, "utf-8", params); this.pageUrl = url; } /** * @return * @Author:lulei * @Description: 章节标题 */ private String getTitle() { return DoRegex.getFirstString(getPageSourceCode(), TITLE, 1); } /** * @return * @Author:lulei * @Description: 字数 */ private int getWordCount() { String wordCount = DoRegex.getFirstString(getPageSourceCode(), WORDCOUNT, 1); return ParseUtil.parseStringToInt(wordCount, 0); } /** * @return * @Author:lulei * @Description: 正文 */ private String getContent() { return DoRegex.getFirstString(getPageSourceCode(), CONTENT, 1); } public static void main(String[] args) throws IOException { // TODO Auto-generated method stub ReadPage readPage = new ReadPage("http://book.zongheng.com/chapter/362857/6001264.html"); System.out.println(readPage.pageUrl); System.out.println(readPage.getTitle()); System.out.println(readPage.getWordCount()); System.out.println(readPage.getContent()); } } ~~~ ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/blogtype/lucene.html
';

纵横小说章节列表采集

最后更新于:2022-04-01 19:48:56

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/44854719](http://blog.csdn.net/xiaojimanman/article/details/44854719) [http://www.llwjy.com/blogdetail/ddcad68eeb91034247ffa331eb461213.html](http://www.llwjy.com/blogdetail/ddcad68eeb91034247ffa331eb461213.html) 个人博客站已经上线了,网址 [www.llwjy.com](http://www.llwjy.com) ~欢迎各位吐槽~ ------------------------------------------------------------------------------------------------- 在上两篇博客中,已经介绍了纵横中文小说的更新列表页和简介页内容的采集,这篇将介绍从简介页采集获得的下一跳章节列表页的信息采集,事例地址:http://book.zongheng.com/showchapter/362857.html **页面分析** 通过对页面的分析,我们可以确定下图中的部分就是我们需要采集信息及下一跳的地址。 ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf11d7d5.jpg) 这里当我们想用鼠标右键--查看网页源代码的时候发现页面已经把鼠标右键这个操作屏蔽了,因此我们只能采用另一种办法来查看源代码,对页面进行分析。在当前页面,按下**F12**,会出现一个新窗口,也就是之前博客中提到的审查元素出现的窗口,选中Network选项卡,按下 Ctrl + F5,会出现如下画面: ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf13bdf8.jpg) 鼠标单机红色选中部分,即可查看网页源代码,效果图如下: ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf15796a.jpg) 对网页源代码做简单的分析,我们很容易找到章节信息所在的部分,如下图: ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf1861fe.jpg) 每一个章节信息都存储在td标签内,因此对这部分信息我们确定最后的正则表达式为“  ”。 **代码实现** 对于章节列表也信息的采集我们采用和简介页相同的方法,创建一个CrawlBase子类,用它来完成相关信息的采集。对于请求伪装等操作参照更新列表页中的介绍,这里只介绍DoRegex类中的一个方法: ~~~ List getListArray(String dealStr, String regexStr, int[] array) ~~~ 第一个参数是需要查询的字符串,第二个参数是正则表达式,第三个是需要提取的信息在正则表达式中的定位,函数的整体功能是返回字符串中所有满足条件的信息。 **运行结果** ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf1ad3d6.jpg) **源代码** 查看最新源代码请访问:http://www.llwjy.com/source/com.lulei.crawl.novel.zongheng.ChapterPage.html ~~~ /** *@Description: 章节列表页 */ package com.lulei.crawl.novel.zongheng; import java.io.IOException; import java.util.HashMap; import java.util.List; import com.lulei.crawl.CrawlBase; import com.lulei.util.DoRegex; public class ChapterPage extends CrawlBase { private static final String CHAPTER = ""; private static final int []ARRAY = {1, 2, 3, 4}; private static HashMap params; /** * 添加相关头信息,对请求进行伪装 */ static { params = new HashMap(); params.put("Referer", "http://book.zongheng.com"); params.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36"); } public ChapterPage(String url) throws IOException { readPageByGet(url, "utf-8", params); } public List getChaptersInfo() { return DoRegex.getListArray(getPageSourceCode(), CHAPTER, ARRAY); } public static void main(String[] args) throws IOException { ChapterPage chapterPage = new ChapterPage("http://book.zongheng.com/showchapter/362857.html"); for (String []ss : chapterPage.getChaptersInfo()) { for (String s : ss) { System.out.println(s); } System.out.println("---------------------------------------------------- "); } } } ~~~ ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/blogtype/lucene.html
';

纵横小说简介页采集

最后更新于:2022-04-01 19:48:54

转载请注明出处:http://blog.csdn.net/xiaojimanman/article/details/44851419 http://www.llwjy.com/blogdetail/1b5ae17c513d127838c2e02102b5bb87.html 个人博客站已经上线了,网址 [www.llwjy.com](http://www.llwjy.com) ~欢迎各位吐槽~ ------------------------------------------------------------------------------------------------- 在上一篇博客中,我们已经对纵横中文小说的更新列表页做了简单的采集,获得了小说简介页的URL,因此这篇博客我们就介绍纵横中文小说简介页信息的采集,事例地址:http://book.zongheng.com/book/362857.html **页面分析** 在开始之前,建议个人先看一下简介页的样子,下图只是我们要采集的信息所在的区域。 ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf05a44f.jpg) 在这一部分,我们需要获取书名、作者名、分类、字数、简介、最新章节名、章节页URL和标签等信息。在页面上,我们通过鼠标右键--查看网页源代码 发现下面一个现象 ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf0800e6.jpg) 纵横小说为了做360的seo,把小说的一些关键信息放到head中,这样就大大减少我们下正则的复杂度,由于这几个正则大同小异,所以就只用书名做简单的介绍,其余的正则可以参照后面的源代码。 这里的书名在上述截图中的**33行**,我们需要提取中间的**飞仙诀** 信息,因此我们提取该信息的正则表达式为”  “ ,其他信息和此正则类似。通过上图这部分源代码我们可以轻易的获取书名、作者名、最新章节、简介、分类和章节列表页URL,对于标签和字数这两个字段,我们就需要继续分析下面的源代码。通过简单的查找,我们可以找到下图中的源代码,这里就包含我们需要的字数和标签两个属性。 ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf0aa64f.jpg) 对于字数这个属性,我们可以通过简单的正则表达式 ” (\d*?) “ 获取,而对于标签这个属性,我们需要通过两步才能得到想要的内容。 **第一步**:获取keyword所在的html代码,也就是上图中的**234行**,这一步的正则表达式为 ”
(.*?)
 “; **第二步**:对第一步获得的部分html做进一步提取,获取想要的内容,这一步的正则表达式为 ” (.*?) “。 **代码实现** 对于非更新列表也的网页信息采集,我们统一继承CrawlBase类,对于如何伪装可以参照上一篇博客,这里就重点介绍DoRegex类中的两个方法 **方法一:** ~~~ String getFirstString(String dealStr, String regexStr, int n) ~~~ 这里的第一个参数是要处理的字符串,这里也就是网页源代码,第二个参数是要查找内容的正则表达式,第三个参数是要提取的内容在正则表达式中的位置,函数的功能是从指定的字符串中查找与正则第一个匹配的内容,返回指定的提取信息。 **方法二:** ~~~ String getString(String dealStr, String regexStr, String splitStr, int n) ~~~ 这里的第1、2、4参数分别对应方法一中的第1、2、3参数,参数splitStr的意义是分隔符,函数的功能是在指定的字符串中查找与正则表达式匹配的内容,之间用指定的分隔符隔开。 **运行结果** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf0cfe92.jpg) **源代码** 通过对上面两个方法的介绍,相信对于下面的源代码也会很简单。 ~~~ /** *@Description: 简介页 */ package com.lulei.crawl.novel.zongheng; import java.io.IOException; import java.util.HashMap; import com.lulei.crawl.CrawlBase; import com.lulei.util.DoRegex; import com.lulei.util.ParseUtil; public class IntroPage extends CrawlBase { private static final String NAME = " "; private static final String AUTHOR = " "; private static final String DESC = " "; private static final String TYPE = " "; private static final String LATESTCHAPTER = " "; private static final String CHAPTERLISTURL = " "; private static final String WORDCOUNT = "(\\d*?)"; private static final String KEYWORDS = "
(.*?)
"; private static final String KEYWORD = "(.*?)"; private String pageUrl; private static HashMap params; /** * 添加相关头信息,对请求进行伪装 */ static { params = new HashMap(); params.put("Referer", "http://book.zongheng.com"); params.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36"); } public IntroPage(String url) throws IOException { readPageByGet(url, "utf-8", params); this.pageUrl = url; } /** * @return * @Author:lulei * @Description: 获取书名 */ private String getName() { return DoRegex.getFirstString(getPageSourceCode(), NAME, 1); } /** * @return * @Author:lulei * @Description: 获取作者名 */ private String getAuthor() { return DoRegex.getFirstString(getPageSourceCode(), AUTHOR, 1); } /** * @return * @Author:lulei * @Description: 书籍简介 */ private String getDesc() { return DoRegex.getFirstString(getPageSourceCode(), DESC, 1); } /** * @return * @Author:lulei * @Description: 书籍分类 */ private String getType() { return DoRegex.getFirstString(getPageSourceCode(), TYPE, 1); } /** * @return * @Author:lulei * @Description: 最新章节 */ private String getLatestChapter() { return DoRegex.getFirstString(getPageSourceCode(), LATESTCHAPTER, 1); } /** * @return * @Author:lulei * @Description: 章节列表页Url */ private String getChapterListUrl() { return DoRegex.getFirstString(getPageSourceCode(), CHAPTERLISTURL, 1); } /** * @return * @Author:lulei * @Description: 字数 */ private int getWordCount() { String wordCount = DoRegex.getFirstString(getPageSourceCode(), WORDCOUNT, 1); return ParseUtil.parseStringToInt(wordCount, 0); } /** * @return * @Author:lulei * @Description: 标签 */ private String keyWords() { String keyHtml = DoRegex.getFirstString(getPageSourceCode(), KEYWORDS, 1); return DoRegex.getString(keyHtml, KEYWORD, " ", 1); } public static void main(String[] args) throws IOException { // TODO Auto-generated method stub IntroPage intro = new IntroPage("http://book.zongheng.com/book/362857.html"); System.out.println(intro.pageUrl); System.out.println(intro.getName()); System.out.println(intro.getAuthor()); System.out.println(intro.getDesc()); System.out.println(intro.getType()); System.out.println(intro.getLatestChapter()); System.out.println(intro.getChapterListUrl()); System.out.println(intro.getWordCount()); System.out.println(intro.keyWords()); } } ~~~ ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/blogtype/lucene.html
';

纵横小说更新列表页抓取

最后更新于:2022-04-01 19:48:51

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/44831003](http://blog.csdn.net/xiaojimanman/article/details/44831003) [http://www.llwjy.com/blogdetail/a2d1df2b69f17696865f086777996fb1.html](http://www.llwjy.com/blogdetail/a2d1df2b69f17696865f086777996fb1.html) 个人博客站已经上线了,网址 [www.llwjy.com](#) ~欢迎各位吐槽~ ------------------------------------------------------------------------------------------------- 之前的博客,已经介绍了如何基于Lucene来开发站内搜索的大部分内容,剩下的就是一些业务逻辑部分的开发以及接口的定义,这一部分在数据采集介绍完毕之后再来介绍。如果你已经对网络爬虫已经相当熟悉,可以忽略之后的几篇博客~ 在之前的博客[《基于HttpClient实现网络爬虫~以百度新闻为例》](http://www.llwjy.com/blogdetail/5c71c9b34b6da162fd0751fc80f35a5e.html)对自己的爬虫底层实现以及如何分析网页结构,抓取网页内容做了部分介绍,对之前介绍过的内容就不再过多介绍了,下面就重点说一下最新底层更新情况。 **CrawlBase** 在之前的CrawlBase类中,自己是这样定义HttpClient的,这样带来的后果就是在多线程的情况下会出现一系列的问题,所以对HttpClient的定义做了如下修改: 修改前: ~~~ private static HttpClient httpClient = new HttpClient(); ~~~ 修改后: ~~~ private static MultiThreadedHttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager(); private static HttpClient httpClient = new HttpClient(httpConnectionManager); ~~~ 同时还支持手动输入网址含有中文,通过CrawlListPageBase类获取的下一跳的网址是不会出现该问题的。具体的修改是对URL做一次处理,具体方法如下: ~~~ private String encodeUrlCh(String url) { try { return DoRegex.encodeUrlCh(url); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return url; } } ~~~ 其他应该没有太多的修改,最新的CrawlBase类还请访问[http://www.llwjy.com/source/com.lulei.crawl.CrawlBase.html](http://www.llwjy.com/source/com.lulei.crawl.CrawlBase.html) **ps:**要获取个人最新java源代码,请访问[http://www.llwjy.com/source.html](http://www.llwjy.com/source.html),只需要在输入框内输入引用的类,即可检索出最新源码,比如:输入 com.lulei.crawl.CrawlBase 即可查看 CrawlBase 类的相信信息。 **CrawlListPageBase** CrawlListPageBase类是自己对更新列表这一类的网页做的一次封装,从网页中只获取下一跳的网址。下面就按照纵横中文小说网站的实际情况来介绍如何使用CrawlListPageBase类来实现更新列表页信息的获取。 访问纵横中文网,可以很容易的就找到免费小说的更新列表页,网址:http://book.zongheng.com/store/c0/c0/b9/u0/p1/v0/s9/t0/ALL.html ,对页面做简单的分析即可发现下图中的内容就是最新更新的小说书目列表。 ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7befe1d62.jpg) 通过鼠标右键--查看网页源代码  不难找到这些数据在网页中的位置,如下图: ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf00d3fa.jpg) 而红色框出来的内容就是我们需要的下一跳网址,因此我们可以很简单的确定获取该信息的正则表达式是 :  params; /** * 添加相关头信息,对请求进行伪装 */ static { params = new HashMap(); params.put("Referer", "http://book.zongheng.com"); params.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36"); } ~~~ 在添加构造方法时,只需要使用params即可,如下: ~~~ public UpdateList(String urlStr) throws IOException { super(urlStr, "utf-8", params); } ~~~ 这样UpdateList子类计算完成了,使用getPageUrls()方法即可获取页面内我们需要的链接。 经过众多数据的测试,这时候你不难发现,纵横中文网的更新列表上的数目并不是全部来自纵横中文网,还有其他的站,因此需要对这些数据做简单的过滤,代码如下: ~~~ public List getPageUrls(boolean exceptOther){ List urls = getPageUrls(); if (exceptOther) { List exceptUrls = new ArrayList(); for (String url : urls) { if (url.indexOf("zongheng") > 0) { exceptUrls.add(url); } } return exceptUrls; } return urls; } ~~~ 我们使用上述方法代替之前说的那个方法即可选择是否舍弃这些网址,在这个项目中,我们选择舍弃。经过上述步骤,纵横中文的更新列表页的采集模版就完成了。 **运行结果** ![img](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bf02cc21.jpg) **源代码** 最新源代码可以访问:[http://www.llwjy.com/source/com.lulei.crawl.novel.zongheng.UpdateList.html](http://www.llwjy.com/source/com.lulei.crawl.novel.zongheng.UpdateList.html) ~~~ /** *@Description: 更新列表页 */ package com.lulei.crawl.novel.zongheng; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.lulei.crawl.CrawlListPageBase; public class UpdateList extends CrawlListPageBase{ private static HashMap params; /** * 添加相关头信息,对请求进行伪装 */ static { params = new HashMap(); params.put("Referer", "http://book.zongheng.com"); params.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36"); } public UpdateList(String urlStr) throws IOException { super(urlStr, "utf-8", params); } @Override public String getUrlRegexString() { return " getPageUrls(boolean exceptOther){ List urls = getPageUrls(); if (exceptOther) { List exceptUrls = new ArrayList(); for (String url : urls) { if (url.indexOf("zongheng") > 0) { exceptUrls.add(url); } } return exceptUrls; } return urls; } public static void main(String[] args) throws IOException { // TODO Auto-generated method stub UpdateList updateList = new UpdateList("http://book.zongheng.com/store/c0/c0/b9/u0/p1/v0/s9/t0/ALL.html"); for (String s : updateList.getPageUrls(true)) { System.out.println(s); } } } ~~~ ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

查询语句创建PackQuery

最后更新于:2022-04-01 19:48:48

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/44656141](http://blog.csdn.net/xiaojimanman/article/details/44656141) [http://www.llwjy.com/blogdetail/162e5e70516d7ddfb6df8f77e6b13a2b.html](http://www.llwjy.com/blogdetail/162e5e70516d7ddfb6df8f77e6b13a2b.html) 个人博客站已经上线了,网址 [www.llwjy.com ](http://www.llwjy.com)~欢迎各位吐槽 ----------------------------------------------------------------------------------------------------------- 在之前的《[基于lucene的案例开发:Query查询](http://www.llwjy.com/blogdetail/e058b44d064c09a0b4a7cbce4e1a9453.html)》这篇博客中对实际开发过程中比较常见的Query做了简单的介绍,这里就介绍下具体的代码实现。查看最新代码[点击这里](http://www.llwjy.com/source/com.lulei.lucene.query.PackQuery.html)或访问 http://www.llwjy.com/source/com.lulei.lucene.query.PackQuery.html  ~~~ /** *@Description: 创建查询Query */ package com.lulei.lucene.query; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene.index.Term; import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.NumericRangeQuery; import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.util.Version; import com.lulei.lucene.index.manager.IndexManager; public class PackQuery { //分词器 private Analyzer analyzer; //使用索引中的分词器 public PackQuery(String indexName) { analyzer = IndexManager.getIndexManager(indexName).getAnalyzer(); } //使用自定义分词器 public PackQuery(Analyzer analyzer) { this.analyzer = analyzer; } /** * @param key * @param fields * @return Query * @throws ParseException * @Author: lulei * @Description: 查询字符串匹配多个查询域 */ public Query getMultiFieldQuery(String key, String[] fields) throws ParseException{ MultiFieldQueryParser parse = new MultiFieldQueryParser(Version.LUCENE_43, fields, analyzer); Query query = null; query = parse.parse(key); return query; } /** * @param key * @param field * @return Query * @throws ParseException * @Author: lulei * @Description: 查询字符串匹配单个查询域 */ public Query getOneFieldQuery(String key, String field) throws ParseException{ if (key == null || key.length() < 1){ return null; } QueryParser parse = new QueryParser(Version.LUCENE_43, field, analyzer); Query query = null; query = parse.parse(key); return query; } /** * @param key * @param fields * @param occur * @return Query * @throws IOException * @Author: lulei * @Description: 查询字符串、多个查询域以及查询域在查询语句中的关系 */ public Query getBooleanQuery(String key, String[] fields, Occur[] occur) throws IOException{ if (fields.length != occur.length){ System.out.println("fields.length isn't equals occur.length, please check params!"); return null; } BooleanQuery query = new BooleanQuery(); TokenStream tokenStream = analyzer.tokenStream("", new StringReader(key)); ArrayList analyzerKeys = new ArrayList(); while(tokenStream.incrementToken()){ CharTermAttribute term = tokenStream.getAttribute(CharTermAttribute.class); analyzerKeys.add(term.toString()); } for(int i = 0; i < fields.length; i++){ BooleanQuery queryField = new BooleanQuery(); for(String analyzerKey : analyzerKeys){ TermQuery termQuery = new TermQuery(new Term(fields[i], analyzerKey)); queryField.add(termQuery, Occur.SHOULD); } query.add(queryField, occur[i]); } return query; } /** * @param querys * @param occur * @return Query * @Author: lulei * @Description: 组合多个查询,之间的关系由occur确定 */ public Query getBooleanQuery(ArrayList querys, ArrayList occurs){ if (querys.size() != occurs.size()){ System.out.println("querys.size() isn't equals occurs.size(), please check params!"); return null; } BooleanQuery query = new BooleanQuery(); for (int i = 0; i < querys.size(); i++){ query.add(querys.get(i), occurs.get(i)); } return query; } /** * @param fieldName * @param value * @return * @Author: lulei * @Description: StringField属性的搜索 */ public Query getStringFieldQuery(String value, String fieldName){ Query query = null; query = new TermQuery(new Term(fieldName, value)); return query; } /** * @param fields * @param values * @return * @Author: lulei * @Description: 多个StringField属性的搜索 */ public Query getStringFieldQuery(String[] values, String[] fields, Occur occur){ if (fields == null || values == null || fields.length != values.length){ return null; } ArrayList querys = new ArrayList(); ArrayList occurs = new ArrayList(); for (int i = 0; i < fields.length; i++){ querys.add(getStringFieldQuery(values[i], fields[i])); occurs.add(occur); } return getBooleanQuery(querys, occurs); } /** * @param key * @param field * @param lucene43 * @return * @throws ParseException * @Author: lulei * @Description: 查询字符串和单个查询域 QueryParser是否使用4.3 */ public Query getOneFieldQuery(String key, String field, boolean lucene43) throws ParseException{ if (key == null || key.length() < 1){ return null; } if (lucene43){ return getOneFieldQuery(key, field); } @SuppressWarnings("deprecation") QueryParser parse = new QueryParser(Version.LUCENE_30, field, analyzer); Query query = null; query = parse.parse(key); return query; } /** * @param key * @param field * @Author: lulei * @Description: key开头的查询字符串,和单个域匹配 */ public Query getStartQuery(String key, String field) { if (key == null || key.length() < 1){ return null; } Query query = new PrefixQuery(new Term(field, key)); return query; } /** * @param key * @param fields * @param occur * @Author: lulei * @Description: key开头的查询字符串,和多个域匹配,每个域之间的关系由occur确定 */ public Query getStartQuery(String key, String []fields, Occur occur){ if (key == null || key.length() < 1){ return null; } ArrayList querys = new ArrayList(); ArrayList occurs = new ArrayList(); for (String field : fields) { querys.add(getStartQuery(key, field)); occurs.add(occur); } return getBooleanQuery(querys, occurs); } /** * @param key * @param fields * @Author: lulei * @Description: key开头的查询字符串,和多个域匹配,每个域之间的关系Occur.SHOULD */ public Query getStartQuery(String key, String []fields) { return getStartQuery(key, fields, Occur.SHOULD); } /** * @param key * @param field * @param slop * @return * @Author:lulei * @Description: 自定每个词元之间的最大距离 */ public Query getPhraseQuery(String key, String field, int slop) { if (key == null || key.length() < 1){ return null; } StringReader reader = new StringReader(key); PhraseQuery query = new PhraseQuery(); query.setSlop(slop); try { TokenStream tokenStream = this.analyzer.tokenStream(field, reader); tokenStream.reset(); CharTermAttribute term = tokenStream.getAttribute(CharTermAttribute.class); while(tokenStream.incrementToken()){ query.add(new Term(field, term.toString())); } reader.close(); } catch (IOException e) { e.printStackTrace(); return null; } return query; } /** * @param key * @param fields * @param slop * @param occur * @return * @Author:lulei * @Description: 自定每个词元之间的最大距离,查询多个域,每个域之间的关系由occur确定 */ public Query getPhraseQuery(String key, String[] fields, int slop, Occur occur) { if (key == null || key.length() < 1){ return null; } ArrayList querys = new ArrayList(); ArrayList occurs = new ArrayList(); for (String field : fields) { querys.add(getPhraseQuery(key, field, slop)); occurs.add(occur); } return getBooleanQuery(querys, occurs); } /** * @param key * @param fields * @param slop * @return * @Author:lulei * @Description: 自定每个词元之间的最大距离,查询多个域,每个域之间的关系是Occur.SHOULD */ public Query getPhraseQuery(String key, String[] fields, int slop) { return getPhraseQuery(key, fields, slop, Occur.SHOULD); } /** * @param key * @param field * @return * @Author:lulei * @Description: 通配符检索 eg:getWildcardQuery("a*thor", "field") */ public Query getWildcardQuery(String key, String field) { if (key == null || key.length() < 1){ return null; } return new WildcardQuery(new Term(field, key)); } /** * @param key * @param fields * @param occur * @return * @Author:lulei * @Description: 通配符检索,域之间的关系为occur */ public Query getWildcardQuery(String key, String[] fields, Occur occur) { if (key == null || key.length() < 1){ return null; } ArrayList querys = new ArrayList(); ArrayList occurs = new ArrayList(); for (String field : fields) { querys.add(getWildcardQuery(key, field)); occurs.add(occur); } return getBooleanQuery(querys, occurs); } /** * @param key * @param fields * @return * @Author:lulei * @Description: 通配符检索,域之间的关系为Occur.SHOULD */ public Query getWildcardQuery(String key, String[] fields) { return getWildcardQuery(key, fields, Occur.SHOULD); } /** * @param keyStart * @param keyEnd * @param field * @param includeStart * @param includeEnd * @return * @Author:lulei * @Description: 范围搜索 */ public Query getRangeQuery (String keyStart, String keyEnd, String field, boolean includeStart, boolean includeEnd) { return TermRangeQuery.newStringRange(field, keyStart, keyEnd, includeStart, includeEnd); } /** * @param min * @param max * @param field * @param includeMin * @param includeMax * @return * @Author:lulei * @Description: 范围搜索 */ public Query getRangeQuery (int min, int max, String field, boolean includeMin, boolean includeMax) { return NumericRangeQuery.newIntRange(field, min, max, includeMin, includeMax); } /** * @param min * @param max * @param field * @param includeMin * @param includeMax * @return * @Author:lulei * @Description: 范围搜索 */ public Query getRangeQuery (float min, float max, String field, boolean includeMin, boolean includeMax) { return NumericRangeQuery.newFloatRange(field, min, max, includeMin, includeMax); } /** * @param min * @param max * @param field * @param includeMin * @param includeMax * @return * @Author:lulei * @Description: 范围搜索 */ public Query getRangeQuery (double min, double max, String field, boolean includeMin, boolean includeMax) { return NumericRangeQuery.newDoubleRange(field, min, max, includeMin, includeMax); } public static void main(String[] args) throws IOException { } } ~~~ PackQuery类的构造方法,可以手动指定分词器也可以使用索引的分词器。个人建议,在项目中使用索引中的分词器,这样就不会因为分词器的不同造成不知名的错误。 ---------------------------------------------------------------------------------------------------- ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

实时索引的修改

最后更新于:2022-04-01 19:48:44

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/44280311](http://blog.csdn.net/xiaojimanman/article/details/44280311) [http://www.llwjy.com/blogdetail/e42fa5c3097f4964fca0fdfe7cd7a9a2.html](http://www.llwjy.com/blogdetail/e42fa5c3097f4964fca0fdfe7cd7a9a2.html) 个人的博客小站已经上线了,网址 [www.llwjy.com](#),欢迎大家来吐槽~ 上一篇博客已经介绍了实时索引的检索功能,这个就相当于数据的的查询功能,我们都知道数据库的增删改查是最常用的四个功能,实时索引也是一样,他也有增删改查操作,这里就介绍一下实时索引的增删改操作,在实时索引管理类的那篇博客中,我们已经提到实时索引中的IndexWriter的操作都是委托给TrackingIndexWriter来操作的,TrackingIndexWriter中的方法和IndexWriter中的方法类似,都实现了索引的基本操作,下面就一一介绍下。 **索引名就可以将我实例化** 这里的NRTIndex和之前的NRTSearch类一样,都是根据索引名来实例化,在NRTIndex类中,我们定义两个属性,一个是TrackingIndexWriter对象,一个是索引名。因此NRTIndex的构造方法如下: ~~~ public NRTIndex(String indexName){ this.indexName = indexName; indexWriter = IndexManager.getIndexManager(indexName).getTrackingIndexWriter(); } ~~~ **操作一条语句足够** TrackingIndexWriter中的操作和IndexWriter一样简单,几乎都是一条语句足够了,下面我们分别看下增删改的操作语句: ~~~ //新增一条记录 indexWriter.addDocument(doc); //删除符合条件的记录 indexWriter.deleteDocuments(query); //所有的记录都不要了 indexWriter.deleteAll(); //按照条件更新记录 indexWriter.updateDocument(term, doc); ~~~ 代码的实现就是如此简单,简简单单的一条语句就实现了索引的操作,当然这一切都要感谢Lucene的强大功能。 **我很懒的** 为了方便后面的操作,我们将这四个方法进行进一步的封装,使后续操作更加简单,NRTIndex类的源码如下: ~~~ /** *@Description: 索引管理操作类,增删改三种操作 */ package com.lulei.lucene.index.operation; import java.io.IOException; import org.apache.lucene.document.Document; import org.apache.lucene.index.Term; import org.apache.lucene.search.NRTManager.TrackingIndexWriter; import org.apache.lucene.search.Query; import com.lulei.lucene.index.manager.IndexManager; public class NRTIndex { private TrackingIndexWriter indexWriter; private String indexName; //直接使用IndexManager中的indexWriter,将索引的修改操作委托给TrackingIndexWriter实现 public NRTIndex(String indexName){ this.indexName = indexName; indexWriter = IndexManager.getIndexManager(indexName).getTrackingIndexWriter(); } /** * @param doc * @return boolean * @Author: lulei * @Description: 增加Document至索引 */ public boolean addDocument(Document doc){ try { indexWriter.addDocument(doc); return true; } catch (IOException e){ e.printStackTrace(); return false; } } /** * @param query * @return boolean * @Author: lulei * @Description: 按照Query条件从索引中删除Document */ public boolean deleteDocument(Query query){ try { indexWriter.deleteDocuments(query); return true; } catch (IOException e) { e.printStackTrace(); return false; } } /** * @return * @Author: lulei * @Description: 清空索引 */ public boolean deleteAll(){ try { indexWriter.deleteAll(); return true; } catch (IOException e) { e.printStackTrace(); return false; } } /** * @param term * @param doc * @return * @Author: lulei * @Description: 按照Term条件修改索引中Document */ public boolean updateDocument(Term term, Document doc){ try { indexWriter.updateDocument(term, doc); return true; } catch (IOException e) { e.printStackTrace(); return false; } } /** * @throws IOException * @Author:lulei * @Description: 合并索引 */ public void commit() throws IOException { IndexManager.getIndexManager(indexName).getIndexWriter().commit(); } } ~~~ 这里的对NRTIndex类的介绍就结束了,后面博客会对NRTSearch和NRTIndex类创建应用子类,是索引的操作更加简单。 ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html)  请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/
';

实时索引的检索

最后更新于:2022-04-01 19:48:41

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/44279753](http://blog.csdn.net/xiaojimanman/article/details/44279753) [http://www.llwjy.com/blogdetail/31bb705106379feaf6d31b58dd777be6.html](http://www.llwjy.com/blogdetail/31bb705106379feaf6d31b58dd777be6.html) 个人博客小站搭建成功,网址 [www.llwjy.com](http://www.llwjy.com),欢迎大家来吐槽~ 在前面的博客中,我们已经介绍了IndexSearcher中的检索方法,也介绍了如何基于lucene中的NRT*类去创建实时索引,在这篇博客中我们就重点介绍下基于实时索引的检索方案。在开始介绍之前,我们首先定一下检索结果的数据结构(这样做的好处是无论是什么检索,得到的数据结构都是一样的,方便以后的处理~) **原来我长这样** 检索结果的数据结构主要包括两个字段,如下: ~~~ private int count; private List datas; ~~~ 这两个字段分别代表着该检索条件下的符合条件的记录条数和本次查询到的记录数组(该记录是索引中的记录),因此检索结果类源代码如下: ~~~ /** * @Description: 索引搜索结果数据结构 */ package com.lulei.lucene.index.model; import java.util.List; import org.apache.lucene.document.Document; public class SearchResultBean { private int count; private List datas; public int getCount() { return count; } public void setCount(int count) { this.count = count; } public List getDatas() { return datas; } public void setDatas(List datas) { this.datas = datas; } } ~~~ **检索原来如此简单** 检索结果介绍完了,下面就看下如何基于上次博客中的实时索引去检索数据,不知还记得上篇博客中封装的getIndexSearcher()方法(如果忘记了,看下该系列的前一篇博客),这个方法提供了当前最新可用的IndexSearcher对象,索引我们再去检索的时候,直接调用该方法即可。IndexManager类实现了另类的单例模式,使用了索引名来标识IndexManager,因此我们在写检索基类的时候,需要添加一个构造方法,如下: ~~~ public NRTSearch(String indexName) { indexManager = IndexManager.getIndexManager(indexName); } ~~~ 在NRTSearch类中,我们主要封装4个方法: ~~~ 1.public int getIndexNum(){} 2.public SearchResultBean search(Query query, int start, int end){} 3.public SearchResultBean search(Query query, int start, int end, Sort sort){} 4.public SearchResultBean search(int start, int count){} ~~~ 在四个方法分别实现: 1.获取索引中的记录条数; 2.根据query查询索引,根据相关读排序,返回[start, end)记录; 3.根据query查询索引,根据指定sort排序,返回[start, end)记录; 4:从索引中的第start条开始,获取后面的count条记录(如果start + count 大于索引中的记录总条数,则从头补齐)。 这四个方法已经可以实现了80%的站内搜索功能,如果还有其他复杂的,可以根据实际情况来拓展,NRTSearch类源代码如下: ~~~ /** * @Description: 索引的查询操作 */ package com.lulei.lucene.index.operation; import java.util.ArrayList; import java.util.List; import org.apache.lucene.document.Document; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.TopDocs; import com.lulei.lucene.index.manager.IndexManager; import com.lulei.lucene.index.model.SearchResultBean; public class NRTSearch { private IndexManager indexManager; /** * @param indexName 索引名 */ public NRTSearch(String indexName) { indexManager = IndexManager.getIndexManager(indexName); } /** * @return * @Author:lulei * @Description: 索引中的记录数量 */ public int getIndexNum() { return indexManager.getIndexNum(); } /** * @param query 查询字符串 * @param start 起始位置 * @param end 结束位置 * @author lulei * @return 查询结果 */ public SearchResultBean search(Query query, int start, int end) { start = start < 0 ? 0 : start; end = end < 0 ? 0 : end; if (indexManager == null || query == null || start >= end) { return null; } SearchResultBean result = new SearchResultBean(); List datas = new ArrayList(); result.setDatas(datas); IndexSearcher searcher = indexManager.getIndexSearcher(); try { TopDocs docs = searcher.search(query, end); result.setCount(docs.totalHits); end = end > docs.totalHits ? docs.totalHits : end; for (int i = start; i < end; i++) { datas.add(searcher.doc(docs.scoreDocs[i].doc)); } } catch (Exception e) { e.printStackTrace(); } finally { indexManager.release(searcher); } return result; } /** * @param query 查询字符串 * @param start 起始位置 * @param end 结束位置 * @param sort 排序条件 * @return 查询结果 */ public SearchResultBean search(Query query, int start, int end, Sort sort) { start = start < 0 ? 0 : start; end = end < 0 ? 0 : end; if (indexManager == null || query == null || start >= end) { return null; } SearchResultBean result = new SearchResultBean(); List datas = new ArrayList(); result.setDatas(datas); IndexSearcher searcher = indexManager.getIndexSearcher(); try { TopDocs docs = searcher.search(query, end, sort); result.setCount(docs.totalHits); end = end > docs.totalHits ? docs.totalHits : end; for (int i = start; i < end; i++) { datas.add(searcher.doc(docs.scoreDocs[i].doc)); } } catch (Exception e) { e.printStackTrace(); } finally { indexManager.release(searcher); } return result; } /** * @param start * @param count * @return * @Author:lulei * @Description: 按序号检索 */ public SearchResultBean search(int start, int count) { start = start < 0 ? 0 : start; count = count < 0 ? 0 : count; if (indexManager == null) { return null; } SearchResultBean result = new SearchResultBean(); List datas = new ArrayList(); result.setDatas(datas); IndexSearcher searcher = indexManager.getIndexSearcher(); result.setCount(count); try { for (int i = 0; i < count; i++) { datas.add(searcher.doc((start + i) % getIndexNum())); } } catch (Exception e) { e.printStackTrace(); } finally { indexManager.release(searcher); } return result; } } ~~~ 到这里为止,NRTSearch类就介绍完毕了,之后就可以根据实际的应用来创建子类,实现一系列的查询操作,如:关键字检索、分类检索、标签检索、作者检索等等,在之后的应用中在继续介绍。 ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://www.llwjy.com/blogtype/lucene.html)  请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 http://www.llwjy.com/
';

实时索引管理类IndexManager

最后更新于:2022-04-01 19:48:39

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/44015983](http://blog.csdn.net/xiaojimanman/article/details/44015983) [http://www.llwjy.com/blogdetail/5757ce8c007754704b563dd6a47ca1ca.html](http://www.llwjy.com/blogdetail/5757ce8c007754704b563dd6a47ca1ca.html) 个人的博客小站也搭建成功,网址:[www.llwjy.com](http://www.llwjy.com) ,欢迎大家来吐槽~ 在前一篇博客中,对实时索引的实现原理做了一些简单的介绍,这里就介绍下,如何利用Lucene来实现索引的管理(Lucene中已经实现了大部分的功能,我们只需要对其再次封装即可)。 **逐个击破** 在Lucene4.3.1中,实现实时索引时,需要将IndexWrite的相关操作委托给TrackingIndexWriter来处理,具体代码实现如下: ps:关于如何创建索引这里就不再介绍了,可以参照之前的博客或者该博客后面的完整代码。 ~~~ this.trackingIndexWriter = new TrackingIndexWriter(this.indexWriter); ~~~ 同时初始化索引管理对象,代码如下: ~~~ this.nrtManager = new NRTManager(this.trackingIndexWriter, new SearcherFactory()); ~~~ 到这里还需要开启两个守护线程:内存索引重读线程和内存数据commit线程。内存索引重读线程执行的频率也就是实时索引的时差,由于内存中的数据不会太多,所以这个延时一般也就是在十几毫秒左右;内存数据commit线程是将内存中的数据写到磁盘上,不至于数据丢失,如果研究过Lucene源码的童鞋也许会发现,即使你不执行commit操作,到内存中的数据达到一定的程度,也会将一部分数据写到磁盘上,只不过重启服务这部分数据就丢失了同时还会造成一系列的问题,[http://bbs.csdn.net/topics/390677902](http://bbs.csdn.net/topics/390677902) 这个地址下就是commit线程死掉之后造成的一系列问题,感兴趣的童鞋可以了解下。 内存重读线程我们只需要配置下参数启动即可,代码如下: ~~~ this.nrtManagerReopenThread = new NRTManagerReopenThread(this.nrtManager, indexReopenMaxStaleSec, indexReopenMinStaleSec); this.nrtManagerReopenThread.setName("NRTManager Reopen Thread"); this.nrtManagerReopenThread.setPriority(Math.min(Thread.currentThread().getPriority()+2, Thread.MAX_PRIORITY)); this.nrtManagerReopenThread.setDaemon(true); this.nrtManagerReopenThread.start(); ~~~ 内存数据commit线程需要自己写代码实现,然后启动该线程即可,代码如下: ~~~ private class IndexCommitThread extends Thread{ private boolean flag; public IndexCommitThread(String name){ super(name); } @SuppressWarnings("deprecation") public void run(){ flag = true; while(flag) { try { indexWriter.commit(); if (bprint) { System.out.println(new Date().toLocaleString() + "\t" + IndexManagerName + "\tcommit"); } TimeUnit.SECONDS.sleep(indexCommitSeconds); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e1) { e1.printStackTrace(); } } } } ~~~ ~~~ this.indexCommitThread = new IndexCommitThread(IndexManagerName + "Index Commit Thread"); this.indexCommitThread.setDaemon(true); this.indexCommitThread.start(); ~~~ 那又如何像普通的索引那样使用IndexSearcher呢?当然NrtManager类也提供了相关的方法,可以获取最新可用的IndexSearcher,代码如下: ~~~ public IndexSearcher getIndexSearcher(){ try { return this.nrtManager.acquire(); } catch (IOException e) { e.printStackTrace(); return null; } } ~~~ 当然在使用之后别忘记释放,代码如下: ~~~ public void release(IndexSearcher searcher){ try { nrtManager.release(searcher); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } ~~~ **另类单例模式** 在之前的博客中,我也多次提到,加载索引是一个相当消耗资源的事情,所以我们不可能每一次索引操作都加载一次索引,所以我们就必须使用单例模式来实现IndexManager类。这里的单例模式又和我们常见的单例模式有所区别,普通的单例模式该类只有一个对象,这里的单例模式是该类有多个对象,下面就简单的介绍下此处另类的单例模式。 通过前一篇博客最后,你也许会注意到,系统中关于索引的配置信息是存在HashSet对象中,这也就是说这里IndexManager类会实例化多少次取决于HashSet对象,也就是你配置文件让他实例化多少次就会实例化多少次。既然这样,怎么还能叫单例模式呢?这里的单例是索引的单例,也就是说一个索引只有一个IndexManager对象,不会存在两个IndexManager对象去操作同一个索引的情况。具体代码实现如下: ~~~ /** * Initialization on Demand Holder式初始化IndexManager */ private static class LazyLoadIndexManager { private static final HashMap indexManager = new HashMap(); static { for (ConfigBean configBean : IndexConfig.getConfigBean()) { indexManager.put(configBean.getIndexName(), new IndexManager(configBean)); } } } /** *@Description: IndexManager私有构造方法 *@Author: lulei *@Version: 1.1.0 */ private IndexManager(ConfigBean configBean){ //... } public static IndexManager getIndexManager(String indexName){ return LazyLoadIndexManager.indexManager.get(indexName); } ~~~ 这样我们就可以通过索引名获取到该索引的IndexManager对象。 **庐山真面目** 说了这么多,下面就把IndexManager的源码附在最后,感兴趣的童鞋可以试试(里面还有一些其他的方法,相信不用介绍也都可以看的懂) ~~~ /** *@Description: 索引管理类 */ package com.lulei.lucene.index.manager; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.concurrent.TimeUnit; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.NRTManager; import org.apache.lucene.search.NRTManager.TrackingIndexWriter; import org.apache.lucene.search.NRTManagerReopenThread; import org.apache.lucene.search.SearcherFactory; import org.apache.lucene.store.Directory; import org.apache.lucene.store.NIOFSDirectory; import org.apache.lucene.util.Version; import com.lulei.lucene.index.model.ConfigBean; import com.lulei.lucene.index.model.IndexConfig; public class IndexManager { private IndexWriter indexWriter; //更新索引文件的IndexWriter private TrackingIndexWriter trackingIndexWriter; //索引文件采用的分词器 private Analyzer analyzer; //索引管理对象 private NRTManager nrtManager; //索引重读线程 private NRTManagerReopenThread nrtManagerReopenThread; //索引写入磁盘线程 private IndexCommitThread indexCommitThread; //索引地址 private String indexPath; //索引重读最大、最小时间间隔 private double indexReopenMaxStaleSec; private double indexReopenMinStaleSec; //索引commit时间 private int indexCommitSeconds; //索引名 private String IndexManagerName; //commit时是否输出相关信息 private boolean bprint = true; /** * Initialization on Demand Holder式初始化IndexManager */ private static class LazyLoadIndexManager { private static final HashMap indexManager = new HashMap(); static { for (ConfigBean configBean : IndexConfig.getConfigBean()) { indexManager.put(configBean.getIndexName(), new IndexManager(configBean)); } } } /** *@Description: IndexManager私有构造方法 *@Author: lulei *@Version: 1.1.0 */ private IndexManager(ConfigBean configBean){ //设置相关属性 analyzer = configBean.getAnalyzer(); indexPath = configBean.getIndexPath(); IndexManagerName = configBean.getIndexName(); indexReopenMaxStaleSec = configBean.getIndexReopenMaxStaleSec(); indexReopenMinStaleSec = configBean.getIndexReopenMinStaleSec(); indexCommitSeconds = configBean.getIndexCommitSeconds(); bprint = configBean.isBprint(); String indexFile = indexPath + IndexManagerName + "/"; //创建或打开索引 IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_43, analyzer); indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND); Directory directory = null; try { directory = NIOFSDirectory.open(new File(indexFile)); if (IndexWriter.isLocked(directory)){ IndexWriter.unlock(directory); } this.indexWriter = new IndexWriter(directory, indexWriterConfig); this.trackingIndexWriter = new TrackingIndexWriter(this.indexWriter); this.nrtManager = new NRTManager(this.trackingIndexWriter, new SearcherFactory()); } catch(IOException e){ e.printStackTrace(); } //开启守护进程 this.setThread(); } /** * @Author: lulei * @Description: 创建索引管理线程 */ private void setThread(){ this.nrtManagerReopenThread = new NRTManagerReopenThread(this.nrtManager, indexReopenMaxStaleSec, indexReopenMinStaleSec); this.nrtManagerReopenThread.setName("NRTManager Reopen Thread"); this.nrtManagerReopenThread.setPriority(Math.min(Thread.currentThread().getPriority()+2, Thread.MAX_PRIORITY)); this.nrtManagerReopenThread.setDaemon(true); this.nrtManagerReopenThread.start(); this.indexCommitThread = new IndexCommitThread(IndexManagerName + "Index Commit Thread"); this.indexCommitThread.setDaemon(true); this.indexCommitThread.start(); } /** * @return * @Author:lulei * @Description: 重启索引commit线程 */ public String setCommitThread() { try { if (this.indexCommitThread.isAlive()){ return "is alive"; } this.indexCommitThread = new IndexCommitThread(IndexManagerName + "Index Commit Thread"); this.indexCommitThread.setDaemon(true); this.indexCommitThread.start(); } catch (Exception e) { e.printStackTrace(); return "failed"; } return "reload"; } /** *@Description: 索引commit线程 *@Author: lulei *@Version: 1.1.0 */ private class IndexCommitThread extends Thread{ private boolean flag; public IndexCommitThread(String name){ super(name); } @SuppressWarnings("deprecation") public void run(){ flag = true; while(flag) { try { indexWriter.commit(); if (bprint) { System.out.println(new Date().toLocaleString() + "\t" + IndexManagerName + "\tcommit"); } TimeUnit.SECONDS.sleep(indexCommitSeconds); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e1) { e1.printStackTrace(); } } } } /** * @return IndexManager * @Author: lulei * @Description: 获取索引管理类 */ public static IndexManager getIndexManager(String indexName){ return LazyLoadIndexManager.indexManager.get(indexName); } /** * @@Description:释放IndexSearcher资源 * @param searcher */ public void release(IndexSearcher searcher){ try { nrtManager.release(searcher); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * @return IndexSearcher * @Author: lulei * @Description: 返回IndexSearcher对象,使用完之后,调用release方法进行释放 */ public IndexSearcher getIndexSearcher(){ try { return this.nrtManager.acquire(); } catch (IOException e) { e.printStackTrace(); return null; } } public NRTManager getNRTManager(){ return this.nrtManager; } public IndexWriter getIndexWriter(){ return this.indexWriter; } public TrackingIndexWriter getTrackingIndexWriter(){ return this.trackingIndexWriter; } public Analyzer getAnalyzer(){ return analyzer; } /** * @return * @Author: lulei * @Description: 获取索引中的记录条数 */ public int getIndexNum(){ return indexWriter.numDocs(); } } ~~~ ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 [www.llwjy.com](http://www.llwjy.com)
';

实现实时索引基本原理

最后更新于:2022-04-01 19:48:37

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43982653](http://blog.csdn.net/xiaojimanman/article/details/43982653) [http://www.llwjy.com/blogdetail/63d4c488a2cccb5851c0498d374951c9.html](http://www.llwjy.com/blogdetail/63d4c488a2cccb5851c0498d374951c9.html) 个人的博客小站也搭建成功,网址:[www.llwjy.com ](http://www.llwjy.com),欢迎大家来吐槽~ **基本原理** 在前面的博客中也说过,程序初始话索引文件是十分消耗系统资源的,因此要想实现实时索引就不能实时的去修改索引文件、重新加载索引文件,就必须考虑如何使用内存来实现这实时索引;在Lucene4.3.1版本(之前的版本也有,但是在后面的版本中就将NRT*相关的类删除了)中NRT*相关类就提供了创建实时索引(伪实时索引)的相关方法,将IndexWrite的相关操作委托给TrackingIndexWriter来处理,实现了内存索引和硬盘索引的结合,通过NRTManager为外部提供可用的索引,当然,在执行commit(之前[创建索引](http://blog.csdn.net/xiaojimanman/article/details/42872711)中有相关介绍)操作之前,操作的数据都是存在内存中,一旦宕机或者服务重,这些数据都将丢失,因此就需要自己添加一个守护线程去不断的执行commit操作(commit操作十分消耗系统资源,索引不可能每一次修改都去执行该操作)。下面就通过几个简单的图来介绍一下实时索引的实现原理: 在系统刚启动时候,存在两个索引:内存索引、硬盘索引,当然此时内存索引中是没有任何数据的,结构如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef84f13.jpg) 在系统运行过程中,一旦有索引的增加、删除、修改等操作,这些操作都是操作内存索引,而不是硬盘索引,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef9834d.jpg) 当程序主动执行commit操作时,这是会将内存索引复制一份,我们称之为合并索引,同时将内存索引清空,用于之后的索引操作,此时系统中就存在内存索引、合并索引、硬盘索引,在想外提供服务的同时,也会将合并索引中的数据写入硬盘,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7befaabd8.jpg) 当合并索引中的数据已经全部写入硬盘之后,程序会对硬盘索引重读,形成新的IndexReader,在新的硬盘IndexReader替换旧的硬盘IndexReader时,删除合并索引的IndexReader,这样系统又重新回到最初的状态(当然此时内存索引中可能会有数据),如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7befbb885.jpg) 如此反复,一个实时索引的系统也就算完成了,当然这里也会有一定的风险,就是在宕机时可能会丢失一部分的数据。关于这个问题,如果数据准确度要求不是太高的话可以忽略,毕竟这种情况发生的概率太小了;如果对数据的准确度要求特别高的话,可以通过添加输出日志来完成。 ps:Lucene内部的实现逻辑比上面复杂的多,这里只是简单的介绍一下实现原理,如要深入了解,还请详细阅读相关书籍、源码。 **配置类** 在这篇博客中就先把这个系列的实时索引的配置类介绍以下,后面就不再介绍了。 **ConfigBean** ConfigBean类中,定义了一些索引的基本属性,如:索引名、硬盘存储位置、采用的分词器、commit操作执行频率、内存索引重读频率等,具体代码如下: ~~~ /** *@Description: 索引基础配置属性 */ package com.lulei.lucene.index.model; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.util.Version; public class ConfigBean { // 分词器 private Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43); // 索引地址 private String indexPath = "/index/"; private double indexReopenMaxStaleSec = 10; private double indexReopenMinStaleSec = 0.025; // 索引commit时间 private int indexCommitSeconds = 60; // 索引名称 private String indexName = "index"; //commit时是否输出相关信息 private boolean bprint = true; public Analyzer getAnalyzer() { return analyzer; } public void setAnalyzer(Analyzer analyzer) { this.analyzer = analyzer; } public String getIndexPath() { return indexPath; } public void setIndexPath(String indexPath) { if (!(indexPath.endsWith("\\") || indexPath.endsWith("/"))) { indexPath += "/"; } this.indexPath = indexPath; } public double getIndexReopenMaxStaleSec() { return indexReopenMaxStaleSec; } public void setIndexReopenMaxStaleSec(double indexReopenMaxStaleSec) { this.indexReopenMaxStaleSec = indexReopenMaxStaleSec; } public double getIndexReopenMinStaleSec() { return indexReopenMinStaleSec; } public void setIndexReopenMinStaleSec(double indexReopenMinStaleSec) { this.indexReopenMinStaleSec = indexReopenMinStaleSec; } public int getIndexCommitSeconds() { return indexCommitSeconds; } public void setIndexCommitSeconds(int indexCommitSeconds) { this.indexCommitSeconds = indexCommitSeconds; } public String getIndexName() { return indexName; } public void setIndexName(String indexName) { this.indexName = indexName; } public boolean isBprint() { return bprint; } public void setBprint(boolean bprint) { this.bprint = bprint; } } ~~~   **IndexConfig** 在一个系统中并不一定只存在一个索引,也可能会是多个,所以又添加了一个IndexConfig类,具体代码如下: ~~~ /** *@Description: 索引的相关配置参数 */ package com.lulei.lucene.index.model; import java.util.HashSet; public class IndexConfig { //配置参数 private static HashSet configBean = null; //默认的配置 private static class LazyLoadIndexConfig { private static final HashSet configBeanDefault = new HashSet(); static { ConfigBean configBean = new ConfigBean(); configBeanDefault.add(configBean); } } public static HashSet getConfigBean() { //如果未对IndexConfig初始化,则使用默认配置 if (configBean == null) { configBean = LazyLoadIndexConfig.configBeanDefault; } return configBean; } public static void setConfigBean(HashSet configBean) { IndexConfig.configBean = configBean; } } ~~~ ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877 或 [http://www.llwjy.com/](http://www.llwjy.com)
';

数据库连接池

最后更新于:2022-04-01 19:48:33

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43272993](http://blog.csdn.net/xiaojimanman/article/details/43272993) 通过java程序去连接数据库时,使用的协议是TCP/IP协议,TCP/IP协议需要进行3次握手。如果每一次数据库操作都需要创建一个新的连接,都要进行3次握手,这是十分浪费资源的,程序的效率也不是很高。为了解决这个问题,我们想可不可以自己维护一些数据库连接,需要数据库操作的时候,直接使用这其中的一个连接,用完了,在还给它,这样的话就不需要每次数据库操作都创建一个新的连接了。这种思维模式就是今天的博客主题数据库连接池。        **基本原理** 连接池技术的核心思想是:连接复用,通过建立一个数据库连接池以及一套连接使用、分配、管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。另外,由于对JDBC中的原始连接进行了封装,从而方便了数据库应用对于连接的使用(特别是对于事务处理),提高了开发效率,也正是因为这个封装层的存在,隔离了应用的本身的处理逻辑和具体数据库访问逻辑,使应用本身的复用成为可能。连接池主要由三部分组成(如下图所示):连接池的建立、连接池中连接的使用管理、连接池的关闭。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef73740.jpg)        **连接池实现** 这里的介绍的连接池是proxool,这里对其做了进一步的封装,使数据库操作更加简单。 **DBPool** DBPool类用于指定数据库连接池的配置文件,源程序如下: ~~~ /** *@Description: 数据库连接池配置 */ package com.lulei.db.manager; import org.apache.log4j.Logger; import com.lulei.util.ClassUtil; public class DBPool { private static DBPool dbPool = null; private String poolPath; private static Logger log = Logger.getLogger(DBPool.class); private static String path = ClassUtil.getClassRootPath(DBPool.class); public static DBPool getDBPool(){ if (dbPool == null){ synchronized(DBPool.class){ if (dbPool == null){ dbPool = new DBPool(); } } } return dbPool; } private DBPool(){ } /** * @param poolPath * @Author: lulei * @Description: 设置数据库连接池配置文件路径 */ public void setPoolPath(String poolPath){ this.poolPath = poolPath; } /** * @return * @Author: lulei * @Description: 返回数据库连接池配置文件路径 */ protected String getPoolPath(){ //如果没有指定配置文件,则使用默认配置文件 if (poolPath == null){ poolPath = path + "proxool.xml"; log.info("Database's poolpath is null, use default path:" + poolPath); } return poolPath; } } ~~~ 在配置文件中,参数介绍如下: fatal-sql-exception: 它是一个逗号分割的信息片段.当一个SQL异常发生时,他的异常信息将与这个信息片段进行比较.如果在片段中存在,那么这个异常将被认为是个致命错误(Fatal SQL Exception ).这种情况下,数据库连接将要被放弃.无论发生什么,这个异常将会被重掷以提供给消费者.用户最好自己配置一个不同的异常来抛出. fatal-sql-exception-wrapper-class:正如上面所说,你最好配置一个不同的异常来重掷.利用这个属性,用户可以包装SQLException,使他变成另外一个异常.这个异常或者继承SQLException或者继承字RuntimeException.proxool自带了2个实现:'org.logicalcobwebs.proxool.FatalSQLException' 和'org.logicalcobwebs.proxool.FatalRuntimeException' .后者更合适. house-keeping-sleep-time: house keeper 保留线程处于睡眠状态的最长时间,house keeper 的职责就是检查各个连接的状态,并判断是否需要销毁或者创建. house-keeping-test-sql:  如果发现了空闲的数据库连接.house keeper 将会用这个语句来测试.这个语句最好非常快的被执行.如果没有定义,测试过程将会被忽略。 injectable-connection-interface: 允许proxool实现被代理的connection对象的方法. injectable-statement-interface: 允许proxool实现被代理的Statement 对象方法. injectable-prepared-statement-interface: 允许proxool实现被代理的PreparedStatement 对象方法. injectable-callable-statement-interface: 允许proxool实现被代理的CallableStatement 对象方法. jmx: 略 jmx-agent-id: 略 jndi-name: 数据源的名称 maximum-active-time: 如果housekeeper 检测到某个线程的活动时间大于这个数值.它将会杀掉这个线程.所以确认一下你的服务器的带宽.然后定一个合适的值.默认是5分钟. maximum-connection-count: 最大的数据库连接数. maximum-connection-lifetime: 一个线程的最大寿命. minimum-connection-count: 最小的数据库连接数 overload-without-refusal-lifetime: 略 prototype-count: 连接池中可用的连接数量.如果当前的连接池中的连接少于这个数值.新的连接将被建立(假设没有超过最大可用数).例如.我们有3个活动连接2个可用连接,而我们的prototype-count是4,那么数据库连接池将试图建立另外2个连接.这和 minimum-connection-count不同. minimum-connection-count把活动的连接也计算在内.prototype-count 是spare connections 的数量. recently-started-threshold:  略 simultaneous-build-throttle:  略 statistics:  连接池使用状况统计。 参数“10s,1m,1d” statistics-log-level:  日志统计跟踪类型。 参数“ERROR”或 “INFO” test-before-use: 略 test-after-use: 略 trace: 如果为true,那么每个被执行的SQL语句将会在执行期被log记录(DEBUG LEVEL).你也可以注册一个ConnectionListener (参看ProxoolFacade)得到这些信息. verbose: 详细信息设置。 参数 bool 值 在本例中数据库连接池配置文件如下: ~~~ novelSelect com.mysql.jdbc.Driver 900000 500000 40 4 select 1 after_transaction novelEdit com.mysql.jdbc.Driver 900000 500000 10 4 select 1 after_transaction ~~~ **DBManager**       DBManager在系统中是单例模式,在初始化只需要简单的两句代码: ~~~ JAXPConfigurator.configure(DBPool.getDBPool().getPoolPath(), false); Class.forName("org.logicalcobwebs.proxool.ProxoolDriver"); ~~~       获取数据库连接也只是简单的一句代码: ~~~ return DriverManager.getConnection(poolName); ~~~       DBManager源代码如下: ~~~ /** *@Description: 数据库连接池管理 */ package com.lulei.db.manager; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import org.logicalcobwebs.proxool.configuration.JAXPConfigurator; public class DBManager { private static DBManager dBManager = null; private DBManager(){ try { JAXPConfigurator.configure(DBPool.getDBPool().getPoolPath(), false); Class.forName("org.logicalcobwebs.proxool.ProxoolDriver"); } catch (Exception e){ e.printStackTrace(); } } /** * @return DBManager * @Author: lulei * @Description: 获取数据库连接池管理对象 */ protected static DBManager getDBManager(){ if (dBManager == null){ synchronized(DBManager.class){ if (dBManager == null){ dBManager = new DBManager(); } } } return dBManager; } /** * @param poolName * @return Connection * @throws SQLException * @Author: lulei * @Description: 获取数据库链接 */ protected Connection getConnection(String poolName) throws SQLException{ return DriverManager.getConnection(poolName); } } ~~~ **DBOperation** 为了简化数据库的操作,对数据库操作进行再一次封装成DBOperation类。在setPres方法中,这里只做了几种简单的数据类型,关于其他复杂的数据类型可以根据项目需要添加。源代码如下: ~~~ /** *@Description: 数据库操作 */ package com.lulei.db.manager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import org.apache.log4j.Logger; public class DBOperation { private static Logger log = Logger.getLogger(DBOperation.class); private Connection conn = null; private String poolName; /** * @param poolName */ public DBOperation(String poolName){ this.poolName = poolName; } /** * @throws SQLException * @Author: lulei * @Description: 获取Connection */ private void open() throws SQLException{ this.conn = DBManager.getDBManager().getConnection(poolName); } /** * @Author: lulei * @Description: 关闭Connection */ public void close() { try { if (this.conn != null) { this.conn.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * @param sql组装的sql字符串 * @param params传入的参数 * @throws SQLException * @throws ClassNotFoundException * @Author: lulei * @Description: 组装PreparedStatement */ private PreparedStatement setPres(String sql, HashMap params) throws SQLException, ClassNotFoundException{ if (null != params) { if (0 < params.size()){ PreparedStatement pres = this.conn.prepareStatement(sql); for (int i = 1; i <= params.size(); i++){ if (params.get(i).getClass() == Class.forName("java.lang.String")){ pres.setString(i, params.get(i).toString()); } else if (params.get(i).getClass() == Class.forName("java.lang.Integer")){ pres.setInt(i, (Integer) params.get(i)); } else if (params.get(i).getClass() == Class.forName("java.lang.Boolean")){ pres.setBoolean(i, (Boolean) params.get(i)); } else if (params.get(i).getClass() == Class.forName("java.lang.Float")){ pres.setFloat(i, (Float) params.get(i)); } else if (params.get(i).getClass() == Class.forName("java.lang.Double")){ pres.setDouble(i, (Double) params.get(i)); } else if (params.get(i).getClass() == Class.forName("java.lang.Long")){ pres.setLong(i, (Long) params.get(i)); } else if (params.get(i).getClass() == Class.forName("java.sql.Date")){ pres.setDate(i, java.sql.Date.valueOf(params.get(i).toString())); } else { log.info("not found class : " + params.get(i).getClass().toString()); return null; } } return pres; } } return null; } /** * @param sql * @return int * @throws SQLException * @Author: lulei * @Description: executeUpdate */ protected int executeUpdate(String sql) throws SQLException{ this.open(); Statement state = this.conn.createStatement(); int re = state.executeUpdate(sql); return re; } /** * executeUpdate * @param sql * @param params * @return int * @throws SQLException * @throws ClassNotFoundException * @Author: lulei * @Description: */ protected int executeUpdate(String sql, HashMap params) throws SQLException, ClassNotFoundException{ this.open(); PreparedStatement pres = setPres(sql, params); int re = 0; if (null != pres) { re = pres.executeUpdate(); } return re; } /** * getGeneratedKeys * @param sql * @return ResultSet * @throws SQLException * @Author: lulei * @Description: */ protected ResultSet getGeneratedKeys(String sql) throws SQLException{ this.open(); Statement state = this.conn.createStatement(); state.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); ResultSet re = state.getGeneratedKeys(); return re; } /** * getGeneratedKeys * @param sql * @param params * @return ResultSet * @throws SQLException * @throws ClassNotFoundException * @Author: lulei * @Description: */ protected ResultSet getGeneratedKeys(String sql, HashMap params) throws SQLException, ClassNotFoundException{ this.open(); PreparedStatement pres = setPres(sql, params); if (null != pres) { pres.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); ResultSet re = pres.getGeneratedKeys(); return re; } return null; } /** * @param sql * @return ResultSet * @throws SQLException * @Author: lulei * @Description: executeQuery */ protected ResultSet executeQuery(String sql) throws SQLException{ this.open(); Statement state = this.conn.createStatement(); ResultSet re = state.executeQuery(sql); return re; } /** * @param sql * @param params * @return ResultSet * @throws SQLException * @throws ClassNotFoundException * @Author: lulei * @Description: executeQuery */ protected ResultSet executeQuery(String sql, HashMap params) throws SQLException, ClassNotFoundException{ this.open(); PreparedStatement pres = setPres(sql, params); if (null != pres) { ResultSet re = pres.executeQuery(); return re; } return null; } } ~~~ **DBServer** DBServer对数据库的增删该查操作进行进一步的细化,源代码如下: ~~~ /** *@Description: 增删改查四个数据库操作接口 */ package com.lulei.db.manager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; public class DBServer { private DBOperation dBOperation; /** * @param poolName * @Description: 在使用该类之前,请保证函数DBPool.getDBPool().setPoolPath()已经运行 */ public DBServer(String poolName){ dBOperation = new DBOperation(poolName); } /** * @Author: lulei * @Description: 释放链接,在执行完数据库操作,必须执行此命令 */ public void close(){ dBOperation.close(); } /** * @param table * @param columns * @param params * @return int * @throws SQLException * @throws ClassNotFoundException * @Author: lulei * @Description: insert 执行完此命令后,执行close()操作 */ public int insert(String table, String columns, HashMap params) throws SQLException, ClassNotFoundException{ String sql = insertSql(columns, table); return dBOperation.executeUpdate(sql, params); } /** * @param sql * @return int * @throws SQLException * @Author: lulei * @Description: insert 执行完此命令后,执行close()操作 */ public int insert(String sql) throws SQLException { return dBOperation.executeUpdate(sql); } /** * @param table * @param columns * @param params * @return ResultSet * @throws SQLException * @throws ClassNotFoundException * @Author: lulei * @Description: insertGetGeneratedKeys 执行完此命令后,执行close()操作 */ public ResultSet insertGetGeneratedKeys(String table, String columns, HashMap params) throws SQLException, ClassNotFoundException{ String sql = insertSql(columns, table); return dBOperation.getGeneratedKeys(sql, params); } /** * @param sql * @return ResultSet * @throws SQLException * @Author: lulei * @Description: insertGetGeneratedKeys 执行完此命令后,执行close()操作 */ public ResultSet insertGetGeneratedKeys(String sql) throws SQLException{ return dBOperation.getGeneratedKeys(sql); } /** * @param table * @param condition * @return int * @throws SQLException * @Author: lulei * @Description: delete 执行完此命令后,执行close()操作 */ public int delete(String table, String condition) throws SQLException{ if(null == table){ return 0; } String sql = "delete from " + table + " " + condition; return dBOperation.executeUpdate(sql); } /** * @param sql * @return int * @throws SQLException * @Author: lulei * @Description: delete 执行完此命令后,执行close()操作 */ public int delete(String sql) throws SQLException{ return dBOperation.executeUpdate(sql); } /** * @param columns * @param table * @param condition * @return ResultSet * @throws SQLException * @Author: lulei * @Description: select 执行完此命令后,执行close()操作 */ public ResultSet select(String columns, String table, String condition) throws SQLException { String sql = "select " + columns + " from " + table + " " + condition; return dBOperation.executeQuery(sql); } /** * @param sql * @return ResultSet * @throws SQLException * @Author: lulei * @Description: select 执行完此命令后,执行close()操作 */ public ResultSet select(String sql) throws SQLException{ return dBOperation.executeQuery(sql); } /** * @param table * @param columns * @param condition * @param params * @return int * @throws SQLException * @throws ClassNotFoundException * @Author: lulei * @Description: update 执行完此命令后,执行close()操作 */ public int update(String table, String columns, String condition, HashMap params) throws SQLException, ClassNotFoundException{ String sql = updateString(table, columns, condition); return dBOperation.executeUpdate(sql, params); } /** * @param sql * @return int * @throws SQLException * @Author: lulei * @Description: update 执行完此命令后,执行close()操作 */ public int update(String sql) throws SQLException{ return dBOperation.executeUpdate(sql); } /** * @param table * @param columns * @param condition * @return String * @Author: lulei * @Description: 组装updateString */ private String updateString(String table, String columns, String condition) { if (null == columns || null == table) { return ""; } String[] column = columns.split(","); StringBuilder stringBuilder = new StringBuilder("update "); stringBuilder.append(table); stringBuilder.append(" set "); stringBuilder.append(column[0]); stringBuilder.append("=?"); for (int i = 1; i < column.length; i++){ stringBuilder.append(", "); stringBuilder.append(column[i]); stringBuilder.append("=?"); } stringBuilder.append(" "); stringBuilder.append(condition); return stringBuilder.toString(); } /** * @param columns * @param table * @return String * @Author: lulei * @Description: 组装insertSql */ private String insertSql(String columns, String table){ if (null == columns || null == table) { return ""; } int colNum = columns.split(",").length; StringBuilder stringBuilder = new StringBuilder("insert into "); stringBuilder.append(table); stringBuilder.append(" ("); stringBuilder.append(columns); stringBuilder.append(") values (?"); for (int i = 1; i < colNum; i++) { stringBuilder.append(",?"); } stringBuilder.append(")"); return stringBuilder.toString(); } } ~~~ 下面是的使用事例是其他项目中的一个例子,这里可以简单的看下,代码如下: ~~~ public boolean support(String docNo){ DBServer dbServer = new DBServer(dbPoolName); String editTime = System.currentTimeMillis() + ""; String sql = "update " + SolutionTable.tableName + " set " + SolutionTable.support + "=" + SolutionTable.support + "+1, " + SolutionTable.editTime + "='"+ editTime+"' where " +SolutionTable.docNo + "='" + docNo + "'"; try { return dbServer.update(sql) > 0; } catch (SQLException e) { e.printStackTrace(); } finally { dbServer.close(); } return false; } ~~~ ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

ParseUtil &amp; ParseRequest

最后更新于:2022-04-01 19:48:31

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43195045](http://blog.csdn.net/xiaojimanman/article/details/43195045) 这篇博客主要介绍ParseUtil类和ParseRequest类,因为这两个类都比较简单,所以这里就不会给出事例程序。 **ParseUtil** ParseUtil类主要实现将字符串(数字)转化为数值,这个在读取配置文件或数据转化过程中有很大的作用。源程序如下: ~~~ /** *@Description: 转换类 */ package com.lulei.util; public class ParseUtil { /** * @param str * @param defaultDouble * @return double * @Author: lulei * @Description: 将字符串转化为double */ public static double parseStringToDouble(String str, double defaultDouble){ double re = defaultDouble; try{ re = Double.parseDouble(str); } catch (Exception e){ } return re; } /** * @param str * @param defaultInt * @return int * @Author: lulei * @Description: 将字符串转化为int */ public static int parseStringToInt(String str, int defaultInt){ int re = defaultInt; try{ re = Integer.parseInt(str); } catch (Exception e){ } return re; } /** * @param str * @param defaultLong * @return * @Author:lulei * @Description:将字符串转化为long */ public static long parseStringToLong(String str, long defaultLong) { long re = defaultLong; try{ re = Long.parseLong(str); } catch (Exception e){ } return re; } } ~~~ **ParseRequest** ParseRequest类主要获取request参数值,并进行简单的数据转化。源程序如下: ~~~ /** *@Description: 获取request参数值 */ package com.lulei.util; import javax.servlet.http.HttpServletRequest; public class ParseRequest { /** * @param request * @param paramName * @param defaultStr * @return String * @Author: lulei * @Description: 获取参数值,返回字符串,去除前后多余的空格 */ public static String getString(HttpServletRequest request, String paramName, String defaultStr){ String param = request.getParameter(paramName); if (param == null){ return defaultStr; } try { return new String(param.getBytes("iso-8859-1"), "utf-8").trim(); } catch (Exception e) { e.printStackTrace(); return defaultStr; } } /** * @param request * @param paramName * @param defaultStr * @return * @Author: lulei * @Description: 获取参数值,返回字符串,参数采用gbk编码,去除前后多余的空格 */ public static String getStringGbk(HttpServletRequest request, String paramName, String defaultStr){ String param = request.getParameter(paramName); if (param == null){ return defaultStr; } try { return new String(param.getBytes("iso-8859-1"), "gbk").trim(); } catch (Exception e) { e.printStackTrace(); return defaultStr; } } /** * @param request * @param paramName * @param defaultInt * @return int * @Author: lulei * @Description: 获取参数值,返回int形整数 */ public static int getInt(HttpServletRequest request, String paramName, int defaultInt){ String param = request.getParameter(paramName); if (param == null){ return defaultInt; } try { int re = Integer.parseInt(param); return re; } catch (Exception e) { return defaultInt; } } /** * @param request * @param paramName * @param defaultlong * @return long * @Author: lulei * @Description: 获取参数值,返回long形数字 */ public static long getLong(HttpServletRequest request, String paramName, long defaultlong){ String param = request.getParameter(paramName); if (param == null){ return defaultlong; } try { long re = Long.parseLong(param); return re; } catch (Exception e) { return defaultlong; } } } ~~~ 这里需要说明以下,之前介绍的一些工具类还有以后将会介绍的工具类,里面的一些方法在案例中并不一定会用到,还有一些方法体十分简单,这样做的目的也是为了使项目代码更加简单,便于维护。 ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

基ClassUtil &amp; CharsetUtil

最后更新于:2022-04-01 19:48:29

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43194793](http://blog.csdn.net/xiaojimanman/article/details/43194793) 这篇博客主要介绍ClassUtil类和CharsetUtil类。这两个也是项目中比较常用的类,一个用于指定文件路径,一个用于检测文件的编码方式。 **ClassUtil** ClassUtil类中的方法主要是返回class文件所在的文件目录或工程的根目录地址,这主要用于指定工程中配置文件的路径,不至于环境迁移而导致配置文件路径错误。源代码如下: ~~~ /** * @Description: 类工具 */ package com.lulei.util; public class ClassUtil { /** * @param c * @return * @Description: 返回类class文件所在的目录 */ public static String getClassPath(Class c) { return c.getResource("").getPath().replaceAll("%20", " "); } /** * @Description: * @param c * @param hasName 是否显示文件名 * @return 返回类class文件的地址 */ public static String getClassPath(Class c, boolean hasName) { String name = c.getSimpleName() + ".class"; String path = c.getResource(name).getPath().replaceAll("%20", " "); if (hasName) { return path; } else { return path.substring(0, path.length() - name.length()); } } /** * @Description: 返回类class文件所在的顶级目录 * @param c * @return */ public static String getClassRootPath(Class c) { return c.getResource("/").getPath().replaceAll("%20", " "); } public static void main(String[] args) { System.out.println(ClassUtil.getClassPath(ClassUtil.class, true)); System.out.println(ClassUtil.getClassPath(Math.class, true)); System.out.println(ClassUtil.getClassRootPath(Math.class)); } } ~~~ main函数运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef3b9e7.jpg) **CharsetUtil** CharsetUtil类是基于cpdetector第三方jar包实现的编码检测工具类。如果接触过实际项目,你绝对会碰到程序读取文件乱码或更新运营文件网站就无法正常显示等一系列问题,而这些问题多数都是因为文件编码问题导致的。当然这个工具类,在下一部分的爬虫程序中也扮演着重要的角色。源程序如下: ~~~ /** *@Description: 编码方式检测类 */ package com.lulei.util; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.charset.Charset; import info.monitorenter.cpdetector.io.ASCIIDetector; import info.monitorenter.cpdetector.io.CodepageDetectorProxy; import info.monitorenter.cpdetector.io.JChardetFacade; import info.monitorenter.cpdetector.io.ParsingDetector; import info.monitorenter.cpdetector.io.UnicodeDetector; public class CharsetUtil { private static final CodepageDetectorProxy detector; static {//初始化探测器 detector = CodepageDetectorProxy.getInstance(); detector.add(new ParsingDetector(false)); detector.add(ASCIIDetector.getInstance()); detector.add(UnicodeDetector.getInstance()); detector.add(JChardetFacade.getInstance()); } /** * @param url * @param defaultCharset * @Author:lulei * @return 获取文件的编码方式 */ public static String getStreamCharset (URL url, String defaultCharset) { if (url == null) { return defaultCharset; } try { //使用第三方jar包检测文件的编码 Charset charset = detector.detectCodepage(url); if (charset != null) { return charset.name(); } } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } return defaultCharset; } /** * @param inputStream * @param defaultCharset * @return * @Author:lulei * @Description: 获取文件流的编码方式 */ public static String getStreamCharset (InputStream inputStream, String defaultCharset) { if (inputStream == null) { return defaultCharset; } int count = 200; try { count = inputStream.available(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { //使用第三方jar包检测文件的编码 Charset charset = detector.detectCodepage(inputStream, count); if (charset != null) { return charset.name(); } } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } return defaultCharset; } public static void main(String[] args) throws Exception { URL url = new URL("http://www.csdn.net"); System.out.println(CharsetUtil.getStreamCharset(url, "default")); } } ~~~ main函数运行结果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef5e312.jpg) ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于[ 基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

JsonUtil &amp; XmlUtil

最后更新于:2022-04-01 19:48:25

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43194015](http://blog.csdn.net/xiaojimanman/article/details/43194015) 从这篇博客开始第二大部分就算正式开始了,不过在介绍搜索后台之前,还是先介绍写可能使用的大工具类,这样在后面的搜索后台介绍中,就不会穿插其他的内容介绍。这篇就主要介绍两个工具类:json、xml格式数据处理类。 **JSON** 在前后台数据通信过程中,json数据格式是一种比较常用的方式。将javabean转化为json格式字符串,可以通过简单的字符串拼接,也可以使用第三方jar包进行处理,这里介绍的类也是基于第三方jar包实现的。代码实现如下: ~~~ /** *@Description: json数据工具 */ package com.lulei.util; import java.io.IOException; import java.util.HashMap; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonUtil { public static final String NO_DATA = "{\"data\":null}"; public static final String NO_RESULT = "{\"result\":false}"; private static ObjectMapper mapper; static{ mapper = new ObjectMapper(); //转换json时,如果对象中属性值为null,则不生成该属性 mapper.setSerializationInclusion(Include.NON_NULL); } /*** * @param json * @return 当解析失败返回null * @Author: lulei * @Description: 给定json字符串获得json对象 */ public static JsonNode josn2Object(String json){ try { return mapper.readTree(json); } catch (JsonProcessingException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } /*** * @param obj * @return 当解析失败返回{datas:null} * @Author: lulei * @Description: 给定java对象生成对应json */ public static String parseJson(Object obj){ if(obj == null){ return NO_DATA; } try { return mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { // TODO Auto-generated catch block e.printStackTrace(); return NO_DATA; } } /*** * @param obj * @param root * @return 当解析失败返回{datas:null} * @Author: lulei * @Description:给定java对象生成对应json,可以指定一个json的root名 */ public static String parseJson(Object obj, String root){ if(obj == null){ return NO_DATA; } try { StringBuilder sb = new StringBuilder(); sb.append("{\""); sb.append(root); sb.append("\":"); sb.append(mapper.writeValueAsString(obj)); sb.append("}"); return sb.toString(); } catch (JsonProcessingException e) { // TODO Auto-generated catch block e.printStackTrace(); return NO_DATA; } } /*** * @param json * @param var * @return 若传入var为null,则默认变量名为datas * @Author: lulei * @Description:将json字符串包装成jsonp,例如var data={}方式 */ public static String wrapperJsonp(String json, String var){ if(var == null){ var = "datas"; } return new StringBuilder().append("var ").append(var).append("=").append(json).toString(); } public static void main(String[] args) { HashMap hash = new HashMap(); hash.put("key1", 1); hash.put("key2", 2); hash.put("key3", 3); System.out.println(JsonUtil.parseJson(hash)); } } ~~~ 当然这里对第三方jar包进行再一次封装在项目中更简单的使用,上述main函数的运行结果如下(数据经过格式话处理): ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef1ca97.jpg) 至于其他方法如若感兴趣可以自行测试。        **XML** 在和前台的通信的过程中,xml数据格式也是一种常用方法,同时xml数据格式也是后台配置文件的一种形式。对xml数据的处理,有很多第三方jar包,这是使用的是dom4j,代码实现如下: ~~~ /** *@Description: Xml工具类 */ package com.lulei.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringWriter; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Node; public class XmlUtil { private static String noResult = "no result"; /** * @param obj * @return * @Author:lulei * @Description: 将java对象转化为xml格式的字符串 */ public static String parseObjToXmlString(Object obj){ if (obj == null) { return noResult; } StringWriter sw = new StringWriter(); JAXBContext jAXBContext; Marshaller marshaller; try { jAXBContext = JAXBContext.newInstance(obj.getClass()); marshaller = jAXBContext.createMarshaller(); marshaller.marshal(obj, sw); return sw.toString(); } catch (JAXBException e) { // TODO Auto-generated catch block e.printStackTrace(); } return noResult; } /** * @param xml * @return * @Author: lulei * @Description: 将xml String对象转化为xml对象 */ public static Document createFromString(String xml){ try { return DocumentHelper.parseText(xml); } catch (DocumentException e) { e.printStackTrace(); return null; } } /** * @param xpath * @param node * @return * @Author: lulei * @Description: 获取指定xpath的文本,当解析失败返回null */ public static String getTextFromNode(String xpath,Node node){ try { return node.selectSingleNode(xpath).getText(); } catch (Exception e) { return null; } } /** * @param path * @Author: lulei * @Description: 读取xml文件 * @return xml文件对应的Document */ public static Document createFromPath(String path){ return createFromString(readFile(path)); } /** * @param path * @Author: lulei * @Description: 读文件 * @return 返回文件内容字符串 */ private static String readFile(String path) { File file = new File(path); FileInputStream fileInputStream; StringBuffer sb = new StringBuffer(); try { fileInputStream = new FileInputStream(file); //错误使用UTF-8读取内容 String charset = CharsetUtil.getStreamCharset(file.toURI().toURL(), "utf-8"); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, charset); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String s; while ((s = bufferedReader.readLine()) != null){ s = s.replaceAll("\t", "").trim(); if (s.length() > 0){ sb.append(s); } } fileInputStream.close(); bufferedReader.close(); fileInputStream.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return sb.toString(); } public static void main(String[] args) { } } ~~~ XmlUtil的一个使用事例如下所示: ~~~ /** *@Description: 章节列表搜索结果 */ package com.lulei.test; import java.util.ArrayList; import javax.xml.bind.annotation.XmlRootElement; import com.lulei.util.XmlUtil; @XmlRootElement(name = "root") public class TestXmlUtil { private int count; private ArrayList result; public TestXmlUtil() { count = 3; result = new ArrayList(); result.add("test1"); result.add("test2"); result.add("test3"); } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public ArrayList getResult() { return result; } public void setResult(ArrayList result) { this.result = result; } public static void main(String[] args) { System.out.println(XmlUtil.parseObjToXmlString(new TestXmlUtil())); } } ~~~ 运行结果如下图所示(数据经过格式话处理): ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef2a5ad.jpg) 在XmlUtil类中使用到了CharsetUtil类,关于CharsetUtil类在以后的博客中再详细介绍(主要作用就是检测文件或者流的编码方式等)。        ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

案例初识

最后更新于:2022-04-01 19:48:23

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43192055](http://blog.csdn.net/xiaojimanman/article/details/43192055) 首先抱歉,这几天在准备案例的整体框架设计,所以更新就断了几天,还请原谅。 **案例整体介绍** 在我们开始正式的案例开发介绍之前,我们先看一下整体的案例demo介绍,明白案例是做什么的。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7beeb7bc6.jpg) 从上图中,我们可以看出,这个案例主要是通过爬虫程序去采集纵横小说上的资源,然后将资源存储到自己的数据库中,将数据库中的需要检索的数据通过lucene建立索引文件,最后通过web服务展示数据。这个过程中,我们需要编写爬虫(采集程序)、后台接口(数据库搜索&Lucene检索)、web前端展示三个部分。下面就对这三个部分将会使用的技术做简单的介绍。 **web前端** web前端将会基于BootStrap框架去做前端界面的设计,和后台的数据交互将通过JavaScript。通过初步设计,前端主要包括四个界面:首页(用作运营推广使用)、书籍列表页(用作关键词、标签、分类等检索结果展示)、简介页、阅读页,四个页面具体如下所示(这四个界面只是简单草图): 首页将展示一些运营或推广数据,数据由运营人员编写。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7beece1af.jpg) 列表页主要用作书籍关键字、分类、标签、作者、状态等检索结果展示。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7beedce25.jpg) 简介页展示书籍的属性信息及展示章节列表信息。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7beeecf8c.jpg) 阅读页展示某一章节内容信息。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bef07564.jpg) **搜索后台** 搜索后台将主要基于lucene做信息检索,数据库将会使用mysql。搜索后台提供web前端展示需要的数据接口。        **爬虫** 爬虫程序将基于HttpClient模拟浏览器行为,采集纵横小说网站内容(免费小说)。        这篇博客主要对案例的整体做简单的介绍,知道这个案例是做什么的,不至于在后面的博客中不知道自己在做什么。 **注:**在开始介绍lucene搜索后台前,将会在几篇博客中,重点介绍搜索后台用到的工具类。虽然有些类在之前的博客中也有相关的介绍,但这里还是会对这些类再次的介绍,以免在后面的代码编写过程中,找不到一些方法或不知道方法是做什么用的。 ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

更新说明

最后更新于:2022-04-01 19:48:21

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43154813](http://blog.csdn.net/xiaojimanman/article/details/43154813) 这系列博客从小说的采集、搜索、展示等多方面的介绍基于lucene站内搜索的开发过程,前面已介绍完毕第一部分,由于白天需要工作,一些博客内容需要晚上或者上班奸细进行整理,所以更新速度上难免会有点慢。 到现在为止,利用上周末的时间,原型和数据部分已快整理完毕,下次更新应该会很快!加油~
';

IndexSearcher中检索方法

最后更新于:2022-04-01 19:48:19

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/43052829](http://blog.csdn.net/xiaojimanman/article/details/43052829) 前面我们介绍了Analyzer和Query,这篇我们就开始该系列最后一个类IndexSearcher的搜索API介绍,Lucene中重点API不止这里介绍的这一点,还有IndexWriter、Field、Highlighter等,这些就不在这一部分做介绍了,如若案例中用到的话,再做简单介绍。 查看Lucene4.3.1中IndexSearcher的API请[点击这里](http://lucene.apache.org/core/4_3_1/core/org/apache/lucene/search/IndexSearcher.html),关于搜索的方法如下图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bee8f08d.jpg) 从上图几个方法可以看出,有几个重点类需要介绍下:Query(上篇博客已介绍)、Filter、Sort、ScoreDoc、Collector **Collector** Collector主要用来对搜索结果做收集、自定义排序、过滤等,在Lucene4.3.1的API中,有两个搜索方法用到了Collector,但是其下面都有一句Lower-level search API(低级别的搜索API),如果没有非用不可的需求,尽量还是使用其他方法。 **Filter** Filter主要是做筛选条件的,用于指定哪些文档可以在搜索结果中,这个自己使用的并不是太多,查询了一些资料,介绍说有Filter的检索过程是先对数据源做筛选预处理(Filter中指定的),然后将筛选的结果交给查询语句,如果是这样的话,使用Filter的代价将会很大,他的查询耗时可能会提高数倍。个人认为也没有必要使用Filter,如果真的需要对结果做筛选,可以把这些筛选条件合并到Query中,而没有必要创建一个Filter对象。 **Sort** Sort在检索方法中指定排序方式,相当于数据库中的order by,创建方式如Sort sort = new Sort(new SortField("time", Type.LONG, true)),这里的SortField构造方法中的三个参数分别代表域名、域数据类型、排序方式(true降序/false升序),这里的例子只是按照一个域进行排序,如果多个域可以直接在构造方法中添加,如sort = new Sort(new SortField("time", Type.LONG, true), new SortField("star", Type.INT, false))。 **ScoreDoc** 从上图方法中,searchAfter方法中使用到ScoreDoc,该方法主要用在分页查询中,当然也可以用search方法替代,但是有一种情况是无法替代的,比如查询第一页10条数据,但由于推广或广告等需求,需要在其中添加几条(具体未知)其他记录,但是前端只能展示10条数据,这样该页的最后几条数据就没有办法显示,但在下一页中又想显示这几条数据,这样使用seach方法实现就有点困难,事例需求图形描述如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7beea3e18.jpg) searchAfter在实现上述的需求时,在取下一页数据时,只需要将上次查询的最后一个ScoreDoc告诉它即可,它可以直接从该条数据开始查询下一页的数据。 检索方法中涉及到的类也算介绍结束了,现在就将这些组装起来,以最简单的一个检索方法为例search(Query query, int n),该方法实现的是检索符合条件query的前N条文档,这里的排序采用的是默认的相关度排序;这样方法中添加其他的对象,就完成了其对应的功能,如search(Query query, int n, Sort sort),该方法指定了其排序方式。这篇博客主要介绍IndexSearcher的搜索相关API,所以这里就不再写测试demo了。 **IndexWriter** 原计划第一部分到现在应该结束的,也不对其他内容做相关介绍的,但读者的反馈说索引的增删改的方法不是太清楚,那这里就先简单的题以下其方法,自己感兴趣的可以先实现下,添加新文档到索引中这个在[创建索引这篇博客](http://blog.csdn.net/xiaojimanman/article/details/42872711)中已经提到,这里就不再介绍。 修改索引 ~~~ public boolean updateDocument(Term term, Document doc){ try { indexWriter.updateDocument(term, doc); return true; } catch (IOException e) { e.printStackTrace(); return false; } } ~~~ 这里的term指定了要修改的索引文档,一般这里使用索引中文档的唯一标识。 删除索引 ~~~ public boolean deleteDocument(Query query){ try { indexWriter.deleteDocuments(query); return true; } catch (IOException e) { e.printStackTrace(); return false; } } ~~~ 这里的query指定了文档需要满足的条件,当然也有方法可以直接清空索引 ~~~ public boolean deleteAll(){ try { indexWriter.deleteAll(); return true; } catch (IOException e) { e.printStackTrace(); return false; } } ~~~ 上述的这些操作,都需要执行indexWriter.commit()之后才会保存,否则是不会有效的。 注:第一部分:lucene的基本原理以及API简单接口的使用 到这里就结束了,按照数据流向下一部分应该介绍数据采集的,但为了在介绍搜索后台部分不忘记lucene的相关知识,这里就把搜索后台部分稍微提前一点。在开始搜索后台部分之前,我也会在1-2篇博客中,介绍一下这个案例的demo以及后台的系统架构,在对整个需求有一定的了解基础之上,我们再开始案例开发。如若对个人的博客在排版或者内容等方面有相关的意见,真心希望在评论中可以提及,我也会积极采纳各位的建议,把这一个系列的博客做好,大家共同进步。 ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于[ 基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';

Query查询

最后更新于:2022-04-01 19:48:16

转载请注明出处:[http://blog.csdn.net/xiaojimanman/article/details/42969443](http://blog.csdn.net/xiaojimanman/article/details/42969443) 在Lucene索引的搜索过程中,构建Query对象是一个十分重要的过程。对于Query的理解,可以把它想象成数据库SQL查询语句中的where条件(当然Query的功能要比sql强大很多),在这篇博客中,我们将重点介绍几种常用的Query子类:**QueryParser**、 **MultiFieldQueryParser**、**TermQuery **、**PrefixQuery**、 **PhraseQuery**、 **WildcardQuery**、  **TermRangeQuery**、 **NumericRangeQuery**、 **BooleanQuery**。 这篇博客稍微和前面的排版不同,先分别对几个类做简单介绍,然后在给出具体的demo程序,如若对其中的某些介绍不理解,可以先阅读后面的demo程序再看前面的介绍。 **QueryParser** QueryParser主要用于对单个域搜索时创建查询query,QueryParser的构造方法需指定具体的域名和分词方法,lucene不同版本,创建的Query对象会略有不同,具体不同还请参照博客:[关于QueryParser类前后修改](http://blog.csdn.net/xiaojimanman/article/details/16972661)。 **MultiFieldQueryParser** MultiFieldQueryParser可以想象成QueryParser的升级版,QueryParser主要用于单个域的搜索,而MultiFieldQueryParser则用于多个域的搜索,其构造方法和具体使用和QueryParser类似。 **TermQuery** TermQuery重要对一个Term(最小的索引块,包含一个field名和值),TermQuery可以用于对关键字域查询时Query的创建,比如分类、文档唯一ID等。 **PrefixQuery** PrefixQuery前缀查询字符串的构建,其效果和“abc*”这种通配符使用WildcardQuery构建Query类似;PrefixQuery只需要前缀指定的若干个字符相同即可,如PrefixQuery(new Term("", "lu")),将会匹配lucene、luke等。 **PhraseQuery** PhraseQuery短语搜索,它可以指定关键词之间的最大距离,如下面demo程序中,指定了“基于”“案例”这两个词之间最大的距离为2,一旦文档中,这两个词的具体大于2就不满足条件。 **WildcardQuery** WildcardQuery通配符搜索,可以想象是PrefixQuery的升级版,WildcardQuery提供了更细的控制和更大的灵活行,lucene中有* ? 这两个通配符,*表示匹配任意多个字符,?表示匹配一个任意字符。如lu*e可以和lucene、luke匹配;lu?e可以和luke匹配,但和lucene却不匹配。 **TermRangeQuery** TermRangeQuery字符串范围搜索,在创建时一般有5个参数分别是 域名、域下限值、域上限值、是否包括下限、是否包括上限,这个和下面的NumericRangeQuery的参数含义相同。 **NumericRangeQuery** NumericRangeQuery数字范围搜索,它针对不同的数据类型(int、float、double),提供的不同的方法,参数含义参照TermRangeQuery。 **BooleanQuery** 上面介绍的Query子类几乎都是针对单个域或多个域单个关键字的,那多个域不同关键字有该如何处理?多个Query对象又如何组成一个Query对象?这些BooleanQuery都可以实现,BooleanQuery可以嵌套非常复杂的查询,其和布尔运算类似,提供与(Occur.MUST)、或(Occur.SHOULD)、非(Occur.MUST_NOT)三种逻辑关系。 当然lucene中提供的Query子类还有很多,这里就只简单的介绍了几种比较常用的,剩下的如在以后的实际项目中遇到再做介绍学习。 **Query测试demo** ~~~ /** *@Description: Query学习demo */ package com.lulei.lucene.study; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.index.Term; import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.NumericRangeQuery; import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.util.Version; public class QueryStudy { public static void main(String[] args) throws Exception { //Query过程中使用的分词器 Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43); //搜索词 String keyValue = "基于lucene的案例开发"; // keyValue = "基于lucene的案例开发 更多内容请访问:http://blog.csdn.net/xiaojimanman/article/category/2841877"; //将搜索词进行下转义,如果搜索词中没有lucene中的特殊字符,可以不进行转义 String key = LuceneKey.escapeLuceneKey(keyValue); //单个搜索域 String field = "content"; //多个搜索域 String []fields = {"name" , "content"}; Query query = null; //对单个域构建查询语句 QueryParser parse = new QueryParser(Version.LUCENE_43, field, analyzer); query = parse.parse(key); System.out.println(QueryParser.class); System.out.println(query.toString()); System.out.println("--------------------------------"); //对多个域创建查询语句 MultiFieldQueryParser parse1 = new MultiFieldQueryParser(Version.LUCENE_43, fields, analyzer); query = parse1.parse(key); System.out.println(MultiFieldQueryParser.class); System.out.println(query.toString()); System.out.println("--------------------------------"); //词条搜索 query = new TermQuery(new Term(field, key)); System.out.println(query.getClass()); System.out.println(query.toString()); System.out.println("--------------------------------"); //前缀搜索 query = new PrefixQuery(new Term(field, key)); System.out.println(query.getClass()); System.out.println(query.toString()); System.out.println("--------------------------------"); //短语搜索 PhraseQuery query1 = new PhraseQuery(); //设置短语间允许的最大间隔 query1.setSlop(2); query1.add(new Term("content", "基于")); query1.add(new Term("content", "案例")); System.out.println(query1.getClass()); System.out.println(query1.toString()); System.out.println("--------------------------------"); //通配符搜索 query = new WildcardQuery(new Term(field, "基于?")); System.out.println(query.getClass()); System.out.println(query.toString()); System.out.println("--------------------------------"); //字符串范围搜索 query = TermRangeQuery.newStringRange(field, "abc", "azz", true, false); System.out.println(query.getClass()); System.out.println(query.toString()); //int范围搜索 query = NumericRangeQuery.newIntRange("star", 0, 3, false, false); System.out.println(query.getClass() + "\tint"); System.out.println(query.toString()); //float范围搜索 query = NumericRangeQuery.newFloatRange("star", 0.0f, 3.0f, false, false); System.out.println(query.getClass() + "\tfloat"); System.out.println(query.toString()); //double范围搜索 query = NumericRangeQuery.newDoubleRange("star", 0.0, 3d, false, false); System.out.println(query.getClass() + "\tdouble"); System.out.println(query.toString()); System.out.println("--------------------------------"); //BooleanQuery BooleanQuery query2 = new BooleanQuery(); query2.add(new TermQuery(new Term("content", "基于")), Occur.SHOULD); query2.add(new TermQuery(new Term("name", "lucene")), Occur.MUST); query2.add(new TermQuery(new Term("star", "3")), Occur.MUST_NOT); System.out.println(query2.getClass()); System.out.println(query2.toString()); System.out.println("--------------------------------"); } } ~~~ 上述demo程序的运行截图如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56ca7bee7e2d7.jpg) 关于上述Query的意思以及查询索引得到的结果,这里就不再做介绍,如对其感兴趣可以自己按照前面的几篇博客,创建对应索引文件以及编写搜索程序。 ps:最近发现其他网站可能会对博客转载,上面并没有源链接,如想查看更多关于 [基于lucene的案例开发](http://blog.csdn.net/xiaojimanman/article/category/2841877) 请[点击这里](http://blog.csdn.net/xiaojimanman/article/category/2841877)。或访问网址http://blog.csdn.net/xiaojimanman/article/category/2841877
';