星期日, 十二月 03, 2006

Using DWR DWRUtil 使用

Ajax是一种提高web站点吸引力和实用性的书写web页面的方法。它从服务器端更新web页面的特殊区域,从而增强用户的交互性。它允许信息在短时间的延迟或不用刷新页面的情况下更新。
DWR减少了开发时间,也减少了一些可能的错误,这些错误是在提供常用的方法函数并消除一些与高交互性web站点有关的重复性代码的时候产生的。
DWR是作为开源软件(ASL verssion 2.0)而可以免费得到的。它凭借它的广阔的库、例子和指南非常易于实现。把它结合到一个现有的站点是非常简单的,同样它也可以简单地与大多数java框架结合。


util.js
util.js包含了一些使用的方法,从而帮助你利用j avascript(可能)从服务器端更新你的web数据。
你可以在DWR之外的地方使用它,因为它并不依赖与DWR而实现。
它包含四个页面处理函数:getValue[s]()、setValue[s]()作用于除tables、lists和images以外的大多数html元素。getText()作用于select lists。
addRows()和removeAllRows()用于编辑tables。addOptions()和removeAllOptions()用于编辑lists(如:select lists、ul、ol)。


$()
$函数(在j avascript中,他的名字是合法的)的思想是从prototype引进的。一般说来,$ = document.getElementById。在以后你花大量时间进行ajax编程的时候,在合适的地方使用这种格式是很有益的。
'$'通过给定的ID在当前HTML页面找到元素,如果多于一个的参数被提交,它就会返回一个包含已找到元素的数组。这个函数从prototype的library中得到的灵感,而且,它还能更好的工作在不同的浏览器中。


Generating Lists
DWR的一个功能可以给一个下拉列表(select list)添加选项,只需使用DWRUtil.addOptions()。
如果你在更新列表之前,希望保留一些选项,你需要写以下一些代码:
var sel = DWRUtil.getValue(id);
DWRUtil.removeAllOptions(id);
DWRUtil.addOptions(id, ...);
DWRUtil.setValue(id, sel);
如果你想有个初始化选项,如:“please select”,你可以直接使用:
DWRUtil.addOptions(id, ["Please select"]);
DWRUtil.addOptions 有5种调用方法:Array: DWRUtil.addOptions(selectid, array) 。selectid为目标ID,array为每一项的text。
Array of objects (option text = option value): DWRUtil.addOptions(selectid, data, prop) 用text和value的集合来为每一个数组元素创建一个选项,pro参数指定text和value的值。
Array of objects (with differing option text and value): DWRUtil.addOptions(selectid, array, valueprop, textprop) 用text和value的集合来为每一个数组元素创建一个选项,valueprop确定value,textprop确定text。
Object: DWRUtil.addOptions(selectid, map, reverse) 为map中每一个属性(property)创建一个选项,属性名作为选项的value,属性的value作为选项的text。这样做看起来是错的,但实际上这种做法的确是正确的。如果reverse参数被设置为true,则属性的value还是被用做选项的value。
Map of objects: DWRUtil.addOptions(selectid, map, valueprop, textprop) 为map中的每一个对象创建一个选项,valueprop指定选项的value,textprop指定选项的text。


Generating Tables
DWRUtil.addRows() 从一个数组(第二个参数)取得值,创建table的每一行。从另一个数组(第三个参数)去得值,为table的每一行创建若干列。
DWRUtil.addRows()的用法:
DWRUtil.addRows(”items”,items,cellFunctions);
第一个items,应该是table的id,第2个items,是远程返回的结果集,cellFunctions,对表格行填充的回调函数一类的。
var cellFunctions = [ function(item) { return item.name; },
function(item) { return item.description; },
function(item) { return item.formattedPrice; },
function(item) { var btn = document.createElement(”button”); btn.innerHTML = “Add to cart”; btn.itemId = item.id; btn.onclick = addToCartButtonHandler; return btn; }
应该是一个function(item)就代表一列,return的就是传入的结果集的某列的内容。那么这个表格就应该有4列。最后一列是静态的html代码了。不过没研究一下,return回去动静混合的内容,就是一个字串会如何。我猜应该也可以吧。

DWRUtil.getText(id)
可以根据id取得text的值,这个方法只能用于select list
DWRUtil.getValue(id)
可以根据id取得value,使用这个方法,你不必在意div和select list的不同。
DWRUtil.getValues()
getValues() is similar to getValue() except that the input is a Javascript object that contains name/value pairs. The names are assumed to be the IDs of HTML elements, and the values are altered to reflect the contents of those IDs. This method does not return the object in question, it alters the value that you pass to it.
这个方法和getValue()一样,只是它传入的是一个包含名字和数值的j avascript对象.这个名字就是HTML元素的ID。这个方法不会返回任何对象,它只会将ID的value映射给传入的value。例:
function doGetValues() {
var text= "{
div:null,
textarea:null,
select:null,
text:null,
password:null,
formbutton:null,
button:null
}";
var object = objectEval(text); //j avascript对象
DWRUtil.getValues(object);
var reply = DWRUtil.toDescriptiveString(object, 2); //toString
reply = reply.replace(/n/g, "<br/>"); //转意
DWRUtil.setValue("getvaluesret", reply); //显示
}


DWRUtil.onReturn
贴一段代码,暂时不理解,用onReturn和不用有什么区别
<script>
function submitFunction()
{
$("alert").style.display = "inline";
setTimeout("unsubmitFunction();", 1000);
}
function unsubmitFunction()
{
$("alert").style.display = "none";
}
</script>
<p><input type="text" onkeydown="DWRUtil.onReturn(event, submitFunction)"/>
<input type="button" onclick="submitFunction()" value="GO"/>
<span id="alert" style="display:none; background:#FFFFDD; font-weight:bold;">submitFunction called</span>
</p>


DWRUtil.selectRange
在一个input box里选一个范围
DWRUtil.selectRange("sel-test", $("start").value, $("end").value);
DWRUtil.setValue(id, value)
用ID找到元素,并更新value
DWRUtil.setValues()
和setValue(id,value)一样,只是它需要的参数是个j avascript对象,如:
DWRUtil.setValues({
div: "new div content",
password: "1234567890"
});


DWRUtil.toDescriptiveString
带debug信息的toString,第一个为将要debug的对象,第二个参数为处理等级。等级如下:
0: Single line of debug 单行调试
1: Multi-line debug that does not dig into child objects 不分析子元素的多行调试
2: Multi-line debug that digs into the 2nd layer of child objects 最多分析到第二层子元素的多行调试
And so on. Level 2 and greater probably produce too much output.
总结:DWR不但屏蔽了许多client与server交互的重复且复杂的代码,而且还提供了一些常用的方法,一些思想还是从prototype继承而来,并有一定的改进。同时,它也考虑到了与struts、hibernate、spring的结合问题。
需要注意的是,DWR是一种把服务器端的java代码通过j avascript直接从浏览器调用的方法(DWR is a way of calling Java code on the server directly from Javascript in the browser.),而不是一个j avascript的库(Generally speaking DWR is not a generic JavaScript library so it does not attempt to provide fill this need. However this is one of these really useful functions to have around if you are doing Ajax work.)能做到怎么多,已经很难得了。
DWR自04年11月草案提出到现在的Version 1.1 beta 3(2005-12-29),已经更新发布了20多次了,但愿在ajax的发展大路上,能始终看见DWR的身影。

JDBCTemplate 事务处理 Transaction

  這邊以一個簡單的例子來示範Spring的事務管理,對於事務管理定義,我們使用Spring提供的DefaultTransactionDefinition。

  以JDBC DataSource為例,我們可以使用org.springframework.jdbc.datasource.DataSourceTransactionManager(PlatformTransactionManager的一個實現)作為我們的事務管理員,我們在Bean定義檔中配置,並將DataSource注入給它,例如:

 代碼:

程序代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans public "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/TestDB</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value></value>
</property>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>

<bean id="userDAO" class="onlyfun.caterpillar.UserDAO">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
</bean>
</beans>


  我們定義了一個簡單的DAO物件,將transactionManager與dataSource注入至其中,UserDAO如下:

 代碼:

UserDAO.java

程序代码:
package onlyfun.caterpillar;

import javax.sql.DataSource;
import org.springframework.jdbc.core.*;
import org.springframework.transaction.*;
import org.springframework.transaction.support.*;
import org.springframework.dao.*;

public class UserDAO {
private DataSource dataSource;
private PlatformTransactionManager transactionManager;

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}

public void insertUser(User user) {
....
}
}


  insertUser()方法中,我們將實現User的存儲,並使用事務管理,首先看看直接使用trransactionManager如何實現事務,我們需要事務定義,作為示範,這邊先使用Spring預設的即可:DefaultTransactionDefinition,我們的程式示範如下:

 代碼:

程序代码:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("INSERT INTO USER VALUES('Spring008', 'caterpillar', 'M', 29)");
jdbcTemplate.update("INSERT INTO USER VALUES('Spring009', 'momor', 'F', 26)");
jdbcTemplate.update("INSERT INTO USER VALUES('Spring010, 'beckyday', 'F', 35)");

} catch (DataAccessException ex) {
transactionManager.rollback(status); // 也可以執行status.setRollbackOnly();
throw ex;
}
transactionManager.commit(status);



  在上面的程式中,我們照常執行我們的SQL(為了簡化,我們直接將資料寫在SQL中了,而不是從User物件中取得),注意到我們故意在最後一行SQL中Spring010少寫了一個 ' ,這使得SQL句子不合語法,因而造成DataAccessException(封裝了SQLException),這個例外被catch區塊捕捉,因而執行rollback()取消之後的SQL,如果沒有發生錯誤,則最後我們使用commit()來提交操作。

  另一個簡便的方法是使用TransactionTemplate,它封裝了上面的事務流程,透過callback來完成事務,例如:

 代碼:

程序代码:
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("INSERT INTO USER VALUES('Spring008', 'caterpillar', 'M', 29)");
jdbcTemplate.update("INSERT INTO USER VALUES('Spring009', 'momor', 'F', 26)");
jdbcTemplate.update("INSERT INTO USER VALUES('Spring010, 'beckyday', 'F', 35)");
}
});


  這邊使用TransactionCallbackWithoutResult,並在它的doInTransactionWithoutResult()中進行SQL操作,如果執行中發生例外,則之前所有的操作取消,否則最後自動提交操作。如果操作完成您想要返回結果物件,則可以使用TransactionCallback,並實現其doInTransaction(),例如:

 代碼:

程序代码:
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
Object result = transactionTemplate.execute(
new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
// 作一些操作
// 收集為結果物件 resultObject

return resultObject;
});

Using JDBCTemplate 使用

  您可以使用JdbcTemplate的execute()方法執行SQL陳述,例如:

 代碼:

jdbcTemplate.execute("CREATE TABLE USER (user_id integer, name varchar(100))");

  如果是UPDATE或INSERT,您可以使用update()方法,一個基本的INSERT例子是:


 代碼:



jdbcTemplate.update("INSERT INTO USER VALUES('"+ user.getId() + "', '"                + user.getName() + "', '"
+ user.getSex() + "', '"
+ user.getAge() + "')");

  使用UPDATE時,您可以在欄位上使用 "?",並指定參數陣列給對應的欄位,例如:


 代碼:



jdbcTemplate.update("UPDATE USER SET name = ? WHERE user_id = ?", new Object[] {name, id});

  在上例中所給定的name與id是String物件,它會取代之前對應順序的 "?",然後執行SQL更新。


  使用參數陣列來指定SQL中的變量相當方便,上例中的INSERT範例也可以這麼作,減輕撰寫SQL與Java物件結合時的負擔:


 代碼:



jdbcTemplate.update("INSERT INTO USER VALUES(?, ?, ?, ?)",
new Object[] {user.getId(), user.getName(), user.getSex(), user.getAge()});

  使用JdbcTemplate進行查詢時,我們可以使用queryForXXX()等方法,例如下面使用queryForInt()方法傳回USER表格中的資料筆數:


 代碼:



int count = jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER");

  您也可以使用queryForObject()傳回一個查詢後的結果物件,例如下例傳回一個String物件:


 代碼:



String name = (String) jdbcTemplate.queryForObject("SELECT name FROM USER WHERE user_id = ?", new Object[] {id}, java.lang.String.class);

  上面兩個例子傳回的都是單一筆資料,如果傳回多筆資料,則可以使用queryForList()方法,例如:


 代碼:



List rows = jdbcTemplate.queryForList("SELECT * FROM USER");

  傳回的List中包括的是Map物件,每個Map物件代表查詢結果中的一筆資料,每筆資料包括多個欄位內容,要取得欄位中的值,要使用欄位名稱作為key值,例如:


 代碼:



List rows = jdbcTemplate.queryForList("SELECT * FROM USER");
Iterator it = rows.iterator();

while(it.hasNext()) {
Map userMap = (Map) it.next();
System.out.print(userMap.get("user_id") + "\t");
System.out.print(userMap.get("name") + "\t");
System.out.print(userMap.get("sex") + "\t");
System.out.println(userMap.get("age") + "\t");
}

  事實上,JdbcTemplate只是將我們使用JDBC的流程封裝起來而已,包括了例外捕捉、SQL的執行、查詢結果的轉換與傳回等等,之前所用到的參數設定,也只是在方法中幫您作SQL語句組合的動作而已,Spring大量使用Template Method模式來封裝固定流程的動作,XXXTemplate等類別都是基於這種方式實現的。


  除了大量使用Template Method來封裝一些低層操作細節,Spring也大量使用callback方式來呼叫相關類別之方法以提供傳統JDBC相關類別的功能,使得傳統JDBC的使用者也能清楚瞭解Spring所提供的相關封裝類別方法之使用。


  例如JDBC的PreparedStatement,我們可以實作PreparedStatementSetter介面來提供:


 代碼:



final String id = user.getId();

final String name = user.getName();
final String sex = user.getSex() + "";
final int age = user.getAge();

jdbcTemplate.update("INSERT INTO USER VALUES(?, ?, ?, ?)",
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, id);
ps.setString(2, name);
ps.setString(3, sex);
ps.setInt(4, age);
}
});

  您可以實作RowCallbackHandler介面,在查詢到資料之後先作一些處理再傳回,例如下面的例子實現了手動的O/R Mapping:


 代碼:



final User user = new User();
jdbcTemplate.query("SELECT * FROM USER WHERE user_id = ?",
new Object[] {id},
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getString("user_id"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex").charAt(0));
user.setAge(rs.getInt("age"));
}
});

  您更可以實作RowMapper介面將手動的O/R Mapping程式碼獨立撰寫,共用在不同的DAO方法之中:


 代碼:



class UserRowMapper implements RowMapper {
public Object mapRow(ResultSet rs, int index) throws SQLException {
User user = new User();

user.setId(rs.getString("user_id"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex").charAt(0));
user.setAge(rs.getInt("age"));

return user;
}
}

  在findAllByRowMapperResultReader()裡面使用UserRowMapper:


 代碼:



public List findAllByRowMapperResultReader() {
String sql = "SELECT * FROM USER";

return jdbcTemplate.query(sql, new RowMapperResultReader(new UserRowMapper()));
}

  在getUser(id)裡面使用UserRowMapper:


 代碼:



public User getUser(final String id) throws DataAccessException {
String sql = "SELECT * FROM USER WHERE user_id=?";
final Object[] params = new Object[] { id };
List list = jdbcTemplate.query(sql, params, new RowMapperResultReader(new UserRowMapper()));

return (User) list.get(0);
}

星期六, 十二月 02, 2006

Using JSTL 使用

此文中,我们将更详细地讲述不同标记描述文件(Tag Library Descriptor,TLD)里的各种标记。我们会通过例子去说明条件、迭代、URL、SQL及XML等标记的使用。文章的目的就是展示JSTL的主要功能,说明如何使用JSTL以及说明如何使用JSTL去改善JSP的开发。

简单来说,JSTL就是一套可以用在多个方面的自定义功能,JAVA标准组织(Java Community Process,JCP)在JSR-52里面定义到,JSTL提供表达式语言(expression language),流程控制及数据检校等功能,JSTL规范的最后版本可以在JCP的网站上找得到。

JSTL要求运行在支持JSP 1.2版的服务器上,JSTL的主旨就是简化JSP的开发以及提供访问、操作数据的简便方式,使用JSTL可以使用大家的工作更轻松。

JSTL包含了多个TLD和JAR文件,这些TLD覆盖很多功能,我们将一个个说明。首先,我们要重点说一下JSTL最突出的功能,表达式语言。

实际上,表达式语言是由JSR-152专家组所提供的,也就是定义JSP 2.0的专家组。JSR-152和JSR-52很多时是在一起工作的,更甚的是,表达式语言已经成为JSP 2.0的一部分。表达式语言提供访问数据的一种简便方法,它支持运算,支持bean,支持集合,同时也支持类型转换和属性的缺省值。

使用表达式语言

表达式语言是通过 ${expression} 这样的形式来调用的,并且它只能用在属性里面,例如:

<c:if test="${product.price >= customer.limit}">
...
</c:if>


在上面的例子,我们使用表达式语言做一个比较运算,它也可以混合一些静态文本,就象下面的例子:

<c:forEach var="current">
<c:out value="Product-${current}"/>
</c:forEach>


在例子里面,我们枚举出一个集合里面的元素,之后将它加在一个字符串后面输出,结果就象:

Product-1
Product-2
Product-3...

从例子里可以看出,相对于以前的代码,使用JSTL之后,在整个过程变得简单清晰。在以前,你需要定义一个对象,要知道对象的类型,并且要掌握一些脚本语言,而这只是去完成一个简单的操作。

现在,有了JSTL,我们可以使用简单的语法去访问数据,表达式语言是非常适合做这样的工作的。

再举一个例子,我们可以将:

<jsp:useBean id="customer" type="sample.Customer" scope="request"/> ...
Customer Name: <%=customer.getName()%>
...
<% if (customer.getState().equals("CO")){ %>
...
<%}%>


转换成:

Customer Name: ${ customer. name}
<c:if test="${customer. state == param. state}">
...
</c:if>


表达式语言允许直接访问JSP中任何作用域的变量,比如可以用${foo}来代替pageContext.findAttributes("foo")。还有可以能过点或索引来访问bean以及它们的属性,如:

${user.address.city}
${products.[product.id]}
${products.["Part-010"]}


表达式语言提供所有你使用到的运算:==, !=, <,>, <=, >=, &&, ||, ! 。这表达式语言中,它们表示为lt, gt, le, ge, eq, ne,这样可以避免与XML的语法重复。表达式语言也有普通的算术运算同布尔运算。另外,表达式语言还具有自动类型转换的功能,如int value = "${request.myValue}"会自动转换。

在表达式语言是可以提供属性的缺省值的,使用缺省值是为了避免空指针异常,下面是一个例子,

<c:set var="city" value="${user.address.city}" default="N/A" />


现在,我们已经对表达式语言有所了解,下面,让我们看一下与EL相关的操作。

核心操作

在核心标记库里使用到表达式语言。<c:out>标记将表达式语言所计算出的值输出到当前的JSP输出流。这个与JSP中的<%= scripting exp %>相似。比如:

<c:out value="${customer.name}"  default="N/A" />


核心标记库里还可以设置和清除变量。变量的缺省作用域是PAGE。例如我们可以用<c:set var="customer" value=${customer}" />设置一个页面变量customer,再用一个<c:remove var="customer" />去清除它。

现在,能过JSTL,我们可以使用一个标记去捕捉java.lang.Throwable,例如:<c:catch var="myError" />。使用这个标记,可以统一页面的异常处理,但这不意味着代替JSP的错误页面机制。通过使用<c:catch>标记,就可以在页面上控制特定的异常,而不用转到错误页面,其实并不是所有异常都需要转到错误页面。通过使用<c:catch>标记,与用户的交互也变得更友好。

条件操作

在条件操作中使用EL是简化JSP的一种强有力的手段。<c:if>标记,它可以构造简单的条件表达式。下面的例子,访问了一个对象的属性:

<c:if test="${user.visitCount == 1}"
Welcome back!
</c:if>


当然,有IF就一定会有ELSE。如果是"if/then/else"结构的,则使用<c:choose>, <c:when>和<c:otherwise>标记。

看一看下面的例子,我们对一个查询结果集做过了一些处理,之后使用标记去显示正确的信息。

<c:choose>
<c:when test="${count == 0}">
No records matched your selection.
</c:when>
<c:otherwise>
<c:out value="${count}"/> records matched your selection.
</c:otherwise>
</c:choose>


迭代操作

在整个JSTL里面,可能最有用的操作就是迭代操作了。迭代操作的标记有<c:forEach>,<c:forToken>和<x:forEach>。最后一个是XML的迭代操作。下面我们会说到XML的操作,但现在我们还是继续核心操作。

迭代操作支持所有的J2SE集合类型,包括List, LinkedList, ArrayList, Vector, Stack, 和Set。还有java.util.Map对象,如HashMap, Hashtable, Properties, Provider, 和Attributes。还有数组。当使用基本数据类型的数组时,它会包装成对应的基本类。在迭代操作中,会输出两个东西,当前的数据项和迭代的状态。看看下面的例子:

<table>
<c:forEach var="product"
items="${products}"
varStatus="status">
<tr>
<td><c:out value="${status.count}"/></td>
<td><c:out value="${product.name}"/></td>
</tr>
</c:forEach>
</table>


例子中,products是一个集合,当前的数据项入在变量product里面,变量status是当前的迭代状态。是不是很简单。

URL操作

除了迭找操作外,核心标记库里也提供了URL相关的操作,它包括超链接,引入和重定向。可以使用<c:url>去设定一个URL。假如我们想指定一个带参数的URL,并在链接中使用,那就象下面的例子:

<c:url=http://mysite.com/register var="myUrl">
<c:param name="name" value="${param.name}"/>
</c:url>
<a href='<c:out value="${myUrl}"/>'>Register</a>


JSTL中有很强大的资源引入机制,它可以指定绝对的URL,在同一应用的相对的URL,不同应用的相对的URL,还有FTP资源。下面给出一些例子:

绝对URL:<c:import url="http://sample.com/Welcome.html"/>
同一应用中的相对的URL:<c:import url="/copyright.html"/>
不同应用中的相对的URL:<c:import url="/myLogo.html" context="/common"/>
FTP资源:<c:import url="ftp://ftp.sample.com/myFile"/>

通过上面的说明可以看出,<c:import>比<jsp:include>强大得多,但是,也有其它理由去使用<jsp:include>的。在JSTL中,对于引入的资源使用了缓存,但有时候缓存会是没用的。如果你使用<jsp:include>,资源的内容会被读入并写到当前的JspWriter,而且是每访问一次就重读一次。而使用<c:import>,资源的内容则只会读取一次。

本地化操作

JSTL的另一样重要功能就是本地化操作,通过当前请求的,或者环境配置中的参数,就可以简便地实现本地化操作。这个操作是使用J2SE中的ResourceBundle机制去存放各种译文的。JSTL通过设定的区域,去找到并使用相应的ResourceBundle。用<fmt:setLocale>去设置区域,如<fmt:setLocale value="es_Es"/>,value属性为语言代码和国家代码。也可以直接指定一个ResourceBundle:<fmt:bundle basename="ApplicationResource_fr"/>。

一旦设置了区域或者绑定特定的信息,那么<fmt:message>标记就会自动地选择正确的ResourceBundle,使用以下的形式就可以输出正确的信息:

<fmt:message key="welcome">
<fmt:param value="${visitCount}" />
<fmt:message/>


你也可以直接使用< fmt:requestEncoding/>标记去设置当前请求的字符集。

获取和显示文字信息只是本地化操作的一半,而另一半就是格式化同解释日期和数字,不同的地域会有不同的日期和数字格式的。使用<fmt:formatNumber>和<fmt:parseNumber>去格式化数字,金额,百分比数,而且还可以指定格式,就如<fmt:formatNumber value="12.3" pattern=".00"/>会输出"12.30"。

日期和时间的处理使用<fmt:formatDate>, <fmt:timeZone>, <fmt:setTimeZone>, 和 <fmt:parseDate>。

SQL操作

SQL操作允许你直接操作数据源,在MVC模式里面是不提倡这样做的,我更是彻底反对在一个正式产品里这样做。它们只适用于快速开发,原型开发或者是一些小的应用里面,它们是不应该用在一些大型的应用上面的。不过也有许多开发人员想使用它,所以它保留在标准里面。下面我们来看一下SQL操作。

SQL标记可以用来设置数据源,执行查询,访问查询结果,执行更新等。所有的SQL操作都是基于某一数据源的。有几种方式可以设置数据源:在配置文件里设置
sql.datasource参数,在程序中直接设置,或者使用<sql:setDataSource>标记。如下面就设置了一个MySql的数据源:

<sql:setDataSource var="datasource"  driver="org.gjt.mm.mysql.driver" url="jdbc:mysql://localhost/db" />


与JDBC中的DriverManager相似,<sql:setDataSource>也只是一个包装。数据源的属性,它可以是一个JNDI资源,也可以是一个JDBC参数。用<sql:query datasource="${datasource}" ... />这样的方式来访问数据。

我们把这些东西都放在一起,它是会是这样的:

<sql:query var="customer" datasource="${datasource}"
SELECT * FROM customers WHERE state = 'CO' ORDER BY city
</sql:query>
<table>
<c:forEach var="row" items="${customers.row}">
<tr>
<td><c:out value="${row.custName}" /></td>
<td><c:out value="${row.address}" /></td>
</tr>
</c:forEach>
</table>


使用事务和执行更新一样也很简单。例如,我们建立一个事务,并执行几个更新,那就是下面代码:

<sql:transaction dataSource="${dataSource}">
<sql:update>
UPDATE account SET Balance =Balance -? WHERE accountNo = ?
<sql:param value="${transferAmount}"/>
<sql:param value="${accountFrom}"/>
</sql:update>
</sql:transaction>


用<sql:dateParam>标记来设定SQL语句中的类型为日期型的参数的值。

XML操作

JSTL中最后一类操作是XML操作,XML操作也可细分成核心操作,流程控制操作和转换操作。JSTL中的XML操作是基于Xpath的,Xpath是XML操作专用的表达式语言。JSTL所有的XML操作中,用"select"属性去指定XPath表达式,这些信息将由XPath引擎解释。

XML核心操作与JSTL的核心操作相似,它包含<x:out>, <x:set>, 和<x:parse>标记。<x:parse>标记提供将XML文档转换成结构化数据的功能,之后,这些数据就能被XPath引擎解释了。例如,有一个关于书籍的XML文档,我们就可以解释它,并打印出来:

<c:import url="http://oreilly.com/book?id=1234" var="xml"/>
<x:parse source="${xml}" var="bookInfo"/>
<x:out select="$bookInfo/title"/>
<x:out select="$bookInfo/author"/>


XML类流程控制操作与核心类的流程控制操作一样,包括if, choose, when, otherwise, 和 forEach 这些标记,区别只是它们使用XPath表达式语言。当表达式计算出来后,根据一些规则,结果将转换成一个布尔值。这些规则如下:

A number is true if and only if it is neither positive or negative zero nor NaN.
一个数字当且仅当它不是一个正数,也不是一个负零,更不是一个NaN(非数字)时,表达式的值为真。
A node-set is true if and only if it is non-empty.
一个节点当且仅当它不是空节点时,表达式的值为真。
A string is true if and only if its length is non-zero.
一个字符串当且仅当它的长度不为0是,表达式的值为真。

XML转换操作,是通过XSL样式表来转换XML文档,转换的结果输出到当前页面,另一方面也可以其中的结果保存到变量中。完成一个转换就是导入一个XML文档和XSL样式表,之后做一个转换那么简单:

<c:import url="/books" var="xml"/>
<c:import url="/WEB-INF/xslt/bookDisplay.xsl" var="xslt"/>
<x:transform source="${xml}" xslt="${xslt}"/>


如果你在转换中使用到参数,你可以用<x:param>指定参数名称及参数值。

小结

现在,你已经学会了使用JSTL中的标记,这样,开发JSP会更容易更快捷。留意JSTL的发展,当JSTL的正式版本发布以后,JSP服务器的提供厂商就会对此优化。你可以在http://jakarta.apache.org/taglibs/doc/standard-doc/intro.html中得到JSTL的最后实现版本。

星期四, 十一月 30, 2006

Using Oracle ADF progressIndicator 进度条使用

页面page:

<af:poll interval="500" id="pollid"/>
<af:progressIndicator id="progressid"
action="progressEnd" value="#{progress.progressModel}" partialTriggers="pollid" >
<af:outputFormatted styleUsage="instruction"
value="#{progress.message}"/>
</af:progressIndicator>

解释:af:poll是timer,每隔0.5秒刷新一次。af:progressIndicator是进度条,设置partialTriggers为af:poll的id,这样,poll每刷新一次就会调用progressIndicator, progressIndicator被调用时会取value指定的#{progress.progressModel},#{}是EL语言,progress是后台的managed bean,在adf-config.xml文件配置。

ManagedBean文件:

星期二, 十一月 28, 2006

Using Log4j 使用

One example:

Logger logger = Logger.getLogger(SZ800BusHandler.class);
PropertyConfigurator.configure("conf/log4j.properties");
logger.debug("debug"); //the lowest level
logger.info("info");
logger.warn("warn");
logger.error("error");
logger.fatal("fatal"); //the highest level

log4j.properties:

log4j.rootLogger=info, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=log\\sinker.log
log4j.appender.logfile.MaxFileSize=512KB
# Keep three backup files.
log4j.appender.logfile.MaxBackupIndex=3
# Pattern to output: date priority [category] - message
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

note that: If you set "info" in the log4j properties, the log will write info,warn,error,fatal log. in the other words, log will record higher level's log.

星期日, 十一月 26, 2006

Oracle Connect by 使用方法

connect by prior start with 经常会被用到一个表中存在递归关系的时候。比如我们经常会将一个比较复杂的目录树存储到一个表中。或者将一些部门存储到一个表中,而这些部门互相有隶属关系。这个时候你就会用到connect by prior start with。

典型的使用方法就是:
select * from table connect by prior cur_id=parent_id start with cur_id=???
例如:
a b
1 0
2 1
3 1
4 2
5 3

如果想查找a=2及其下面的所有数据,则:
select * from table connect by prior a=b start with a=2
a b
2 1
4 2

这些只是基础,皮毛。其实只要你灵活的构造查询语句。可以得出意想不到的结果。比如生成树每一个路径。
但是这些记录组成的树必须正常才可以。如果有互为父子的情况,就会出现循环错误!

在eclipse中实现nutch运行

无法查看这则摘要。请 点击此处查看博文。

Nutch Architecture 体系结构

WebDB cycle. WebDB is a persistent custom database that tracks every known page and relevant link. It maintains a small set of facts about each, such as the last-crawled date. WebDB is meant to exist for a long time, across many months of operation.

Since WebDB knows when each link was last fetched, it can easily generate a set of fetchlists. These lists contain every URL we're interested in downloading. WebDB splits the overall workload into several lists, one for each fetcher process. URLs are distributed almost randomly; all the links for a single domain are fetched by the same process, so it can obey politeness constraints.

The fetchers consume the fetchlists and start downloading from the Internet. The fetchers are "polite," meaning they don't overload a single site with requests, and they observe the Robots Exclusion Protocol. (This allows Web-site owners to mark parts of the site as off-limits to automated clients such as our fetcher.) Otherwise, the fetcher blindly marches down the fetchlist, writing down the resulting downloaded text.

Fetchers output WebDB updates and Web content. The updates tell WebDB about pages that have appeared or disappeared since the last fetch attempt. The Web content is used to generate the searchable index that users will actually query.

Note that the WebDB-fetch cycle is designed to repeat forever, maintaining an up-to-date image of the Web graph.

Indexing and Querying. Once we have the Web content, Nutch can get ready to process queries. The indexer uses the content to generate an inverted index of all terms and all pages. We divide the document set into a set of index segments, each of which is fed to a single searcher process.

We can thus distribute the current set of index segments over an arbitrary number of searcher processes, allowing us to scale easily with the query load. Further, we can copy an index segment to multiple machines and run a searcher over each one; that allows more good scaling behavior and reliability in case one or more of the searcher machines fail.

Each searcher also draws upon the Web content from earlier, so it can provide a cached copy of any Web page.

Finally, a pool of Web servers handle interactions with users and contact the searchers for results. Each Web server interacts with many different searchers to learn about the entire document set. In this way, the Web server is simultaneously acting as an HTTP server and a Nutch-search client.

Web servers contain very little state and can be easily reproduced to handle increased load. They need to be told only about the existing pool of searcher machines. The only state they do maintain is a list of which searcher processes are available at any time; if a given segment's searcher fails, the Web server will query a different one instead.

Java文件操作:判断两个路径是否指向同一个文件

我们知道在Windows操作系统下文件名是不区分大小写;另外在不同的操作系统下可以用 . 来表示当前目录,或者直接只写文件名也表示默认为当前目录,例如当前目录是D:\work,那么文件D:\work\aaa.txt和.\Aaa.txt 实际上指的是磁盘上的同一个文件,但是程序怎么来判断这种情况呢?请看下面代码

/**
* 判断两个File对象是否指向同一个文件
* @throws IOException
*/
protected static void testCanonicalFile() throws IOException{
File f1 = new File("D:\work\AAA.txt");
File f2 = new File("./aaa.txt");
boolean sameFile = f1.getCanonicalFile().
equals(f2.getCanonicalFile());
System.out.println(sameFile);
}

通过getCanonicalFile方法来获取某个文件在当前操作系统下对应的目标文件,只要两个路径指向同一个文件,则两个文件对象的getCanonicalFile返回的对象一定相等,因此上面的例子打印的值是 true.

Nutch Page分析

nutch中每个page就是webdb中page database的一行(row), page中含有如下代码内容:

Page 1: Version: 4
URL: http://keaton/tinysite/A.html
ID: fb8b9f0792e449cda72a9670b4ce833a
Next fetch: Thu Nov 24 11:13:35 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 1
Score: 1.0
NextScore: 1.0

其中:

The ID field is the MD5 hash of the page contents, the same contents have same MD5 hash

Nutch结构概要

Nutch的工作分为两个阶段:抓取和搜索。抓取阶段取得网页并把他们处理成倒排的索引,后面的搜索阶段的工作基于这些索引来进行。

我按照Nutch Tutorial中对Intranet搜索的介绍,抓取了两个网站上的数据。生成了如下的文件结构:

Nutch的抓取系统使用Nutch的“CrawlTool(CrawTool.java)”工具以及一些列相关工具来建立和维护上面这几类数据结构,包括“web database”、一系列“segment”以及“index”。

Web 数据库(web database,或者WebDB),是用来保存抓取得Web图(Web Graph)的持久化的数据结构。只要被抓取的Web页面还存在,WebDB就会一直存在。WebDB只在抓取过程中起作用,并不参与搜索的过程。 WebDB保存两类实体:页面(page)和链接(link)。一个页面就相对于Web上的一个网页,网页的URL和内容的MD5哈希码被存储在页面中,同时,网页上的链接数、抓取信息以及计算的网页的权重(score)都被存储在页面中。而一个链接则表示两个网页之间链接。WebDB中的Web图就是由页面和链接组成,页面是节点,链接是边。

一个片断(Segment)是抓取器在一次抓取中取回和索引的网页。如上图所示,一个fetchlist是从WebDB中生成的URL列表,将由抓取器去抓取。

而索引则是系统所抓回的所有网页内容的倒排索引,是由多个片断融合而成。Nutch使用Lucene进行索引,所以所有的Lucene工具和API都可以在 Nutch生成的索引上工作。要注意的是,Lucene中也有片断(segment)这种说法,不过他和Nutch片断是不一样的。Lucene片断是 Lucene索引的一部分,而Nutch片断只是在WebDB的抓取和索引中用到,并不是最后的索引。

Nutch抓取过程

抓取是一个循环的过程:抓取蜘蛛从WebDB中生成了一个 fetchlist 集合;抽取工具根据fetchlist从网络上下载网页内容;蜘蛛程序根据抽取工具发现的新链接更新WebDB;然后再生成新的fetchlist;周而复始。(注:蜘蛛是分两个部分的。有一次在公司的一个讨论会上还就此争论了一番,google也是如此,以后会给出例子。)这个抓取循环在nutch中经常指: generate/fetch/update 循环。

一般来说同一域名下的 url 链接会被合成到同一个 fetchlist。这样做的考虑是:当同时使用多个蜘蛛抓取的时候,不会产生重复抓取的现象。Nutch 遵循 Robots Exclusion Protocol, 你可以用robots.txt 定义保护私有网页数据不被抓去。

上面这个抓取工具的组合是Nutch的最外层的,你也可以直接使用更底层的工具,自己组合这些底层工具的执行顺序达到同样的结果。这就是Nutch吸引人的地方吧。下面把上述过程分别详述一下,括号内就是底层工具的名字:

  1. 创建一个新的WebDB (admin db -create)。
  2. 把开始抓取的跟Url 放入WebDb (inject)。
  3. 从WebDb的新 segment 中生成 fetchlist (generate)。
  4. 根据 fetchlist 列表抓取网页的内容 (fetch)。
  5. 根据抓取回来的网页链接url更新 WebDB (updatedb)。
  6. 重复上面3-5个步骤直到到达指定的抓取层数。
  7. 用计算出来的网页url权重 scores 更新 segments (updatesegs)。
  8. 对抓取回来的网页建立索引(index)。
  9. 在索引中消除重复的内容和重复的url (dedup)。
  10. 合并多个索引到一个大索引,为搜索提供索引库(merge)。

在创建了一个新的WebDB后,抓取循环 generate/fetch/update 就根据 最先第二步指定的根 url 在一定周期下自动循环了。当抓取循环结束后,就会生成一个最终的索引。从第7步到第10步。

需要说明的是:上面第 8 步中每个 segment 的索引都是单独建立的,之后才消重(第9步)。第10步就是大功告成,合并单独的索引到一个大索引库。

Dedup 工具可以从 segment 的索引中去除重复的url。因为 WebDB 中不允许重复的url , 也就是说 fetchlist 中不会有重复的url,所以不需要对 fetchlist 执行 dedup 操作。上文说过,默认的抓取周期是30天,如果已经生成的旧 fetch 没有删除,而又生成了新的fetch 这是还是会出现重复的url的。当只有一个抓取程序运行的时候是不会发生上述情况的。

从上面的介绍可以看出,一般情况下我们只要从头执行的程序就可以了,不需要接触底层的工具。但是搜索引擎有很多“意外”,很多的时间需要花费在维护上,所以底层的工具也是需要掌握的。我将会在下文给你演示如何运行上述过程。

开篇说过,本文是面向一个中型的搜索引擎的,如果做像百度这样的抓取互联网数据的引擎,你就需要参考下面的资源。

资源列表:

1、Nutch project page Nutch项目的大本营,想必大家都知道。
2、邮件列表: nutch-user 和 nutch-dev

Nutch CrawTool代码粗析

CrawlTool.java文件是整个Nutch的入口, 从这里着手分析可以从宏观上掌握nutch的结构. 为了加强可读性, 如下代码进行过删减.

String db = new File(dir + "/db").getCanonicalPath();
String segments = new File(dir + "/segments").getCanonicalPath();
NutchFileSystem nfs = NutchFileSystem.parseArgs(args, 0);

// 创建一个新的WebDB
WebDBAdminTool.main(prependFileSystem(fs, nameserver, new String[] { db, "-create"}));

//把开始抓取的根Url放入WebDb
WebDBInjector.main(prependFileSystem(fs, nameserver, new String[] { db, "-urlfile", rootUrlFile }));

for (int i = 0; i < depth; i++) {
//从WebDb的新segment中生成fetchlist
FetchListTool.main(prependFileSystem(fs, nameserver, new String[] { db, segments } ));
String segment = getLatestSegment(nfs, segments);

//根据 fetchlist 列表抓取网页的内容
Fetcher.main(prependFileSystem(fs, nameserver, new String[] { "-threads", ""+threads, segment } ));

// 根据抓取回来的网页链接url更新 WebDB
UpdateDatabaseTool.main(prependFileSystem(fs, nameserver, new String[] { db, segment } ));
}

//用计算出来的网页url权重scores更新segments
UpdateSegmentsFromDb updater = new UpdateSegmentsFromDb(nfs, db, segments, dir);
updater.run();

File workDir = new File(dir, "workdir");
File[] segmentDirs = nfs.listFiles(new File(segments));

//对抓取回来的网页建立索引,每个segment的索引都是单独建立的
for (int i = 0; i < segmentDirs.length; i++) {
IndexSegment.main(prependFileSystem(fs, nameserver, new String[] { segmentDirs[i].toString(), "-dir", workDir.getPath() } ));
}

//在segment的索引中消除重复的内容和重复的url
DeleteDuplicates.main(prependFileSystem(fs, nameserver, new String[] { segments }));

//合并多个segment的索引到一个大索引,为搜索提供索引库
IndexMerger merger = new IndexMerger(nfs, segmentDirs, new File(dir + "/index"), workDir);
merger.merge();

对KooXoo网的研究

KooXoo(http://www.kooxoo.com)的侧重点在于搜索, 提供了很多不同行业的搜索

其中有几点有特色:

1.把更新时间作为一个重要参数提供在搜索结果中, 并以为default的排序方法, 这对于有时效性的搜索很有帮助

2.地图搜索, 和mapbar合作, 可以拉框选择搜索, 很直观, 有想法

3.冒泡, 实时refresh客户查询结果

WebDB 研究

I created a contrived example with just four pages to understand the steps involved in the crawl process. Figure 1 illustrates the links between pages. C and C-dup (C-duplicate) have identical content.

Before we run the crawler, create a file called urls that contains the root URLs from which to populate the initial fetchlist. In this case, we'll start from page A.

echo 'http://keaton/tinysite/A.html' > urls

The crawl tool uses a filter to decide which URLs go into the WebDB (in steps 2 and 5 in the breakdown of crawl above). This can be used to restrict the crawl to URLs that match any given pattern, specified by regular expressions. Here, we just restrict the domain to the server on my intranet (keaton), by changing the line in the configuration file conf/crawl-urlfilter.txt from

+^http://([a-z0-9]*\.)*MY.DOMAIN.NAME/

to

+^http://keaton/

Now we are ready to crawl, which we do with a single command:

bin/nutch crawl urls -dir crawl-tinysite -depth 3 >& crawl.log

The crawl uses the root URLs in urls to start the crawl, and puts the results of the crawl in the directory crawl-tinysite. The crawler logs its activity to crawl.log. The -depth flag tells the crawler how many generate/fetch/update cycles to carry out to get full page coverage. Three is enough to reach all of the pages in this example, but for real sites it is best to start with five (the default), and increase it if you find some pages aren't being reached.

We shall now look in some detail at the data structures crawl has produced.

The first thing to look at is the number of pages and links in the database. This is useful as a sanity check to give us some confidence that the crawler did indeed crawl the site, and how much of it. The readdb tool parses the WebDB and displays portions of it in human-readable form. We use the -stats option here:

bin/nutch readdb crawl-tinysite/db -stats

which displays:

Number of pages: 4
Number of links: 4


As expected, there are four pages in the WebDB (A, B, C, and C-duplicate) and four links between them. The links to Wikipedia are not in the WebDB, since they did match the pattern in the URL filter file. Both C and C-duplicate are in the WebDB since the WebDB doesn't de-duplicate pages by content, only by URL (which is why A isn't in twice). Next, we can dump all of the pages, by using a different option for readdb:

bin/nutch readdb crawl-tinysite/db -dumppageurl

which gives:

Page 1: Version: 4
URL: http://keaton/tinysite/A.html
ID: fb8b9f0792e449cda72a9670b4ce833a
Next fetch: Thu Nov 24 11:13:35 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 1
Score: 1.0
NextScore: 1.0

Page 2: Version: 4
URL: http://keaton/tinysite/B.html
ID: 404db2bd139307b0e1b696d3a1a772b4
Next fetch: Thu Nov 24 11:13:37 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 3
Score: 1.0
NextScore: 1.0

Page 3: Version: 4
URL: http://keaton/tinysite/C-duplicate.html
ID: be7e0a5c7ad9d98dd3a518838afd5276
Next fetch: Thu Nov 24 11:13:39 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 0
Score: 1.0
NextScore: 1.0

Page 4: Version: 4
URL: http://keaton/tinysite/C.html
ID: be7e0a5c7ad9d98dd3a518838afd5276
Next fetch: Thu Nov 24 11:13:40 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 0
Score: 1.0
NextScore: 1.0


Each page appears in a separate block, with one field per line. The ID field is the MD5 hash of the page contents: note that C and C-duplicate have the same ID. There is also information about when the pages should be next fetched (which defaults to 30 days), and page scores. It is easy to dump the structure of the web graph, too:

bin/nutch readdb crawl-tinysite/db -dumplinks

which produces:

from http://keaton/tinysite/B.html
to http://keaton/tinysite/A.html
to http://keaton/tinysite/C-duplicate.html
to http://keaton/tinysite/C.html

from http://keaton/tinysite/A.html
to http://keaton/tinysite/B.html


For sites larger than a few pages, it is less useful to dump the WebDB in full using these verbose formats. The readdb tool also supports extraction of an individual page or link by URL or MD5 hash. For example, to examine the links to page B, issue the command:

bin/nutch readdb crawl-tinysite/db -linkurl http://keaton/tinysite/B.html

to get:

Found 1 links.
Link 0: Version: 5
ID: fb8b9f0792e449cda72a9670b4ce833a
DomainID: 3625484895915226548
URL: http://keaton/tinysite/B.html
AnchorText: B
targetHasOutlink: true


Notice that the ID is the MD5 hash of the source page A.

There are other ways to inspect the WebDB. The admin tool can produce a dump of the whole database in plain-text tabular form, with one entry per line, using the -textdump option. This format is handy for processing with scripts. The most flexible way of reading the WebDB is through the Java interface. See the Nutch source code and API documentation for more details. A good starting point is org.apache.nutch.db.WebDBReader, which is the Java class that implements the functionality of the readdb tool (readdb is actually just a synonym for org.apache.nutch.db.WebDBReader).

innerHTML 用法

使用innerHTML可以动态改变网页的样子和行为, 把下例代码建立一个html文件可以试一试:

<DIV ID="swappable" onClick="setValue()">test</DIV>
<SCRIPT LANGUAGE="JavaScript">
function setValue(){
document.getElementById("swappable").innerHTML="<input name='' type='text' value='test'><br><input name='' type='button' value='save' onClick='save()'>";
}

function save(){
document.getElementById("swappable").innerHTML="test";
}
</SCRIPT>

Nutch Segment 试用

Segments

The crawl created three segments in timestamped subdirectories in the segments directory, one for each generate/fetch/update cycle. The segread tool gives a useful summary of all of the segments:

bin/nutch segread -list -dir crawl-tinysite/segments/

giving the following tabular output (slightly reformatted to fit this page):

PARSED? STARTED           FINISHED          COUNT DIR NAME
true 20051025-12:13:35 20051025-12:13:35 1 crawl-tinysite/segments/20051025121334
true 20051025-12:13:37 20051025-12:13:37 1 crawl-tinysite/segments/20051025121337
true 20051025-12:13:39 20051025-12:13:39 2 crawl-tinysite/segments/20051025121339
TOTAL: 4 entries in 3 segments.


The PARSED? column is always true when using the crawl tool. This column is useful when running fetchers with parsing turned off, to be run later as a separate process. The STARTED and FINISHED columns indicate the times when fetching started and finished. This information is invaluable for bigger crawls, when tracking down why crawling is taking a long time. The COUNT column shows the number of fetched pages in the segment. The last segment, for example, has two entries, corresponding to pages C and C-duplicate.

Sometimes it is necessary to find out in more detail what is in a particular segment. This is done using the -dump option for segread. Here we dump the first segment (again, slightly reformatted to fit this page):

s=`ls -d crawl-tinysite/segments/* | head -1`
bin/nutch segread -dump $s


Recno:: 0
FetcherOutput::
FetchListEntry: version: 2
fetch: true
page: Version: 4
URL: http://keaton/tinysite/A.html
ID: 6cf980375ed1312a0ef1d77fd1760a3e
Next fetch: Tue Nov 01 11:13:34 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 0
Score: 1.0
NextScore: 1.0

anchors: 1
anchor: A
Fetch Result:
MD5Hash: fb8b9f0792e449cda72a9670b4ce833a
ProtocolStatus: success(1), lastModified=0
FetchDate: Tue Oct 25 12:13:35 BST 2005

Content::
url: http://keaton/tinysite/A.html
base: http://keaton/tinysite/A.html
contentType: text/html
metadata: {Date=Tue, 25 Oct 2005 11:13:34 GMT, Server=Apache-Coyote/1.1,
Connection=close, Content-Type=text/html, ETag=W/"1106-1130238131000",
Last-Modified=Tue, 25 Oct 2005 11:02:11 GMT, Content-Length=1106}
Content:












Alligators live in freshwater environments such as ponds,
marshes, rivers and swamps. Although alligators have
heavy bodies and slow metabolisms, they are capable of
short bursts of speed that can exceed 30 miles per hour.
Alligators' main prey are smaller animals that they can kill
and eat with a single bite. Alligators may kill larger prey
by grabbing it and dragging it in the water to drown.
Food items that can't be eaten in one bite are either allowed
to rot or are rendered by biting and then spinning or
convulsing wildly until bite size pieces are torn off.
(From
the
Wikipedia entry for Alligator
.)


B







ParseData::
Status: success(1,0)
Title: 'A' is for Alligator
Outlinks: 2
outlink: toUrl: http://en.wikipedia.org/wiki/Alligator
anchor: the Wikipedia entry for Alligator
outlink: toUrl: http://keaton/tinysite/B.html anchor: B
Metadata: {Date=Tue, 25 Oct 2005 11:13:34 GMT,
CharEncodingForConversion=windows-1252, Server=Apache-Coyote/1.1,
Last-Modified=Tue, 25 Oct 2005 11:02:11 GMT, ETag=W/"1106-1130238131000",
Content-Type=text/html, Connection=close, Content-Length=1106}

ParseText::
'A' is for Alligator Alligators live in freshwater environments such
as ponds, marshes, rivers and swamps. Although alligators have heavy
bodies and slow metabolisms, they are capable of short bursts of
speed that can exceed 30 miles per hour. Alligators' main prey are
smaller animals that they can kill and eat with a single bite.
Alligators may kill larger prey by grabbing it and dragging it in
the water to drown. Food items that can't be eaten in one bite are
either allowed to rot or are rendered by biting and then spinning or
convulsing wildly until bite size pieces are torn off.
(From the Wikipedia entry for Alligator .) B


There's a lot of data for each entry--remember this is just a single entry, for page A--but it breaks down into the following categories: fetch data, raw content, and parsed content. The fetch data, indicated by the FetcherOutput section, is data gathered by the fetcher to be propagated back to the WebDB during the update part of the generate/fetch/update cycle.

The raw content, indicated by the Content section, contains the page contents as retrieved by the fetcher, including HTTP headers and other metadata. (By default, the protocol-httpclient plugin is used to do this work.) This content is returned when you ask Nutch search for a cached copy of the page. You can see the HTML page for page A in this example.

Finally, the raw content is parsed using an appropriate parser plugin--determined by looking at the content type and then the file extension. In this case, parse-html was used, since the content type is text/html. The parsed content (indicated by the ParseData and ParseText sections) is used by the indexer to create the segment index.

吃遍深圳

1、上步国商那边有家韩国料理叫“相约烤吧”还不错价格也挺实惠。  

2、华强北民间瓦罐旁有一家四川小吃店,店不大但东东挺不错价格也还可以,口水鸡好象是10元一份。     

3、地王附近的工商局后面一条小巷有家湘菜馆也不错,好象叫“湖南人家”那的腊肠做的不错。     

4、草原情餐厅,电话:26974816.地点:南山区中山园路188号.正宗蒙古羊肉,价格便宜。  

5、南山区政府对面的富豪城,好像主要是粤菜吧,不记得了。只记得在一楼大厅吃饭,啤酒不要钱任喝。  

6、南圆路加洲红对面的那家顺德蛇城喝茶挺便宜的,大、中、小都是3:00元,味道最正的是那里的烤乳鸽,又香又便宜。 

7、佳和城大酒楼,中航路与深南中路交界的地方.喜欢喝酒的朋友们可以上那里去,菜也不太贵,每次下来每人20元左右。  

8、八登街有一家西安菜,做的十分不错,价钱又便宜,强烈推荐:羊肉拌面,10块银子,天上绝色。     

9、八登街的鱼香米坊,便宜而且最主要那二楼的环境顺德人开的,广东风味。  
10、八卦三路有个明记烧鹅王还不错,新开的,送餐电话82450696 

11、著名的潮州打冷,这里只提供最正宗且干净的地点,就在晒布路的深运大酒店。    

12、南油酒店中午喝茶是很便宜啊,半价!还有盐插虾8元/斤,片皮鸭18元/只,但二者只能选一。 

13、蛇口风华剧院那有家陕西风味不错哦~~价格巨便宜,土豆饼才3元/张,绿豆稀饭2元一大碗,撑死了别找我~~~ 

14、银湖车站有个金湖大酒楼,门前车总是满满的,基围虾1元一斤,炒菜9元,酒免费,但会收服务费若干,如果聚会这里也是个不错的选择,吃完后可上银湖公园遛遛。 

15、振兴路自上步路口起,到华富路口止,大小食肆林立。自东向西依次是:稻香村 军分区旁边的小巴站处 有海鲜,含部分徽菜口味 中午吃饭花上18元吃一条清蒸鲈鱼,真是很不错耶。 

16、醉翁亭 在稻香村后面 这是总店,徽菜口味。诚意推荐菜式:野鸭,鱼头煲,炒菜心,鸭黄豆腐,炒猪肝,炒腰花。物廉价美,尤其适合安徽,江浙一带,非常下饭,每次都吃的很饱才走。

17、巴蜀风 在燕南路和振兴路的交界处 深圳最火爆的川菜馆,其中的牛杆菌炒双椒,大漠风沙牛肉,冒菜什么的都很好吃,小心它的泡椒鱼头煲啊,我的嘴就是这样吃肿的。主食里的西红柿鸡蛋面也是不错的,就是要提前一点点,否则等好久。 

18、永和大王 巴蜀风西侧 不用我说了,一般无所事事,会去小吃一顿,干净卫生。 

19、家常饭 永和大王西侧 江西口味。不要点它的汤喝,反正我不喜欢。井冈豆皮,黄豆猪蹄,干焗大肠,糯米排骨或牛肉都是上好的东东。

20、乌江活鱼一条街 在巴蜀风后面的一条路上,没有路名。上面有很多家专吃鱼的店家,什么都一处啊,正宗乌江活鱼啊。四五个人进去选条三斤多的鱼,吃的很开心了。

21、外婆桥1  沿着活鱼一条街,往前走点,就可以看到一家外婆桥。这一家是专吃小吃的,没有炒菜,只有煲啊什么的。里面的蒜泥白肉和手撕饼味道很不错,糖醋排骨也是美味的, 就是没什么肉,而且我觉得做的焦了一点。 

22、外婆桥2 这里外婆桥有两家,往南走一些,左拐到底就是另一家。这一家主要做炒菜,我喜欢吃它的鸭血煲,说实话,这是我吃过的最鲜美的鸭血煲。!!!三星推荐。还有,千万不要吃它的全家福,味道还可以,就是太咸。 

23、潮州大碗粥 沿着振兴路往西走,过了招商银行,就有一家粥城。里面随便点吧,反正很便宜就是了。潮州的粥味道很不错的,他们用大米炒过之后才下锅,配一条红鱼,吃起来有 滋有味的。以后我会介绍更好的一个专门吃粥的地方给大家。

24、香辣蟹 过了大碗粥,拐进去就到了振兴食街,有三家做香辣蟹的。三家香辣蟹味道都不错,不过我喜欢中间那家,虽然右边那家最早味道也最好,但是中间那家态度好,吃的舒服。还可以点黄骨鱼吃,就是浑身黄色斑点的一种鱼,大小大概三个手指这么大,烫火锅吃真的很美味。

25、东北春饼店 在香辣蟹对面,进去就是死吃活吃。不过我最怕葱,蒜,菜,忌口,所以很少去。记得份量不要点太多,否则吃不完。 

26、名典及热带雨林 回到振兴路,就可以看到了。是两家咖啡屋,名典里面的生果沙拉和菠萝饭我还是马虎爱吃,但是跟好的西餐比起来就差劲多了。几个朋友聚会一起,倒是不错 的地方,海阔天空的聊。热带雨林挺差劲的,虽然当初上当有了它的贵宾卡,不如不要。过了华强北,就是振兴西路了,我这里把中航路也给包括进去一起说了。 

27、沿着振兴路西行,逐渐就超出单位500米圆范围了,所以就不是很熟悉了。路的中段西侧有一个南昌菜馆,吃过两次,具体吃的什么也就忘记了。但是这里是一个很重要的交界口,因为有好几家好吃的地方。  

28、华神火锅那个辣哟!天!店名好像应该是华神川菜馆,但是最著名的该是它的火锅。 下了班要是晚到了,就只有等位的份。店有两层,一群人围炉来个火锅,最是惬意不过。 只不过俺也算是逐渐成长起来的一个辣子手,到了那里就只有甘拜下风。不过同样的是, 几个四川、湖南的同事同样是对开锅汤不敢下嘴,冰冻可乐倒是去的飞快。反而是一个东北妹妹,刺溜刺溜吃个利索。我们是战战兢兢等她把汤面的辣油几乎扫光了,才开始呲牙 咧嘴的吃。星级推荐:猪肝,烫火锅从来没有这么好吃过。 

29、东北人 一句:翠花,上酸菜。着实把生意本来就红火的东北人,又给捧了一下。里面的姑娘小伙都是民族服装,啥民族服装,就是翠花和翠华那汉子呗。里面东西吃起来自然是大鱼大肉,吃的就是一个俗字,味道呢,个人觉得,酱骨架还是面点王的好吃。不过只要人多,随便点个什么吃,都是非常香。而且姑娘们一句一个大哥,叫得人心里暖呼呼的。 

30、面点王 始终觉得面点王还是开办的很成功的,无论如何一个石油企业,竟然开办起一个风牛马不相及的饮食行业,还是心生佩服。面点王的酱骨架,水饺,鸡蛋炒面,桂圆红枣粥,都是闲来吃饭的好选择。经常会在值班的时候去打包一份回来。华强北地区有两家,一家在燕南路和振兴路交界沿着燕南路往南走20米左右,一家在红荔路和华强北交界处,友情提醒,午饭或晚饭时间,请估计好具体人数再去,否则等位。 

31、家家鸡煲 在振华路和燕南路交界口往南走10米。原来有两家鸡煲店,后来家家味道好,服务也不错,就把另一家给吞并了。进去自然是吃它的传统奇香鸡煲,配上炸的金黄的腐竹,金针菇,味道很好。尤其是它的炸腐竹,是腐竹泡水后又沥干水分之后炸的,不像有些火锅店,不泡水就干炸,怎么煮都煮不开腐竹的皮,真是太没专业精神了。 

32、天天渔港 振中路上,铜锣湾量贩的对面。说实话,这是我们老百姓最能体现物廉价美的海鲜吃处。环境好,服务好,海鲜味道做的也很不错。其他不说了,就推荐它的点心--菠萝包, 里面的馅心真的有菠萝的喔,全鹏城独此一家。我每次有同学来要吃海鲜,都是带他们去那里吃。 

33、海上皇 振中路和华发北路交界处。是吃早茶,午茶,下午茶的绝佳去处。最喜欢吃的是它的牛杂,皮蛋瘦肉粥,白灼菜心(吃的时候淋上调好的蚝油)。其实还有一个很好吃,就是它的点心拼盘。不过一般不是熟客它不卖,你可以理直气壮的点,虽然菜牌上不写,坚持坚持,它就给做了,20元一份,每份是八种点心一起上笼蒸。在这里吃晚餐也不错,推荐它的芝士焗生蚝,才8元一个,每次都去吃两个,HOHO. 

34、小肥羊 振华路与燕南路交界,不过实在振华路口。羊的火锅。之所以这么说,是因为里面几乎都是羊的天下,看名字就知道路。什么羊肉,羊肝,羊蛋什么的,不过我坚决只 吃羊肉。这里吃火锅,没有小碗调料的。火锅的汤味道很好,不知道放了什么东西调味的,据说还不准打包,免得外泄。吃东西就是点羊肉吃咯,外加火锅通常都有的,不过味道真是很不错,可以理解我两星期内竟然去吃了四次,要预订。 

35、漓江又一轩 沿着小肥羊西行,就看见远远的一块绿色招牌,就是它了。这里是广西干锅口味,鸡煲鱼煲和鸭煲,不过我还是爱吃鸡煲,每种都试过了。两三个人去吃点个鸡煲就够了,青菜和豆腐是送的。它的吃饭是先吃干锅,然后快吃完了才加入汤水煮锅。千万不要吃它的任何鱼,反正我从来没有爱吃过。点心里面倒是有很多好的,对了它的糯米牛肉不错的喔,还有土豆片,香芋饼,可以一顿吃好几个。要是上火,还可以点个龟苓膏吃 ,降火的,据说是梧州的。其实我吃过正宗的梧州龟苓膏,苦死了,我还是喜欢吃甜点的。

36、北海渔村 深圳这个渔村那个渔村是铺天盖地,我喜欢来这家吃。随便点些海鲜吃,但 是最主要的是我爱吃这里的鲍鱼粥。不是那种大的,而是特价三元一个地,点30个小鲍鱼,然后煮粥喝,要半个多小时才能上桌。上次献血完元气大伤,狠狠吃了一顿鲍鱼粥,仿佛心理上才恢复过来。

37、穆斯林 去了很多次,还是不知道叫什么名字,反正那里就是穆斯林吃的地方。羊肉,羊肉。位置在南园路和爱华路的交界处二楼。里面都是新疆姑娘喔,好吃东西很多啦,比 如大盘鸡,羊肉汤等等。最好吃的当属羊腿,15元一个,这么便宜,吃的时候人手一腿,抓起来大啃特啃,不是一般的好。友情提醒:别点多了,吃个羊腿已经打倒一半了。 

38、巴香鱼头 沿着穆斯林店,往爱华路南走几步,就看见了。里面的鱼头做法很有特色,是炸过之后再放进汤里煮,配火锅吃的汤料也是干货,吃的时候勺火锅的汤进去搅和,然后再吃。味道奇特,值得一吃。 

39、图们烧烤 就在巴香鱼头的马路对面拉。爱吃它的烤鸡架子,就是爪子的筋,吃起来嘎本嘎本特别脆。其他的爱吃啥就点啥吧,可以让店员帮你们烤,免得自己回去都灰头土脸的。

40、山天 沿着穆斯林店,朝南园路的东面走100米吧,就看见山天了。我们喊它三个一, 因为商标就是这样的。里面也是吃火锅的,但是是吃菌类的,进去不用人指点,爱吃啥就点啥吃吧,火锅的汤底都是菌汤熬的,很鲜。菌丝炒饭是很不错的主食。最喜欢菌汤煮的鱼头了,一顿可以吃四五个,还意犹未尽。牛杆菌是我的最爱。 

41、沅绿回转寿司店,在振兴东路靠近华强北的地方,对面就是紫荆城。寿司倒不是非常感兴趣,爱吃的是它的鳗鱼饭,可惜我的肚子有限,每次都只能吃一份就饱了。不过配上的豆豉汤,真不是一般的难喝,不过有同事每次去都是帮我包喝的。 

42、江之鹤 振兴东路中段有个建设银行,旁边一拐就看见了。哇塞,好贵的说。晚餐每个人如果是吃自助,要168。吃过三次,最好吃的就是烤鳗鱼条,味道比沅绿好多了。尤其 是它的银鳕鱼西京烧,天哪,我有一回疯狂的点了四份吃,小姐肯定对我有意见了。第二次去的时候,他们就开始限制吃饭时间了,只给三个小时的点菜时间,其他随便。到第三个小时的时候,我已经饱的不能动了。 

43、舞鹤 又是一个广告贼多的料理店,在上海宾馆。不过说实话,自从上次自费去过一次之后,打死我都不会再去了,估计都是补偿广告费去了,不过骗妹妹不错喔。贵,而且我觉得味道不如江之鹤的好。不过料理吃过了,也就这么个样。罗湖商业城下面有一家料理店,是深圳味道最好的地方,有空大家可以去试一下,名字忘记了。 

44、罐罐鸡 这是一家小吃。就在我刚才说过的海上皇旁边,就吃的是一个一个砂锅的面或者粉。自然主打的是鸡,可以配猪肝,或者其他的菜牌上有的东西。吃的时候,从罐子里捞面条出来到小碗里,配以咸菜花生,汤本就是鲜的,很是爽口的说。     

45、家家长沙米粉店 唉,挤死了,每次去都是人山人海的,空调又不足,还这么多人爱吃。华强北地区就有三家,两家在华发北路上,靠近红荔路的地方。老板一定发财了,本来就只有一家的,一年之内在深圳市内连开四家分店。里面卖粉,也卖面条。什么大肉面啊,冬菇肉饼蒸鸡蛋面啊,酸辣面啊,什么什么的,都挂墙上,大家爱吃什么点什么。粉是宽粉,我不爱吃粉。面是碱水面,很筋斗的,唉,热就热吧,谁让你爱吃呢。每次吃完都是大汗淋漓。吃的时候还可以点红烧猪蹄,豆干或者绿豆汤喝,都是不错的。

46、深运粥城 名字是不是叫这个忘记了,位置在红荔路靠近华富路的地方。反正你沿着红荔路往华富路走就是了,在路的北侧。这里的粥集合了潮州粥的大全,不会点不要紧, 吃几次就总结出经验来了。 

47、香积世界素菜馆 在华强北丽人世界的背后,振兴西路靠近华强北的路口。里面都是吃素的,偶尔换个口味,去吃一下,也是挺不错滴,就是吃完了容易饿,毕竟现在的人都是肉食动物。 

48、深井烧鹅 据说是香港同胞过来开的。门面不大,不注意还找不着。在家家长沙米粉的对面,注意,这里说的家家是在华发北路东侧的那家,也就是说烧鹅店在华发北西侧。 进去如果一个人的话,可以点个叉鹅饭,意思就是烧鹅和叉烧,如果两个人以上,可以来个叉鹅例盘,吃个痛快。味道很正宗的说,的确如此。不过价格也不菲喔,简单一个饭要15,如果是吃一个烧鹅腿,要25。它用来蘸烧鹅的小碟汁,真是爽死了。

49、谭鱼头:新华宾馆对面,那个鲜、那个辣、那个麻真是不摆了

50、人民公社:西丽转盘往白芒关走,不到5分钟车程地方(路痴女人,交代不清,见

谅),所有服务生和店面装扮都是文化大革命时期。一律土锅、土碗,有很多鸟可以吃,干锅的,吃完做火锅,青菜免费提供,最绝的是楼上还有免费电影看,什么小兵张噶、渡江侦察记、、、很是过瘾川菜:

51、干锅系列:八卦三路平安大厦附近有一家佳和长沙米粉店,湘菜馆,服务不错,最爱吃他们的干锅肥肠,分量十足,蒸饭很香。

52、湘鄂情:   我弟弟狂喜那儿的瓦缸猪手

53、车工庙丹桂轩:  精致粤菜,  喜欢座在露台看高尔夫美景

54、上步吉之岛对正南面小巷一小店,有很正宗的双皮奶,拆鱼粥,陈村粉

55、湖南人家——皇岗中学附近,湖南人家在深圳开了6家分店了,但唯有皇岗店最好,是我吃过的认为最好吃最实惠的湘菜馆,推荐:香菜野山椒炒牛肉丝,干锅鸡杂,干锅田鸡,剁椒鱼头...价格不贵,就是份量少点。怕辣的人最好不要去吃

56、老四川——科技园深南花园的后边,专做川菜和粤菜,吃过一次川菜,12元一份的凉拌猪耳,28元的干锅鸡杂,份量足,狂辣,好吃。

57、喝早茶去格兰晴天二楼,小吃非常精致,猪肚、鸡爪、炒饭...好吃...

58、在深南路设计院 后面的凤凰路上, 有一家老成都(在沃尔玛附近),四楼的火锅也还可以,价格合理, 重点推荐的是三楼的自助餐--粗粮坊,各种小点,等,品种丰富,每位18元,有肚量大的朋友, 可以吃到很舒服。每次我去吃着吃着就不行了, 朋友门直惋惜。        很超值!

 59、在春风路穆斯林宾馆的对面, 名字叫撒哈拉一千零一夜。是深圳最正宗的中东风味的穆斯林餐厅。我和朋友常常去,羊肉茄子、哈达丝

MapReduce简介zz

MapReduce是Google 的一项重要技术,它是一个编程模型,用以进行大数据量的计算。对于大数据量的计算,通常采用的处理手法就是并行计算。至少现阶段而言,对许多开发人员来说,并行计算还是一个比较遥远的东西。MapReduce就是一种简化并行计算的编程模型,它让那些没有多少并行计算经验的开发人员也可以开发并行应用。在我看来,这也就是MapReduce的价值所在,通过简化编程模型,降低了开发并行应用的入门门槛。相对于现在普通的开发而言,并行计算需要更多的专业知识,有了MapReduce,并行计算就可以得到更广泛的应用。

MapReduce的名字源于这个模型中的两项核心操作:Map和 Reduce。也许熟悉Functional Programming的人见到这两个词会倍感亲切。简单的说来,Map是把一组数据一对一的映射为另外的一组数据,其映射的规则由一个函数来指定,比如对[1, 2, 3, 4]进行乘2的映射就变成了[2, 4, 6, 8]。Reduce是对一组数据进行归约,这个归约的规则由一个函数指定,比如对[1, 2, 3, 4]进行求和的归约得到结果是10,而对它进行求积的归约结果是24。

Map操作是独立的对每个元素进行操作,在FP中,操作是没有副作用的,换句话说,Map操作将产生一组全新的数据,而原来的数据保持不变。因此,它是高度并行的。Reduce操作虽然不如Map操作并行性那么好,但是它总会得到一个相对简单的结果,大规模运算也相对独立,因此也是比较适合并行的。

无论是Map还是Reduce都是以另外的函数作为参数,在 FP中,这样的函数被称为高阶函数(high-order function)。正是因为它们可以同其它函数相结合,所以,我们只要把Map和Reduce这两个高阶函数进行并行化处理,而无需面面俱到的把所有的函数全部考虑到。这样便形成了一个以Map和Reduce为基础的框架,具体应用相关代码写在用户代码中,之后与MapReduce结合获得并行处理的能力。当然,这么做的前提是按照这个框架的要求,把计算归结为Map和Reduce操作。为什么是Map和Reduce?从前面的内容我们可以看出,在 Map过程中,我们将数据并行,也就是将数据分开,而Reduce则把分开的数据合到了一起,换句话说,Map是一个分的过程,Reduce则对应着合,这一分一合便在不知不觉中完成了计算。所以,站在计算的两端来看,与我们通常熟悉的串行计算没有任何差别,所有的复杂性都在中间隐藏了。

所有这些并行化能力的获得都与FP有着密不可分的关系。事实上,不仅仅是MapReduce从FP中获得了灵感,其它一些并行编程模型也走上了同样的道路。 FP中有很多的好东西,比如自动内存管理,比如动态类型。在遥远的年代里,因为机器性能的原因,它们无法得到广泛应用,当机器性能不再是瓶颈,这些东西便逐渐复活了。

前面提到过,并行计算对于普通开发人员来说,有一个比较高的门槛。从前我们或许可以不理会并行计算,但是随着Intel开始将多核带入人们的日常生活,并行计算将会变得更加平民化,毕竟谁也不希望自己机器里面的多核只有一个在干活。现在的许多操作系统会把多核视为多处理器,但那也得有多任务才能在CPU处多分得一杯羹。对于服务器端应用来说,拥有多任务的能力是一个正常的现象。但对于很多桌面应用来说,一条道跑到黑的情况比较多见。而且,多任务并非为并行计算专门准备的,所以,控制粒度是很大的。如果需要更细粒度的并行计算,至少从表达能力上来说,多任务就有些麻烦了。

并行计算进入日常开发的难度就在于编程模型,太复杂的东西会被人唾弃的,CORBA在这方面已经是个反面教材了。MapReduce已经为我们演示了一种可以接受的编程模型,接下来,变化还会有,Intel和AMD都在努力。不过,具体的进程得取决于多核CPU占领市场的进度。