Java理论与实践:在没有数据库的情况下进行数据库查询

我最近仔细考察了一个项目,该项目涉及相当多的 Web 快速搜索。当爬虫程 序爬过不同的 Web 站点时,它将建立一个数据库,该数据库中包括它所爬过的 站点和网页、每一页所包含的链接、每一页的分析结果等数据。最终结果是一组 报告,详细说明经过了哪些站点和页面、哪些是一直链接的、哪些链接已经断开 、哪些页面有错误、计算出的页面规格,等等。开始的时候,没人确切知道需要 什么样的报告,或者应当采用什么样的格式 —— 只知道有一些内容要报告。这 表明报告开发阶段会是一个反复的阶段,要经过多次反馈、修改,并且可能尝试 使用不同的结构。惟一确定的报告要求是,报告应当以 XML 形式展示,也可能 以 HTML 形式展示。因此,开发和修改报告的过程必须是轻量级的,因为报告要 求是“动态发现”的,而不是预先指定的。

不需要数据库

对这个问题的“最显而易见的”解决方法是将所有东西都放入 SQL 数据库中 —— 页面、链接、度量标准、HTTP 结果代码、计时结果和其他元数据。这个问 题可以借助关系表示来很好地解决,特别是因为这种方法不需要存储已访问页面 的内容,只需要存储它们的结构和元数据。

到目前为止,这个项目看起来像是一个典型的数据库应用程序,并且它并不 缺少可供选择的持久性策略。但是,或许可以避免使用数据库持久存储数据的复 杂性 —— 这个快速搜索工具(crawler)只访问数万个页面。这个数字不是很 大,因此可以将整个数据库放在内存中,当需要持久存储数据时,可以通过序列 化来实现它。(是的,加载和保存操作要花费较长的时间,但是这些操作并不经 常执行。)懒惰反而带来了一个好处 —— 不需要处理持久性极大地缩短了开发 应用程序的时间,因而显著地减少了开发工作量。构建和操纵内存中的数据结构 要比每次添加、提取或者分析数据时都使用数据库容易得多。不管选择了哪种持 久存储模型,都会限制任何触及到数据的代码的构造。

内存中的数据结构是一种树型结构,如清单 1 所示,它的根是快速搜索过的 各个网站的主页,因此 Visitor 模式是搜索这些主页或者从中提取数据的理想 模式。(构建一个防止陷入链接循环 —— A 链接到 B、B 链接到 C、C 链接到 A —— 的基本 Visitor 类并不是很难。)

清单 1. Web 爬行器的一个简化方案

public class Site {
   Page homepage;
   Collection<Page> pages;
   Collection<Link> links;
}
public class Page {
   String url;
   Site site;
   PageMetrics metrics;
}
public class Link {
   Page linkFrom;
   Page linkTo;
   String anchorText;
}

这个快速搜索工具的应用程序中有十多个 Visitor,它们所做的事情类似于 选择页面做进一步分析、选择不带链接的页面、列出“被链接最多”的页面,等 等。因为所有这些操作都很简单,所以 Visitor 模式(如清单 2 所示)可以工 作得很好,由于数据结构可以放到内存中,因此就算进行彻底搜索,花费也不是 很大:

清单 2. 用于 Web 快速搜索工具数据库的 Visitor 模式

public interface Visitor {
   public void visitSite(Site site);
   public void visitLink(Link link);
}

噢,忘记报告了

如果不运行报告的话,Visitor 策略在访问数据方面会做得非常好。使用数 据库进行持久存储的一个好处是:在生成报告时,SQL 的能力就会大放光彩 — — 几乎可以让数据库做任何事情。甚至用 SQL 生成报告原型也很容易 —— 运 行原型报告,如果结果不是所需要的结果,那么可以修改 SQL 查询或者编写新 的查询,然后再试一试。如果改变的只是 SQL 查询的话,那么这个编辑-编译- 运行周期可能很快。如果 SQL 不是存储在程序中,那么您甚至可以跳过这个周 期的编译部分,这样可以快速生成报告的原型。确定所需要的报告后,将它们构 建到应用程序中就很容易了。

因此,虽然对于添加新结果、寻找特定的结果和进行特殊传输来说,内存中 的数据结构都表现得很不错,但是对于报告来说,这些变成了不利条件。对于所 有其自身结构与数据库结构不同的报告,Visitor 都必须创建一个全新的数据结 构,以包含报告数据。因此,每一种报告类型都需要有自己的、特定于报告的中 间数据结构来存放结果,还需要一个用来填充中间数据结构的访问者,以及用来 将中间数据结构转换成最终报告的后处理(post-processing)代码。似乎需要 做很多工作,尤其在大多数原型报告将被抛弃时。例如,假定您想要列出所有从 其他网站链接到某个给定网站的页面的报告、所有外部页面的列表报告,以及站 点上链接该页面的那些页面的列表,然后,根据链接的数量对报告进行归类,链 接最多的页面显示在最前面。这个计划基本上将数据结构从里到外翻了个个儿。 为了用 Visitor 实现这种数据转换,需要获得从某个给定网站可以到达的外部 页面链接的列表,并根据被链接的页面对它们进行分类,如清单 3 所示:

清单 3. Visitor 列出被链接最多的页面,以及链接到它们的页面

public class InvertLinksVisitor {
   public Map<Page, Set<Page>> map = ...;

   public void visitLink(Link link) {
     if (link.linkFrom.site.equals(targetSite)
       && !link.linkTo.site.equals(targetSite)) {
       if (!map.containsKey(link.linkTo))
         map.put(link.linkTo, new HashSet<Page> ());
       map.get(link.linkTo).add(link.linkFrom);
     }
   }
}

清单 3 中的 Visitor 生成一个映射,将每一个外部页面与链接它的一组内 部页面相关联。为了准备该报告,还必须根据关联页面的大小对这些条目进行分 类,然后创建报告。虽然没有任何困难步骤,但是每一个报告需要的特定于报告 的代码数量却很多,因此快速报告原型就成为一个重要的目标(因为没有提出报 告要求),试验新报告的开销比理想情况更高。许多报告需要多次传递数据,以 便对数据进行选择、汇总和分类。

时间: 2024-10-03 12:58:36

Java理论与实践:在没有数据库的情况下进行数据库查询的相关文章

Java理论与实践专题

Java理论与实践: JDK 5.0中更灵活.更具可伸缩性的锁定机制 Java理论和实践: 一个有缺陷的微基准的剖析 Java理论和实践: 理解JTS ― 平衡安全性和性能 Java理论和实践: 理解JTS ― 幕后魔术 Java理论和实践: 安全构造技术 Java理论与实践: 平衡测试,第3部分:用方面检验设计约束 Java理论与实践:平衡测试,第2部分:编写和优化bug检测器 Java理论与实践:平衡测试,第1部分:不要仅编写测试,还要编写bu Java理论与实践: 您的小数点到哪里去了?

Java 理论与实践:变还是不变?

不变对象具有许多能更方便地使用它们的特性,包括不严格的同步需求和不必考虑数据讹误就能自由地共享和高速缓存对象引用.尽管不变性可能未必对于所有类都有意义,但大多数程序中至少有一些类将受益于不可变.在本月的 Java 理论与实践中,Brian Goetz 说明了不变性的一些长处和构造不变类的一些准则.请在附带的论坛中与作者和其他读者分享您关于本文的心得.(也可以单击文章顶部或底部的"讨论"来访问论坛.) 不变对象是指在实例化后其外部可见状态无法更改的对象.Java 类库中的 String.

Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制

伸缩 内容: synchronized 快速回顾 对 synchronized 的改进 比较 ReentrantLock 和 synchronized 的可伸缩性 条件变量 这不公平 结束语 参考资料 关于作者 对本文的评价 相关内容: Java 理论与实践 系列 Synchronization is not the enemy Reducing contention IBM developer kits for the Java platform (downloads) 订阅: develop

Java理论与实践: 您的小数点到哪里去了?

许多程序员在其整个开发生涯中都不曾使用定点或浮点数,可能的例外是, 偶尔在计时测试或基准测试程序中会用到.Java语言和类库支持两类非整数类型 ― IEEE 754 浮点( float 和 double ,包装类(wrapper class)为 Float 和 Double ),以及任意精度的小数( java.math.BigDecimal ).在本月的 Java 理论和实践中,Brian Goetz 探讨了在 Java 程序中使用非整数类型时一 些常碰到的陷阱和"gotcha". 虽

Java理论与实践: 垃圾收集简史

Java 语言可能是使用最广泛的依赖于垃圾收集的编程语言,但是它并不是第 一个.垃圾收集已经成为了包括 Lisp.Smalltalk.Eiffel.Haskell.ML. Scheme和 Modula-3 在内的许多编程语言的一个集成部分,并且从 20 世纪 60 年代早期就开始使用了.在 Java 理论与实践的本篇文章中,Brian Goetz 描述 了垃圾收集最常用的技术. 垃圾收集的好处是无可争辩的 ―― 可靠性提高.使内存管理与类接口设计 分离,并使开发者减少了跟踪内存管理错误的时间.著

Java理论与实践: 构建一个更好的HashMap

ConcurrentHashMap 是 Doug Lea的 util.concurrent 包的一部分,它提供 比Hashtable 或者 synchronizedMap 更高程度的并发性.而且,对于大多数成 功的 get() 操作它会设法避免完全锁定,其结果就是使得并发应用程序有着非 常好的吞吐量.这个月,BrianGoetz 仔细分析了 ConcurrentHashMap的代码, 并探讨 Doug Lea 是如何在不损失线程安全的情况下取得这么骄人成绩的. 在7月份的那期 Java理论与实践

Java理论与实践: 消除bug

很多有关编程风格的建议都是为了创建高质量.可维护的代码,这很合理, 因为最容易修复 bug 的时间就是在产生 bug 之前(少量的预防措施--).遗 憾的是,只预防往往是不够的,虽然有一些精巧的工具可以帮助您创建好的代码 ,但是很少有工具可以帮助您分析.维护或提高现有代码的质量. 写线程安全的类很难,而分析现有类的线程安全性更难,增强类使其仍然保 持线程安全也很难.以隐含假定.不变式以及预期用例(虽然在开发人员的头脑 中很清晰,但是没有以设计笔记.注释或者文档的方式记录下来)的方式编写完 类之后

Java理论与实践: 修复Java内存模型,第1部分

活跃了将近三年的 JSR 133,近期发布了关于如何修复 Java 内存模型 (Java Memory Model, JMM)的公开建议.原始 JMM 中有几个严重缺陷,这导 致了一些难度高得惊人的概念语义,这些概念原来被认为很简单,如 volatile .final 以及 synchronized.在这一期的 Java 理论与实践 中,Brian Goetz 展示了如何加强 volatile 和 final 的语义,以修复 JMM.这些更改有些已经 集成在 JDK 1.4 中:而另一些将会包含

Java理论与实践:做个好的(事件)侦听器

观察者模式在 Swing 开发中很常见,在 GUI 应用程序以外的场景中,它对 于消除组件的耦合性也非常有用.但是,仍然存在一些侦听器登记和调用方面的 常见缺陷.在 Java 理论与实践 的这一期中,Java 专家 Brian Goetz 就如何 做一个好的侦听器,以及如何对您的侦听器也友好,提供了一些感觉很好的建议 .请在相应的 讨论论坛 上与作者和其他读者分享您对这篇文章的想法.(您也 可以单击本文顶部或底部的 讨论 访问论坛.) Swing 框架以事件侦听器的形式广泛利用了观察者模式(也称