JSP流量统计系统

<

小小豆叮

Java嵌入式开发之j2me--五

MIDP要求平台设备提供一个机制用来储存简单的数据记录,通过正常的平台事件,比如重新启动和电池更新维护系统的完整性。 MIDP称一个持久数据库为 RecordStore。 在我们的示例中, MIDlet打开并添加一条记录到 " MutualFundQuotes " RecordStore。 正如我们的演示程序,能添加到 RecordStore中的唯一一种类型的记录是字节数组。 相同的 RecordStore是一个资源,它可以通过套件共享。 根据 MIDP规范, 当 MIDlet从平台中删除后,RecordStore也会被从平台中删除。

  PDA简表

  Palm公司是开发PDA简表规范的领头人, 这个简表也是完善了 CLDC,在相当长的一段时间内,它都将是 kjava类程序包的替代品。 Java规范建议这个 profile至少应当提供两个核心功能片段: 一个用户界面显示工具包,适合于 "有限的尺寸和深度显示 "和一个持久数据存储器机制。 显示工具包应该是抽象窗口工具包的一个子集, 而持久机制将为应用程序、数据、配置/环境信息提供简单的数据存储。

  Foundation简表

  下面三种简表不是非常常见, 这三种简表的职责都是为了完善 CDC。 Personal和 RMI简表实际上是 Foundation简表的扩展。 Foundation简表的任务是担任一个基础简表,便于以后开发出来的提供图形用户接口、 网络等功能的简表附着在它之上。 除了用于基础简表, Foundation简表还提供完整网络的支持,不管有没有使用图形用户接口。

  Personal简表

  在当前的规范需求下, Personal简表提供下一代 PersonalJava环境。这个简表允诺,提供互联网连接性和 Web保真度以及一个能够运行 Java applets的 GUI。

  RMI简表

  回想一下 CDC配置为共享的、固定网络连接信息设备提供最小的 Java环境。 RMI简表将通过提供 Java到 Java的RMI来协助提供更好的网络连接性。 通过使用 J2SE ( 1.2.x或更高版本的 ) RMI,这个简表将允许这些网络设备与其他系统应用程序交互操作 (这个系统不必也运行 J2ME )。

  kjava类

  正如前面提到的那样, kjava类是最初提供的一个供测试用的类,在 Palm设备上运行早期的 KVM和配置版本。 它们将被 PDA简表代替。 kjava类扩展了 CLDC并且提供一个图形用户接口、 Palm数据库访问,简单集合类和一个三角法计算器。

  在代码段2中,我使用 com.sun.kjava重写了 MIDP FundTracker程序,让它在 Palm上工作。 和前面的程序一样,这个简单的程序允许用户输入一个公基金代号并从WWW上的金融报价服务商那里取回报价。

  kjava应用程序被称作 spotlet。 事实上,一个应用程序可以由很多 spotlet组成,但是在任何时间只有一个 spotlet可以显示在 Palm屏幕上。 在我们的例子中,我们创建一个基本 spotlet-- RequestFormSpotlet.java,为我们的两个 spotlets子类提供用户界面。代码段 2扩展了基本的 RequestFormSpotlet以便得到并储存一个报价。 RetrieveSpotlet也扩展了基本 RequestFormSpotlet并允许储存的报价被取回(见图)

  代码段2

import com.sun.kjava.*;

public final class FundSpotlet extends RequestFormSpotlet {

public static void main (String args[]) {
new FundSpotlet().draw();
}

private void draw() {
initForm();
setTitle("Fund Quote Requested");
}

public void penDown(int x, int y){
if (getExitButton().pressed(x,y)){
getGraphic().playSound(Graphics.SOUND_CONFIRMATION);
System.exit(0);
}
if (getSymField().pressed(x,y))
getSymField().setFocus();
if (getGetButton().pressed(x,y)) {
quoteRequested();
}
}

private void storeQuote (String fund, String newQuote) {

int dbType = 0x46554e44;
int dbCreator = 0x43415454;
com.sun.kjava.Database quoteDB;

try {
quoteDB = new com.sun.kjava.Database(dbType,
dbCreator, com.sun.kjava.Database.READWRITE);
if (!quoteDB .isOpen()) {

com.sun.kjava.Database.create(0, "MutualFundQuotes",
dbCreator, dbType, false);
quoteDB = new com.sun.kjava.Database(dbType,
dbCreator, com.sun.kjava.Database.READWRITE);
}
byte[] data = (fund + "#" + newQuote).getBytes();
quoteDB.addRecord(data);
quoteDB.close();
}
catch (Exception recordException) {
System.out.println("Unable to store quote and/or use
Mutual Fund Quote database.");
}

}

private void getAndDisplayQuote() {
String fundSymbol = getSymField().getText();
if (fundSymbol.length() > 0) {
String theQuote = QuoteService.getQuote(fundSymbol);
if (theQuote != null) {
storeQuote(fundSymbol, theQuote);
message(theQuote);
}
else
message("No quote. Check Symbol");
}
}

private void quoteRequested() {
message("");
getGraphic().playSound(Graphics.SOUND_STARTUP);
if ((getSymField().getText().length() > 0)) {
getAndDisplayQuote();
} else
{
message("Symbol required!");
}
}

}


  在 RequestFormSpotlet程序中,类似于 MIDP中的 Display对象,单独的 Graphics管理许多 spotlet用户界面显示。它考虑到了屏幕会被清除,显示边界会被建立。 不象 MIDlet,没有屏幕或画布对象来让我们添加用户界面小组件, 取而代之的是按钮、文本字段等等,直接描画在 spotlet上。 paint()方法利用图形环境从独一无二的 Graphics在屏幕上显示小组件。

  我们的MIDP程序的 QuoteService类的大部分可以重新使用。 因为 kjava没有象 MIDP中HttpConnection这样特定的连接器界面,所以我们必须利用更多标准的一般的连接器结构表单获取 HTTP链接。 为了做到这一点,使用代码段 3中的代码替换 getQuotePage()方法。注意注意使用 Connector,就像在 MIDP中我们使用 HttpConnection一样。

  代码段3

private static String getQuotePage(String symbolString) {
StringBuffer quotePage = new StringBuffer();
int ch;
try {
InputStream in = Connector.openInputStream (
"testhttp://someurl/some_application?page=++&mode=fund&symbol="+
symbolString);
while ((ch = in.read()) > 0) {
quotePage.append((char)ch);
}
in.close();
return quotePage.toString();
} catch (IOException ex) {
System.out.println("Exception reading quote from
  HTTP Connection");
 return null;
 }
}


  Palm设备广泛利用数据库, 你的 Palm中的通讯簿、备忘录和记事本应用程序都与数据库有关。 kjava程序包提供了一个非常小的 Database类,不仅可以创建并保持应用程序数据,而且可以访问现有的数据库。 如果你熟悉 Palm数据库,你可能会对 kjava Database类提供的功能和信息感到失望。 然而,请再次记住, kjava只是一个演示的版本。

  在我们的例子中,我们的 spotlet访问一个 Palm数据库 (如果不存在的话,则创建一个新的数据库)来储存公基金报价。每个 Palm数据库都必须有名字、创建者 ID (一个 Palm登记的唯一的标识号 ) 和一个指定到某个单独应用程序的类型号。 试图打开数据库要通过尝试创建一个带有 ID信息的数据库实例来实现。 就象 MIDP RecordStore,记录被添加进 kjava数据库,通过把一个字节数组当成记录添加到数据库中的形式。
Java嵌入式开发之j2me--一 Java嵌入式开发之j2me--二 Java嵌入式开发之j2me--三 Java嵌入式开发之j2me--四 Java嵌入式开发之j2me--五 Java嵌入式开发之j2me--六 <

小小豆叮

Java嵌入式开发之j2me--四

 第四节 谈谈J2ME简表

  虽然配置为一组通用设备提供了最小的 Java平台,但是应用程序开发者感兴趣的是为一个个别的设备生产应用程序,当他们只是使用配置的话,他们编写的应用程序就会有一些欠缺。 配置必须满足所有的设备的最小的要求, 用户界面、输入机制和数据持久性有高度地设备具体性,每一种设备都有自己的用户界面、输入机制和数据存储方法,这些往往不在配置所满足的最小要求的范围之内。

  简表为相同消费电子设备的不同的生产商提供了标准化的 Java类库, 事实上,虽然配置规范的开发由 Sun领导,但是许多简表规范仍将继续由特殊设备的供应商领导。 比如说, Motorola领导了行动电话和呼叫器简表规范的开发,又如 Palm 领导 PDA简表的开发。

  现在,五个已知简表已经有了规范, 记住,每个简表的责任都是为了完善配置的不足,下表列出了这五个简表:

简 表 完善配置
Mobile information devices profile (MIDP) 移动电话和呼叫器 CLDC
Personal digital assistant profile Palm和Handspring的PDA 设备 CLDC
Foundation profile 用于所有不需要GUI的CDC设备的标准简表 CDC
Personal profile 替代PersonalJava的Foundation完善的简表 CDC
RMI profile 提供RMI的Foundation完善的简表 CDC
 
  现在我想谈一谈另一个Java类库集,它现在差不多可以被认为是另一个简表了。当Sun为Palm开发第一个KVM时,他们需要一组类来 开发Palm的演示程序。这套类库被封装进 com.sun.kjava程序包, 在 CLDC早期的开发中,这些类被广泛的使用来测试和演示 J2ME。因为 kjava是唯一的允许应用程序开发者使用 J2ME和 KVM开发应用程序的类,所以它就被广泛使用了。甚至到了今天,一个用于 PDA或更特殊一点的 Palm的简表多已经在开发中,许多开发者仍然希望使用 kjava类来开发 PDA应用程序。尽管 kjava类不被支持,并且仅仅用于设计测试程序或演示程序,并且它们将被一个即将到来的简表所替代,但是开发者们仍然热衷于使用它来开发。

  MIDP

  Mobile Information Device Profile(移动信息设备简表 ,简称 MIDP ),第一个实现的简表,补充了 CLDC并且提供应用程序语义和控件、用户界面、持久存储器、网络和用于移动电话的计时器、双通道呼叫器和其他无线电设备。 因为 MIDP和 CLDC两者都有引用实现,我们可以使用一个例程来研究一下这个简表。

  下面的例子是一个允许用户输入代表想知道的基金报价的代号的例子。应用程序然后通过 HTTP接到一个金融网站,获得基金报价,把价格储存在一个数据库,然后把价格返回给用户。

// 到如需要的J2ME类
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.rms.*;

// 扩展MIDlet类来构建我们的自定义MIDlet
public class FundTracker extends MIDlet implements
CommandListener {

file://显示管理者变量
private Display display = null;
file://MIDlet的表单变量
private RequestForm reqForm = null;

file://MIDlet构建器
public FundTracker () {
display = Display.getDisplay(this);
reqForm = new RequestForm("Fund Tracker");
reqForm.initForm();
reqForm.setCommandListener(this);
}

file://开始 MIDlet 应用程序
protected void startApp() {
display.setCurrent(reqForm);
}

file://暂停 Midlet
protected void pauseApp() {
}

file://销毁Midlet
protected void destroyApp(boolean unconditional) {
}

file://通过监听者响应命令
public void commandAction(Command c, Displayable s) {
if (c == reqForm.getExitCommand()) {
destroyApp(false);
notifyDestroyed();
return;
}
if ((c == reqForm.getGetCommand()) &&
(reqForm.getSymField().getString().length() > 0)) {
getAndDisplayQuote();
} else
{
reqForm.getMsgString().setText("Symbol required");
}
}

file://储存由#分开的成对的基金字符串和报价字符串
private void storeQuote (String fund, String newQuote) {

file://数据库变量

RecordStore quoteDB = null;

try {
quoteDB = RecordStore.openRecordStore(
"FundQuotes", true);
byte[] data = (fund + "#" + newQuote).getBytes();
int size = data.length;
quoteDB.addRecord(data, 0, size);
quoteDB.closeRecordStore();
}
catch (Exception recordException) {
System.out.println("Unable to store quote and/or
use Fund Quote database.");
}
}

file://通过QuoteService类取回提交的代号表示的基金报价
private void getAndDisplayQuote(){
String fundSymbol = reqForm.getSymField().getString();
if (fundSymbol.length() > 0) {
String theQuote = QuoteService.getQuote(fundSymbol);
if (theQuote != null) {
storeQuote(fundSymbol, theQuote);
reqForm.getMsgString().setText(theQuote);
}
else
reqForm.getMsgString().setText("No quote" +
'\n' + "Check Symbol");
}
}
}


  MIDP应用程序称为 MIDlet, 为了创建一个 MIDlet,你必须写一个扩展基本 MIDlet类的类 (就像我们在上面代码段中列出的那样)。 这有点类似常见的 applet或 servlet。 MIDlets独有的东西是把多个 MIDlet组成一个 MIDlet套件的能力。 这就允许 MIDlet在一个单独的 JVM环境中共享资源,比如一个数据库等等。 事实上,我们上面给出的例子还包括一个 MIDlet ( RetrieveQuote,见上段程序),用于取回所报价格。 当MIDlet被请求时, MIDlet通过构造程序实例化,然后调用实例的 startApp()方法。

  在 FundTracker例子中, MIDlet的用户界面或显示是由 Display类的一个实例管理的。 对于每个 MIDlet,只有一个显示管理器实例。 所有可以显示的项目,像屏幕或画布(canvas),通过这个管理器都能够成为可见的。因为行动电话和呼叫器能力的多样化,又因为用于这些设备的应用程序类型的差异, MIDP规范提供了两种类型的用户界面。一个可移植性稍差、明确设备、低水平的应用程序接口,允许图形元素精确的控制和放置。 这个接口类型是用于应用程序特性比较典型的设备特别设计的,比如电子游戏。 一个可移植性稍好的、抽象的、高级的 GUI应用程序接口,提供来用于商业应用程序。

  我们的例程使用的是高级的应用程序接口和典型的用户界面组件 (文本框,列表等等 ),是这类界面通用的。比如说,实际的表单和所有的小组件在一个单独的文件中都已定义。 就像在代码段一中列出的那样,当 MIDlet创建时,一个表单的实例与 MIDlet关联。 在调用 MIDlet startApp()方法的时候,通过 Display对象显示表单。 使用一个用于表单的类,允许我们在我们简单的报价检索应用程序中重新使用这个表单 ( RetrieveQuote )。为了清晰性和风格,我们通过一个单独的类来定义报价服务。 为了演示一般连接器结构的能力,我们的报价服务类通过一个 Connector实例取回报价。
Java嵌入式开发之j2me--一 Java嵌入式开发之j2me--二 Java嵌入式开发之j2me--三 Java嵌入式开发之j2me--四 Java嵌入式开发之j2me--五 Java嵌入式开发之j2me--六 <

小小豆叮

Java嵌入式开发之j2me--二

 第二节 J2ME的体系结构

  现在个人计算机系统的数量和种类已经发展到无法控制的地步,请你想一想,你编写的程序运行在“信息家电”舞台的情景吧,这些信息家电包括呼叫器,行动电话,像Palm这样的个人数字助手(PDA),电视机顶盒,POS终端以及其他的消费电子设备。现在全世界上光是手提电话生产商就有许多,更不用说别的家电设备了,而且每一种家电设备又有不同的特性和界面。所以,你可以想到, Java应用程序的轻便性以及能够解决开发这么许多不同的设备程序的能力,使大家对J2ME有很大的期许。当然,为了更好的开发这些信息家电,就要求把Java的精髓压缩进一个非常小的程序包中,这就是J2ME。

  J2ME是一种通过许多部件和规范的技术, 这众多的部件和规范帮助 J2ME来满足这众多的消费产品的不同的需要。和所有的爪哇程式语言技术一样,在它的核心属于一种虚拟机。 就像使用所有 Java技术一样,J2ME的核心也在一种虚拟机中。 最初,用于 J2ME应用程序虚拟机的被称作 Kilobyte virtual machine或简称 KVM。就像它名称的含义, KVM比较小,通常只有 128K或更少。这比起我们通常了解和使用的 Java 2标准版 Java虚拟机 ( JVM )的 32 MB来说就小得多了。

  用于连接虚拟机的是一系列配置和简表,它们提供了用于特定 J2ME环境的类应用程序接口(见图二)。 每个配置和简表处理一般或具体的消费产品,配置和简表规范是由多种多样的设备生产商和用户共同开发并建立的。配置是用于一组通用设备的最小的 Java平台, 常常归为一种横向的设备分组,相对来说,横向分组设备是那些共享相同的内存安排,通信带宽,能量需求以及用户能力的设备,一般认为配置能够提供这众多的设备的所有需求。


图二解释: J2ME层次 Java虚拟机是 J2ME技术的核心,但是配置和简表提供特殊环境的类应用程序接口。配置是用于一组通用设备的最小的 Java平台,而简表则为具体的设备家族或特别的应用程序提供更具体的能力。

  J2ME领域的新的开发者常常被这些事实困惑的, 事实上, Sun的第一个配置(现在只是一种配置的引用实现 )带有称为 KVM的虚拟机引用实现, KVM满足配置的虚拟机的必要条件。然而, Sun的 KVM也可以被另外一个虚拟机所代替,现在, 正是因为配置和虚拟机结合得有点紧密,因此导致了这么多的混乱。

  另一方面,简表完善了 配置,为某个具体的设备家族或某个具体的工业片段应用程序提供更高的性能。 换言之, 简表为具体的纵向市场的设备比如说行动电话提供更多的性能。这里的关键就是 简表必须完善 配置, 没有 配置和虚拟机提供核心类应用程序接口和运行期环境的话,简表也不会工作。

  通常,简表为一种给定的垂直分组设备提供用户界面、输入法、持久性机制。这类 简表被认为是发展这些设备应用程序的完整的工具包。我们见到最多的应用程序简表的例子就移动电话简表和个人数字助手(PDA)简表,其他简表为范围宽广的设备提供非常特殊的功能或应用程序可移植性,这方面的例子就是提供远程方法调用 ( RMI )功能的简表和提供统一银行事务的简表。

  虚拟机、 配置、 简表…你是不是已经被搞迷糊了? 如果这样的话,我们就来简化一下 J2ME体系结构吧。 如果你想为小型信息家电编写 Java应用程序的话,你就需要两个前提:一个 配置和至少一个 简表。 现在, 一般是配置捆绑了虚拟机和一套针对你的平台所能够用的横向分组设备的Java类库。其次,你至少还需要一个 简表来为你的平台提供附加的 Java类,这个 简表通常会为你的设备提供用户界面、输入和数据库类。有了这两个前提,你就了使用 Java为你的设备编写应用程序的基本的J2ME环境。
Java嵌入式开发之j2me--一 Java嵌入式开发之j2me--二 Java嵌入式开发之j2me--三 Java嵌入式开发之j2me--四 Java嵌入式开发之j2me--五 Java嵌入式开发之j2me--六 <

小小豆叮

Java嵌入式开发之j2me--三

 第三节 详细谈谈J2ME配置

  J2ME可以在好几个不同的配置中进行配置。 就像先前提到的,每个配置为一组通用设备提供最小的 Java平台,到目前为止,只有两种配置规范。通过 Java规范定义的这两种配置是 Connected Limited Device Configuration (有限连接设备配置, CLDC )和 Connected Device Configuration (连接设备配置 CDC )。

  CLDC是为使用较小的存储容量的设备设计的 (参见图3 )。 CLDC用于内存在128到 512K之间的消费电子设备, 这一类别中典型代表的设备包含呼叫器、行动电话、PDA和POS终端;而另一方面, CDC用于比 PC机小但是具有比 512K内存多的设备,这一类设备包括互联网络电视系统、机顶盒、POS系统、汽车导航以及娱乐系统。一般来说, CDC使小型设备只要具有少量的资源,至少比台式机要少的资源就能进行Java编程,而CLDC使小型设备所拥有的资源只要比一张智能卡多一点就可以进行Java编程了。



图二解释 设备覆盖的范围 J2ME有两个配置CLDC和 CDC,CLDC是为使用较小的存储容量的设备设计的,而CDC用于比 PC机小但是具有比 512K内存多的设备。


  除了在容量大小和能力上对虚拟机规定了必要条件,配置还规定了类应用程序接口要包含常见的 java.io、 java.net、 java.util和 java.lang包,配置可能还要包括其他需要的程序包。

  CLDC

  CLDC起源可以追溯到1999年JavaOne大会上介绍的Sun的第一个袖珍版 Java和第一个 KVM以及相关的类库,虽然 CLDC和所有的配置都满足成为虚拟机的条件,可它本身还不是虚拟机,CLDC的引用实现只是包含在当前的分布中的 KVM。

  根据规范中所说,运行 CLDC的设备应该有 512K或更少的内存空间、一个有限的电源供给 (通常是使用电池)、有限的或断断续续的网络连接性 ( 9600 bps或更少 )以及多样化的用户界面甚至没有用户界面。 通常说来,这个配置是为个人化的、移动的、有限连接信息设备而设计,比如呼叫器、移动电话和 PDA等。

  与 J2SE相比, CLDC缺少下列所说的这些特征:

  AWT(抽象窗口开发包), Swing或其他图形库

  用户定义类装载器

  类实例的最终化

  弱的引用

  RMI

  Reflection(映射)

  CLDC有四个包: java.lang、 java.util、 java.io和 javax.microedition。 除了 microedition包以外,其他的这几个包都是J2SE包的核心子集,CLDC采用这些J2SE类库,但是把其中一些在微型设备中用不到类、属性、方法去掉了。因此 CLDC类库有许多细微的差别。 如果您想研究J2SE和 CLDC类库之间的差别,请参阅相关文档,在此就不详细说明了。

  想要理解为什么CLDC去除这么多J2SE中重要的类和特征,请回想一下与 CLDC相关的两条基本原理。首先,它只有 512K的内存空间, 而像RMI和映射需要的内存太大了。 其次,配置必须满足为一组通用设备提供最小的 Java平台。 在个人移动信息设备领域中,许多系统都不能支持 J2SE中的众多的高级特征。 例如,许多消费电子产品不能支持浮点数; 因此 Float(浮点类)和 Double(双精度类)就被删除了。 再看另外一个例子,许多系统没有或不提供访问一个文件系统的功能或权限。 因此与文件有关的类也被丢弃了。又如,错误处理是一个代价非常高的过程处理,在许多消费电子设备中,故障恢复是很难的甚至是不可能的。 所以在 CLDC中,许多错误处理类也被删除了。

  java.microedition程序包提供了一个一般的结构来替代许多 J2SE网络输入/输出类。 CLDC一般连接器结构还定义了一个 Connector类,允许许多不同类型的连接能够使用静态方法,下表列出使用同一个Connector类创建和打开五种不同类型的连接的方法:

  HTTP Connector.open("http://www.xyz.com");

  套接字 Connector.open("socket://111.222.111.222:9000");

  通讯端口 Connector.open("comm:1;baudrate=9600");

  数据报 Connector.open("datagram://111.222.111.222");

  文件 Connector.open("file:/xyz.dat");

  一般连接器结构提供给应用程序开发者一个到通用低水平硬件的简单的映射表。成功执行 open语句将返回一个实现一般连接界面的对象。

  CDC

  CDC涵盖了个人电脑与有至少 512K内存的小型设备之间的中间地带。现在,这一类设备通常是共享的、固定的 (不用移动)网络连接信息设备,像电视机机顶盒,网络电视系统、互联网电话与汽车导航/娱乐系统等等。

  首先,CDC基于 J2SE 1.3应用程序接口,包含所有定义在CLDC规范(包括 javax.microedition程序包)中的Java语言应用程序接口。与CLDC相比, CLDC所有缺少的特性和类在 CDC中都被补齐,包含映射、最终化、所有的错误处理类、浮点数、属性、输入/输出 ( File、 FileInputStream等等 )和弱的引用。 一般说来, CDC中预期的类包括一个J2SE子集和一个完整的 CLDC超集,如图4中所示:



图4 :J2SE、CLDC和CDC的关系

  就像使用所有的配置一样,CDC有基层虚拟机的具体的必要条件。 根据 CDC规范,基层虚拟机必须提供实现完整的 Java虚拟机的支持 。 如果虚拟机实现有一个用于激活设备的本地方法的界面,它必须兼容 JNI 1.1版本。 如果虚拟机实现有一个调试界面,它必须兼容 Java虚拟机调试界面 ( JVMDI )规范。 如果虚拟机有一个简表界面,它必须兼容 Java虚拟机简表界面 ( JVMPI )规范。 可见,为了实现这些功能,CDC肯定会变得很大,就不能称其为K虚拟机了,因此,我们通常称用于CDC的虚拟机为 CVM,这里的 C代表 compact、connected、consumer。
Java嵌入式开发之j2me--一 Java嵌入式开发之j2me--二 Java嵌入式开发之j2me--三 Java嵌入式开发之j2me--四 Java嵌入式开发之j2me--五 Java嵌入式开发之j2me--六 <

小小豆叮

JSP聊天室

<

小小豆叮

一个用jsp和access做的bbs

<

小小豆叮

在win2000下的安装 JSP

1.下载jdk1.3和tomcat; 2.安装jdk1.3和解压tomcat到c:盘根目录下; 3.进入tomcat的目录里,找到startup.bat文件,打开编辑。 在行call tomcat start的前面加上两行: SET TOMCAT_HOME=c:\tomcat 和 SET JAVA_HOME=c:\jdk1.3 注:c:\jdk1.3是jdk1.3安装的位置。 4.然后鼠标右键点击“我的电脑”,选择属性。在系统属性中打开高级选项,点击"环境变量",在“用户变量”和“系统变量”中都加入两个变量classpath和path,它们的值是: classpath的值c:\jdk1.3\bin\tools.jar;C:\jdk1.3\lib\dt.jar path的值为 c:\jdk1.3\bin;c:\jdk1.3 5.运行tomcat服务器(即startup.bat文件),假如有个dos窗口停留着没关闭,并有一些运行成功tomcat和初始化信息,证明tomcat运行成功。 6.ie地址栏中输入127.0.0.1:8080,假如成功的话,就会出现tomcat的欢迎画面,上面有一些jsp和servlet的例子,至此jsp环境搭建完成。 本人也是得人指点,安装完成了jsp的环境,有很多东西还在学,以上有什么不妥的地方,请多多指点。最近比较忙,没时间继续深入去学jsp,也希望大家互相帮助,共同进步。 <

小小豆叮

iPortal 应用服务器的试用版

<

小小豆叮

JSP主页基地管理系统

<

小小豆叮

Java嵌入式开发之j2me--六

 第五节 再谈谈一些J2ME规范

  在 J2ME内还有很多子规范, J2ME的重要的部分如下:PersonalJava、K虚拟机 (KVM)、Java嵌入服务器以及 PersonalJava的两个扩展规范: JavaPhone和 JavaTV应用程序接口。 你可以想象, JavaPhone是一个定位于无线电智能电话和互联网络可视电话的应用程序接口,而 JavaTV则满足机顶盒市场的需求。

  下面我想详细的谈一谈以上的规范:

  1、PersonalJava

  PersonalJava应用程序环境目标是 Web连接消费设备----常常执行来自网络的小应用程序。问题是 PersonalJava如何适合 J2ME的配置和简表方案。 答案是 PersonalJava将被包容进 Connected Device Configuration中,最终将被定义为 Personal简表,即前面所谈到的Personal简表。

  另一方面,有一段时间将有两个 Java应用程序接口为嵌入开发世界服务: PersonalJava和 EmbeddedJava。 PersonalJava偎依在 J2ME大伞之下, 可为什么 EmbeddedJava不呢? EmbeddedJava不和 PersonalJava同在 J2ME内,是因为在 PersonalJava和 EmbeddedJava应用程序之间有一个基本的差别。 PersonalJava应用程序期望连接到某类网络中下载并执行小应用程序。 按照这种观点, PersonalJava设备就是一般用途的消费设备; 它们的能力可以被扩展。

  相比之下, EmbeddedJava设备则惨了点。 它们执行的功能都非常具体的,基本没有必要提供下载新的代码到 EmbeddedJava设备的能力。 Hence, PersonalJava设备使用可扩展 Java应用程序接口; 而EmbeddedJava设备则没有,因为没有必要使用。

  PersonalJava可以以两种形式得到: 由原码形式的,提供给那些对把PersonalJava移植到其他设备感兴趣的开发者,那些已经把 PersonalJava移植到某个具体的操作系统和处理机的组织提供二进制形式的 PersonalJava环境。有兴趣探索 PersonalJava的开发者如果没有二进制平台也可以使用 PersonalJava模拟环境 ( PJEE )。 这个模拟器运行于 Solaris/SPARC或 Windows,并且在许多配置中可用。 这些多种多样的配置基于“ look and feel”和类库支持 (环境是否提供 PersonalJava规范中规定的最低限度的或最大的类库)。PJEE包括类文件,一个应用程序 launcher和一个 appletviewer (两者都是为了调试功能并使其最优化)和其它的附带的文件 (例如字体叙述文件)。

  J2ME家族的另一位成员 JavaCheck实用程序,提供了 PersonalJava的补充支持。 你把应用程序传过 JavaCheck,它将告诉你你的应用程序在一个 PersonalJava环境中能否顺利地执行。 JavaCheck检查类之间的依赖关系,如果应用程序调用了一个在 PersonalJava不可用的应用程序接口,它就会给出一个警报信号。 (据我所知,目前有两种JavaCheck的版本可用,一个是用于检验 PersonalJava 1.0版应用程序,另一个用于检验 1.1.x版程序。 当前的 PersonalJava应用程序接口规范是 1.2,用于这一版本的 JavaCheck还没有。 读者请去Sun相关网站去看看( http : file://java.sun.com/products/personaljava)。

  2、KVM

  前面我也说过,KVM是用于 J2ME平台最小的虚拟机,并且是用于CLDC配置的虚拟机。可是J2ME应用程序并不一定非要使用 KVM,J2ME技术可以使用任何虚拟机,不过至少应当有 KVM这样的功能。

  为了满足基于KVM的设备一般只有狭小的内存空间和有限的处理能力的事实, KVM使用 C编写 (它不是现有的VM改进了的以后的产品)。 此外, KVM是模块化的, 也就是说,它是由模块构建的,当某个模块实现了预先设定的目标后,就可以很容易地把这一模块卸载。 可选的某块包括: 大的数据类型 ( long、 float和 double ),多维数组、类文件验证等。

  KVM的本地界面以轻便性为原则构建,所以在KVM中任务切换不依赖硬件产生的记时器中断,因此在这种意思上来说不是抢先式。任务切换发生在虚拟机执行了一个预设编号的字节码之后。 并且, KVM的无用单元收集利用一个标记清扫(mark and sweep)算法来实现无用单元释放。 因此,对象引用是直接的,就像标准 Java一样。

  当然,除了虚拟机以外还有许多可用的执行环境,在小型设备中,虚拟机必须要么被扩展,要么在附加工具协助下提供一个更加完整的运行期环境,正是这个原因, KVM需要附带的工具,比如说, JavaCodeCompact工具提供了预链接和预加载类, 允许Java类被直接地链接进虚拟机中。((设备上所有的应用程序使用的类 can直接地嵌入虚拟机。)

  KVM一个可选的附件就是 Java Application Manager ( Java应用程序管理器,简称 JAM )。JAM的工作就是处理下载、安装、执行和卸载 CLDC设备上的应用程序的细节问题,因为资源有限,在CLDC设备上有可能不存在这些功能。JAM也处理更新安装应用程序的操作。(如果更新过程失败,它甚至可以重新使用旧的应用程序。 )

  3、Java Embedded Server(Java嵌入服务器)

  Java Embedded Server( Java嵌入服务器,简称 JES),在 PersonalJava基础上建立,是一个用于嵌入式网络设备的运行期环境。为了理解 JES,你必须理解两个核心概念:服务和服务空间结构。后者是前者的容器。服务程序是运行于一个 JES服务器上的组件化程序;服务空间结构是为服务程序提供生命周期 支持的环境。

  技术上说,服务程序是界面的实现,事实上,它是一个实现特定活动的Java类集合。比如说,假如把 JES配置为一个家庭的气候控制系统的服务器,可以把从模数转换器读到的温度数据放进一个数据组件程序中。我就可以称这个组件为ReadThermostats服务程序。

  在 JES的领域,服务的封装媒介称为 bundle。简单地说,bundle就是一个带有特殊内容的JAR文件。服务程序和bundle之间有一对一关系,一个bundle带有一个服务程序。服务程序和 bundle之间有一对一关系,一个 bundle带有一个服务程序。可这也不一定,一个 bundle可以设置多个服务程序索引 (注意, JES提供的所有的核心服务,每个 bundle中只有一个 )。

  正如前面提到的那样,服务空间的一项工作就是管理服务程序的生命周期,这个工作的很大的部分包括解决服务隶属关系。bundle内容的一个重要的部分是bundle服务的依赖信息。所以,当服务空间打开一个bundle安装它的服务时,服务空间就可以确定外部需要什么服务。而且,一个服务的依赖关系并不是静止不变的,它们可以随某些事件改变。比如说当服务程序更新时的变化就是一个很好的例子。一个服务的新的版本可以添加或去除依赖关系。服务空间跟踪并解决这样的动态依赖关系。如果服务空间处理所有服务程序的生命周期,这就暗示了服务空间被赋予知晓一切的能力,那就是说,它能够推论结构、依赖、安装的细微差别等所有它负责的服务。服务空间通过在 bundle内伴随服务的 Java代码模块处理一些任务,这些模块被称作 wizard(向导)。JES向导是根据它们完成的任务命名的:

  Dependencies -向导告诉调用者一个bundle依赖关系是什么。

  Installer-向导处理bundle中服务的安装和删除操作。

  Activator -向导知道如何启动和终止服务。

  Updater -向导控件更新bundle中的服务。(更新向导不仅知道更新一个服务,而且知道在何时和什么情况下更新服务。 )

  About -这个向导,就像它名称意味的那样,返回关于 bundle内容的信息。

  Dispatcher -这是一种元向导(meta-wizard)。服务空间调用dispatcher向导定位一个bundle的其他向导。

  当一个 JES服务器启动的时候,服务空间并不是完全没有启动服务。JES定义一组核心服务(可选),这些都是任何 JES服务器的组成部分。这些核心服务包含:

  HTTP服务

  日志 -记录错误和事件日志
  
  日期 -精确到秒的日期/时间服务
  
  连接管理器 -提供网络服务和Socket绑定,也处理连接接收。

  线程管理器 -管理服务器提供的线程。thread管理器支持线程池并允许有效使用线程上界的规范。

  计划程序 -提供未来的事件计划安排 (可用于告诉服务器某某动作必须在某某事件发生 )

  RMI

  SNMP

  控制台 -提供远程管理服务器功能

  基于 HTTP的远程应用程序接口实现

  基于 RMI的远程应用程序接口实现
 
  如果你把服务空间结构当成 JavaBean中的容器的话, JES就变得容易理解了。在这种类比关系中,服务程序就相当 JavaBean。那么,正象组件容器提供一个环境供 JavaBeans实例化、运行一样,服务空间就是以实例化的服务的聚集地。服务空间管理安装、实例化、执行、终止以及卸载服务;它也提供应用程序接口供服务交互作用。
Java嵌入式开发之j2me--一 Java嵌入式开发之j2me--二 Java嵌入式开发之j2me--三 Java嵌入式开发之j2me--四 Java嵌入式开发之j2me--五 Java嵌入式开发之j2me--六 <

小小豆叮

WINWAP(在Windows下查看WAP页面,1,264K)

<

小小豆叮

Phone.com UP.SDK4.0开发工具包

<

小小豆叮

Ericsson R380模拟WAP手机

<

小小豆叮

如何在Oracle 中实现类似自动增加 ID 的功能?

我们经常在设计数据库的时候用一个系统自动分配的ID来作为我们的主键,但是在ORACLE 中没有这样的 功能,我们可以通过采取以下的功能实现自动增加ID的功能 1.首先创建 sequence create sequence seqmax increment by 1 2.使用方法 select seqmax.nextval ID from dual 就得到了一个ID 如果把这个语句放在 触发器中,就可以实现和ms sql 的自动增加ID相同的功能! <

小小豆叮

Windows2000下安装J2EE和部署J2EE应用程序

1. 安装 可以从以下网址下载一个J2EE(j2sdkee-1_3-beta2-win.exe):http://java.sun.com/j2ee/j2sdkee-beta/index.html。也许你已装了旧版的J2EE SDK 产品,如果是,在安装新下载的J2EE之前请先卸载或删掉旧版的J2EE SDK。运行j2sdkee-1_3-beta2-win.exe,按安装步骤安装好J2EE。这里假设你的J2EE安装在:C:\j2sdkee1.3 目录下。 2. 设置环境变量 在运行J2EE SDK之前,你必须设置以下环境变量: J2EE_HOME - 你的J2EE SDK所安装的目录。如本例中的:C:\j2sdkee1.3 。 JAVA_HOME - 你的Java 2 SDK 所安装的目录。 PATH - 设置为你安装J2EE SDK目录下的bin目录。如本例的的:C:\j2sdkee1.3\bin 。 ClassPath - 增添%J2EE_HOME%\lib\j2ee.jar到ClassPath中。本例中也可写为:C:\j2sdkee1.3\lib\j2ee.jar 3. 运行J2EE Dos命令行敲入以下命令: %J2EE_HOME%\bin\j2ee -verbose 显示以下信息表示运行成功:(不同的版本显示可能不同) J2EE server listen port: 1050 Naming service started:1050 Binding DataSource, name = jdbc/EstoreDB, url = jdbc:cloudscape:rmi:CloudscapeDB;create=true Binding DataSource, name = jdbc/DB2, url = jdbc:cloudscape:rmi:CloudscapeDB;create=true Binding DataSource, name = jdbc/Cloudscape, url = jdbc:cloudscape:rmi:CloudscapeDB;create=true Binding DataSource, name = jdbc/InventoryDB, url = jdbc:cloudscape:rmi:CloudscapeDB;create=true Binding DataSource, name = jdbc/DB1, url = jdbc:cloudscape:rmi:CloudscapeDB;create=true Binding DataSource, name = jdbc/XACloudscape, url = jdbc/XACloudscape__xa Binding DataSource, name = jdbc/XACloudscape__xa, dataSource = COM.cloudscape.core.RemoteXaDataSource@330913 Starting JMS service ... Initialization complete - waiting for client requests Binding : < JMS Destination : jms/Queue , javax.jms.Queue > Binding : < JMS Destination : jms/Topic , javax.jms.Topic > Binding : < JMS Cnx Factory : jms/TopicConnectionFactory , Topic , No properties > Binding : < JMS Cnx Factory : TopicConnectionFactory , Topic , No properties > Binding : < JMS Cnx Factory : jms/QueueConnectionFactory , Queue , No properties > Binding : < JMS Cnx Factory : QueueConnectionFactory , Queue , No properties > Starting web service at port:8000 Starting secure web service at port:7000 Apache Tomcat/4.0-b4-dev Starting web service at port:9191 Apache Tomcat/4.0-b4-dev J2EE server startup complete. 启动成功后,在IE浏 览 器 中 访 问 HTTP://localhost:8000 可 以 看 到 默 认 的 主 页 信 息 。 4. 编写和运行HelloWorld程序 J2EE应用程序一般使用RMI(远程方法调用)来完成客户端与服务器的交互。当然,其间也少不了EJB的作用。本例为一个J2EE应用程序:客户端向服务器发送一个问候语:“Hello,Remote Object”。服务器收到该问候语后打印该问候语,并返回一字符串作为应答。客户端收到此应答后打印它。 RemoteInterface.java /** * 第一步: * 定义一个新的接口继承javax.ejb.EJBObject。新定义的接口中的每一个方法都必须抛出 * java.rmi.RemoteException异常。 */ public interface RemoteInterface extends javax.ejb.EJBObject { public String message(String str)throws java.rmi.RemoteException; } RemoteObject.java /** * 第二步: * 定义一个类来实现javax.ejb.SessionBean接口。并在该类中实现在第一步中编写的接口中所定义的方法。 */ public class RemoteObject implements javax.ejb.SessionBean { public String message(String str)throws java.rmi.RemoteException { System.out.println("Remote Object Received From Client: \""+str+"\""); //打印(从客户端)接收到的字符串。 return "Hello,I'm Remote Object,I received your message: \'"+str+"\'"; //返回一应答字符串。 } public RemoteObject() {} public void ejbCreate() {} public void ejbRemove() {} public void ejbActivate() {} public void ejbPassivate() {} public void setSessionContext(javax.ejb.SessionContext sc) {} } RemoteHome.java /** * 第三步: * 定义一个类继承javax.ejb.EJBHome 。 */ public interface RemoteHome extends javax.ejb.EJBHome { RemoteInterface create()throws java.rmi.RemoteException,javax.ejb.CreateException; } Client.java /** * 第四步: * 定义客户端类。 */ public class Client { public static void main(String[] args) { try { javax.naming.Context initContext=new javax.naming.InitialContext(); Object obj=initContext.lookup("HelloWorld"); //远程查找,由名字得到对应的对象。 RemoteHome home=(RemoteHome)javax.rmi.PortableRemoteObject.narrow(obj,RemoteHome.class); RemoteInterface remote=home.create(); String receiveFromRemote=remote.message("Hello,Remote Object!"); //远程方法调用 System.out.println("Client Received From Remote Object: \""+receiveFromRemote+"\""); } catch(Exception e) { e.printStackTrace(); } } } 假设以上四个Java文件存于C:\HelloWorld\下,编译它们如:C:\HelloWorld>javac *.java 。 5. 部署应用程序 启动Application Dopolyment Tool:新开一个Dos窗口,键入以下命令,%J2EE_HOME%\bin\deploytool 。该工具启动速度可能比较慢,要耐心等待。启动成功后会出现主界面(此时不要关闭Dos窗口)。在该界面中选 择 File菜 单 ,再选New Application项。在 Application File Name 输 入 :C:\HelloWorld\HelloWorld.ear 。在 Application Disply Name 输 入 你所喜欢的显示名如:HelloWorld。点 击 OK,在主界面的树形结构Files-->Applications下将增加新的一项:HelloWorld。这意味着产生了一个新的应用程序。接下来我们要做的就是部署该应用程序。在主界面的树形结构下选中HelloWorld,然后再在主界面的File菜单中选取New-->Enterprise Bean,在弹出的名为“New Enterprise Bean - Introduction”窗口中选取Next跳过第一步,在接下来的一步中,Create New EJB File in Application项中选HelloWorld,在EJB Display Name中填上你喜欢的名字如:Hello World EJB,点击Edit按钮,在弹出的窗口中,Start Directory中填:C:\HelloWorld\,在Available Files中展开树形结构C:\HelloWorld\,选取RemoteInterface.class、RemoteObject.class、RemoteHome.class、Client.class四项,点Add按钮添加,然后按OK确定。此时在Contents框中增加了该四个class。点Next进入下一步。Session项选Stateless,意为不保存session状态。Enterprise Bean Class选RemoteObject。Enterprise Bean Name中填上你喜欢的名字如:Hello World Bean。Remote Home Interface中选RemoteHome,Remote Interface中选RemoteInterface。选Next进入下一步。接下来的步骤可直接点Finish。这时主界面的树形结构中Files-->Application-->Hello World中将出现Hello World EJB-->Hello World Bean子项。在主界面的树形结构下选中Hello World,然后再在主界面的Tools菜单中选取Deploy,将弹出新的窗口名为“Deploy Hello World - Introduction”。Object to deploy中选Hello World,Target server中选localhost,选中Retuen Client Jar,在Client Jar File Name中填上:C:\HelloWorld\HelloWorldClient.jar。选Next进入下一步,在Application框的JNDI Name框中双击并填上HelloWorld,注意必须与Client.java中Object obj=initContext.lookup("HelloWorld")的“HelloWorld”保持一致。点Next进入下一步。点Finish完成。这时将出现Deployment Progress窗口。如果有误,该窗口将出现异常信息。如果一切正常,点OK便完成了部署工作。 6. 运行应用程序 新开一个Dos窗口。进入C:\HelloWorld\Classes目录下运行:C:\ HelloWorld\Classes>java -classpath %J2EE_HOME%\lib\j2ee.jar;.;HelloWorldClient.jar; Client 。运行成功则出现如下信息:Client Received From Remote Object: "Hello,I'm Remote Object,I received your message: 'Hello,Remote Object!'" 。而服务端Dos窗口(j2ee -verbose)中出现如下信息:Remote Object Received From Client: "Hello,Remote Object!" 。 本程序很适J2EE的入门,简单而明了。本程序由Charly设计编写,并由其本人多次运行确保无误。如不能正常运行,请检查你的环境变量设置是否正确,最好不要直接将以上各命令Copy到Dos窗口中运行,某些字符可能受中文全角字符的影响而不被识别。另外如果某个应用程序部署不成功,可以使用Application Deployment Tool主界面的Edit菜单的Delete项将这个应用程序(如:Hello World)或其子项删掉。此工具的右键功能几乎没有,愦憾。 7. 总结 Sun为rmi、Servlet(JSP)、JavaBean等Java技术摇旗呐喊已有多年。J2EE的出现不可谓不是一个大的综合和封装。它的出现应该说是这些技术走向成熟的结果。举个例子,JSP技术去年(2000年)在大中华范围的书店中都少有介绍,几近没有。现在业已铺天盖地。ASP似乎已成明日黄花,不过Microsoft C#的推出是否会让它有所改变?有待观察。面对竞争对手的压力,Sun自然对J2EE寄予厚望。应该说,Sun的Java设计一直都很规格和严谨。在这方面,当今还没有其它哪门语言能与之匹敌。J2EE的设计自然也是大手笔。J2EE被设计成多层次的结构。客户端可包括浏览器,桌面应用程序,甚至其它设备如PDA程序。服务端可以为Web服务和应用(Application)服务,这分别是Servlet(JSP)和rmi技术的体现。客户端通过网络协议向服务端提出请求(Web服务或应用服务),该请求如果需要后台企业信息系统(如数据库)支持,服务端则直接或在EJB支持下来操作后台企业信息系统(如数据库),并将结果返回至客户端;如果不需要后台企业信息系统(数据库)支持,服务端则直接或在EJB支持下处理该请求,并将结果返回至客户端。当然其间各环节都少不了安全控制:J2EE的标 准 的 公 开 存 取 控 制 规 则 。应该说,J2EE的Web应用会取得很大成功,因为Servlet(JSP)在Web方面有数不尽的优势:速度快(比ASP、PHP可能快好几倍)、跨平台、稳定(这不能不说是得益于Java的规格和严谨)、成熟(实际应用已久且应用广泛)、功能强大。而J2EE的应用服务应该也算不错,只是其使用的标准界面Swing实在是太耗资源且效率低。君不见,当今用Swing写的程序哪个不存在以下情形:程序启动时象国产拖拉机,轰隆轰隆地响--那是读硬盘的声音(有些夸张,但不过分);界面刷新慢,明显的延时;操作不方便,快捷键和右键功能少(当然这与软件生产者有关,但Swing要负一部分责任)。此如说Sun的Forte4Java、J2EE SDK、Oracle的Jdeveloper、IBM的Websphere都有此症。作为程序员,我们也许还能接受,以不断提升硬件来迷补其不足(虽然至今还是不足)。但作为客户企业,你用Swing给人家做了一个ERP或其中的某一环节,用起来有如此顽症,怎么向人家解释?向他(她)说,这是用Swing写的,所以很慢?对不起,人家不会关心你用什么写的,人家给你的也许只有抱怨。除了界面部分外,J2EE的应用服务应该说是很不错的,如分布式计算。 附:J2EE模型 Client-Side Server-Side Server-Side Enterprise Presentation Presentation Business Logic Information System +-------------+ +----------+ +----------+ +----------+ | Browser | | Web | | EJB | | | | | | Server | | Container| | | | +-------+ | | +------+ | | +------+ | | | | |Pure | | | |JSP | | | |EJB | | | | | |HTML | | | | | | | | | | | | | +-------+ | | +------+ | | +------+ | | | | |<:::::>| |<:::::>| |<:::::>|+--------+| | +-------+ | | +------+ | | +------+ | || || | |Java | | | |JSP | | | |EJB | | || || | |Applet | | | | | | | | | | || || | +-------+ | | +------+ | | +------+ | || || +-------------+ | | | | ||Database|| | |<::::::|::::::::::|::::::>|| & || +-------------+ | | | | ||legacy || | Desktop | | | | | ||Systems || |+-----------+| |+--------+| | +------+ | || || ||Java ||<:::::>||Java ||<:::::>| |EJB | |<:::::>|| || ||Application|| ||Servlet || | | | | || || |+-----------+| |+--------+| | +------+ | |+--------+| +-------------+ | | | | | | | | | | | | +-------------+ | | | | | | |Other Device | | | | | | | | +-------+ | |+--------+| |+--------+| | | | |J2EE | |<:::::>||J2EE ||<:::::>||J2EE ||<:::::>| | | |Client | | ||Platform|| ||Platform|| | | | +-------+ | |+--------+| |+--------+| | | +-------------+ +----------+ +----------+ +----------+ <

小小豆叮

WAP规范1.1版本(规范全部内容,2,763K)

<

小小豆叮

j2sdkee-1_3-beta2-win.exe

<

小小豆叮

Ericsson Wap IDE2.08开发工具包

<

小小豆叮

Nokia Wap Tookit 开发工具包

<

小小豆叮

Java的套接字编程(IBM教材)

<

小小豆叮

oracle 9i:为什么你需要灵活的数据分区

macro 数据服务器管理、坏系统的恢复及低效率应用所带来的花费常常是惊人的。采用数据分区维护大数据存储将会使这些费用降低。 数据分区是通过将大的数据分割成较小的易于管理的部分,从而降低成本。但是一些数据服务器在增加了这一功能时却多分区的数据进行了限定,限制了你的业务发展。 Oracle没有任何这种数据大小的限制。 其它数据服务器虽然提供了分区,但仅提供了有限的数据分区方法。Oracle的分区( Partitioning Option)方式有 hash, range和composite 多种。这种灵活的分区方式好处是: 目标准确的数据服务器管理 高可用性 应用性能提高 由于结构的限制,多数服务器的分区导致为提高性能以牺牲目标准确的数据服务器管理和高可用性为代价。 你必须在它们之间作出选择。Oracle的composite 分区方法则消除了这种情况。 采用Oracle的分区,数据的存储、管理、访问和备份都完全按你的业务要求。例如许多公司喜欢按日期分区 ,当数据到达一定的日期后,数据就不能再被查询。Oracle的Range 分区使过期的分区,仍然可被查询。 Oracle的分区显著地改进了数据的可用性。单一分区可被单独离线,不影响其它数据运行。查询永远是在所有分区正常的情况下才进行。 Oracle决不会提供不完整的查询结果。 <

小小豆叮

用Java程序生成文本的捷径(一)

来源:www.ccidnet.com 大多数程序都需要输出一些文本,比如邮件消息、HTML文件或控制台输出。但是,计算机本质上只能处理二进制数据,程序员必须让软件来生成可理解的文本。在这篇文章中,我要介绍的是在生成和输出文本时,为何使用模板引擎能够节省时间。你将了解模板的优点,如何针对不同的情形创建高效的模板。和System.println说再见! 虽然程序员可以很轻松地编写出输出文字信息的代码(因为这毕竟是从Hello World范例学到的第一件事情),但通常而言,程序员不是写作或组织文字信息(如邮件)的最佳人选。因此,我们常常让市场部门或公关部门去做那些事情。但遗憾的是,即使对于最普通的邮件,编写者也常常依赖程序输出来完成任务。无论是对于邮件编写者还是程序员,这种合作方式都很容易带来误解和造成失误。 请看一个例子:一个Java程序从某个数据源收集一些客户信息,通过email给公司的每一个客户发送帐户余额信息。下面是完成这个任务的Java程序(完整的示例程序代码可以从本文最后下载): for (int i=0; i { Customer customer = (Customer)customers.get(i); StringBuffer message = new StringBuffer(); message.append ("尊敬的先生/女士: "); message.append (customer.getCustName()); message.append ("\n"); message.append ("\n"); message.append ("您的帐户余额是 "); message.append (customer.getAccountTotal()); message.append ("\n"); message.append ("\n"); message.append ("致礼!"); message.append ("\n"); message.append ("某某装饰品公司"); // 发送email mm.sendMail (customer.getFirstName(), customer.getEmail(), "Account", message.toString()); } 上面的例子可谓发送消息最差劲的方法之一。由于消息嵌入到了程序代码之中,如果没有程序员的帮助,其他人几乎不可能对消息进行编辑。同时,即使对于专业的程序员,如果他不了解代码,要进行编辑也很困难。如果你预见了这些麻烦,把代码写成下面这种形式: static public final String STR_HELLO="尊敬的先生/女士: "; static public final String STR_MESSAGE="您的帐户余额是 "; static public final String STR_BEY="致礼!\n某某装饰品公司"; 如果说上述代码使得消息编辑更容易,那么这种帮助也不会很多。很难要求一个不搞程序设计的人理解static和final的含义。此外,如果要改变消息的结构,上面这种代码也不够灵活。例如,人们可能要求你在邮件消息中加入更多来自数据源的信息,这时,你就得修改构造邮件的代码,或许还要添加更多的static final String对象。 模板简介 从文本文件装入消息文本可以解决部分问题——但不能提供动态内容,而这对于系统来说是很重要的。你需要有一种方法把动态内容插入到预先编写好的文本消息。但是,如果使用某种文本模板引擎,它就能够帮助你完成所有复杂的工作。 模板引擎解决了把动态内容插入文本消息的问题。使用模板引擎时,我们不再把消息直接嵌入程序,而是创建一个包含文本内容的简单文本文件,称为“文本模板”。模板引擎解析文本模板,借助一些简单的模板指令,把动态内容插入模板输出结果。 我选择的模板引擎是Jakarta Project的Velocity,但你可以任意选择其他许多模板引擎之一。Velocity和WebMacro或许是当前功能最丰富、最受欢迎的两个引擎,而且两者都按照源代码开放协议免费提供。虽然我在本文例子中使用Velocity,你可以方便地把这些例子移植到不同的模板引擎,只需遵照目标引擎的语法即可。 我们来看看用Velocity完成的email程序例子。要编译和运行修改后的程序,你必须下载Velocity并把它加入到classpath。如果要让email部分也能正常运行,你还需要JavaMail。 for (int i=0; i

小小豆叮

用Java程序生成文本的捷径(三)

我将在下面的HTML例子中使用前面email例子的数据模式。这个HTML页面是一个假想的企业Intranet页面,它显示出客户帐户的详细信息。本例中的控制器类是一个Java Servlet,视图部分则包含一个HTML模板。下面的代码显示了Servlet类中最主要的代码。为使这个例子更具有代表性,我从头开始手工编写这个Servlet。然而,一般情况下,模板会提供一些Servlet工具,帮助用户减轻一些编写代码的负担。 // 装入模板 Template template = Velocity.getTemplate("html.vm"); // 创建环境 VelocityContext context = new VelocityContext(); context.put ("customers",Customer.getCustomers());// 解析模板,输出应答ServletOutputStream output = response.getOutputStream();Writer writer = new OutputStreamWriter (output);template.merge(context, writer);writer.flush(); 这个例子也没有什么令人惊异的地方。和前面的例子一样,我只是把必需的对象加入到VelocityContext,然后输出解析模板的结果。但是请注意,在前面的例子中,我只把一个Customer加入到VelocityContext,这里加入到VelocityContext的却是一组Customer对象。我可以用#foreach指令迭代访问所有的Customer对象。下面是相应的HTML模板:

客户报告

#foreach ($customer in $customers)

$customer.CustName

#foreach ($transaction in $customer.Transactions)#end
$transaction.Date$transaction.Description$transaction.Amount
$customer.AccountTotal
#end 如果你正在规划一个工程,这个工程的需求远远超过几个HTML模板,请考虑众多以模板为基础的框架之一。这些框架不仅为生成HTML提供了模板引擎所带来的便利,而且提供许多实用工具,比如数据库连接池和安全。两个常见的例子是Turbine和Melati,它们都和Velocity以及WebMacro兼容,都是免费且源代码开放的产品。 性能和配置 对于大多数程序来说,模板的速度看来已经足够快;但对于大容量的Web网站,你可能要认真地考虑一下性能问题。在性能方面,模板引擎最大的特点在于模板缓冲。在模板缓冲机制的作用下,模板不再是每次出现请求的时候从磁盘读取,而是以最理想的方式在内存中保存和解析。在开发期间,模板缓冲通常处于禁用状态,因为这时请求数量较少,而且要求对页面的修改立即产生效果。部署完毕之后,模板一般不再改变,性能就成了优先考虑的问题。因此,这时你应该启用模板缓冲功能。 对于大多数模板引擎,你可以通过应用一个设置选项或编辑Java属性文件方便地启用模板缓冲功能。在Velocity中,你可以通过Properties对象初始化模板。至于Properties对象的创建方法,你既可以手工创建,就象我前面所做的那样;或者也可以从属性文件装入。在实际应用中,后者也许是较为理想的方法。 Properties props = new Properties(); props.setProperty( "file.resource.loader.cache", "true" ); props.setProperty( "file.resource.loader.modificationCheckInterval", "3600" ); Velocity.init (props); 通过file.resource.loader.cache属性可以把缓冲设置成true或false,而file.resource.loader.modificationCheckInterval属性设置的是检查文件是否改变的间隔秒数。在这里我无法详细介绍所有的属性,请参考模板引擎的文档了解更多信息。 ■ 结束语 免费的高级模板引擎使我们能够把模板功能加入到几乎所有的Java应用。这些模板引擎为程序员提供了易用的工具,为模板编写者提供了简单的模板语言,使得开发者更有信心编写出高质量的代码。 模板分离了程序代码和应用的表现部分,极大地方便了程序员和内容制作者的工作。模板把程序员从混合了大量文本信息的杂乱代码中解放出来;使得制作文本内容的人无需面对程序逻辑,就可以轻松地编写和修改内容。 模板清楚地分离了程序逻辑和文本表现代码,从而也为设计更好的MVC系统提供了方便。因此,模板为替换其他内容发布系统(比如JSP)提供了一种有吸引力的方案,因为它能够在不增加复杂性的情况下,改进应用的整体设计。 ■ 参考资源 下载本文示例的完整代码 其他模板应用的例子 Velocity WebMacro 了解更多有关MVC的知识,看看它能够为你的程序设计带来什么帮助 基于模板的Web应用框架: Turbine The Melati project 对象-关系工具: The ExoLab Group Osage 用Java程序生成文本的捷径(一) 用Java程序生成文本的捷径(二) 用Java程序生成文本的捷径(三) <

小小豆叮

探索 Java 中的 TimeZone

探索 Java 中的 Date, Calendar, TimeZone 和Timestamp 对象

宋晟 (shengsong@hotmail.com)

2001 年 11 月

如何计时,自古以来就是一个有着各种答案的问题。中国人从习惯使用农历计时,到选择便于与世界沟通的公元计时。在生活中这两者又一起使用,这已经融入到文化中。在计算机世界里,计时也是一个丰富多采的话题。各个系统各有一套方法,例如 2000 年引起全世界关注的"千年虫",就是因为计时系统设计上的限制而产生的。那么 Java 作为一种通用编程语言和运行平台,它的计时系统是如何设计的,我们在应用程序的开发中又如何驾驭它呢?对象的状态总是要被储存起来的,那么我们又要注意哪些情况呢?

1 最常用的对象(概念最易被混逍的对象) Date
在 Java 中,如何获得系统当前的时间?恐怕一个合格的 Java 程序员可以列举出至少下面这种:
Date date = new Date();
// 打印出具体的年,月,日,小时,分钟,秒钟 以及时区
System.out.println(date);

看起来 Date 这个对象包含了相当丰富的时间信息。那么我们能不能用它来构件任何一个指定的时间呢?很多的应用程序是需要用户输入一个特定的时间。


// 我们能不能用下面的代码构件出 2001/8/8 8:8
    import java.io.*;
    import java.util.*;

    public class WhatIsDate
    {
        public static void main(String[] args) {
            Date date = new Date(2001, 8, 8, 8, 8, 8);
            System.out.println(date);
        }
    }

Java 的编译器竟然报如下信息 (Sun JDK1.3, Windows 2000 中文下)


注意:
WhatIsDate.java 使用或覆盖一个不鼓励使用的API。
注意:
使用-deprecation重新编译,以得到详细信息。!

我们使用 javac -deprecation WhatIsDate.java, 得到如下信息:


WhatIsDate.java:7: 警告:
在java.util.Date中的Date(int,int,int,int,int,int)已经不提倡使用
            Date date = new Date(2001, 8, 8, 8, 8, 8);
                        ^
1 warning

那么 Date 对象究竟是为了满足哪个需求呢?看来它不是用来实现基于年/月/日 小时:分钟 的时间表述。我们查看 Java 的文档,我们看到有 getTime() 方法,它返回的竟然是一个 long 值。
System.out.println(date.getTime());

文档进一步又告诉我们这个值代表了当前系统的时间离1970/1/1 0:0 的毫秒差,而且是在 GMT 时区下(也被称为 EPOC)。如果我们指定的时间是在此之前的,那它将返回一个负数值。

这个发现让我们对 Date 对象有了一个全新的认识-Date 存放的是与 EPOC 的偏差值。换而言之我们也可通过 long 类型来表示时间?对了,这个猜想是得到了 Java 的支持:


    // 第二种获得当前时间的方法
    long dateInMilliSeconds = System.currentTimeMillis();
    // 这时候打印出的只是一串数字而已
    System.out.println(dateInMilliSeconds);

对程序执行效率敏感的程序员可以发现这个方法只是生成一个 Java 的原始类型 (primitive type) long, 不需要实例化一个对象。因此如果我们对时间的处理只是在内部进行时,可以用 long 来代替 Date 对象。

最典型的应用就是在一段代码开始和结束时,分别获得系统当前的时间,然后计算出代码执行所需的时间(微秒级)。


    long start = System.currentTimeMillis();
    // 代码段
    System.out.println("需要 "+(System.currentTimeMillis()-start)+" 微秒");

那么当我们要把这个 long 值已更为友好的表现形式显示处理的时候,我们可以用它来构造 Date 对象:
Date date = new Date(dateInMilliSeconds);
System.out.println(date);

我们看到了在 Java 中对时间最为基本的表示,有通过对EPOC 的偏差值进行处理。Date 对象是对它的一个对象的封装。我们同时也看到了,在现时世界中我们对时间的描述通常是通过"某年某月某日某时某分"来定义的。Date 的显示(实际上是 toString() 方法)描述了这些信息,但 Java 并不建议我们用这种方式直接来构件 Date 对象。因此我们需要找出哪个对象可以实现这个需求。这就是我们下面就要讲述的 Calendar 对象的功能。

在我们进一步研究 Calendar 之前,请记住 Date 只是一个对 long 值(基于 GMT 时区)的对象封装。它所表现出来的年/月/日 小时:分钟 时区的时间表述,只是它的 toString() 方法所提供的。千万不要为这个假象所迷惑。

2 强大的 Calendar
首先请记住 Calendar 只是一个抽象类, 也就是说你无法直接获得它的一个实例,换而言之你可以提供一个自己开发的 Calendar 对象。

那究竟什么是一个 Calendar 呢?中文的翻译就是日历,那我们立刻可以想到我们生活中有阳(公)历、阴(农)历之分。它们的区别在哪呢?

比如有:
月份的定义 - 阳`(公)历 一年12 个月,每个月的天数各不同;阴(农)历,每个月固定28天
每周的第一天 - 阳(公)历 星期日是第一天;阴(农)历,星期一是第一天

实际上,在历史上有着许多种纪元的方法。它们的差异实在太大了,比如说一个人的生日是"八月八日" 那么一种可能是阳(公)历的八月八日,但也可以是阴(农)历的日期。所以为了计时的统一,必需指定一个日历的选择。那现在最为普及和通用的日历就是 "Gregorian Calendar"。也就是我们在讲述年份时常用 "公元几几年"。Calendar 抽象类定义了足够的方法,让我们能够表述日历的规则。Java 本身提供了对 "Gregorian Calendar" 规则的实现。我们从 Calendar.getInstance() 中所获得的实例就是一个 "GreogrianCalendar" 对象(与您通过 new GregorianCalendar() 获得的结果一致)。

下面的代码可以证明这一点:


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendar
    {
        public static void main(String[] args) {
            Calendar calendar = Calendar.getInstance();
            if (calendar instanceof GregorianCalendar)
                System.out.println("It is an instance of GregorianCalendar");
        }
    }

Calendar 在 Java 中是一个抽象类(Abstract Class),GregorianCalendar 是它的一个具体实现。

我们也可以自己的 Calendar 实现类,然后将它作为 Calendar 对象返回(面向对象的特性)。在 IBM alphaWorks 上,IBM 的开发人员实现了多种日历(http://www.alphaworks.ibm.com/tech/calendars)。同样在 Internet 上,也有对中国农历的实现。本文对如何扩展 Calendar 不作讨论,大家可以通过察看上述 Calendar 的源码来学习。

Calendar 与 Date 的转换非常简单:


    Calendar calendar = Calendar.getInstance();
    // 从一个 Calendar 对象中获取 Date 对象
    Date date = calendar.getTime();
    // 将 Date 对象反应到一个 Calendar 对象中,
    // Calendar/GregorianCalendar 没有构造函数可以接受 Date 对象
    // 所以我们必需先获得一个实例,然后设置 Date 对象
    calendar.setTime(date);

Calendar 对象在使用时,有一些值得注意的事项:

1. Calendar 的 set() 方法

set(int field, int value) - 是用来设置"年/月/日/小时/分钟/秒/微秒"等值

field 的定义在 Calendar 中

set(int year, int month, int day, int hour, int minute, int second) 但没有

set(int year, int month, int day, int hour, int minute, int second, int millisecond) 前面 set(int,int,int,int,int,int) 方法不会自动将 MilliSecond 清为 0。

另外,月份的起始值为0而不是1,所以要设置八月时,我们用7而不是8。

calendar.set(Calendar.MONTH, 7);

我们通常需要在程序逻辑中将它清为 0,否则可能会出现下面的情况:


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendarWrite
    {
        public static void main(String[] args) throws Exception{
            ObjectOutputStream out =
                new ObjectOutputStream(
                    new FileOutputStream("calendar.out"));
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0, 0);
            out.writeObject(cal1);
            Calendar cal2 = Calendar.getInstance();
            cal2.set(2000, 7, 1, 0, 0, 0);
            cal2.set(Calendar.MILLISECOND, 0);
            out.writeObject(cal2);
            out.close();
        }
    }

我们将 Calendar 保存到文件中


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendarRead
    {
        public static void main(String[] args) throws Exception{
            ObjectInputStream in =
                new ObjectInputStream(
                    new FileInputStream("calendar.out"));
            Calendar cal2 = (Calendar)in.readObject();
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0, 0);
            if (cal1.equals(cal2))
                System.out.println("Equals");
            else
                System.out.println("NotEqual");
            System.out.println("Old calendar "+cal2.getTime().getTime());
            System.out.println("New calendar "+cal1.getTime().getTime());
            cal1.set(Calendar.MILLISECOND, 0);
            cal2 = (Calendar)in.readObject();
            if (cal1.equals(cal2))
                System.out.println("Equals");
            else
                System.out.println("NotEqual");
            System.out.println("Processed Old calendar "+cal2.getTime().getTime());
            System.out.println("Processed New calendar "+cal1.getTime().getTime());
        }
    }

然后再另外一个程序中取回来(模拟对数据库的存储),但是执行的结果是:


NotEqual
Old calendar 965113200422 <------------ 最后三位的MilliSecond与当前时间有关
New calendar 965113200059 <-----------/
Equals
Processed Old calendar 965113200000
Processed New calendar 965113200000

另外我们要注意的一点是,Calendar 为了性能原因对 set() 方法采取延缓计算的方法。在 JavaDoc 中有下面的例子来说明这个问题:


Calendar cal1 = Calendar.getInstance();
    cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
    cal1.set(Calendar.MONTH, Calendar.SEPTEMBER); //应该是 2000-9-31,也就是 2000-10-1
    cal1.set(Calendar.DAY_OF_MONTH, 30); //如果 Calendar 转化到 2000-10-1,那么现在的结果就该是 2000-10-30
    System.out.println(cal1.getTime()); //输出的是2000-9-30,说明 Calendar 不是马上就刷新其内部的记录

在 Calendar 的方法中,get() 和 add() 会让 Calendar 立刻刷新。Set() 的这个特性会给我们的开发带来一些意想不到的结果。我们后面会看到这个问题。

2. Calendar 对象的容错性,Lenient 设置
我们知道特定的月份有不同的日期,当一个用户给出错误的日期时,Calendar 如何处理的呢?


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendar
    {
        public static void main(String[] args) throws Exception{
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 1, 32, 0, 0, 0);
            System.out.println(cal1.getTime());
            cal1.setLenient(false);
            cal1.set(2000, 1, 32, 0, 0, 0);
            System.out.println(cal1.getTime());
        }
    }

它的执行结果是:


    Tue Feb 01 00:00:00 PST 2000
    Exception in thread "main" java.lang.IllegalArgumentException
        at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:1368)
        at java.util.Calendar.updateTime(Calendar.java:1508)
        at java.util.Calendar.getTimeInMillis(Calendar.java:890)
        at java.util.Calendar.getTime(Calendar.java:871)
        at WhatIsCalendar.main(WhatIsCalendar.java:12)

当我们设置该 Calendar 为 Lenient false 时,它会依据特定的月份检查出错误的赋值。

3. 不稳定的 Calendar

我们知道 Calendar 是可以被 serialize 的,但是我们要注意下面的问题


    import java.io.*;
    import java.util.*;

    public class UnstableCalendar implements Serializable
    {

        public static void main(String[] args) throws Exception{
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0 , 0);
            cal1.set(Calendar.MILLISECOND, 0);
            ObjectOutputStream out =
                new ObjectOutputStream(
                new FileOutputStream("newCalendar.out"));
            out.writeObject(cal1);
            out.close();
            ObjectInputStream in =
                new ObjectInputStream(
                new FileInputStream("newCalendar.out"));
            Calendar cal2 = (Calendar)in.readObject();
            cal2.set(Calendar.MILLISECOND, 0);
            System.out.println(cal2.getTime());
        }
    }

运行的结果竟然是: Thu Jan 01 00:00:00 PST 1970

它被复原到 EPOC 的起始点,我们称该 Calendar 是处于不稳定状态。这个问题的根本原因是 Java 在 serialize GregorianCalendar 时没有保存所有的信息,所以当它被恢复到内存中,又缺少足够的信息时,Calendar 会被恢复到 EPOCH 的起始值。Calendar 对象由两部分构成:字段和相对于 EPOC 的微秒时间差。字段信息是由微秒时间差计算出的,而 set() 方法不会强制 Calendar 重新计算字段。这样字段值就不对了。

下面的代码可以解决这个问题:


    import java.io.*;
    import java.util.*;

    public class StableCalendar implements Serializable
    {

        public static void main(String[] args) throws Exception{
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0 , 0);
            cal1.set(Calendar.MILLISECOND, 0);
            ObjectOutputStream out =
                new ObjectOutputStream(
                new FileOutputStream("newCalendar.out"));
            out.writeObject(cal1);
            out.close();
            ObjectInputStream in =
                new ObjectInputStream(
                new FileInputStream("newCalendar.out"));
            Calendar cal2 = (Calendar)in.readObject();
            cal2.get(Calendar.MILLISECOND); //先调用 get(),强制 Calendar 刷新
            cal2.set(Calendar.MILLISECOND, 0);  //再设值
            System.out.println(cal2.getTime());
        }
    }

运行的结果是: Tue Aug 01 00:00:00 PDT 2000

这个问题主要会影响到在 EJB 编程中,参数对象中包含 Calendar 时。经过 Serialize/Deserialize 后,直接操作 Calendar 会产生不稳定的情况。

4. add() 与 roll() 的区别

add() 的功能非常强大,add 可以对 Calendar 的字段进行计算。如果需要减去值,那么使用负数值就可以了,如 add(field, -value)。

add() 有两条规则:

当被修改的字段超出它可以的范围时,那么比它大的字段会自动修正。如:
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
cal1.add(Calendar.MONTH, 1); //2000-9-31 => 2000-10-1, 对吗?
System.out.println(cal1.getTime()); //结果是 2000-9-30

另一个规则是,如果比它小的字段是不可变的(由 Calendar 的实现类决定),那么该小字段会修正到变化最小的值。

以上面的例子,9-31 就会变成 9-30,因为变化最小。

Roll() 的规则只有一条:
当被修改的字段超出它可以的范围时,那么比它大的字段不会被修正。 如:


Calendar cal1 = Calendar.getInstance();
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
cal1.roll(Calendar.WEEK_OF_MONTH, -1); //1999-6-1, 周二
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
cal1.add(Calendar.WEEK_OF_MONTH, -1); //1999-5-30, 周日
WEEK_OF_MONTH 比 MONTH 字段小,所以 roll 不能修正 MONTH 字段。

由于 Calendar 对象可以将一个时间转化成我们所需要的不同字段信息,可以帮助我们的开发任务。

下一部分我们会介绍 TimeZone, Timestamp 以及如何将 Calendar/Date 通过 DateFormat 灵活地显示给用户。

<

小小豆叮

用Java程序生成文本的捷径(二)

Velocity引擎读取模板文件时,它直接输出文件中所有的文本,但以$字符开头的除外。$符号标识着一个位置,在模板的输出结果中,对象的值应该插入到$符号所指示的位置。例如,Java代码中有一个context.put ("CustName",customer.getCustName())语句,当Velocity模板引擎解析并输出模板的结果时,模板中所有出现$CustName的地方都将插入客户的名字;即,被加入到VelocityContext的对象的toString()方法返回值将替代Velocity变量(模板中以$开头的变量)。 模板引擎中最强大、使用最频繁的功能之一是它通过内建的映像(Reflection)引擎查找对象信息的能力。这个映像引擎允许用一种方便的Java“.”类似的操作符,提取任意加入到VelocityContext的对象的任何公用方法的值,或对象的任意数据成员。映像引擎还带来了另外一个改进:快速引用JavaBean的属性。使用JavaBean属性的时候,我们可以忽略get方法和括号(欲知详细信息,请参考模板引擎的说明文档)。请看下面这个模板的例子。由于在前面的Java代码示例中,我把Customer对象加入到了VelocityContext,所以我可以把模板改写成下面这种形式: 尊敬的先生/女士: $customer.CustName您的帐户余额是 $customer.AccountTotal致礼!某某装饰品公司 除了替换变量之外,象Velocity和WebMacro这类高级引擎还能做其他许多事情。它们有用来比较和迭代的内建指令(尽管比较和迭代功能是两个模板引擎之间的共同点,但它们的语法差异很大,不能完全兼容。在选择模板引擎或者更换模板引擎时,务必注意这一点)。 举一个例子。十二月份,你的老板想要向所有的客户发一个圣诞节问候的email。你可以把这个消息加入到模板,以后再删除它。但这样的话,你得在新年那一天上班工作,以便删除圣诞问候消息。 一种更好的办法是指示模板何时显示圣诞问候消息。为此,你首先要把当前的月份加入到VelocityContext: int month = (new GregorianCalendar()).get(Calendar.MONTH); // 把month值加1,因为它从0开始计算 context.put ("month", new Integer(month+1) ); 现在,你只需在模板中进行比较: 尊敬的先生/女士: $customer.CustName您的帐户余额是 $customer.AccountTotal致礼!某某装饰品公司#if ($month == 12)祝您和您的家人圣诞节快乐!#end #if指令的作用很清楚:对一个逻辑表达式进行测试,从而决定是否在模板输出结果中包含该指令块内的内容。除了简单的等于比较之外,你还可以执行更复杂的比较,但这方面的功能都与特定的模板引擎密切关联,所以这里我不再介绍。 迭代指令和#if指令一样简单。模板引擎支持迭代Java Collections Framework的任意实现,包括Array、List和Iterator。对于JDK 1.2或者更高版本,Java的Vector和ArrayList都实现了List接口,因此它们也适合在模板引擎的迭代指令中使用。 假设我们现在不想输出帐户余额,而是想通过迭代遍历帐户的交易记录,输出详细的报表。Customer对象的getTransactions()方法(参见下载包中完整的示例代码)返回一个List对象,List对象包含零个或者多个Transaction对象。由于List属于Java Collections Framework的一部分,我们可以用#foreach指令迭代其内容。我们不用担心如何定型对象的类型——映像引擎会为我们完成这个任务。从下面这个例子中,我们可以看出迭代的工作过程: 尊敬的先生/女士: $customer.CustName#foreach ($transaction in $customer.Transactions)$transaction.Description $transaction.Amount#end您的帐户余额是 $customer.AccountTotal致礼!某某装饰品公司 #foreach指令的一般格式是“#foreach in ”。#foreach指令迭代list,把list中的每个元素放入item参数,然后解析#foreach块内的内容。对于list内的每个元素,#foreach块的内容都会重复解析一次。从效果上看,它相当于告诉模板引擎说:“把list中的每一个元素依次放入item变量,每次放入一个元素,输出一次#foreach块的内容”。 MVC设计模型 在看下一个例子之前,请回顾一下前面我们所讨论的内容。使用模板引擎最大的好处在于,它分离了代码(或程序逻辑)和表现(输出)。由于这种分离,你可以修改程序逻辑而不必担心邮件消息本身;类似地,你(或公关部门的职员)可以在不重新编译程序的情况下,重新编写邮件消息。 实际上,我们分离了系统的数据模式(Data Model,即提供数据的类)、控制器(Controller,即邮件程序)以及视图(View,即模板)。这种三层体系称为Model-View-Controller模型(MVC)。如果遵从MVC模型,代码分成三个截然不同的层,简化了软件开发过程中所有相关人员的工作(MVC的出现已经有一段时间,参见本文最后的“参考资源”了解更多信息)。 结合模板引擎使用的数据模式可以是任何Java对象,最好是使用Java Collection Framework的对象。控制器只要了解模板的环境(如VelocityContext),一般这种环境都很容易使用。一些关系数据库的“对象-关系”映射工具能够和模板引擎很好地协同,简化JDBC操作;对于EJB,情形也类似。 模板引擎与MVC中视图这一部分的关系更为密切。模板语言的功能很丰富、强大,足以处理所有必需的视图功能,同时它往往很简单,不熟悉编程的人也可以使用它。模板语言不仅使得设计者从过于复杂的编程环境中解脱出来,而且它保护了系统,避免了有意或无意带来危险的代码。例如,模板的编写者不可能编写出导致无限循环的代码,或侵占大量内存的代码。不要轻估这些安全机制的价值;大多数模板编写者不懂得编程,从长远来看,避免他们接触复杂的编程环境相当于节省了你自己的时间。 许多模板引擎的用户相信,在采用模板引擎的方案中,控制器部分和视图部分的明确分离,再加上模板引擎固有的安全机制,使得模板引擎足以成为其他内容发布系统(比如JSP)的替代方案。因此,Java模板引擎最常见的用途是替代JSP也就不足为奇了。 HTML处理 由于人们总是看重模板引擎用来替换JSP的作用,有时他们会忘记模板还有更广泛的用途。到目前为止,模板引擎最常见的用途是处理HTML Web内容。但我还用模板引擎生成过SQL、email、XML甚至Java源代码。在这里我只能涉及模板的部分应用,但你可以从本文最后的参考资源找到更多的例子。 用Java程序生成文本的捷径(一) 用Java程序生成文本的捷径(二) 用Java程序生成文本的捷径(三) <

小小豆叮

oracle 9i:为什么你需要非递增的行级锁

macro 在今天的在线世界,许多客户就是不愿意等待。 数据记录的访问能力是你业务的关键所在。但是数据访问能力、时间限制及业务记录的准确性需要相互妥协的,这是由于你的事务处理系统的原因。 当一个员工修改信息时,数据服务器会锁住这一信息直到操作完成。在这一期间其他人都不可更改被锁住的信息。锁定信息直至一个变化完成被成为数据锁定。这是所有事务处理系统的基本功能。 许多服务器甚至阻止员工读取锁定的数据,这造成了一些不必要的业务延误。 表面上,多数数据服务器好象只提供行级锁,仅锁住那些工作中的数据行。事实上,多数数据服务器锁住的数据远不只这些。服务器是使用内存跟踪锁定的信息,这意味着它们只能跟踪到有限的细节。 随着系统活动水平的提高,这些服务器开始锁住更大面积的信息用于管理内存的使用。 由于锁的递增,用户必须等候其他用户的任务执行后,即使等候完成的可能是完全不同的一条信息。 Oracle则通过采用非递增行级锁(non-escalating row-level locking)消除了这一困难。 Oracle数据服务器永远是只锁住正在更新中的数据行。 其它数据行不会受到影响。 Oracle并不使用计算机的内存跟踪锁定信息。这使Oracle可锁住的行数没有限制,所有员工都可同时更新数据,不会延误业务。 <

小小豆叮

WebLogic 6.1

<

小小豆叮

如何在Web页上实现文件上传

public class UploadServlet extends HttpServlet {  //default maximum allowable file size is 100k  static final int MAX_SIZE = 102400;  //instance variables to store root and success message  String rootPath, successMessage;  /**   * init method is called when servlet is initialized.   */  public void init(ServletConfig config) throws ServletException  {   super.init(config);   //get path in which to save file   rootPath = config.getInitParameter("RootPath");   if (rootPath == null)   {    rootPath = "/";   }   /*Get message to show when upload is complete. Used only if    a success redirect page is not supplied.*/   successMessage = config.getInitParameter("SuccessMessage");   if (successMessage == null)   {    successMessage = "File upload complete!";   }  }  /**   * doPost reads the uploaded data from the request and writes   * it to a file.   */  public void doPost(HttpServletRequest request,   HttpServletResponse response)  {   ServletOutputStream out=null;   DataInputStream in=null;   FileOutputStream fileOut=null;   try   {    /*set content type of response and get handle to output     stream in case we are unable to redirect client*/    response.setContentType("text/plain");    out = response.getOutputStream();   }   catch (IOException e)   {    //print error message to standard out    System.out.println("Error getting output stream.");    System.out.println("Error description: " + e);    return;   }   try   {    //get content type of client request    String contentType = request.getContentType();    //make sure content type is multipart/form-data    if(contentType != null && contentType.indexOf(     "multipart/form-data") != -1)    {     //open input stream from client to capture upload file     in = new DataInputStream(request.getInputStream());     //get length of content data     int formDataLength = request.getContentLength();     //allocate a byte array to store content data     byte dataBytes[] = new byte[formDataLength];     //read file into byte array     int bytesRead = 0;     int totalBytesRead = 0;     int sizeCheck = 0;     while (totalBytesRead < formDataLength)     {      //check for maximum file size violation      sizeCheck = totalBytesRead + in.available();      if (sizeCheck > MAX_SIZE)      {       out.println("Sorry, file is too large to upload.");       return;      }      bytesRead = in.read(dataBytes, totalBytesRead,       formDataLength);      totalBytesRead += bytesRead;     }     //create string from byte array for easy manipulation     String file = new String(dataBytes);     //since byte array is stored in string, release memory     dataBytes = null;     /*get boundary value (boundary is a unique string that      separates content data)*/     int lastIndex = contentType.lastIndexOf("=");     String boundary = contentType.substring(lastIndex+1,      contentType.length());     //get Directory web variable from request     String directory="";     if (file.indexOf("name=\"Directory\"") > 0)     {      directory = file.substring(       file.indexOf("name=\"Directory\""));      //remove carriage return      directory = directory.substring(       directory.indexOf("\n")+1);      //remove carriage return      directory = directory.substring(       directory.indexOf("\n")+1);      //get Directory      directory = directory.substring(0,       directory.indexOf("\n")-1);      /*make sure user didn't select a directory higher in       the directory tree*/      if (directory.indexOf("..") > 0)      {       out.println("Security Error: You can't upload " +        "to a directory higher in the directory tree.");       return;      }     }     //get SuccessPage web variable from request     String successPage="";     if (file.indexOf("name=\"SuccessPage\"") > 0)     {      successPage = file.substring(       file.indexOf("name=\"SuccessPage\""));      //remove carriage return      successPage = successPage.substring(       successPage.indexOf("\n")+1);      //remove carriage return      successPage = successPage.substring(       successPage.indexOf("\n")+1);      //get success page      successPage = successPage.substring(0,       successPage.indexOf("\n")-1);     }     //get OverWrite flag web variable from request     String overWrite;     if (file.indexOf("name=\"OverWrite\"") > 0)     {      overWrite = file.substring(       file.indexOf("name=\"OverWrite\""));      //remove carriage return      overWrite = overWrite.substring(       overWrite.indexOf("\n")+1);      //remove carriage return      overWrite = overWrite.substring(       overWrite.indexOf("\n")+1);      //get overwrite flag      overWrite = overWrite.substring(0,       overWrite.indexOf("\n")-1);     }     else     {      overWrite = "false";     }     //get OverWritePage web variable from request     String overWritePage="";     if (file.indexOf("name=\"OverWritePage\"") > 0)     {      overWritePage = file.substring(       file.indexOf("name=\"OverWritePage\""));      //remove carriage return      overWritePage = overWritePage.substring(       overWritePage.indexOf("\n")+1);      //remove carriage return      overWritePage = overWritePage.substring(       overWritePage.indexOf("\n")+1);      //get overwrite page      overWritePage = overWritePage.substring(0,       overWritePage.indexOf("\n")-1);     }     //get filename of upload file     String saveFile = file.substring(      file.indexOf("filename=\"")+10);     saveFile = saveFile.substring(0,      saveFile.indexOf("\n"));     saveFile = saveFile.substring(      saveFile.lastIndexOf("\\")+1,      saveFile.indexOf("\""));     /*remove boundary markers and other multipart/form-data      tags from beginning of upload file section*/     int pos; //position in upload file     //find position of upload file section of request     pos = file.indexOf("filename=\"");     //find position of content-disposition line     pos = file.indexOf("\n",pos)+1;     //find position of content-type line     pos = file.indexOf("\n",pos)+1;     //find position of blank line     pos = file.indexOf("\n",pos)+1;     /*find the location of the next boundary marker      (marking the end of the upload file data)*/     int boundaryLocation = file.indexOf(boundary,pos)-4;     //upload file lies between pos and boundaryLocation     file = file.substring(pos,boundaryLocation);     //build the full path of the upload file     String fileName = new String(rootPath + directory +      saveFile);     //create File object to check for existence of file     File checkFile = new File(fileName);     if (checkFile.exists())     {      /*file exists, if OverWrite flag is off, give       message and abort*/      if (!overWrite.toLowerCase().equals("true"))      {       if (overWritePage.equals(""))       {        /*OverWrite HTML page URL not received, respond         with generic message*/        out.println("Sorry, file already exists.");       }       else       {        //redirect client to OverWrite HTML page        response.sendRedirect(overWritePage);       }       return;      }     }     /*create File object to check for existence of      Directory*/     File fileDir = new File(rootPath + directory);     if (!fileDir.exists())     {      //Directory doesn't exist, create it      fileDir.mkdirs();     }     //instantiate file output stream     fileOut = new FileOutputStream(fileName);     //write the string to the file as a byte array     fileOut.write(file.getBytes(),0,file.length());     if (successPage.equals(""))     {      /*success HTML page URL not received, respond with       generic success message*/      out.println(successMessage);      out.println("File written to: " + fileName);     }     else     {      //redirect client to success HTML page      response.sendRedirect(successPage);     }    }    else //request is not multipart/form-data    {     //send error message to client     out.println("Request not multipart/form-data.");    }   }   catch(Exception e)   {    try    {     //print error message to standard out     System.out.println("Error in doPost: " + e);     //send error message to client     out.println("An unexpected error has occurred.");     out.println("Error description: " + e);    }    catch (Exception f) {}   }   finally   {    try    {     fileOut.close(); //close file output stream    }    catch (Exception f) {}    try    {     in.close(); //close input stream from client    }    catch (Exception f) {}    try    {     out.close(); //close output stream to client    }    catch (Exception f) {}   }  } } <

小小豆叮

字符转换工具(windows)

<

小小豆叮

在JSP中处理虚拟路径

摘要 在为服务器端组件编程时,你很可能要从相对于web根的路径来取得某个文件的真实路径,但此文件实际上在站点的一个虚拟路径上。 什么是虚拟路径? 在一个web服务器上,虚拟路径将物理上分离的各文件组合在一起,放在同一个站点路径上,在应用服务器上,每个应用定位于其自己的虚拟路径上,实际上相互之间有着完美地分离。 getRealPath()方法 JSP servlet API提供了getRealPath(path)方法,返回给定虚拟路径的真实路径,如果转换错误,则返回null。 getRealPath语法定义:   public java.lang.String getRealPath(java.lang.String path)   返回一个字符串,包含一个给定虚拟路径的真实路径。例如,虚拟路径 "/index.html" 不管在服务器文件系统上具有怎样的真实路径,使用"/index.html"总可以找到它。返回的真实路径使用了相近于servlet容器(srvlet container)所在计算机或操作系统的格式,包含了适当的路径分隔符。如果servlet容器无法转换则这个方法将返回null。   参数:     path -一个描述了虚拟路径的字符串   返回值:     描述真实路径的字符串或者null 遗憾的是,getRealPath常常返回不同的东西,这取决于服务器或jsp文件调用此方法的路径位置。 一个example站点 假设我们的站点组织如下: 根路径包含了我们的站点的根: http://address/ a_virtual目录包含了我们站点提供的虚拟路径的文件,例如: http://addess/virtual_dir/ 我们查找file1.txt和file2.txt的真实路径,它们一个在站点根路径下,一个在虚拟路径下。 getRealPath("/file1.txt") 应该返回“C:\site\site_root\file1.txt", getRealPath("/virtual_dir/file2.txt")应该返回"C:\site\a_virtual\file2.txt" getRealPath("/file3.txt")应该返回null,因为这个文件不存在。 但getRealPath()并不总是返回同样的结果,这还取决与你使用的js引擎。 JSP引擎 Tomcat 3.1 Tomcat返回的结果具有应用的独立性(application dependant): 它取决与调用getRealPath方法的那个jsp文件所在的位置。 实际上,当page1.jsp (位于站点根处)对file1.txt和file2.txt调用txtgetRealPath(), 它返回正确的结果。(这是在tomcat 3.1, 3.0版则对file2.txt返回错误的路径) 但是当page2.jsp(位于另一个应用,在一个虚拟路径中)调用getRealPath,它返回了错误的路径:它连接了该jsp文件所在的路径和请求的虚拟路径。 例如,从page2.jsp中调用getRealPath(/file1.txt)将返回 C:\site\a_virtual\file1.txt。 这一行为其实是使不同的应用相互独立的典型的处理方法。 JRun 2.3.3和INPRISE APPLICATION SERVER 4.0 (IAS) JRun和IAS对file1.txt和file2.txt都返回正确的结果。 然而所有这些引擎有一个共同的行为: 当getRealPath处理不存在的文件时,它们都不返回null! 解决之道 既然getRealPath总是返回一个路径,我们怎么知道它是否正确呢?最简单的方法是检查这个返回的路径是否存在。 这就是isVirtual方法要做的:在对一个给定的文件调用getRealPath以后,它使用了java.io来 存取这个文件,于是就可以知道它是否存在。 /** * isVirtual * * Check if the path name is a virtual or not. * * @param pathName The name of the path to check. */ private boolean isVirtual(String pathName) {   // Check if it is a virtual path   if (m_application.getRealPath(pathName)!=null) {     java.io.File virtualFile = new java.io.File(m_application.getRealPath(pathName));     if (virtualFile.exists()) {return true;}     else {return false;}   }   else {return false;} } <

小小豆叮

用Reflection实现Visitor模式

Visitor模式的常用之处在于,它将对象集合的结构和对集合所执行的操作分离开来。例如,它可以将一个编译器中的分析逻辑和代码生成逻辑分离开来。有了这样的分离,想使用不同的代码生成器就会很容易。更大的好处还有,其它一些公用程序,如lint,可以在使用分析逻辑的同时免受代码生成逻辑之累。不幸的是,向集合中增加新的对象往往需要修改已经写好的Visitor类。本文提出了一种在Java中实现Visitor模式的更灵活的方法:使用Reflection(反射)。 集合(Collection)普遍应用于面向对象编程中,但它也经常引发一些和代码有关的疑问。例如,"如果一个集合存在不同的对象,该如何对它执行操作?" 一种方法是,对集合中的每个元素进行迭代,然后基于所在的类,对每个元素分别执行对应的操作。这会很难办,特别是,如果你不知道集合中有什么类型的对象。例如,假设想打印集合中的元素,你可以写出如下的一个方法(method): public void messyPrintCollection(Collection collection) { Iterator iterator = collection.iterator() while (iterator.hasNext()) System.out.println(iterator.next().toString()) } 这看起来够简单的了。它只不过调用了Object.toString()方法,然后打印出对象,对吗?但如果有一组哈希表怎么办?事情就会开始变得复杂起来。你必须检查从集合中返回的对象的类型: public void messyPrintCollection(Collection collection) { Iterator iterator = collection.iterator() while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof Collection) messyPrintCollection((Collection)o); else System.out.println(o.toString()); } } 不错,现在已经解决了嵌套集合的问题,但它需要对象返回String,如果有其它不返回String的对象存在怎么办?如果想在String对象前后添加引号以及在Float后添加f又该怎么办?代码还是越来越复杂: public void messyPrintCollection(Collection collection) { Iterator iterator = collection.iterator() while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof Collection) messyPrintCollection((Collection)o); else if (o instanceof String) System.out.println("""+o.toString()+"""); else if (o instanceof Float) System.out.println(o.toString()+"f"); else System.out.println(o.toString()); } } 可以看到,事情的复杂度会急剧增长。你当然不想让一段代码到处充斥着if-else语句!那怎么避免呢?Visitor模式可以帮你。 要实现Visitor模式,得为访问者建立一个Visitor接口,还要为被访问的集合建立一个Visitable接口。然后,让具体类实现Visitor和Visitable接口。这两个接口如下所示: public interface Visitor { public void visitCollection(Collection collection); public void visitString(String string); public void visitFloat(Float float); } public interface Visitable { public void accept(Visitor visitor); } 对于具体的String,可能是这样: public class VisitableString implements Visitable { private String value; public VisitableString(String string) { value = string; } public void accept(Visitor visitor) { visitor.visitString(this); } } 在accept方法中,对this类型调用正确的visitor方法: visitor.visitString(this) 这样,就可以如下实现具体的Visitor: public class PrintVisitor implements Visitor { public void visitCollection(Collection collection) { Iterator iterator = collection.iterator() while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof Visitable) ((Visitable)o).accept(this); } public void visitString(String string) { System.out.println("""+string+"""); } public void visitFloat(Float float) { System.out.println(float.toString()+"f"); } } 实现VisitableFloat和VisitableCollection类的时候,它们也是各自调用合适的Visitor方法,所得到的效果和前面那个用了if-else的messyPrintCollection方法一样,但这里的手法更干净。在visitCollection()中,调用的是Visitable.accept(this),然后这个调用又返回去调用一个合适的Visitor方法。这被称做 "双分派";即,Visitor先调用了Visitable类中的方法,这个方法又回调到Visitor类中。 虽然通过实现visitor消除了if-else语句,却也增加了很多额外的代码。最初的String和Float对象都要用实现了Visitable接口的对象进行包装。这有点讨厌,但一般说来不是问题,因为你可以让经常被访问的集合只包含那些实现了Visitable接口的对象。 但似乎这还是额外的工作。更糟糕的是,当增加一个新的Visitable类型如VisitableInteger时,会发生什么呢?这是Visitor模式的一个重大缺陷。如果想增加一个新的Visitable对象,就必须修改Visitor接口,然后对每一个Visitor实现类中的相应的方法一一实现。你可以用一个带缺省空操作的Visitor抽象基类来代替接口。那就很象Java GUI中的Adapter类。那个方法的问题在于,它需要占用单继承;而你往往想保留单继承,让它用于其它什么东西,比如继承StringWriter。那个方法还有限制,它只能够成功访问Visitable对象。 幸运的是,Java可以让Visitor模式更灵活,使得你可以随心所欲地增加Visitable对象。怎么做?答案是,使用Reflection。比如,可以设计这样一个ReflectiveVisitor接口,它只需要一个方法: public interface ReflectiveVisitor { public void visit(Object o); } 就这样,很简单。至于Visitable,还是和前面一样,我过一会儿再说。现在先用Reflection来实现PrintVisitor: public class PrintVisitor implements ReflectiveVisitor { public void visitCollection(Collection collection) { ... same as above ... } public void visitString(String string) { ... same as above ... } public void visitFloat(Float float) { ... same as above ... } public void default(Object o) { System.out.println(o.toString()); } public void visit(Object o) { // Class.getName() returns package information as well. // This strips off the package information giving us // just the class name String methodName = o.getClass().getName(); methodName = "visit"+ methodName.substring(methodName.lastIndexOf(".")+1); // Now we try to invoke the method visit try { // Get the method visitFoo(Foo foo) Method m = getClass().getMethod(methodName, new Class[] { o.getClass() }); // Try to invoke visitFoo(Foo foo) m.invoke(this, new Object[] { o }); } catch (NoSuchMethodException e) { // No method, so do the default implementation default(o); } } } 现在不需要Visitable包装类。仅仅只是调用visit(),请求就会分发到正确的方法上。很不错的一点是,只要认为适合,visit()就可以分发。这并非必须使用reflection--它可以使用其它完全不同的机制。 新的PrintVisitor中,有针对Collection,String和Float而写的方法,但然后它又在catch语句中捕捉所有未处理的类型。你要扩展visit()方法,使得它也能够处理所有的父类。首先,得增加一个新方法,称为getMethod(Class c),它返回的是要调用的方法;为了找到这个相匹配的方法,先在类c的所有父类中寻找,然后在类c的所有接口中寻找。 protected Method getMethod(Class c) { Class newc = c; Method m = null; // Try the superclasses while (m == null && newc != Object.class) { String method = newc.getName(); method = "visit" + method.substring(method.lastIndexOf(".") + 1); try { m = getClass().getMethod(method, new Class[] {newc}); } catch (NoSuchMethodException e) { newc = newc.getSuperclass(); } } // Try the interfaces. If necessary, you // can sort them first to define "visitable" interface wins // in case an object implements more than one. if (newc == Object.class) { Class[] interfaces = c.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { String method = interfaces[i].getName(); method = "visit" + method.substring(method.lastIndexOf(".") + 1); try { m = getClass().getMethod(method, new Class[] {interfaces[i]}); } catch (NoSuchMethodException e) {} } } if (m == null) { try { m = thisclass.getMethod("visitObject", new Class[] {Object.class}); } catch (Exception e) { // Can"t happen } } return m; } 看起来有些复杂,其实不然。实际上,它只是根据传进来的类名去寻找相应的方法而已。如果没找到,就在父类中找;还没找到,再到接口中找。最后,就拿visitObject()作为缺省。 注意,为了照顾那些熟悉传统Visitor模式的读者,我对方法的名称采用了传统的命名方式。但正如你们一些人所注意到的,把所有的方法命名为 "visit" 然后让参数类型作为区分会更高效。但这样做的话,你得把主visit(Object o)方法的名字改为dispatch(Object o)之类。否则,就没有一个缺省方法可用了,你就得在调用visit(Object o)时将类型转换为Object,以保证visit采用的是正确的调用方式。 现在可以修改visit()方法,以利用getMethod(): public void visit(Object object) { try { Method method = getMethod(getClass(), object.getClass()); method.invoke(this, new Object[] {object}); } catch (Exception e) { } } 现在,visitor对象的功能强大多了。你可以传进任何对象,并且有某个方法处理它。另外一个好处是,还有一个缺省方法visitObject(Object o),它可以捕捉任何未知的对象。再多花点工夫,你还可以写出一个visitNull()方法。 我在上面对Visitable接口避而不谈自有原因。传统Visitor模式的另一个好处是,它允许Visitable对象来控制对对象结构的访问。例如,假设有一个实现了Visitable的TreeNode对象,你可以让一个accept()方法来遍历它的左右节点: public void accept(Visitor visitor) { visitor.visitTreeNode(this); visitor.visitTreeNode(leftsubtree); visitor.visitTreeNode(rightsubtree); } 这样,只用对Visitor类再进行一点修改,就可以进行Visitable控制访问: public void visit(Object object) throws Exception { Method method = getMethod(getClass(), object.getClass()); method.invoke(this, new Object[] {object}); if (object instanceof Visitable) { callAccept((Visitable) object); } } public void callAccept(Visitable visitable) { visitable.accept(this); } 如果已经实现了一个Visitable对象结构,可以保留callAccept()方法并使用Visitable控制访问。如果想在visitor中访问结构,只需改写callAccept()方法,使之什么也不做。 想让数个不同的访问者对同一个对象集合进行访问时,Visitor模式可以发挥它的强大作用。假设已经有一个解释器,一个中缀写作器,一个后缀写作器,一个XML写作器和一个SQL写作器,它们都作用在同一个对象集合上。那么,也可以很容易地为相同的对象集合写出一个前缀写作器和一个SOAP写作器。另外,这些写作器可以正常地和它们所不知道的对象工作;当然,如果愿意,也可以让它们抛出异常。 结论 通过使用Java Reflection,你可以增强Visitor模式,使之具有操作对象结构的强大功能,并在增加新Visitable类型方面提供灵活性。希望你在以后的程序设计中能够应用这一模式。 <

小小豆叮

用缓冲技术提高JSP应用的性能和稳定性

在Web应用中,有些报表的生成可能需要数据库花很长时间才能计算出来;有的网站提供天气信息,它需要访问远程服务器进行SOAP调用才能得到温度信息。所有这一切都属于复杂信息的例子。在Web页面中加入过多的复杂信息可能导致Web服务器、数据库服务器负荷过重。JSP代码块缓冲为开发者带来了随意地增加各种复杂信息的自由。 JSP能够在标记库内封装和运行复杂的Java代码,它使得JSP页面文件更容易维护,使得非专业开发人员使用JSP页面文件更加方便。现在已经有许多标记库,它们或者是商业产品,或者是源代码开放产品。但这些产品中的大多数都只是用标记库的形式实现原本可以用一个简单的Java Scriptlet实现的功能,很少有产品以某种创造性的方式使用定制标记,提供在出现JSP定制标记库之前几乎不可能实现的用法。 OSCache标记库由OpenSymphony设计,它是一种开创性的JSP定制标记应用,提供了在现有JSP页面之内实现快速内存缓冲的功能。虽然已经有一些供应商在提供各种形式的缓存产品,但是,它们都属于面向特定供应商的产品。OSCache能够在任何JSP 1.1兼容的服务器上运行,它不仅能够为所有用户缓冲现有JSP代码块,而且能够以用户为单位进行缓冲。OSCache还包含一些提高可伸缩性的高级特性,比如:缓冲到磁盘,可编程的缓冲刷新,异常控制,等等。另外,正如OpenSymphony的其他产品,OSCache的代码也在一个开放源代码许可协议之下免费发行。 本文以一个假想的拍卖网站设计过程为例,介绍OSCache的工作过程。这个假想的Web网站将包含:一个报告最近拍卖活动的管理页面;一个功能完整、带有各种宣传信息的主页;一个特殊的导航条,它包含了用户所有尚未成交的拍卖活动信息。 二、管理页面 拍卖网站包含一个管理报表,数据库服务器需要数秒时间才能创建这样一个报表。报表生成时间长这一点很重要,因为我们可能让多个管理员监视系统运行情况,同时又想避免管理员每次访问时都重新生成这个报表。为了实现这一点,我们将把整个页面封装到一个应用级的缓冲标记之内,这个缓冲标记每隔1小时刷新。其他供应商提供的一些产品也具有类似的功能,只是OSCache比它们做得更好。 为简单计,我们将不过多地关注格式问题。在编写管理页面时,我们首先把标记库声明加入到页面: 接下来我们要用cache标记来包围整个页面。cache标记的默认缓冲时间是1小时。 .... 复杂的管理报表 .... 现在管理页面已经被缓冲。如果管理员在页面生成后的一个小时之内再次访问同一页面,他看到的将是以前缓存的页面,不需要由数据库服务器再次生成这个报表。 三、主页 拍卖网站的主页显示网站活动情况,宣传那些即将结束的拍卖活动。我们希望显示出正在进行的拍卖活动数量,当前登录用户数量,在短期内就要结束的拍卖活动的清单,以及当前时间。这些信息有着不同的时间精确度要求。网站上的拍卖活动通常持续数天,因此我们可以把缓冲有效拍卖活动数量的时间定为6个小时。用户数量的变化显然要频繁一些,但这里我们将把这个数值每次缓冲15分钟。最后,我们希望页面中显示的当前时间总是精确的页面访问时间。 接下来,我们要显示一个清单,列出那些将在短期内结束的拍卖活动: 最后,我们希望显示出正在进行的拍卖活动的数量,这个数字需要缓冲6小时。由于cache标记需要的是缓冲数据的秒数,我们把6小时转换成21600秒: 本网站正在进行的拍卖活动有个! 可以看到,我们只用少量的代码就构造出了一个带有复杂缓冲系统的主页。这个缓冲系统对页面各个部分分别进行缓冲,而且各个部分的缓冲时间完全符合它们各自的信息变化频繁程度。由于有了缓冲,现在我们可以在主页中放入更多的内容;而在以前没有缓冲的情况下,主页中放入过多的内容会导致页面访问速度变慢,甚至可能给数据库服务器带来过重的负载。 四、导航条 假设在规划网站的时候,我们决定在左边导航条的下方显示购物车内容。我们将显示出用户所拍卖的每一种商品的出价次数和当前报价,以及所有那些当前用户出价最高的商品的清单。 我们利用会话级的缓冲能力在导航条中构造上述功能。把下面的代码放入模板或者包含文件,以便网站中的其他页面引用这个导航条: 在这里我们引入了两个重要的属性,即key和scope。在本文前面的代码中,由于cache标记能够自动为代码块创建唯一的key,所以我们不需要手工设置这个key属性。但在这里,我们想要从网站的其余部分引用这个被缓冲的代码块,因此我们显式定义了该cache标记的key属性。第二,scope属性用来告诉cache标记当前代码块必须以用户为单位缓冲,而不是为所有用户缓冲一次。 在使用会话级缓冲时应该非常小心,应该清楚:虽然我们可以让复杂的导航条减少5倍或10倍的服务器负载,但它将极大地增加每个会话所需要的内存空间。在CPU能力方面增加可能的并发用户数量无疑很理想,但是,一旦在内存支持能力方面让并发用户数量降低到了CPU的限制之下,这个方案就不再理想。 正如本文前面所提到的,我们希望从网站的其余部分引用这个缓冲的代码块。这是因为,当一个用户增加了一个供拍卖的商品、或者出价竞购其他用户拍卖的商品时,我们希望刷新缓冲,使得导航条下一次被读取时具有最新的内容。虽然这些数据可能因为其他用户的活动而改变,但如果用户在网站上执行某个动作之后看到自己的清单仍未改变,他可能会感到非常困惑。 OSCache库提供的flush标记能够刷新缓冲内容。我们可以把下面的代码加入到处理用户动作且可能影响这一区域的页面之中: 用缓冲技术提高JSP应用的性能和稳定性 当用户下次访问它时,navbar缓冲块将被刷新。 至此为止,我们这个示例网站的构造工作已经完成且可以开始运行。下面我们来看看OSCache的异常处理能力。即使缓冲的内容已经作废,比如在缓冲块内出现了Java异常,OSCache标记库仍旧允许我们用编程的方法显示这些内容。有了这种异常控制功能,我们可以拆除数据库服务器和Web服务器之间的连接,而网站仍能够继续运行。JSP 1.2规范引入了TryCatchFinally接口,这个接口允许标记本身检测和处理Java异常。因此,标记可以结合这种异常处理代码,使得JSP页面更简单、更富有条理。 OpenSymphony正在计划实现其他的缓冲机制以及一个可管理性更好的主系统,它将使我们能够对缓冲使用的RAM和磁盘空间进行管理。一旦有了这些功能,我们就能够进一步提高网站的响应速度和可靠性。 【结束语】OSCache能够帮助我们构造出更丰富多彩、具有更高性能的网站。有了OSCache标记库的帮助,现在我们能够用它解决一些影响网站响应能力的问题,比如访问量高峰期、数据库服务器负荷过重等。 <

小小豆叮

在JSP中处理虚拟路径

摘要 在为服务器端组件编程时,你很可能要从相对于web根的路径来取得某个文件的真实路径,但此文件实际上在站点的一个虚拟路径上。 什么是虚拟路径? 在一个web服务器上,虚拟路径将物理上分离的各文件组合在一起,放在同一个站点路径上,在应用服务器上,每个应用定位于其自己的虚拟路径上,实际上相互之间有着完美地分离。 getRealPath()方法 JSP servlet API提供了getRealPath(path)方法,返回给定虚拟路径的真实路径,如果转换错误,则返回null。 getRealPath语法定义:   public java.lang.String getRealPath(java.lang.String path)   返回一个字符串,包含一个给定虚拟路径的真实路径。例如,虚拟路径 "/index.html" 不管在服务器文件系统上具有怎样的真实路径,使用"/index.html"总可以找到它。返回的真实路径使用了相近于servlet容器(srvlet container)所在计算机或操作系统的格式,包含了适当的路径分隔符。如果servlet容器无法转换则这个方法将返回null。   参数:     path -一个描述了虚拟路径的字符串   返回值:     描述真实路径的字符串或者null 遗憾的是,getRealPath常常返回不同的东西,这取决于服务器或jsp文件调用此方法的路径位置。 一个example站点 假设我们的站点组织如下: 根路径包含了我们的站点的根: http://address/ a_virtual目录包含了我们站点提供的虚拟路径的文件,例如: http://addess/virtual_dir/ 我们查找file1.txt和file2.txt的真实路径,它们一个在站点根路径下,一个在虚拟路径下。 getRealPath("/file1.txt") 应该返回“C:\site\site_root\file1.txt", getRealPath("/virtual_dir/file2.txt")应该返回"C:\site\a_virtual\file2.txt" getRealPath("/file3.txt")应该返回null,因为这个文件不存在。 但getRealPath()并不总是返回同样的结果,这还取决与你使用的js引擎。 JSP引擎 Tomcat 3.1 Tomcat返回的结果具有应用的独立性(application dependant): 它取决与调用getRealPath方法的那个jsp文件所在的位置。 实际上,当page1.jsp (位于站点根处)对file1.txt和file2.txt调用txtgetRealPath(), 它返回正确的结果。(这是在tomcat 3.1, 3.0版则对file2.txt返回错误的路径) 但是当page2.jsp(位于另一个应用,在一个虚拟路径中)调用getRealPath,它返回了错误的路径:它连接了该jsp文件所在的路径和请求的虚拟路径。 例如,从page2.jsp中调用getRealPath(/file1.txt)将返回 C:\site\a_virtual\file1.txt。 这一行为其实是使不同的应用相互独立的典型的处理方法。 JRun 2.3.3和INPRISE APPLICATION SERVER 4.0 (IAS) JRun和IAS对file1.txt和file2.txt都返回正确的结果。 然而所有这些引擎有一个共同的行为: 当getRealPath处理不存在的文件时,它们都不返回null! 解决之道 既然getRealPath总是返回一个路径,我们怎么知道它是否正确呢?最简单的方法是检查这个返回的路径是否存在。 这就是isVirtual方法要做的:在对一个给定的文件调用getRealPath以后,它使用了java.io来 存取这个文件,于是就可以知道它是否存在。 /** * isVirtual * * Check if the path name is a virtual or not. * * @param pathName The name of the path to check. */ private boolean isVirtual(String pathName) {   // Check if it is a virtual path   if (m_application.getRealPath(pathName)!=null) {     java.io.File virtualFile = new java.io.File(m_application.getRealPath(pathName));     if (virtualFile.exists()) {return true;}     else {return false;}   }   else {return false;} } <

小小豆叮

java性能讨论

Java语言特别强调准确性,但可靠的行为要以性能作为代价。这一特点反映在自动收集垃圾、严格的运行期检查、完整的字节码检查以及保守的运行期同步等等方面。对一个解释型的虚拟机来说,由于目前有大量平台可供挑选,所以进一步阻碍了性能的发挥。 “先做完它,再逐步完善。幸好需要改进的地方通常不会太多。”(Steve McConnell的《About performance》[16]) 本附录的宗旨就是指导大家寻找和优化“需要完善的那一部分”。 D.1 基本方法 只有正确和完整地检测了程序后,再可着手解决性能方面的问题: (1) 在现实环境中检测程序的性能。若符合要求,则目标达到。若不符合,则转到下一步。 (2) 寻找最致命的性能瓶颈。这也许要求一定的技巧,但所有努力都不会白费。如简单地猜测瓶颈所在,并试图进行优化,那么可能是白花时间。 (3) 运用本附录介绍的提速技术,然后返回步骤1。 为使努力不至白费,瓶颈的定位是至关重要的一环。Donald Knuth[9]曾改进过一个程序,那个程序把50%的时间都花在约4%的代码量上。在仅一个工作小时里,他修改了几行代码,使程序的执行速度倍增。此时,若将时间继续投入到剩余代码的修改上,那么只会得不偿失。Knuth在编程界有一句名言:“过早的优化是一切麻烦的根源”(Premature optimization is the root of all evil)。最明智的做法是抑制过早优化的冲动,因为那样做可能遗漏多种有用的编程技术,造成代码更难理解和操控,并需更大的精力进行维护。 D.2 寻找瓶颈 为找出最影响程序性能的瓶颈,可采取下述几种方法: D.2.1 安插自己的测试代码 插入下述“显式”计时代码,对程序进行评测: long start = System.currentTimeMillis(); // 要计时的运算代码放在这儿 long time = System.currentTimeMillis() - start; 利用System.out.println(),让一种不常用到的方法将累积时间打印到控制台窗口。由于一旦出错,编译器会将其忽略,所以可用一个“静态最终布尔值”(Static final boolean)打开或关闭计时,使代码能放心留在最终发行的程序里,这样任何时候都可以拿来应急。尽管还可以选用更复杂的评测手段,但若仅仅为了量度一个特定任务的执行时间,这无疑是最简便的方法。 System.currentTimeMillis()返回的时间以千分之一秒(1毫秒)为单位。然而,有些系统的时间精度低于1毫秒(如Windows PC),所以需要重复n次,再将总时间除以n,获得准确的时间。 D.2.2 JDK性能评测[2] JDK配套提供了一个内建的评测程序,能跟踪花在每个例程上的时间,并将评测结果写入一个文件。不幸的是,JDK评测器并不稳定。它在JDK 1.1.1中能正常工作,但在后续版本中却非常不稳定。 为运行评测程序,请在调用Java解释器的未优化版本时加上-prof选项。例如: java_g -prof myClass 或加上一个程序片(Applet): java_g -prof sun.applet.AppletViewer applet.html 理解评测程序的输出信息并不容易。事实上,在JDK 1.0中,它居然将方法名称截短为30字符。所以可能无法区分出某些方法。然而,若您用的平台确实能支持-prof选项,那么可试试Vladimir Bulatov的“HyperPorf”[3]或者Greg White的“ProfileViewer”来解释一下结果。 D.2.3 特殊工具 如果想随时跟上性能优化工具的潮流,最好的方法就是作一些Web站点的常客。比如由Jonathan Hardwick制作的“Tools for Optimizing Java”(Java优化工具)网站: http://www.cs.cmu.edu/~jch/java/tools.html D.2.4 性能评测的技巧 ■由于评测时要用到系统时钟,所以当时不要运行其他任何进程或应用程序,以免影响测试结果。 ■如对自己的程序进行了修改,并试图(至少在开发平台上)改善它的性能,那么在修改前后应分别测试一下代码的执行时间。 ■尽量在完全一致的环境中进行每一次时间测试。 ■如果可能,应设计一个不依赖任何用户输入的测试,避免用户的不同反应导致结果出现误差。 D.3 提速方法 现在,关键的性能瓶颈应已隔离出来。接下来,可对其应用两种类型的优化:常规手段以及依赖Java语言。 D.3.1 常规手段 通常,一个有效的提速方法是用更现实的方式重新定义程序。例如,在《Programming Pearls》(编程拾贝)一书中[14],Bentley利用了一段小说数据描写,它可以生成速度非常快、而且非常精简的拼写检查器,从而介绍了Doug McIlroy对英语语言的表述。除此以外,与其他方法相比,更好的算法也许能带来更大的性能提升——特别是在数据集的尺寸越来越大的时候。欲了解这些常规手段的详情,请参考本附录末尾的“一般书籍”清单。 D.3.2 依赖语言的方法 为进行客观的分析,最好明确掌握各种运算的执行时间。这样一来,得到的结果可独立于当前使用的计算机——通过除以花在本地赋值上的时间,最后得到的就是“标准时间”。 运算 示例 标准时间 本地赋值 i=n; 1.0 实例赋值 this.i=n; 1.2 int增值 i++; 1.5 byte增值 b++; 2.0 short增值 s++; 2.0 float增值 f++; 2.0 double增值 d++; 2.0 空循环 while(true) n++; 2.0 三元表达式 (x<0) ?-x : x 2.2 算术调用 Math.abs(x); 2.5 数组赋值 a[0] = n; 2.7 long增值 l++; 3.5 方法调用 funct(); 5.9 throw或catch异常 try{ throw e; }或catch(e){} 320 同步方法调用 synchMehod(); 570 新建对象 new Object(); 980 新建数组 new int[10]; 3100 通过自己的系统(如我的Pentium 200 Pro,Netscape 3及JDK 1.1.5),这些相对时间向大家揭示出:新建对象和数组会造成最沉重的开销,同步会造成比较沉重的开销,而一次不同步的方法调用会造成适度的开销。参考资源[5]和[6]为大家总结了测量用程序片的Web地址,可到自己的机器上运行它们。 1. 常规修改 下面是加快Java程序关键部分执行速度的一些常规操作建议(注意对比修改前后的测试结果)。 将... 修改成... 理由 接口 抽象类(只需一个父时) 接口的多个继承会妨碍性能的优化 非本地或数组循环变量 本地循环变量 根据前表的耗时比较,一次实例整数赋值的时间是本地整数赋值时间的1.2倍,但数组赋值的时间是本地整数赋值的2.7倍 链接列表(固定尺寸) 保存丢弃的链接项目,或将列表替换成一个循环数组(大致知道尺寸) 每新建一个对象,都相当于本地赋值980次。参考“重复利用对象”(下一节)、Van Wyk[12] p.87以及Bentley[15] p.81 x/2(或2的任意次幂) X>>2(或2的任意次幂) 使用更快的硬件指令 D.3.3 特殊情况 ■字串的开销:字串连接运算符+看似简单,但实际需要消耗大量系统资源。编译器可高效地连接字串,但变量字串却要求可观的处理器时间。例如,假设s和t是字串变量: System.out.println("heading" + s + "trailer" + t); 上述语句要求新建一个StringBuffer(字串缓冲),追加自变量,然后用toString()将结果转换回一个字串。因此,无论磁盘空间还是处理器时间,都会受到严重消耗。若准备追加多个字串,则可考虑直接使用一个字串缓冲——特别是能在一个循环里重复利用它的时候。通过在每次循环里禁止新建一个字串缓冲,可节省980单位的对象创建时间(如前所述)。利用substring()以及其他字串方法,可进一步地改善性能。如果可行,字符数组的速度甚至能够更快。也要注意由于同步的关系,所以StringTokenizer会造成较大的开销。 ■同步:在JDK解释器中,调用同步方法通常会比调用不同步方法慢10倍。经JIT编译器处理后,这一性能上的差距提升到50到100倍(注意前表总结的时间显示出要慢97倍)。所以要尽可能避免使用同步方法——若不能避免,方法的同步也要比代码块的同步稍快一些。 ■重复利用对象:要花很长的时间来新建一个对象(根据前表总结的时间,对象的新建时间是赋值时间的980倍,而新建一个小数组的时间是赋值时间的3100倍)。因此,最明智的做法是保存和更新老对象的字段,而不是创建一个新对象。例如,不要在自己的paint()方法中新建一个Font对象。相反,应将其声明成实例对象,再初始化一次。在这以后,可在paint()里需要的时候随时进行更新。参见Bentley编著的《编程拾贝》,p.81[15]。 ■异常:只有在不正常的情况下,才应放弃异常处理模块。什么才叫“不正常”呢?这通常是指程序遇到了问题,而这一般是不愿见到的,所以性能不再成为优先考虑的目标。进行优化时,将小的“try-catch”块合并到一起。由于这些块将代码分割成小的、各自独立的片断,所以会妨碍编译器进行优化。另一方面,若过份热衷于删除异常处理模块,也可能造成代码健壮程度的下降。 ■散列处理:首先,Java 1.0和1.1的标准“散列表”(Hashtable)类需要造型以及特别消耗系统资源的同步处理(570单位的赋值时间)。其次,早期的JDK库不能自动决定最佳的表格尺寸。最后,散列函数应针对实际使用项(Key)的特征设计。考虑到所有这些原因,我们可特别设计一个散列类,令其与特定的应用程序配合,从而改善常规散列表的性能。注意Java 1.2集合库的散列映射(HashMap)具有更大的灵活性,而且不会自动同步。 ■方法内嵌:只有在方法属于final(最终)、private(专用)或static(静态)的情况下,Java编译器才能内嵌这个方法。而且某些情况下,还要求它绝对不可以有局部变量。若代码花大量时间调用一个不含上述任何属性的方法,那么请考虑为其编写一个“final”版本。 ■I/O:应尽可能使用缓冲。否则,最终也许就是一次仅输入/输出一个字节的恶果。注意JDK 1.0的I/O类采用了大量同步措施,所以若使用象readFully()这样的一个“大批量”调用,然后由自己解释数据,就可获得更佳的性能。也要注意Java 1.1的“reader”和“writer”类已针对性能进行了优化。 ■造型和实例:造型会耗去2到200个单位的赋值时间。开销更大的甚至要求上溯继承(遗传)结构。其他高代价的操作会损失和恢复更低层结构的能力。 ■图形:利用剪切技术,减少在repaint()中的工作量;倍增缓冲区,提高接收速度;同时利用图形压缩技术,缩短下载时间。来自JavaWorld的“Java Applets”以及来自Sun的“Performing Animation”是两个很好的教程。请记着使用最贴切的命令。例如,为根据一系列点画一个多边形,和drawLine()相比,drawPolygon()的速度要快得多。如必须画一条单像素粗细的直线,drawLine(x,y,x,y)的速度比fillRect(x,y,1,1)快。 ■使用API类:尽量使用来自Java API的类,因为它们本身已针对机器的性能进行了优化。这是用Java难于达到的。比如在复制任意长度的一个数组时,arraryCopy()比使用循环的速度快得多。 ■替换API类:有些时候,API类提供了比我们希望更多的功能,相应的执行时间也会增加。因此,可定做特别的版本,让它做更少的事情,但可更快地运行。例如,假定一个应用程序需要一个容器来保存大量数组。为加快执行速度,可将原来的Vector(矢量)替换成更快的动态对象数组。 1. 其他建议 ■将重复的常数计算移至关键循环之外——比如计算固定长度缓冲区的buffer.length。 ■static final(静态最终)常数有助于编译器优化程序。 ■实现固定长度的循环。 ■使用javac的优化选项:-O。它通过内嵌static,final以及private方法,从而优化编译过的代码。注意类的长度可能会增加(只对JDK 1.1而言——更早的版本也许不能执行字节查证)。新型的“Just-in-time”(JIT)编译器会动态加速代码。 ■尽可能地将计数减至0——这使用了一个特殊的JVM字节码。 D.4 参考资源 D.4.1 性能工具 [1] 运行于Pentium Pro 200,Netscape 3.0,JDK 1.1.4的MicroBenchmark(参见下面的参考资源[5]) [2] Sun的Java文档页——JDK Java解释器主题: http://java.sun.com/products/JDK/tools/win32/java.html [3] Vladimir Bulatov的HyperProf http://www.physics.orst.edu/~bulatov/HyperProf [4] Greg White的ProfileViewer http://www.inetmi.com/~gwhi/ProfileViewer/ProfileViewer.html D.4.2 Web站点 [5] 对于Java代码的优化主题,最出色的在线参考资源是Jonathan Hardwick的“Java Optimization”网站: http://www.cs.cmu.edu/~jch/java/optimization.html “Java优化工具”主页: http://www.cs.cmu.edu/~jch/java/tools.html 以及“Java Microbenchmarks”(有一个45秒钟的评测过程): http://www.cs.cmu.edu/~jch/java/benchmarks.html D.4.3 文章 [6] “Make Java fast:Optimize! How to get the greatest performanceout of your code through low-level optimizations in Java”(让Java更快:优化!如何通过在Java中的低级优化,使代码发挥最出色的性能)。作者:Doug Bell。网址: http://www.javaworld.com/javaworld/jw-04-1997/jw-04-optimize.html (含一个全面的性能评测程序片,有详尽注释) [7] “Java Optimization Resources”(Java优化资源) http://www.cs.cmu.edu/~jch/java/resources.html [8] “Optimizing Java for Speed”(优化Java,提高速度): http://www.cs.cmu.edu/~jch/java/speed.html [9] “An Empirical Study of FORTRAN Programs”(FORTRAN程序实战解析)。作者:Donald Knuth。1971年出版。第1卷,p.105-33,“软件——实践和练习”。 [10] “Building High-Performance Applications and Servers in Java:An Experiential Study”。作者:Jimmy Nguyen,Michael Fraenkel,RichardRedpath,Binh Q. Nguyen以及Sandeep K. Singhal。IBM T.J. Watson ResearchCenter,IBM Software Solutions。 http://www.ibm.com/java/education/javahipr.html D.4.4 Java专业书籍 [11] 《Advanced Java,Idioms,Pitfalls,Styles, and Programming Tips》。作者:Chris Laffra。Prentice Hall 1997年出版(Java 1.0)。第11章第20小节。 D.4.5 一般书籍 [12] 《Data Structures and C Programs》(数据结构和C程序)。作者:J.Van Wyk。Addison-Wesly 1998年出版。 [13] 《Writing Efficient Programs》(编写有效的程序)。作者:Jon Bentley。Prentice Hall 1982年出版。特别参考p.110和p.145-151。 [14] 《More Programming Pearls》(编程拾贝第二版)。作者:JonBentley。“Association for Computing Machinery”,1998年2月。 [15] 《Programming Pearls》(编程拾贝)。作者:Jone Bentley。Addison-Wesley 1989年出版。第2部分强调了常规的性能改善问题。 [16] 《Code Complete:A Practical Handbook of Software Construction》(完整代码索引:实用软件开发手册)。作者:Steve McConnell。Microsoft出版社1993年出版,第9章。 [17] 《Object-Oriented System Development》(面向对象系统的开发)。作者:Champeaux,Lea和Faure。第25章。 [18] 《The Art of Programming》(编程艺术)。作者:Donald Knuth。第1卷“基本算法第3版”;第3卷“排序和搜索第2版”。Addison-Wesley出版。这是有关程序算法的一本百科全书。 [19] 《Algorithms in C:Fundammentals,Data Structures, Sorting,Searching》(C算法:基础、数据结构、排序、搜索)第3版。作者:RobertSedgewick。Addison-Wesley 1997年出版。作者是Knuth的学生。这是专门讨论几种语言的七个版本之一。对算法进行了深入浅出的解释。 <

小小豆叮

走进Java 原型开发

在软件开发市场上,Java原型开发已逐渐成为大中型软件开发公司的发展方向 在现时的软件市场上,软件开发公司通常采用生命周期模式(结构化设计模式),它是面向功能或过程的方法。其实现技术是结构化系统分析、设计与结构化程序设计。开发人员通过与相关业务人员交流或直接深入实际工作,根据原始资料写出用户需求说明草本。经修改,得到相关人员的确定、认可后双方签字,形成合同式需求说明书。开发人员根据需求说明书进行系统设计、编程。系统实现后双方组织人员进行测试,然后便进入系统的运行、维护期。利用生命周期模式开发MIS系统基于两个假设:(1)用户能清楚地、完整地提供系统要求;(2)开发者能完整地、严格地理解和定义要求。但在实际开发中,以上两个假设显然无法满足。首先,用户难以准确地描述出系统需求;其次,口述具有两义性,这往往使开发人员产生误解,从而提高了准确定义用户需求的难度。同时,开发者也由于这样或那样的主客观原因,难以跨越与用户交流的鸿沟。其结果是系统开发完毕后,不能很好地满足用户需求,达到预期目标,需要经常修改、维护的开销过大,有时甚至造成系统预算严重超支,系统验收一再拖延,以致开发双方的项目合作破裂。生命周期模式是封闭式的,缺少灵活性。这在用户需求定义方面尤为突出。为了克服这一缺点,产生了原型开发模式。 该模式基于以下认识:并非一切需求都能在开发前准确预见。 参与项目的双方存在着相互沟通的障碍。 大量的反复是不可避免的,并且是必要的。 基于以上认识,原型法要求经过对用户需求的简单快速分析,利用高级开发工具及环境,快速完成原型系统(系统的一个可运行的早期版本,它反映了最终系统的部分重要特征)的设计和实施,提供给用户评价。在评价过程中,开发人员不断从用户那里得到反馈信息,修正原型的用户需求定义,进而对原型系统作相应改进,逐步减少分析与交互过程中的误解,弥补遗漏,从而提高最终系统的质量。 在软件开发市场上,Java原型开发已逐渐成为大中型软件开发公司的发展方向。Java原型开发的主要思想是利用Java优越的继承性,使用Javabean开发出通用的程序逻辑,即常用的一些数据库操作,如对数据库表记录的插入、删除、修改、查询等。另外还要建立一套清晰明确的数据库接口,可以方便的切换到别的数据库上。使用Java原型开发方法能够适应现代客户业务变化时的软件需求,采用它有几点理由: ●很多客户(尤其是现在大多数中小企业)对要实现的信息系统没有一个清晰的概念。他们没有任何类似的经验及有效的指导。 ●技术人员在理解客户的业务流程上有一定的难度和需要一定的时间。 Java原型法的特点是:与用户交互,在试用原型的基础上逐步获得系统清晰的业务流程和功能需求。其中最主要的交互手段是原型软件的界面。原型开发不仅仅可以用于准确获取用户的需求,开发出来的原型本身可以作为下一步开发的基础,增量式地完成开发。 一、实施阶段的描述 首先建立一个能反映用户主要需求的原型(仅包括未来系统的主要功能以及系统的重要接口),让用户实际看一看系统的概貌以判断模型修改方向,然后进行模型的反复改进,最终形成完全符合用户要求的新系统。Java原型化软件开发方法因有用户的介入和反馈使得系统更能适应需求,但因为短时间内要构造原型系统和快速响应用户要求都会对开发环境提出很高的要求,因而这在一定程度上影响了该种方法的迅速推广与发展。 (1)计划时期: ●问题的提出。这是计划时期的第一步,对Java原型化方法则只需先找出主要问题; ●可行性研究。对前面提出的问题寻求是否有解决方案,是否值得去着手进行; (2)开发时期: ●需求分析。确定问题的需求,并以准确的形式表达出来。 ●需求设计。建立系统的总体结构和单个模块的设计构想,进行系统的逻辑模型设计考虑。 ●编码与测试。进行系统的物理实现(因为使用快速原型化方法,此步之后仍需返回前面重新进行直至用户满意为止。) (3)运行时期: 进行软件维护,保证软件满足用户的需求和延长软件的使用寿命。 二、Java原型开发法的优势 开放性 由于Java拥有良好的跨平台性和开放性,因此Java 原型开发模型也具有优越的开放性,在操作系统的应用上可跨越Microsoft Windows NT / Windows 2000、Unix、Sun Solaris、Linux等平台,亦能在多种Web Server及Application Server上应用,如Apache、Tomcat、Weblogic等。 稳定性 由于系统的大部分业务逻辑和程序方法都封装在Javabean里,在真正使用阶段页面基本上都是调用Javabean的方法,页面只会保留有关程序逻辑的部分,因此相对传统的开发模型来说,Java 原型开发模型具有相当好的稳定性。 可扩展性 在Java 原型开发模型采用了数据缓冲池技术,即在数据库服务器内存划分一个区域为缓冲池,进程可在缓冲池内建立多个连接。因此该模型能支持多个并发用户访问,并能把索引和数据页预读取至数据缓冲池,可以减少等待 I/O 完成所用的时间,以有助于改进性能。 另外Java 原型开发模型是一个集成化软件,它既可适应老系统,但又易保障快速建立新的应用系统,Java 原型开发模型模块化结构组合,独立运行功能强,满足复杂和易变的业务管理要求。 易维护性 由于Java 原型开发模型采用Javabean+JSP模式进行开发,对数据库的基本操作基本上在底层的Javabean中实现,而且Java 原型开发模型模块化结构清晰,具有简单易用的数据库接口,Javabean原型均有固定的格式,在实际应用上只需要把Javabean原型复制出来,继承原有的父类,更改数据表名和字段名即可连接到对应的新表中。JSP原型也有一定的格式,将JSP原型复制并更改调用的Javabean,在根据客户的要求修改页面相应的部分即可。基于Java固有的可继承性,Java 原型开发模型具有很强的易维护性,即使是Java的初级程序员经过简单的培训后,也可以很方便地根据实际情况对Javabean和JSP模型进行修改。 三、使用Java原型开发法的要点 在原型开发法中,界面设计应当作为数据库设计和程序结构设计之后的另一个重要设计,因为在占据主要局面的GUI软件中,友好、简洁界面已经是衡量一个软件质量的重要标准。 在使用可视化工具开发的软件当中,界面所体现的窗体(form)从某种程度上讲就是程序的结构。 数据库设计,程序结构设计,界面设计之间互相作用、互相影响,在原型法中很难再有明显的时间界限,它们需要被完整地、系统地考虑。 <

小小豆叮

摩托罗拉推出Java移动游戏开发平台

摩托罗拉公司称,该公司和游戏出版商In-Fusio公司已制作出“摩托罗拉嵌入式参考实现(MERI)移动游戏引擎”。该产品有一个普通的应用程序接口,可用来开发在使用摩托罗拉植入的J2ME微型版本的设备上应用的游戏。   J2ME已经成为无线设备广泛应用的平台。摩托罗拉公司将该平台应用到了自己的设备中。   摩托罗拉公司在声明中说,这个游戏引擎能使开发者制作出较小的游戏,这些较小的游戏运行速度更快,更容易下载到手机上。这个平台还将提供增强的图片、动画和声音效果以及诸如抖动之类的特殊效果。   摩托罗拉公司没有提供该产品的上市时间。 <

小小豆叮

在WIN200和WIN98下Tomcat服务器安装实例

以下假设你的Tomcat服务器和Jdk1.3在D盘的根目录下,其它盘雷同,只需改盘符而已 在window 2000 Server的配置: 桌面--> 右键单击“我的电脑”--> 选择属性“高级”--> 选择“环境变量”--> 打开一个窗口在“系统变量”栏选择“新建”项后弹出一对话框叫做“新建系统变量” 分别如下设置: 1.变量名:Tomcat_home 变量值:d:\tomcat; 2.变量名:Java_home 变量值:d:\Jdk1.3; 3.变量名:Classpath 变量值:d:\jdk1.3\lib\tools.jar; d:\jdk1.3\lib\dt.jar;d:\TOMCAT\lib; 4.变量名:Path 变量值:d:\jdk1.3\bin; 最后,打开IE浏览器在地址栏敲入IP地址访问,如http://192.168.0.168:8080;也可以这样http://localhost:8080 在win98下的配置: 打开C盘下的AUTOEXEC.BAT文件,将下列数据Copy到AUTOEXEC.BAT文件保存,重新启动机器,然后打开D:\tomcat\bin\STARTUP.exe,接着打开IE游览器在址栏敲入IP地址,即可正确浏览,如http://192.168.0.168:8080(不要忘了要敲入端口号8080哦) set Tomcat_home=d:\tomcat; set Java_home=d:\jdk1.3 set Classpath=d:\jdk1.3\lib\tools.jar;d:\jdk1.3\lib\dt.jar;d:\TOMCAT\lib; set Path=d:\jdk1.3\bin; <

小小豆叮

Java编程规则

(1) 类名首字母应该大写。字段、方法以及对象(句柄)的首字母应小写。对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母。例如: ThisIsAClassName thisIsMethodOrFieldName 若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母。这样便可标志出它们属于编译期的常数。 Java包(Package)属于一种特殊情况:它们全都是小写字母,即便中间的单词亦是如此。对于域名扩展名称,如com,org,net或者edu等,全部都应小写(这也是Java 1.1和Java 1.2的区别之一)。 (2) 为了常规用途而创建一个类时,请采取“经典形式”,并包含对下述元素的定义: equals() hashCode() toString() clone()(implement Cloneable) implement Serializable (3) 对于自己创建的每一个类,都考虑置入一个main(),其中包含了用于测试那个类的代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动,可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。 (4) 应将方法设计成简要的、功能性单元,用它描述和实现一个不连续的类接口部分。理想情况下,方法应简明扼要。若长度很大,可考虑通过某种方式将其分割成较短的几个方法。这样做也便于类内代码的重复使用(有些时候,方法必须非常大,但它们仍应只做同样的一件事情)。 (5) 设计一个类时,请设身处地为客户程序员考虑一下(类的使用方法应该是非常明确的)。然后,再设身处地为管理代码的人考虑一下(预计有可能进行哪些形式的修改,想想用什么方法可把它们变得更简单)。 (6) 使类尽可能短小精悍,而且只解决一个特定的问题。下面是对类设计的一些建议: ■一个复杂的开关语句:考虑采用“多形”机制 ■数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现 ■许多成员变量在特征上有很大的差别:考虑使用几个类 (7) 让一切东西都尽可能地“私有”——private。可使库的某一部分“公共化”(一个方法、类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变其他任何东西。在多线程环境中,隐私是特别重要的一个因素——只有private字段才能在非同步使用的情况下受到保护。 (8) 谨惕“巨大对象综合症”。对一些习惯于顺序编程思维、且初涉OOP领域的新手,往往喜欢先写一个顺序执行的程序,再把它嵌入一个或两个巨大的对象里。根据编程原理,对象表达的应该是应用程序的概念,而非应用程序本身。 (9) 若不得已进行一些不太雅观的编程,至少应该把那些代码置于一个类的内部。 (10) 任何时候只要发现类与类之间结合得非常紧密,就需要考虑是否采用内部类,从而改善编码及维护工作(参见第14章14.1.2小节的“用内部类改进代码”)。 (11) 尽可能细致地加上注释,并用javadoc注释文档语法生成自己的程序文档。 (12) 避免使用“魔术数字”,这些数字很难与代码很好地配合。如以后需要修改它,无疑会成为一场噩梦,因为根本不知道“100”到底是指“数组大小”还是“其他全然不同的东西”。所以,我们应创建一个常数,并为其使用具有说服力的描述性名称,并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。 (13) 涉及构建器和异常的时候,通常希望重新丢弃在构建器中捕获的任何异常——如果它造成了那个对象的创建失败。这样一来,调用者就不会以为那个对象已正确地创建,从而盲目地继续。 (14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码置于一个良好定义的方法里,采用类似于cleanup()这样的名字,明确表明自己的用途。除此以外,可在类内放置一个boolean(布尔)标记,指出对象是否已被清除。在类的finalize()方法里,请确定对象已被清除,并已丢弃了从RuntimeException继承的一个类(如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定finalize()能够在自己的系统中工作(可能需要调用System.runFinalizersOnExit(true),从而确保这一行为)。 (15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用下述方法:初始化对象;若成功,则立即进入一个含有finally从句的try块,开始清除工作。 (16) 若在初始化过程中需要覆盖(取消)finalize(),请记住调用super.finalize()(若Object属于我们的直接超类,则无此必要)。在对finalize()进行覆盖的过程中,对super.finalize()的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需要基础类组件的时候它们依然有效。 (17) 创建大小固定的对象集合时,请将它们传输至一个数组(若准备从一个方法里返回这个集合,更应如此操作)。这样一来,我们就可享受到数组在编译期进行类型检查的好处。此外,为使用它们,数组的接收者也许并不需要将对象“造型”到数组里。 (18) 尽量使用interfaces,不要使用abstract类。若已知某样东西准备成为一个基础类,那么第一个选择应是将其变成一个interface(接口)。只有在不得不使用方法定义或者成员变量的时候,才需要将其变成一个abstract(抽象)类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实施细节。 (19) 在构建器内部,只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法,因为那些方法可能被其他人覆盖或取消,从而在构建过程中产生不可预知的结果(参见第7章的详细说明)。 (20) 对象不应只是简单地容纳一些数据;它们的行为也应得到良好的定义。 (21) 在现成类的基础上创建新类时,请首先选择“新建”或“创作”。只有自己的设计要求必须继承时,才应考虑这方面的问题。若在本来允许新建的场合使用了继承,则整个设计会变得没有必要地复杂。 (22) 用继承及方法覆盖来表示行为间的差异,而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色,这是绝对应该避免的:应直接使用一个“颜色”字段。 (23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到了类路径问题,请试试在类路径的每一个起点,搜索一下同名的.class文件。 (24) 在Java 1.1 AWT中使用事件“适配器”时,特别容易碰到一个陷阱。若覆盖了某个适配器方法,同时拼写方法没有特别讲究,最后的结果就是新添加一个方法,而不是覆盖现成方法。然而,由于这样做是完全合法的,所以不会从编译器或运行期系统获得任何出错提示——只不过代码的工作就变得不正常了。 (25) 用合理的设计方案消除“伪功能”。也就是说,假若只需要创建类的一个对象,就不要提前限制自己使用应用程序,并加上一条“只生成其中一个”注释。请考虑将其封装成一个“独生子”的形式。若在主程序里有大量散乱的代码,用于创建自己的对象,请考虑采纳一种创造性的方案,将些代码封装起来。 (26) 警惕“分析瘫痪”。请记住,无论如何都要提前了解整个项目的状况,再去考察其中的细节。由于把握了全局,可快速认识自己未知的一些因素,防止在考察细节的时候陷入“死逻辑”中。 (27) 警惕“过早优化”。首先让它运行起来,再考虑变得更快——但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。除非用专门的工具分析瓶颈,否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于理解,而且难于维护。 (28) 请记住,阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解的程序,但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己,还是对后来的人,它们都是相当重要的。如对此仍有怀疑,那么请试想自己试图从联机Java文档里找出有用信息时碰到的挫折,这样或许能将你说服。 (29) 如认为自己已进行了良好的分析、设计或者实施,那么请稍微更换一下思维角度。试试邀请一些外来人士——并不一定是专家,但可以是来自本公司其他部门的人。请他们用完全新鲜的眼光考察你的工作,看看是否能找出你一度熟视无睹的问题。采取这种方式,往往能在最适合修改的阶段找出一些关键性的问题,避免产品发行后再解决问题而造成的金钱及精力方面的损失。 (30) 良好的设计能带来最大的回报。简言之,对于一个特定的问题,通常会花较长的时间才能找到一种最恰当的解决方案。但一旦找到了正确的方法,以后的工作就轻松多了,再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报(甚至无可估量)。而且由于自己倾注了大量心血,最终获得一个出色的设计方案,成功的快感也是令人心动的。坚持抵制草草完工的诱惑——那样做往往得不偿失。 (31) 可在Web上找到大量的编程参考资源,甚至包括大量新闻组、讨论组、邮寄列表等。下面这个地方提供了大量有益的链接: <

小小豆叮