2005年07月25日

 关键字:Java, JSP, Servlet, template, 模板, Apache, Jakarta, Velocity
读者要求:了解 Java Servlet 基本概念

 Velocity 是一个基于 Java 的通用模板工具,来自于 jakarta.apache.org 。

Velocity 的介绍请参考 Velocity — Java Web 开发新技术。这里是它的一个应用示例。

这个例子参照了 PHP-Nuke 的结构, 即所有 HTTP 请求都以 http://www.some.com/xxx/Modules?name=xxx&arg1=xxx&bbb=xxx 的形式进行处理。例子中所有文件都是 .java 和 .html , 没有其他特殊的文件格式。除了 Modules.java 是 Java Servlet, 其余的 .java 文件都是普通的 Java Class.

所有 HTTP 请求都通过 Modules.java 处理。Modules.java 通过 Velocity 加载 Modules.htm。 Modules.htm 有页头,页脚,页左导航链接,页中内容几个部分。其中页头广告、页中内容是变化部分。页头广告由 Modules.java 处理,页中内容部分由 Modules.java dispatch 到子页面类处理。

1) Modules.java

     
 
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.velocity.*;
import org.apache.velocity.context.*;
import org.apache.velocity.exception.*;
import org.apache.velocity.servlet.*;
import commontools.*;

public class Modules
  extends VelocityServlet {
  public Template handleRequest(HttpServletRequest request,
                 HttpServletResponse response,
                 Context context) {
    //init
    response.setContentType("text/html; charset=UTF-8");
    response.setCharacterEncoding("utf-8");

    //prepare function page
    ProcessSubPage page = null;
    ProcessSubPage mainPage = new HomeSubPage();
    String requestFunctionName = (String) request.getParameter("name");
    boolean logined = false;

    String loginaccount = (String) request.getSession(true).getAttribute(
      "loginaccount");
    if (loginaccount != null) {
      logined = true;
    }

    //default page is mainpage
    page = mainPage;
    if (requestFunctionName == null||requestFunctionName.equalsIgnoreCase("home")) {
      page = mainPage;
    }

    //no login , can use these page
    else if (requestFunctionName.equalsIgnoreCase("login")) {
      page = new LoginProcessSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase("ChangePassword")) {
      page = new ChangePasswordSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase("ForgetPassword")) {
      page = new ForgetPassword();
    }
    else if (requestFunctionName.equalsIgnoreCase("about")) {
      page = new AboutSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase("contact")) {
      page = new ContactSubPage();
    }

    //for other page, need login first
    else if (logined == false) {
      page = new LoginProcessSubPage();
    }

    else if (requestFunctionName.equalsIgnoreCase("listProgram")) {
      page = new ListTransactionProgramSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase(
      "ViewProgramItem")) {
      page = new ViewTransactionProgramItemSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase(
      "UpdateProgramObjStatus")) {
      page = new UpdateTransactionProgramObjStatusSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase(
      "Search")) {
      page = new SearchSubPage();
    }

    //check if this is administrator
    else if (Utilities.isAdministratorLogined(request)) {
      //Utilities.debugPrintln("isAdministratorLogined : true");
      if (requestFunctionName.equalsIgnoreCase("usermanagement")) {
        page = new UserManagementSubPage();
      }
      else if (requestFunctionName.equalsIgnoreCase(
        "UploadFiles")) {
        page = new UploadFilesSubPage();
      }
      else if (requestFunctionName.equalsIgnoreCase(
        "DownloadFile")) {
        page = new DownloadFileSubPage();
      }
      else if (requestFunctionName.equalsIgnoreCase(
        "Report")) {
        page = new ReportSubPage();
      }

    }
    else {
      //no right to access.
      //Utilities.debugPrintln("isAdministratorLogined : false");
      page = null;
    }
    //Utilities.debugPrintln("page : " + page.getClass().getName());

    if(page != null){
      context.put("function_page",
            page.getHtml(this, request, response, context));
    }else{
      String msg = "Sorry, this module is for administrator only.
You are not administrator.
";       context.put("function_page",msg);     }          context.put("page_header",getPageHeaderHTML());     context.put("page_footer",getPageFooterHTML());     Template template = null;     try {       template = getTemplate("/templates/Modules.htm"); //good     }     catch (ResourceNotFoundException rnfe) {       Utilities.debugPrintln("ResourceNotFoundException 2");       rnfe.printStackTrace();     }     catch (ParseErrorException pee) {       Utilities.debugPrintln("ParseErrorException2 " + pee.getMessage());     }     catch (Exception e) {       Utilities.debugPrintln("Exception2 " + e.getMessage());     }     return template;   }   /**    * Loads the configuration information and returns that information as a Properties, e    * which will be used to initializ the Velocity runtime.    */   protected java.util.Properties loadConfiguration(ServletConfig config) throws     java.io.IOException, java.io.FileNotFoundException {     return Utilities.initServletEnvironment(this);   } }
 
     

2) ProcessSubPage.java , 比较简单,只定义了一个函数接口 getHtml

     
 
import javax.servlet.http.*;
import org.apache.velocity.context.*;
import org.apache.velocity.servlet.*;
import commontools.*;

public abstract class ProcessSubPage implements java.io.Serializable {
  public ProcessSubPage() {
  }

  public String getHtml(VelocityServlet servlet, HttpServletRequest request,
             HttpServletResponse response,
             Context context) {
    Utilities.debugPrintln(
      "you need to override this method in sub class of ProcessSubPage:"
      + this.getClass().getName());
    return "Sorry, this module not finish yet.";
  }

}
 
     

他的 .java 文件基本上是 ProcessSubPage 的子类和一些工具类。 ProcessSubPage 的子类基本上都是一样的流程, 用类似
context.put("page_footer",getPageFooterHTML());
的写法置换 .html 中的可变部分即可。如果没有可变部分,完全是静态网页,比如 AboutSubPage, 就更简单。

3) AboutSubPage.java

     
 
import org.apache.velocity.servlet.VelocityServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.velocity.context.Context;

public class AboutSubPage extends ProcessSubPage {
  public AboutSubPage() {
  }

  public String getHtml(VelocityServlet servlet, HttpServletRequest request,
             HttpServletResponse response, Context context) {
    //prepare data
    //context.put("xxx","xxxx");         
             
    Template template = null;
    String fileName = "About.htm";
    try {
      template = servlet.getTemplate(fileName);
      StringWriter sw = new StringWriter();
      template.merge(context, sw);
      return sw.toString();
    }
    catch (Exception ex) {
      return "error get template " + fileName + " " + ex.getMessage();
    }
  }
}
 
     

其他 ProcessSubPage 的子类如上面基本类似,只不过会多了一些 context.put("xxx","xxxx") 的语句。

通过以上的例子,我们可以看到,使用 Velocity + Servlet , 所有的代码为: 1 个 java serverlet + m 个 java class + n 个 Html 文件

这里是用了集中处理,然后分发(dispatch)的机制。不用担心用户在没有登陆的情况下访问某些页面。用户验证,页眉页脚包含都只写一次,易于编写、修改和维护。代码比较简洁,并且很容易加上自己的页面缓冲功能。可以随意将某个页面的 html 在内存中保存起来,缓存几分钟,实现页面缓冲功能。成功、出错页面也可以用同样的代码封装成函数,通过参数将 Message/Title 传入即可。

因为 Java 代码与 Html 代码完全在不同的文件中,美工与java代码人员可以很好的分工,每个人修改自己熟悉的文件,基本上不需要花时间做协调工作。而用 JSP, 美工与java代码人员共同修改维护 .jsp 文件,麻烦多多,噩梦多多。而且这里没有用 xml ,说实话,懂 xml/xls 之类的人只占懂 Java 程序员中的几分之一,人员不好找。

因为所有 java 代码人员写的都是标准 Java 程序,可以用任何 Java 编辑器,调试器,因此开发速度也会大大提高。美工写的是标准 Html 文件,没有 xml, 对于他们也很熟悉,速度也很快。并且,当需要网站改版的时候,只要美工把 html 文件重新修饰排版即可,完全不用改动一句 java 代码。

爽死了!!

4) 工具类 Utilities.java

     
 
import java.io.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import java.util.Date;
import javax.naming.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.velocity.*;
import org.apache.velocity.app.*;
import org.apache.velocity.context.Context;
import org.apache.velocity.servlet.*;

public class Utilities {
  private static Properties m_servletConfig = null;

  private Utilities() {
  }

  static {
    initJavaMail();
  }

  public static void debugPrintln(Object o) {
    String msg = "proj debug message at " + getNowTimeString() +
      " ------------- ";
    System.err.println(msg + o);
  }

  public static Properties initServletEnvironment(VelocityServlet v) {
    // init only once
    if (m_servletConfig != null) {
      return m_servletConfig;
    }

    //debugPrintln("initServletEnvironment....");

    try {
      /*
       * call the overridable method to allow the
       * derived classes a shot at altering the configuration
       * before initializing Runtime
       */
      Properties p = new Properties();

      ServletConfig config = v.getServletConfig();

      // Set the Velocity.FILE_RESOURCE_LOADED_PATH property
      // to the root directory of the context.
      String path = config.getServletContext().getRealPath("/");
      //debugPrintln("real path of / is : " + path);
      p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);

      // Set the Velocity.RUNTIME_LOG property to be the file
      // velocity.log relative to the root directory
      // of the context.
      p.setProperty(Velocity.RUNTIME_LOG, path +
             "velocity.log");
// Return the Properties object.
//return p;

      Velocity.init(p);
      m_servletConfig = p;
      return p;
    }
    catch (Exception e) {
      debugPrintln(e.getMessage());
      //throw new ServletException("Error initializing Velocity: " + e);
    }
    return null;

    //this.getServletContext().getRealPath("/");
  }

  private static void initJavaMail() {
  }

  public static Connection getDatabaseConnection() {
    Connection con = null;
    try {
      InitialContext initCtx = new InitialContext();
      javax.naming.Context context = (javax.naming.Context) initCtx.
        lookup("java:comp/env");
      javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup(
        "jdbc/TestDB");
      //Utilities.debugPrintln("ds = " + ds);
      con = ds.getConnection();
    }
    catch (Exception e) {
      Utilities.debugPrintln("Exception = " + e.getMessage());
      return null;
    }
    //Utilities.debugPrintln("con = " + con);
    return con;
  }

  public static java.sql.ResultSet excuteDbQuery(Connection con, String sql,
    Object[] parameters) {
    //Exception err = null;
    //Utilities.debugPrintln("excuteDbQuery" + parameters[0] + " ,sql=" + sql);

    try {
      java.sql.PreparedStatement ps = con.prepareStatement(sql);
      for (int i = 0; i < parameters.length; i++) {
        processParameter(ps, i + 1, parameters[i]);
      }
      return ps.executeQuery();
    }
    catch (Exception e) {
      //Utilities.debugPrintln(e.getMessage());
      e.printStackTrace();
    }
    return null;
  }

  public static void excuteDbUpdate(String sql, Object[] parameters) {
    Connection con = Utilities.getDatabaseConnection();
    excuteDbUpdate(con, sql, parameters);
    closeDbConnection(con);
  }

  public static void excuteDbUpdate(Connection con, String sql,
                   Object[] parameters) {
    Exception err = null;
    try {
      java.sql.PreparedStatement ps = con.prepareStatement(sql);
      for (int i = 0; i < parameters.length; i++) {
        processParameter(ps, i + 1, parameters[i]);
      }
      ps.execute();
    }
    catch (Exception e) {
      err = e;
      //Utilities.debugPrintln(err.getMessage());
      e.printStackTrace();
    }
  }

  private static void processParameter(java.sql.PreparedStatement ps,
                     int index, Object parameter) {
    try {
      if (parameter instanceof String) {
        ps.setString(index, (String) parameter);
      }
      else {
        ps.setObject(index, parameter);
      }
    }
    catch (Exception e) {
      //Utilities.debugPrintln(e.getMessage());
      e.printStackTrace();
    }
  }

  public static void closeDbConnection(java.sql.Connection con) {
    try {
      con.close();
    }
    catch (Exception e) {
      Utilities.debugPrintln(e.getMessage());
    }
  }

  public static String getResultPage(
    String title, String message, String jumpLink,
    VelocityServlet servlet, HttpServletRequest request,
    HttpServletResponse response, Context context) {

    Template template = null;

    context.put("MessageTitle", title);
    context.put("ResultMessage", message);
    context.put("JumpLink", jumpLink);

    try {
      template = servlet.getTemplate(
        "/templates/Message.htm");
      StringWriter sw = new StringWriter();
      template.merge(context, sw);
      return sw.toString();
    }
    catch (Exception ex) {
      return "error get template Message.htm " + ex.getMessage();
    }

  }

  public static String mergeTemplate(String fileName, VelocityServlet servlet,
                    Context context) {
    Template template = null;

    try {
      template = servlet.getTemplate(fileName);
      StringWriter sw = new StringWriter();
      template.merge(context, sw);
      return sw.toString();
    }
    catch (Exception ex) {
      return "error get template " + fileName + " " + ex.getMessage();
    }
  }

}
 
     

 

注意:基于排版的需要,代码中使用了中文全角空格。如果要复制代码,请在复制后进行文字替换。


说明: 
前一段时间,看到网上一个网友贴出一个用 Java 跨平台的生成 Jpeg 图片缩影图的方法。最近整理了一下,形成了一个通用的类。并制作成一个 jar 包。以方便重用。并给出了使用示例。(作者: abnerchai ;联系: josserchai@yahoo.com ) 

一、源代码 
1、JpegTool.java 
// JpegTool.java 

package com.abner.jpegtool; 

import javax.imageio.ImageIO; 

import javax.imageio.IIOException; 

import java.awt.image.BufferedImage; 

import java.awt.Image; 

import java.io.File; 

import java.awt.image.AffineTransformOp; 

import java.awt.geom.AffineTransform; 

import com.abner.jpegtool.JpegToolException; 

import java.io.*; 

/** 

* 本类实现一个对 JPG/JPEG 图像文件进行缩影处理的方法 

* 即给定一个 JPG 文件,可以生成一个该 JPG 文件的缩影图像文件 (JPG 格式 ) 

* 提供三种生成缩影图像的方法: 

* 1 、设置缩影文件的宽度,根据设置的宽度和源图像文件的大小来确定新缩影文件的长度来生成缩影图像 

* 2 、设置缩影文件的长度,根据设置的长度和源图像文件的大小来确定新缩影文件的宽度来生成缩影图像 

* 3 、设置缩影文件相对于源图像文件的比例大小,根据源图像文件的大小及设置的比例来确定新缩影文件的大小来生成缩影图像 

* 新生成的缩影图像可以比原图像大,这时即是放大源图像。 

* @author abnerchai contact:josserchai@yahoo.com 

* @version 1.0 

* @exception JpegToolException 

*/ 

public class JpegTool  

// 对象是否己经初始化 

private boolean isInitFlag = false; 

// 定义源图片所在的带路径目录的文件名 

private String pic_big_pathfilename = null; 

// 生成小图片的带存放路径目录的文件名 

private String pic_small_pathfilename = null; 

// 定义生成小图片的宽度和高度,给其一个就可以了 

private int smallpicwidth = 0; 

private int smallpicheight = 0; 

// 定义小图片的相比原图片的比例 

private double picscale = 0; 

/** 

* 构造函数 

* @param 没有参数 

*/ 

public JpegTool(){ 

this.isInitFlag = false; 

{} 

/** 

* 私有函数,重置所有的参数 

* @param 没有参数 

* @return 没有返回参数 

*/ 

private void resetJpegToolParams() 

this.picscale = 0; 

this.smallpicwidth = 0; 

this.smallpicheight = 0; 

this.isInitFlag = false; 

{} 

/** 

* @param scale 设置缩影图像相对于源图像的大小比例如 0.5 

* @throws JpegToolException 

*/ 

public void SetScale(double scale) throws JpegToolException  

if(scale<=0){ 

throw new JpegToolException(" 缩放比例不能为 0 和负数! "); 

{} 

this.resetJpegToolParams(); 

this.picscale = scale; 

this.isInitFlag = true; 



/** 

* @param smallpicwidth 设置缩影图像的宽度 

* @throws JpegToolException 

*/ 

public void SetSmallWidth(int smallpicwidth) throws JpegToolException  

if(smallpicwidth<=0){ 

throw new JpegToolException(" 缩影图片的宽度不能为 0 和负数! "); 

{} 

this.resetJpegToolParams(); 

this.smallpicwidth = smallpicwidth; 

this.isInitFlag = true; 



/** 

* @param smallpicheight 设置缩影图像的高度 

* @throws JpegToolException 

*/ 

public void SetSmallHeight(int smallpicheight) throws JpegToolException  

if(smallpicheight<=0){ 

throw new JpegToolException(" 缩影图片的高度不能为 0 和负数! "); 

{} 

this.resetJpegToolParams(); 

this.smallpicheight = smallpicheight; 

this.isInitFlag = true; 



/** 

* 生成源图像的缩影图像 

* @param pic_big_pathfilename 源图像文件名,包含路径(如 windows 下 C:\pic.jpg ; Linux 下 /home/abner/pic/pic.jpg ) 

* @param pic_small_pathfilename 生成的缩影图像文件名,包含路径(如 windows 下 C:\pic_small.jpg ; Linux 下 /home/abner/pic/pic_small.jpg ) 

* @throws JpegToolException 

*/ 

public void doFinal(String pic_big_pathfilename,String pic_small_pathfilename) throws JpegToolException  

if(!this.isInitFlag){ 

throw new JpegToolException(" 对象参数没有初始化! "); 

{} 

if(pic_big_pathfilename==null || pic_small_pathfilename==null) 

throw new JpegToolException(" 包含文件名的路径为空! "); 

{} 

if((!pic_big_pathfilename.toLowerCase().endsWith("jpg")) && (!pic_big_pathfilename.toLowerCase().endsWith("jpeg"))) 

throw new JpegToolException(" 只能处理 JPG/JPEG 文件! "); 

{} 

if((!pic_small_pathfilename.toLowerCase().endsWith("jpg")) && (!pic_small_pathfilename.toLowerCase().endsWith("jpeg"))) 

throw new JpegToolException(" 只能处理 JPG/JPEG 文件! "); 

{} 

int smallw = 0; 

int smallh = 0; 

// 新建源图片和生成的小图片的文件对象 

File fi = new File(pic_big_pathfilename); 

File fo = new File(pic_small_pathfilename); 

// 生成图像变换对象 

AffineTransform transform = new AffineTransform(); 

// 通过缓冲读入源图片文件 

BufferedImage bsrc = null; 

try  

bsrc = ImageIO.read(fi); 

{}catch (IOException ex)  

throw new JpegToolException(" 读取源图像文件出错! "); 

{} 

int srcw = bsrc.getWidth();// 原图像的长度 

int srch = bsrc.getHeight();// 原图像的宽度 

double scale = (double)srcw/srch;// 图像的长宽比例 

if(this.smallpicwidth!=0)// 根据设定的宽度求出长度 

smallw = this.smallpicwidth;// 新生成的缩略图像的长度 

smallh = (smallw*srch)/srcw ;// 新生成的缩略图像的宽度 

{}else if(this.smallpicheight!=0)// 根据设定的长度求出宽度 

smallh = this.smallpicheight;// 新生成的缩略图像的长度 

smallw = (smallh*srcw)/srch;// 新生成的缩略图像的宽度 

{}else if(this.picscale!=0)// 根据设置的缩小比例设置图像的长和宽 

smallw = (int)((float)srcw*this.picscale); 

smallh = (int)((float)srch*this.picscale); 

{}else 

throw new JpegToolException(" 对象参数初始化不正确! "); 

{} 

System.out.println(" 源文件大小: "+srcw+"X"+srch); 

System.out.println(" 新文件大小: "+smallw+"X"+smallh); 

double sx = (double)smallw/srcw;// 小 / 大图像的宽度比例 

double sy = (double)smallh/srch;// 小 / 大图像的高度比例 

transform.setToScale(sx,sy);// 设置图像转换的比例 

// 生成图像转换操作对象 

AffineTransformOp ato = new AffineTransformOp(transform,null); 

// 生成缩小图像的缓冲对象 

BufferedImage bsmall = new BufferedImage(smallw,smallh,BufferedImage.TYPE_3BYTE_BGR); 

// 生成小图像 

ato.filter(bsrc,bsmall); 

// 输出小图像 

try  

ImageIO.write(bsmall, "jpeg", fo); 

{}catch (IOException ex1)  

throw new JpegToolException(" 写入缩略图像文件出错! "); 

{} 





2、JpegToolException.java 
// JpegToolException.java 

package com.abner.jpegtool; 

/** 

* <p>Description: JpegTool 使用的异常类 </p> 

* @author abnerchai 

* @version 1.0 

*/ 

public class JpegToolException extends Exception  

private String errMsg = ""; 

public JpegToolException(String errMsg) { 

this.errMsg = errMsg; 

{} 

public String getMsg() 

return "JpegToolException:"+this.errMsg; 

{} 



二、 jar包制作 
编译上面两个文件,形成两个 class 文件,均存放在目录:当前目录 comabnerjpegtool 目录下面。进入当前目录,执行: jar cvf jpegtool.jar com/* ;便在当前目录生成一个 jpegtool.jar 文件。发布这个 jar 包,以后便可以像使用其它 jar 包一样使用这个工具了,下面我们给出一个示例。 

三、使用示例 
为了方便,我们给出在 JSP 文件中使用该包的示例 JSP 代码,首先,设置你的类库路径,将 jpegtool.jar 文件放入你的类库路径中。然后设置你的应用服务器的类库路径,将 jpegtool.jar 放入你的应用服务器的类库路径中。然后测试下面的 JSP 代码: 

<!—test.jsp – > 

<%@page contentType="text/html; charset=GB2312" %> 

<%@page import = "com.abner.jpegtool.JpegTool"%> 

<html><head><title>test</title></head> 

<body bgcolor="#ffffff"> 

<% 

try 

JpegTool jpegTool = new JpegTool(); 

jpegTool.SetSmallWidth(100); 

jpegTool.doFinal("C:\big.jpeg","C:\small.jpeg"); 

{}catch(Exception e) 

e.printStackTrace(); 

{} 

%> 

</body> 

</html> 

对于在 Servlet 和 Java 程序中使用,类似于上面的 JSP 。 

注意:以上程序必须在支持 JDK1.4 的平台上运行。否则有些类库找不到。 
AbnerChai [原作]   

2005年07月24日

作者:肖菁

文章摘要

JFreeReport用于生成报表。JFreeReport的数据继承自Swing组件的TableModel接口,使用基于XML的报表格式定义文件对报表进行格式化。JFreeReport生成的报表可以分页预览、打印或者保存为多种格式的文件如pdf、Excel、html等。作者在本文中详细的介绍了如何定义报表格式定义文件、如何使用JFreeReport生成/预览报表和将报表转为其它各种格式。

关键词

JFreeReport  报表生成  预览

报表生成一直是企业信息化过程中重要的一环,也是目前比较难于实现的一环,今天作者给大家介绍一种新的报表生成组件――JFreeReport。JFreeReport是JFreeReport.Org基于LGPL授权协议提供的一组java包,用于生成各类报表,JFreeReport的数据继承自Swing组件的TableModel接口,使用基于XML的报表格式定义文件对报表进行格式化。JFreeReport生成的报表可以分页预览、打印,而且支持导出为多种格式的文件如pdf、Excel、CSV、html等。更重要的是,JFreeReport不仅支持基于C/S结构的系统,而且支持基于B/S结构的系统中的在线报表显示。更详细的关于JFreeReport的介绍请大家访问JFreeReport的官方网站JFree.org

1  环境准备

1.1  JFreeReport组件

请大家到http://prdownloads.sourceforge.net/jfreereport/jfreereport-0.8.4_7.zip?download下载JFreeReport组件,下载的是一个ZIP文件,然后将ZIP文件解压缩到c:\jfreereport(后面的章节中将使用%jfreereport_home%表示这个目录)目录下。

1.2  JFreeReport扩展组件

请大家到http://www.jfree.org/jfreereport/jfreereport-ext-0.8.4_7.zip下载JFreeReport扩展组件,他用于支持JFreeReport组件生成的报表的在线显示。请大载后解压缩到c:\jfreereport-ext目录下(后面的章节中将使用%jfreereport_ext_home%表示这个目录)

1.3  Ant工具

Apache公司提供的一个基于JAVA的自动化脚本引擎,请大家到http://ant.apache.org/下载ant的可执行文件,关于如何使用ant请大家查看ant的帮助文档或者http://ant.apache.org/网站上的在线帮助文档。示例中主要是用ant来负责编译java代码。

1.4  作者提供的代码

为了运行本文中作者提到的例子和相关资源文件,请大家下载作者提供的vivianjDemo.zip文件和中文转换工具gb2unicode.jar。然后解压缩到%jfreereport_home%\vivianjDemo(后面的章节中将使用%demo _home%表示这个目录)目录下。

2  JFreeReport生成报表的基本步骤

我们首先演示一个简单的例子,说明使用JFreeReport生成报表的一些必要的步骤。

2.1  实例说明

在这个例子中,我们将循环生成100条数据放入TableModel中,然后使用JFreeReport组件提供的预览功能在屏幕上显示生成的报表。

[注] 为了简化,这里仅仅是逐条显示数据,不作任何修饰和统计工作,所以也不使用报表格式定义文件。

2.2  代码编制

整个演示实例(HelloWorld.java)的代码和相关注释如下,如果你执行了1.3中规定的步骤,你可以在%demo _home%/src/org/vivianj/jfreereport/看到这个文件。

/**
 * HelloWorld.java
 */

package org.vivianj.jfreereport;

import java.awt.Color;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Point2D;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import org.jfree.report.Boot;
import org.jfree.report.ElementAlignment;
import org.jfree.report.JFreeReport;
import org.jfree.report.ReportProcessingException;
import org.jfree.report.elementfactory.TextFieldElementFactory;
import org.jfree.report.modules.gui.base.PreviewDialog;
import org.jfree.ui.FloatDimension;

/**
 * 使用JFreeReport生成报表的简单例子,用于演示使用JFreeReport生成报表的一些基本步骤
 *
 * 本例子中,为了简化操作,报表定义是使用java直接编码
 *
 * @ 作者 : bookman
 */
public class HelloWorld
{
  /**
   * 处理窗口关闭事件
   */
  protected static class CloseHandler extends WindowAdapter
  {
    public void windowClosing(final WindowEvent event)
    {
      System.exit(0);
    }
  }

  /**
   * 创建和显示简单的报表
   */
  public HelloWorld()
  {
	// 获得创建报表需要用到的数据
    final TableModel data = createData();
	//获得报表要用到的报表定义内容
    final JFreeReport report = createReportDefinition();
	//将报表定义和数据结合
    report.setData(data);
    try
    {
	  //将生成的报表放到预览窗口中
      final PreviewDialog preview = new PreviewDialog(report);
      preview.addWindowListener(new CloseHandler());
      preview.pack();
	  //显示报表预览窗口
      preview.setVisible(true);
    }
    catch (ReportProcessingException e)
    {
      System.out.println(e);
    }

  }

  /**
   * 创建生成报表需要用到的数据
   *
   * @返回一个TableModel实例
   */
  private TableModel createData()
  {

    final Object[] columnNames = new String[]{"Column1", "Column2"};
    final DefaultTableModel result = new DefaultTableModel(columnNames, 100);
	int rownum = 0;
	int colnum = 0;
	for (;rownum < 100 ; rownum++)
	{
        result.setValueAt("say Hello " + rownum + "次", rownum, 0);
        result.setValueAt("say World " + rownum + "次" , rownum, 1);
	}

    return result;

  }

  /**
   * 创建一个报表定义
   *
   * @返回一个报表定义实例
   */
  private JFreeReport createReportDefinition()
  {

    final JFreeReport report = new JFreeReport();
    report.setName("A Very Simple Report");
	/**
	  * 定义要显示报表第一列的样式
	 */
    TextFieldElementFactory factory = new TextFieldElementFactory();
    factory.setName("T1");
    factory.setAbsolutePosition(new Point2D.Float(0, 0));
    factory.setMinimumSize(new FloatDimension(150, 20));
    factory.setColor(Color.black);
    factory.setHorizontalAlignment(ElementAlignment.LEFT);
    factory.setVerticalAlignment(ElementAlignment.MIDDLE);
    factory.setNullString("-");
    factory.setFieldname("Column1");
    report.getItemBand().addElement(factory.createElement());
	/**
	  * 定义要显示报表第二列的样式
	 */
    factory = new TextFieldElementFactory();
    factory.setName("T2");
    factory.setAbsolutePosition(new Point2D.Float(200, 0));
    factory.setMinimumSize(new FloatDimension(150, 20));
    factory.setColor(Color.black);
    factory.setHorizontalAlignment(ElementAlignment.LEFT);
    factory.setVerticalAlignment(ElementAlignment.MIDDLE);
    factory.setNullString("-");
    factory.setFieldname("Column2");
    report.getItemBand().addElement(factory.createElement());
	/**
	  * 返回一个报表定义的实例
	 */
    return report;

  }

  public static void main(final String[] args)
  {
    // 初始化JFreeReport
    Boot.start();

    //调用演示实例
    new HelloWorld();
  }

}

2.3  运行例子

如果你执行了1.3中规定的步骤,你可以进入命令行界面,然后进入%demo_home%目录下,修改setenv.cmd中的相关设置,执行serenv.cmd设置环境变量。执行java org.vivianj.jfreereport.HelloWorld查看运行结果。下面这个图片是作者执行后结果的屏幕截图:

大家可以看到,JFreeReport已经自动帮我们实现了分页。上面这个图片显示的是第一页的数据,你可以通过工具栏中的查看其它页面中的内容。

2.4  基本步骤解释

使用JFreeReport生成报表通常需要以下三个基本步骤:

  1. 生成可通过TableModel接口访问的数据,如本例中的createData方法完成的功能
  2. 生成一个JFreeReport实例,他定义了我们如何格式化显示数据,如本例中的createReportDefinition方法完成的功能
  3. 将数据和JFreeReport实例连接起来,并且将该JFreeReport实例传给PreviewDialog的一个实例显示给用户

3  使用JFreeReport生成复杂报表

3.1  报表定义文件

报表定义文件是JFreeReport生成复杂报表的重要文件,他就是一个XML文档,主要描述如何使用指定的格式生成复杂的报表,同时使用报表定义文件也可以在报表格式需要修改时只需要更新该报表定义文件,而不需要修改应用代码。

3.1.1  报表定义文件分类

JFreeReport中使用了两种基于XML的报表定义文件来保存报表定义信息:简单格式和扩展格式.很明显,简单格式不能够完全的描述JFreeReport支持的全部报表定义信息,但是他更易于上手使用。而扩展格式则能够对JFreeReport的报表定义提供完整的支持,但是扩展格式太详细了,不太容易使用。

关于这两种报表定义格式文件所支持的标签内容以及如何编写这两种格式的报表定义文件请大家参考%jfreereport_home%下的jfreereport-0.8.3-A4.pdf中的相关部分,该文件附录中还包括了这两种格式的报表定义文件的DTD文档供大家参考.当然大家也JFreeReport的例子中提供了多种形式的报表定义文件,基本上涵盖了常用的报表格式定义,大家可以参考这些例子编写自己的报表定义文件,。

3.2  代码编制

这个例子和2.2中的代码基本一致,只是报表定义内容不再由java编码实现,而是由报表定义文件提供,所以调用方面稍微有点不同,详细的代码如下,请大家注意其中加粗显示的部分:

/**
 * JFreeReport.java
 */

 package org.vivianj.jfreereport;

import java.io.File;
import java.text.MessageFormat;
import javax.swing.table.TableModel;
import javax.swing.JFrame;

import org.jfree.ui.RefineryUtilities;
import org.jfree.report.Boot;
import org.jfree.report.modules.gui.base.PreviewFrame;
import org.jfree.report.modules.parser.base.ReportGenerator;
import org.jfree.report.JFreeReport;

import org.vivianj.jfreereport.tablemodel.SampleData;

/**
 * 使用JFreeReport生成复杂报表的例子,
 * 用于演示使用JFreeReport生成复杂报表的一些基本步骤
 *
 * 本例子中,报表定义使用了报表定义文件,该文件是保存在c:\下的report3.xml文件
 * 本例中使用的报表定义使用了简单报表定义格式
 *
 * @ 作者 : bookman
 */
public class JFreeTest
{

	public JFreeTest(final String urlname, final TableModel data)
  {
	//创建对报表定义文件的引用
    final File in = new File(urlname);;
    if (in == null)
    {
      System.out.print(" in is null");
      return;
    }

    System.out.print("Processing Report: " + in);
    final ReportGenerator gen = ReportGenerator.getInstance();

    try
    {
      //从报表定义文件中获得要用到的报表定义内容
      final JFreeReport report1 = gen.parseReport(in);
      if (report1 == null)
      {
        System.out.print(" report1 is null");
        return;
      }
      //将报表定义和数据结合
      report1.setData(data);
	  //将生成的报表放到预览窗口中
      final PreviewFrame frame1 = new PreviewFrame(report1);
      frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame1.pack();
      RefineryUtilities.positionFrameRandomly(frame1);
      frame1.setVisible(true);
      frame1.requestFocus();
    }
    catch (Exception e)
    {
      System.out.print("report.definitionfailure-----------------------\r\n" + e);
    }
  }

  public static void main(String[] args)
	{
		Boot.start();

		final TableModel data3 = new SampleData();
		JFreeTest jft = new JFreeTest("c:\\report3.xml",data3);
	}
}

4  中文乱码问题

大家在参看报表定义文件的时候会发现,里面的报表头定义中有一些显示内容,如果你直接将他修改成中文,显示在报表上就会乱码,这是因为这些报表定义文件都是XML文档,他的encoding默认设置是ISO-8859-1,所以出现了中文乱码的问题,解决办法有两个:

1.最简单的方法就是将encoding的设置修改为GB2312

2.还有一个方法就是不修改encoding的设置,而是将这些中文内容使用他们的unicode码来代替。

[注] 作者提供了一个GUI的界面提供这种转化功能,你只需要输入中文就可以获得对应的unicode码,请大家下载作者提供的gb2unicode.jar,设置好java运行环境,然后执行java -jar gb2unicode.jar就可以了。

5  总结

报表问题是企业信息化中比较关注的一个焦点,也比较难于实现客户化,作者在本文中给大家介绍了一个新的报表生成包-JfreeReport,他可以根据XML文档中定义的报表格式,生成符合客户要求的报表。文章中给出了2个简单的例子,演示了如何使用JFreeReport生成报表的详细步骤,希望能够帮助大家熟悉JFreeReport的工作方式。同时文章中给出了使用JFreeReport过程中可能遇到的中文问题的解决办法。

其实JFreeReport还支持在JSP、Servlet中显示报表,只是还需要用到他的扩展组件,作者这里没有给出例子,大家可以参考下载的JFreeReport的扩展组件中的例子。

参考资料

JFreeReport 在线帮助文档 http://www.jfree.org/jfreereport/index.html

JFreeReport 的JavaDoc http://www.jfree.org/jfreereport/javadoc/index.html

关于作者

肖菁目前是湖南省长沙铁道学院科创计算机系统集成有限公司软件中心软件工程师,IBM developerworks/BEA dev2dev撰稿人,主要研究J2EE编程技术、Web Service技术以及他们在websphere、weblogic、apache平台上的实现,拥有IBM 的 Developing With Websphere Studio证书,个人网站:http://vivianj.go.nease.net/

Java Image功能一直都在增强,但是向磁盘写一个图像文件或者返回一个PNG或JPEG依然比较难实现。但是我们可以使用Java Advanced Imaging(JAI)API来解决这个问题。JAI可以从SUN的Java站点下载,它包含在JDK 1.4的javax.imageio包中。

Windows中提供一个installation.exe文件提供JAI的自动安装,而UNIX和Linux中则没有。虽然JAI可以运行在纯Java模式,但是同时也提供有Windows、Linux以及UNIX的本地库,这样可以提高运行速度。

下面的例子是安装在苹果的OS X上的JAI,所以我们选择纯Java模式。要安装JAI,你需要jai tar.gz文件中的一个。将三个重要的jar文件,mlibwrapper_jai.jar, jai_codec.jar, and jai_core.jar放到你的classpath中。我们推荐将他们放到你的JDK的jre/lib/ext目录下。

对于Java来说,JAI是一个奇怪的系统,不像其他的方面有很多方法需要了解,这里只有一个叫做JAI的高级类和一些辅助方法。这些方法的第一个参数是一个操作类型名字,所以我们将会看到这样的代码:

source = JAI.create("fileload", .. );
JAI.create("extrema", src, …);
JAI.create("histogram", src, …);

在这个系统中,你可以很容易的添加你自己的或者第三方的功能,对类型的要求比较松,所以利用它进行开发难度比较大。

将一个AWT图像转化成一个PNG文件需要下面的一小段代码:

import java.awt.Image;
import java.awt.image.renderable.ParameterBlock;

import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
…..
Image img = ….
OutputStream out =….
ParameterBlock pb = new ParameterBlock().add(img);
PlanarImage src = (PlanarImage) JAI.create("awtImage", pb);
JAI.create("encode", src, out, "PNG", null);

从上面的例子可以看出向create方法传递参数有两种方法。新方法是使用ParameterBlock包含所有的参数。我们使用这种方法实现了awtImage操作。老方法是则使用一个重载方法,现在已不提倡使用,但是我们可以使用这种方法实现解码操作。

上面的代码将把一个Image编码成为一个PNG并将其写入OutputStream中。要得到更多有关JAI的信息,我们强烈推荐你阅读指南。
2005年07月22日

  下面代码中用到的sourceImage是一个已经存在的Image对象

图像剪切

  对于一个已经存在的Image对象,要得到它的一个局部图像,可以使用下面的步骤:

//import java.awt.*;
//import java.awt.image.*;

Image croppedImage;
ImageFilter cropFilter;
CropFilter =new CropImageFilter(25,30,75,75); //四个参数分别为图像起点坐标和宽高,即CropImageFilter(int x,int y,int width,int height),详细情况请参考API
CroppedImage= Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(sourceImage.getSource(),cropFilter));


  如果是在Component的子类中使用,可以将上面的Toolkit.getDefaultToolkit().去掉。FilteredImageSource是一个ImageProducer对象。

图像缩放

  对于一个已经存在的Image对象,得到它的一个缩放的Image对象可以使用Image的getScaledInstance方法:

Image scaledImage=sourceImage. getScaledInstance(100,100, Image.SCALE_DEFAULT); //得到一个100X100的图像
Image doubledImage=sourceImage. getScaledInstance(sourceImage.getWidth(this)*2,sourceImage.getHeight(this)*2, Image.SCALE_DEFAULT); //得到一个放大两倍的图像,这个程序一般在一个swing的组件中使用,而类Jcomponent实现了图像观察者接口ImageObserver,所有可以使用this。
//其它情况请参考API



灰度变换

  下面的程序使用三种方法对一个彩色图像进行灰度变换,变换的效果都不一样。一般而言,灰度变换的算法是将象素的三个颜色分量使用R*0.3+G*0.59+B*0.11得到灰度值,然后将之赋值给红绿蓝,这样颜色取得的效果就是灰度的。另一种就是取红绿蓝三色中的最大值作为灰度值。java核心包也有一种算法,但是没有看源代码,不知道具体算法是什么样的,效果和上述不同。

/* GrayFilter.java*/
/*@author:cherami */
/*email:cherami@163.net*/

import java.awt.image.*;

public class GrayFilter extends RGBImageFilter {
int modelStyle;
public GrayFilter() {
modelStyle=GrayModel.CS_MAX;
canFilterIndexColorModel=true;
}
public GrayFilter(int style) {
modelStyle=style;
canFilterIndexColorModel=true;
}
public void setColorModel(ColorModel cm) {
if (modelStyle==GrayModel
else if (modelStyle==GrayModel
}
public int filterRGB(int x,int y,int pixel) {
return pixel;
}
}

/* GrayModel.java*/
/*@author:cherami */
/*email:cherami@163.net*/


import java.awt.image.*;

public class GrayModel extends ColorModel {
public static final int CS_MAX=0;
public static final int CS_FLOAT=1;
ColorModel sourceModel;
int modelStyle;

public GrayModel(ColorModel sourceModel) {
super(sourceModel.getPixelSize());
this.sourceModel=sourceModel;
modelStyle=0;
}

public GrayModel(ColorModel sourceModel,int style) {
super(sourceModel.getPixelSize());
this.sourceModel=sourceModel;
modelStyle=style;
}

public void setGrayStyle(int style) {
modelStyle=style;
}

protected int getGrayLevel(int pixel) {
if (modelStyle==CS_MAX) {
return Math.max(sourceModel.getRed(pixel),Math.max(sourceModel.getGreen(pixel),sourceModel.getBlue(pixel)));
}
else if (modelStyle==CS_FLOAT){
return (int)(sourceModel.getRed(pixel)*0.3+sourceModel.getGreen(pixel)*0.59+sourceModel.getBlue(pixel)*0.11);
}
else {
return 0;
}
}

public int getAlpha(int pixel) {
return sourceModel.getAlpha(pixel);
}

public int getRed(int pixel) {
return getGrayLevel(pixel);
}

public int getGreen(int pixel) {
return getGrayLevel(pixel);
}

public int getBlue(int pixel) {
return getGrayLevel(pixel);
}

public int getRGB(int pixel) {
int gray=getGrayLevel(pixel);
return (getAlpha(pixel)<<24)+(gray<<16)+(gray<<8)+gray;
}
}


  如果你有自己的算法或者想取得特殊的效果,你可以修改类GrayModel的方法getGrayLevel()。

色彩变换

  根据上面的原理,我们也可以实现色彩变换,这样的效果就很多了。下面是一个反转变换的例子:

/* ReverseColorModel.java*/
/*@author:cherami */
/*email:cherami@163.net*/

import java.awt.image.*;

public class ReverseColorModel extends ColorModel {
ColorModel sourceModel;
public ReverseColorModel(ColorModel sourceModel) {
super(sourceModel.getPixelSize());
this.sourceModel=sourceModel;
}

public int getAlpha(int pixel) {
return sourceModel.getAlpha(pixel);
}

public int getRed(int pixel) {
return ~sourceModel.getRed(pixel);
}

public int getGreen(int pixel) {
return ~sourceModel.getGreen(pixel);
}

public int getBlue(int pixel) {
return ~sourceModel.getBlue(pixel);
}

public int getRGB(int pixel) {
return (getAlpha(pixel)<<24)+(getRed(pixel)<<16)+(getGreen(pixel)<<8)+getBlue(pixel);
}
}
/* ReverseColorModel.java*/
/*@author:cherami */
/*email:cherami@163.net*/


import java.awt.image.*;

public class ReverseFilter extends RGBImageFilter {
public ReverseFilter() {
canFilterIndexColorModel=true;
}

public void setColorModel(ColorModel cm) {
substituteColorModel(cm,new ReverseColorModel(cm));
}

public int filterRGB(int x,int y,int pixel) {
return pixel;
}
}


  要想取得自己的效果,需要修改ReverseColorModel.java中的三个方法,getRed、getGreen、getBlue。
  下面是上面的效果的一个总的演示程序。

/*GrayImage.java*/
/*@author:cherami */
/*email:cherami@163.net*/

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.awt.color.*;

public class GrayImage extends JFrame{
Image source,gray,gray3,clip,bigimg;
BufferedImage bimg,gray2;
GrayFilter filter,filter2;
ImageIcon ii;
ImageFilter cropFilter;
int iw,ih;

public GrayImage() {
ii=new ImageIcon(\"images/11.gif\");
source=ii.getImage();
iw=source.getWidth(this);
ih=source.getHeight(this);
filter=new GrayFilter();
filter2=new GrayFilter(GrayModel.CS_FLOAT);
gray=createImage(new FilteredImageSource(source.getSource(),filter));
gray3=createImage(new FilteredImageSource(source.getSource(),filter2));
cropFilter=new CropImageFilter(5,5,iw-5,ih-5);
clip=createImage(new FilteredImageSource(source.getSource(),cropFilter));
bigimg=source.getScaledInstance(iw*2,ih*2,Image.SCALE_DEFAULT);
MediaTracker mt=new MediaTracker(this);
mt.addImage(gray,0);
try {
mt.waitForAll();
} catch (Exception e) {
}
}

public void paint(Graphics g) {
Graphics2D g2=(Graphics2D)g;
bimg=new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB);
Graphics2D srcG = bimg.createGraphics();
RenderingHints rhs = g2.getRenderingHints();
srcG.setRenderingHints(rhs);
srcG.drawImage(source, 0, 0, null);
ColorSpace graySpace=ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorConvertOp op=new ColorConvertOp(graySpace,rhs);
gray2=new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB);
op.filter(bimg,gray2);
g2.drawImage(source,40,40,this);
g2.drawImage(gray,80,40,this);
g2.drawImage(gray2,120,40,this);
g2.drawImage(gray3,160,40,this);
g2.drawImage(clip,40,80,this);
g2.drawImage(bigimg,80,80,this);
}

public void update(Graphics g) {
paint(g);
}

public static void main(String args[]) {
GrayImage m=new GrayImage();
m.setSize(400,400);
m.setVisible(true);
}
}

2005年07月08日

JDK 1.5版本包含了Java语法方面的主要改进。

自从Java 1.0版本首次受到开发人员欢迎以来,Java语言的语法就没有发生过太大的变化。虽然1.1版本增加了内部类和匿名内部类,1.4版本增加了带有新的assert关键字的assertion(断定)功能,但Java语法和关键字仍然保持不变–像编译时常量一样处于静态。它将通过J2SE 1.5(代号Tiger)发生改变。

过去的J2SE版本主要关注新类和性能,而Tiger的目标则是通过使Java编程更易于理解、对开发人员更为友好、更安全来增强Java语言本身,同时最大限度地降低与现有程序的不兼容性。该语言中的变化包括generics(泛化)、autoboxing、一个增强的“for”循环、 typesafe enums(类型安全的枚举类型)、一个静态导入工具(static import facility)和varargs。

通过generics来改进类型检查

generics使你能够指定一个集合中使用的对象的实际类型,而不是像过去那样只是使用Object。generics也被称为“参数化类型”,因为在generics中,一个类的类型接受影响其行为的类型变量。

generics并不是一个新概念。C++中有模板,但是模板非常复杂并且会导致代码膨胀。C++编码人员能够仅使用C++模板,通过一些小的技巧来执行阶乘函数,然后看着编译器生成C++源代码来处理模板调用。Java开发人员已经从C++语言中学到了很多关于generics的知识,并经过了足够长时间的实践,知道如何正确使用它们。Tiger的当前计划是从健壮的Generic Java (GJ)方案演变而来的。GJ方案的口号是“使Java的类型简化、再简化。”

为了了解generics,让我们从一个不使用generics的例子开始。下面这段代码以小写字母打印了一个字符串集合:


//获得一个字符串集合
public void lowerCase(Collection c) {
  Iterator itr = c.iterator();
  while (itr.hasNext()) {
    String s = (String) itr.next();
    System.out.println(s.toLowerCase());
  }
}


这个方法不保证只接收字符串。编程人员负责记住传给这个方法什么类型的变量。Generics通过显式声明类型来解决这个问题。Generics证明并执行了关于集合包含什么东西的规则。如果类型不正确,编译器就会产生一个错误。在下面的改写代码中,注意Collection和Iterator是如何声明它们只接收字符串对象的:


public void lowerCase(
     Collection<String> c)  {
  Iterator<String> itr = c.iterator();
  while (itr.hasNext()) {
    System.out.println(
      itr.next().toLowerCase());
  }
}


现在,该代码包含了更强大的类型,但它仍然包含许多键盘类型。我们将在后面加以介绍。注意,你可以存储类型参数的任何子类型。接下来,我们将使用这个特性draw()一个形状集合。


// 获得孩子集合…
public void drawAll(Collection<Shape> c) {
  Iterator<Shape> itr = c.iterator();
  while (itr.hasNext()) {
    itr.next().draw();
  }
}


尖括号中的值被称为类型变量。参数化类型能够支持任何数量的类型变量。例如,java.util.Map就支持两个类型变量–一个用于键类型,一个用于值类型。下面的例子使用了一个带有指向一列元素对象的字符串查找键的map:


public static void main(String[] args) {
  HashMap<String, List<Element>> map =
    new HashMap<String,
          List<Element>>();
  map.put("root",
    new ArrayList<Element>());
  map.put("servlet",
    new LinkedList<Element>());
}


这个类定义声明了它支持多少个类型变量。类型参数的数量必须精确地与所期望的相匹配。而且,类型变量一定不能是原始类型(primitive types)。


List<String, String> // takes one
List<int>            // 无效的,原始类型

即使在期望使用一个普通类型(raw type)的时候,你也可以使用一个参数化类型。当然,你也可以反过来做,但这么做会收到一条编译时警告:


public static void oldMethod(List list) {
  System.out.println(list.size());
}

public static void main(String[] args) {
  List<String> words =
    new ArrayList<String>();
  oldMethod(words);   // 没问题
}


这就实现了轻松的向后兼容:接受一个原始列表的老方法能够直接接受一个参数化List<String>。接受参数化List<String>的新方法也能够接受一个原始列表,但是因为原始列表不声明或执行相同的类型约束,所以这个动作会触发一个警告。可以保证的是:如果在编译时你没有得到名为unchecked(未检查)的警告,那么在运行时编译器生成的强制类型转换(cast)将不会失败。

有趣的是,参数化类型和普通类型被编译为相同的类型。没有专门的类来指定这一点,使用编译器技巧就可以完成这一切。instanceof检查可以证明这一点。


words instanceof List             // true
words instanceof ArrayList        //true
words instanceof ArrayList<String> // true


这个检查产生了一个问题:“如果它们是相同的类型,这种检查能起多大作用?”这是一条用墨水而不是用血写的约束。这段代码将产生一个编译错误,因为你不能向List<String>中添加新的Point:


List<String> list =
  new ArrayList<String>();
list.add(new Point());  // 编译错误

但是这段代码被编译了!


List<String> list =
  new ArrayList<String>();
((List)list).add(new Point());


它将参数化类型强制转换为一个普通类型,这个普通类型是合法的,避免了类型检查,但正如前面所解释的那样,却产生了一个调用未检查的警告:


warning: unchecked call to add(E) as a member of the raw type
 java.util.List
    ((List)list).add(new Point());
    ^


写一个参数化类型

Tiger提供了一个写参数化类型的新语法。下面显示的Holder类可以存放任意引用类型。这样的类很便于使用,例如,通过引用语义支持CORBA传递,而不需要生成单独的Holder类:


public class Holder<A> {
  private A value;
  Holder(A v) { value = v; }
  A get() { return value; }
  void set(A v) { value = v; }
}


使用一个参数化的Holder类型,你能够安全地得到和设置数据,而不需进行强制类型转换:


public static void main(String[] args) {
  Holder<String> holder =
    new Holder<String>("abc");
  String val = holder.get();  // "abc"
  holder.set("def");
}


“A”类型参数名可以是任何标准的变量名。它通常是一个单一的大写字母。你也可以声明类型参数必须能够扩展另一个类,如下所示:


// 也可以
public class Holder<C extends Child>


关于是否能够声明任何其他的类型参数仍然存在争议。你对generics了解的越深,你需要的特殊规则就越多,但是特殊规则越多,generics就会越复杂。

Tiger中设计用来保存线程局部变量(thread local variable)的核心类java.lang.ThreadLocal,将可能变得与下面这个 Holder类的作用类似:


public class ThreadLocal<T> {
  public T get();
  public void set(T value);
}


我们也将看见java.lang.Comparable的变化,允许类声明与它们相比较的类型:


public interface Comparable<T> {
  int compareTo(T o);
}
public final class String implements Comparable<String> {
  int compareTo(String anotherString);
}


Generics不仅仅用于集合,它们有更为广泛的用途。例如,虽然你不能基于参数化类型(因为它们与普通类型没有什么不同)进行捕捉(catch),但是你可以抛出(throw)一个参数化类型。换句话说,你可以动态地决定throws语句中抛出什么。

下面这段令人思维混乱的代码来自generics规范。该代码通过扩展Exception的类型参数E定义了一个 Action接口。Action类有一个抛出作为E 出现的任何类型的run()方法。然后,AccessController类定义一个接受Action<E>的静态exec()方法,并声明exec()抛出E。声明该方法自身是参数化的需要该方法标记(method signature)中的特殊<E extends Exception>。

现在,事情变得有点棘手了。main()方法调用在Action 实例(作为一个匿名内部类实现)中传递的AccessController.exec()方法。该内部类被参数化,以抛出一个 FileNotFoundException。main()方法有一个捕捉这一异常类型的catch语句。如果没有参数化类型,你将不能确切地知道run()会抛出什么。有了参数化类型,你能够实现一个泛化的Action类,其中run()方法可以任意实现,并可以抛出任意异常(Exception):


interface Action<E extends Exception> {
  void run() throws E;
}

class AccessController {
  public static <E extends Exception>
  void exec(Action<E> action) throws E {
    action.run();
  }
}

public class Main {
  public static void main(String[] args) {
    try {
      AccessController.exec(
      new Action<FileNotFoundException>() {
        public void run()
        throws FileNotFoundException {
          // someFile.delete();
        }
      });
    }
    catch (FileNotFoundException f) { }
  }
}


协变返回类型

下面进行一个随堂测验:下面的代码是否能够成功编译?


class Fruit implements Cloneable {
  Fruit copy() throws
    CloneNotSupportedException {
   return (Fruit)clone();
  }
}
class Apple extends Fruit
           implements Cloneable {
  Apple copy()
    throws CloneNotSupportedException {
   return (Apple)clone();
  }
}


答案:该代码在J2SE 1.4中不能编译,因为改写一个方法必须有相同的方法标记(包括返回类型)作为它改写的方法。然而,generics有一个叫做协变返回类型的特性,使上面的代码能够在Tiger中进行编译。该特性是极为有用的。

例如,在最新的JDOM代码中,有一个新的Child接口。Child有一个detach()方法,返回从其父对象分离的Child对象。在Child接口中,该方法当然返回Child:


public interface Child {
  Child detach();
  // etc
}


当Comment类实现detach()时,它总是返回一个Comment,但如果没有协变返回类型,该方法声明必须返回Child:


public class Comment {
  Child detach() {
    if (parent != null)
      parent.removeContent(this);
    return this;
  }
}


这意味着调用者一定不要将返回的类型再向下返回到一个Comment。协变返回类型允许Comment 中的detach()返回 Comment。只要Comment是Child的子类就行。除了能够返回Document的DocType和能够返回Element的EntityRef,该特性对立刻返回Parent 的Child.getParent()方法也能派上用场。协变返回类型将确定返回类型的责任从类的用户(通过强制类型转换确认)转交给类的创建者,只有创建者知道哪些类型彼此之间是真正多态的。 这使应用编程接口(API)的用户使用起来更容易,但却稍微增加了API设计者的负担。

Autoboxing

Java有一个带有原始类型和对象(引用)类型的分割类型系统。原始类型被认为是更轻便的,因为它们没有对象开销。例如,int[1024]只需要4K存储空间,以及用于数组自身的一个对象。然而,引用类型能够在不允许有原始类型的地方被传递,例如,传递到一个List。这一限制的标准工作场景是在诸如list.add(new Integer(1))的插入操作之前,将原始类型与其相应的引用类型封装(box或wrap)在一起,然后用诸如((Integer)list.get(0)).intValue()的方法取出(unbox)返回值。

新的 autoboxing特性使编译器能够根据需要隐式地从int转换为Integer,从char 转换为Character等等。auto-unboxing进行相反的操作。在下面的例子中,我不使用autoboxing计算一个字符串中的字符频率。我构造了一个应该将字符型映射为整型的Map,但是由于Java的分割类型系统,我不得不手动管理Character和Integer封箱转换(boxing conversions)。


public static void countOld(String s) {
  TreeMap m = new TreeMap();
  char[] chars = s.toCharArray();
  for (int i=0; i < chars.length; i++) {
    Character c = new Character(chars[i]);
    Integer val = (Integer) m.get(c);
    if (val == null)
      val = new Integer(1);
    else
      val = new Integer(val.intValue()+1);
    m.put(c, val);
  }
  System.out.println(m);
}


Autoboxing使我们能够编写如下代码:


public static void countNew(String s) {
  TreeMap<Character, Integer> m =
    new TreeMap<Character, Integer>();
  char[] chars = s.toCharArray();
  for (int i=0; i < chars.length; i++) {
    char c = chars[i];
    m.put(c, m.get(c) + 1);  // unbox
  }
  System.out.println(m);
}


这里,我重写了map,以使用generics,而且我让autoboxing给出了map能够直接存储和检索char 和int值。不幸的是,上面的代码有一个问题。如果m.get(c)返回空值(null),会发生什么情况呢?怎样取出null值?在抢鲜版(early access release)(参见下一步)中,取出一个空的Integer 会返回0。自抢鲜版起,专家组决定取出null值应该抛出一个NullPointerException。因此,put()方法需要被重写,如下所示:


m.put(c, Collections.getWithDefault(
  m, c) + 1);


新的Collections.getWithDefault()方法执行get()函数,在该方法中,如果值为空值,它将返回期望类型的默认值。对于一个int类型来说,则返回0。

虽然autoboxing有助于编写更好的代码,但我的建议是谨慎地使用它。封箱转换仍然会进行并仍然会创建许多包装对象(wrapper-object)实例。当进行计数时,采用将一个int与一个长度为1的的 int数组封装在一起的旧方法更好。然后,你可以将该数组存储在任何需要引用类型的地方,获取intarr[0]的值并使用intarr[0]++递增。你甚至不必再次调用put(),因为会在适当的位置产生增量。使用这一方法和其他一些方法,你能够更有效地进行计数。使用下面的算法,执行100万个字符的对时间会从650毫秒缩短为30毫秒:


 
public static void countFast(String s) {
  int[] counts = new int[256];
  char[] chars = s.toCharArray();
  for (int i=0; i < chars.length; i++) {
    int c = (int) chars[i];
    counts[c]++;  // no object creation
  }
  for (int i = 0; i < 256; i++) {
    if (counts[i] > 0) {
      System.out.println((char)i + ":"
           + counts[i]);
    }
  }
}


在C#中,我们可以看到一个类似但稍微不同的方法。C#有一个统一的类型系统,在这个系统中值类型和引用类型都扩展System.Object。但是,你不能直接看到这一点,因为C#为简单的值类型提供了别名和优化。int是System.Int32的一个别名,short是System.Int16的一个别名,double是System.Double的一个别名。在C#中,你能够调用“int i = 5; i.ToString();”,它是完全合法的。这是因为每个值类型都有一个在它被转换为引用类型时创建的相应隐藏引用类型(在值类型被转换为一个引用类型时创建的)。


int x = 9;
object o = x;  //创建了引用类型
int y = (int) o;


当基于一个不同的类型系统时,最终结果与我们在J2SE 1.5中看到的非常接近。

 

对于循环的增强

还记得前面的这个例子么?


public void drawAll(Collection<Shape> c) {
  Iterator<Shape> itr = c.iterator();
  while (itr.hasNext()) {
    itr.next().draw();
  }
}


你再也不用输入这么多的文字了!这里是Tiger版本中的新格式。


public void drawAll(Collection<Shape> c) {
  for (Shape s : c) {
    s.draw();
  }
}


你可以阅读这样一段代码“foreach Shape s in c”。我们注意到设计者非常聪明地避免添加任何新的关键字。考虑到很多人都用“in”来输入数据流,我们对此应该感到非常高兴。编译器将该新的语法自动扩展到其迭代表中。


for (Iterator<Shape> $i = c.iterator();
     $i.hasNext(); ) {
  Shape s = $i.next();
  s.draw();
}


你可以使用该语法来对普通(raw,非参数化的)类型进行迭代,但是编译器会输出一个警告,告诉你必须的类型转换可能会失败。你可以在任何数组和对象上使用“foreach”来实现新的接口java.lang.Iterable。


public interface Iterable<T> {
  SimpleIterator<T> iterator();
}
public interface SimpleIterator<T> {
  boolean hasNext();
  T next();
}


java.lang中的新的接口避免对java.util的任何语言依赖性。Java语言在java.lang之外必须没有依赖性。要注意通过next()方法来更巧妙地使用协变返回类型。需要说明的一点是,利用该“foreach”语法和SimpleIterator接口,就会丧失调用iterator.remove()的能力。如果你还需要该项能力,则必须你自己迭代该集合。

与C#对比一下,我们会看到相似的语法,但是C#使用“foreach”和“in”关键字,从最初版本开始它们就被作为保留字。


// C#
foreach (Color c in colors) {
  Console.WriteLine(c);
}


C#的“foreach”对任何集合(collection)或者数组以及任何可列举的实现都有效。我们再一次看到了在Java和C#之间的非常相似之处。

(类型安全的枚举类型)typesafe enum

enums 是定义具有某些命名的常量值的类型的一种方式。你在C,C++中已经见过它们,但是显然,它们曾经在Java中不用。现在,经过了八年之后,Java重又采用它们,并且大概比先前的任何语言都使用得更好。让我们首先来看看先前我们是如何解决enum问题的?不知道你有没有编写过如下代码?


class PlayingCard {
  public static final int SUIT_CLUBS
    = 0;
  public static final int SUIT_DIAMONDS
    = 1;
  public static final int SUIT_HEARTS
    = 2;
  public static final int SUIT_SPADES
    = 3;
  // …
}


这段代码很简单也很常见,但是它有问题。首先,它不是类型安全的(typesafe)。可以给一个方法传递文字“5”来获取一个suit,并且将被编译。同时,这些值用这些常量直接被编译成每个类。Java通过这些常量进行这种"内联"(inlining)来达到优化的目的,但其风险在于,如果对这些值重新排序并且只重新编译该类,则其他类将会错误地处理这些suits。 而且,该类型是非常原始的,它不能被扩展或者增强,同时如果你输出这些值中的一个,你只会得到一个含意模糊的整型量,而不是一个好记的有用名字。这种方法非常简单,这也正是我们为什么要这样做的原因,但是它并不是最好的方法。所以,也许需要尝试一下下面的这个方法:


class PlayingCard {
  class Suit {
     public static final Suit CLUBS
       = new Suit();
     public static final Suit DIAMONDS
       = new Suit();
     public static final Suit HEARTS
       = new Suit();
     public static final Suit SPADES
       = new Suit();
     protected Suit() { }
  }
}


它是类型安全的(typesafe)且更加具有可扩展性,并且,属于面向对象设计的类。然而,这样简单的一种方法并不支持序列化,没有合法值的列表,无法将这些值排序,并且,不能作为一个有意义的字符串来打印一个值。你当然可以添加这些特性,Josh Bloch在他的Effective Java一书中(第五章,第21条)为我们准确展示了如何解决这些问题。然而,你最终得到的是几页蹩脚的代码。

Java新的enum特性具有一个简单的单行语法:


class PlayingCard {
  public enum Suit { clubs,
        diamonds, hearts, spades }
}


被称之为enum(枚举)类型的该suit,对应于每个enum常量都有一个成员项。每个enum类型都是一个实际类,它可以自动扩展新类java.lang.Enum。编译器赋予enum类以有意义的String()、 hashCode(), 和equals() 方法, 并且自动提供Serializable(可序列化的)和Comparable(可比较的)能力。令人高兴地是enum类的声明是递归型的:


public class Enum<E extends Enum<E>>
     implements Comparable<E>,
               Serializable {


使用最新的enum类型可以提供很多好处:包括:比整型操作(int operations)更好的性能,编译时更好的类型安全性,不会被编译到客户端并且可以被重新命名和排序的常量,打印的值具有含意清晰的信息,能够在集合(collections)甚至switch中被使用,具有添加域(fields)和方法的能力,以及实现任意接口的能力。

每个enum具有一个字符串名字和一个整型顺序号值:


out.println(Suit.clubs);       // "clubs"
out.println(Suit.clubs.name);  // "clubs"
out.println(Suit.clubs.ordinal);    // 0
out.println(Suit.diamonds.ordinal); // 1
Suit.clubs == Suit.clubs     // true
Suit.clubs == Suit.diamonds  // false
Suit.clubs.compareTo(Suit.diamonds) // -1


enum可以拥有构造器和方法。甚至一个main()方法都是合法的。下面的例子将值赋给罗马数字:


public enum Roman {
  I(1), V(5), X(10), L(50), C(100),
  D(500), M(1000);

  private final int value;

  Roman(int value) { this.value = value; }

  public int value() { return value; }

  public static void main(String[] args) {
    System.out.println(Roman.I);
  }
}


非常奇怪的是,不能将序列数值赋给一个enum,比如说“enum Month{jan=1,feb=2,….}”。 然而,却可以给enum常量添加行为。比如说,在JDOM中,XMLOutputter支持数种空白处理方法。如果JDOM是参照J2SE1.5构建的,那么这些方法就可以用一个enum来定义,并且enum类型本身可以具有这种处理行为。不管这种编码模式是不是会被证明是有用的,我们都会逐渐了解它。肯定这是一个异常有趣的概念。


public abstract enum Whitespace {
  raw {
    String handle(String s) { return s; }
  },
  trim {
    String handle(String s) {
      return s.trim(); }
  },
  trimFullWhite {
    String handle(String s) {
      return s.trim().equals("") ? "":s; }
  };
  abstract String handle(String s);

  public static void main(String[] args) {
    String sample = " Test string ";
    for (Whitespace w : Whitespace.VALUES)
      System.out.println(w + ": ‘"
          + w.handle(sample) + "’");
}}


很少有公开的出版物谈及Java新的enum。enum常量名是不是都应该都大写?对于常量来说,这是一个标准,但是规范指出小写名称“于更好的字符串格式,一般应该避免使用定制的toString方法。”另外,名字和顺序号该是域还是方法?这是封装方法一再引起争论的问题。在向J2SE1.5添加关键字方面,该特性也落了一个不太好的名声。令人伤心的是,它还是一个通常被用作存储Enumerator(计数器)实例的词。如果你已经在你的代码中使用了“enum”,那么在你为J2SE 1.5的应用编译之前,必须修改它。现在,你已得到了充分的警示。

让我们看一下C#,所有的enum都扩展成System.Enum。每个enum都具有可以被赋值的整型(或者字节型或者其他类型)值。enum还拥有静态方法,以便从字符串常量来初始化enum,获取有效值列表,从而,可以看到某个值是不是被支持。通过使用[flags]属性来标记一个enum,你可以确保值支持位屏蔽,并且系统负责打印被屏蔽的值的有用输出:


// C#
[Flags]
public enum Credit : byte {
  Visa = 1, MC = 2, Discover = 4 }

Credit accepted = Credit.Visa | Credit.MC;

c.WriteLine(accepted);         // 3
c.WriteLine(accepted.Format());//"Visa|MC"


静态导入

静态导入使得我们可以将一套静态方法和域放入作用域(scope)。它是关于调用的一种缩写,可以忽略有效的类名。比如说,对Math.abs(x)的调用可以被简单地写成 abs(x)。为了静态地导入所有的静态域和方法,我们可以使用“import static java.lang.Math”,或者指定要导入的具体内容,而使用“import static java.lang.System.out”–在这里没有什么令人激动的新特性,只是缩写而已。它让你可以不用Math而来完成math(数字计算)。


import static java.lang.Math.*;
import static java.lang.System.out;

public class Test {
  public static void main(String[] args) {
    out.println(abs(-1) * PI);
  }
}


注意,“static”关键字的重用是为了避免任何新的关键词。语言越成熟,对于“static”关键词的使用就越多。如果在静态成员之间发生冲突的话,就会出现含混的编译错误,这一点跟类的导入一样。是否将java.lang.Math.* 作为固有的导入引发了一定的争论,不过在获知其将会触发含混的错误之后,这种争论不会再发生了。

Varargs

“varargs”表示“参数的变量”,存在于C语言中,并且支持通用的printf()和scanf()函数。 在Java中,我们通过编写一些接受Object[]、List、Properties(属性)的方法以及可以描述多个值的其它简单数据结构–比如说,Method.invoke(Object obj,Object[] args–来模拟这一特性。)。这要求调用程序将数据封装到这种单一的容器结构中。varargs允许调用程序传递值的任意列表,而编译器会为接收程序将其转化为数组。其语法就是在参数声明中的参数名之后添加“…”,以便使其成为vararg。它必须是最后一个参数–比如说,编写一个sum()函数以便将任意数量的整数相加:


out.println(sum(1, 2, 3));

public static int sum(int args…) {
  int sum = 0;
  for (int x : args) { sum += x; }
  return sum;
}


在抢鲜版本中,vararg符号使用方括号,就像sum(int[] args…)。然而,在之后的讨论中,根据James Gosling的提议,方括号被去掉了。在这里的例子中,我们不使用方括号,但是如果你需要在抢鲜版本中使用这些代码的话,就需要将方括号添加上去。借助autoboxing,可以通过接受一个Object args…,可以接受任何类型的参数,包括原始类型。这与printf()类型的一些方法一样,它们接受任何数量的所有类型的参数。实际上,该Tiger版本可以使用这一特性通过format方法(其行为与printf()一样)来提供一个Formattable(可格式化)的接口。这是我们在以后的文章中将要讨论的话题。目前,我们只编写简单的printf():


public static void printf(String fmt,
                        int args…) {
  int i = 0;
  for (char c : fmt.toCharArray()) {
    out.print(c == ‘%’ ? args[i++] : c);
  }
}

public static void main(String[] args) {
  printf("My values are % and %
",
         1, 2);
}

 
在Tiger版本中,你会发现采用一个新格式的invoke()函数: invoke(Object obj, Object args…). 这看上去更加自然。


结论

J2SE1.5版努力使Java的编程更加简便、安全和更加富有表现力。这些特性和谐完美的被结合在一起。如果你跟我一样,总是喜欢用老的“for”循环,你肯定希望你拥有Tiger。

然而,需要记住的是,该规范并没有最终完成,很多地方还需要修改。管理这些变化的专家小组(JSR-14,JSR-175以及JSR-201)会在2003年年末的beta版本发布之前,以及预期在2004年发布最终版发布之前,会做出很多修改。然而,Sun表达了对JavaOne的信心,认为总体上主要原则不会改变太多。如果你想体验一下,那么你可以从下面的站点获取抢鲜版本。 从中你会找到可能从任何一个预览版软件都会遇到的错误,但是也会看到很多激动人心的新特性。我强烈建议你尝试一下。
 

2005年07月03日
全面挖掘Java Excel API 使用方法
作者佚名 来源InterNet 加入时间:2005-1-28
使用Windows操作系统的朋友对Excel(电子表格)一定不会陌生,但是要使用Java语言来操纵Excel文件并不是一件容易的事。在Web应用日益盛行的今天,通过Web来操作Excel文件的需求越来越强烈,目前较为流行的操作是在JSP或Servlet 中创建一个CSV (comma separated values)文件,并将这个文件以MIME,text/csv类型返回给浏览器,接着浏览器调用Excel并且显示CSV文件。这样只是说可以访问到Excel文件,但是还不能真正的操纵Excel文件,本文将给大家一个惊喜,向大家介绍一个开放源码项目,Java Excel API,使用它大家就可以方便地操纵Excel文件了。

  Java Excel API简介

  Java Excel是一开放源码项目,通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。使用该API非Windows操作系统也可以通过纯Java应用来处理Excel数据表。因为是使用Java编写的,所以我们在Web应用中可以通过JSP、Servlet来调用API实现对Excel数据表的访问。

  现在发布的稳定版本是V2.0,提供以下功能:

   从Excel 95、97、2000等格式的文件中读取数据;
   读取Excel公式(可以读取Excel 97以后的公式);
   生成Excel数据表(格式为Excel 97);
   支持字体、数字、日期的格式化;
   支持单元格的阴影操作,以及颜色操作;
   修改已经存在的数据表;

  现在还不支持以下功能,但不久就会提供了:

   不能够读取图表信息;
   可以读,但是不能生成公式,任何类型公式最后的计算值都可以读出;
 
   应用示例

  1、从Excel文件读取数据表

  Java Excel API既可以从本地文件系统的一个文件(.xls),也可以从输入流中读取Excel数据表。读取Excel数据表的第一步是创建Workbook(术语:工作薄),下面的代码片段举例说明了应该如何操作:(完整代码见ExcelReading.java)


import java.io.*;
import jxl.*;
… … … …
try
{
//构建Workbook对象, 只读Workbook对象
//直接从本地文件创建Workbook
//从输入流创建Workbook
InputStream is = new FileInputStream(sourcefile);
jxl.Workbook rwb = Workbook.getWorkbook(is);
}
catch (Exception e)
{
e.printStackTrace();
}


  一旦创建了Workbook,我们就可以通过它来访问Excel Sheet(术语:工作表)。参考下面的代码片段:


//获取第一张Sheet表
Sheet rs = rwb.getSheet(0);


  我们既可能通过Sheet的名称来访问它,也可以通过下标来访问它。如果通过下标来访问的话,要注意的一点是下标从0开始,就像数组一样。

  一旦得到了Sheet,我们就可以通过它来访问Excel Cell(术语:单元格)。参考下面的代码片段:


//获取第一行,第一列的值
Cell c00 = rs.getCell(0, 0);
String strc00 = c00.getContents();

//获取第一行,第二列的值
Cell c10 = rs.getCell(1, 0);
String strc10 = c10.getContents();

//获取第二行,第二列的值
Cell c11 = rs.getCell(1, 1);
String strc11 = c11.getContents();

System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());


  如果仅仅是取得Cell的值,我们可以方便地通过getContents()方法,它可以将任何类型的Cell值都作为一个字符串返回。示例代码中Cell(0, 0)是文本型,Cell(1, 0)是数字型,Cell(1,1)是日期型,通过getContents(),三种类型的返回值都是字符型。

  如果有需要知道Cell内容的确切类型,API也提供了一系列的方法。参考下面的代码片段:


String strc00 = null;
double strc10 = 0.00;
Date strc11 = null;

Cell c00 = rs.getCell(0, 0);
Cell c10 = rs.getCell(1, 0);
Cell c11 = rs.getCell(1, 1);

if(c00.getType() == CellType.LABEL)
{
LabelCell labelc00 = (LabelCell)c00;
strc00 = labelc00.getString();
}
if(c10.getType() == CellType.NUMBER)
{
NmberCell numc10 = (NumberCell)c10;
strc10 = numc10.getvalue();
}
if(c11.getType() == CellType.DATE)
{
DateCell datec11 = (DateCell)c11;
strc11 = datec11.getDate();
}

System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());


  在得到Cell对象后,通过getType()方法可以获得该单元格的类型,然后与API提供的基本类型相匹配,强制转换成相应的类型,最后调用相应的取值方法getXXX(),就可以得到确定类型的值。API提供了以下基本类型,与Excel的数据格式相对应,如下图所示:

  每种类型的具体意义,请参见Java Excel API document.

  当你完成对Excel电子表格数据的处理后,一定要使用close()方法来关闭先前创建的对象,以释放读取数据表的过程中所占用的内存空间,在读取大量数据时显得尤为重要。参考如下代码片段:


//操作完成时,关闭对象,释放占用的内存空间
rwb.close();


  Java Excel API提供了许多访问Excel数据表的方法,在这里我只简要地介绍几个常用的方法,其它的方法请参考附录中的Java Excel API document.
Workbook类提供的方法

  1. int getNumberOfSheets()

  获得工作薄(Workbook)中工作表(Sheet)的个数,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
int sheets = rwb.getNumberOfSheets();


  2. Sheet[] getSheets()

  返回工作薄(Workbook)中工作表(Sheet)对象数组,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
Sheet[] sheets = rwb.getSheets();


  3. String getVersion()

  返回正在使用的API的版本号,好像是没什么太大的作用。


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
String apiVersion = rwb.getVersion();


  Sheet接口提供的方法

  1) String getName()

  获取Sheet的名称,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
String sheetName = rs.getName();


  2) int getColumns()

  获取Sheet表中所包含的总列数,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsColumns = rs.getColumns();


  3) Cell[] getColumn(int column)

  获取某一列的所有单元格,返回的是单元格对象数组,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getColumn(0);


  4) int getRows()

  获取Sheet表中所包含的总行数,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsRows = rs.getRows();


  5) Cell[] getRow(int row)

  获取某一行的所有单元格,返回的是单元格对象数组,示例子:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getRow(0);


  6) Cell getCell(int column, int row)

  获取指定单元格的对象引用,需要注意的是它的两个参数,第一个是列数,第二个是行数,这与通常的行、列组合有些不同。


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell cell = rs.getCell(0, 0);


  2、生成新的Excel工作薄

  下面的代码主要是向大家介绍如何生成简单的Excel工作表,在这里单元格的内容是不带任何修饰的(如:字体,颜色等等),所有的内容都作为字符串写入。(完整代码见ExcelWriting.java)

  与读取Excel工作表相似,首先要使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象,这里要注意的是,只能通过API提供的工厂方法来创建Workbook,而不能使用WritableWorkbook的构造函数,因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下:


import java.io.*;
import jxl.*;
import jxl.write.*;
… … … …
try
{
//构建Workbook对象, 只读Workbook对象
//Method 1:创建可写入的Excel工作薄
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile));

//Method 2:将WritableWorkbook直接写入到输出流
/*
OutputStream os = new FileOutputStream(targetfile);
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os);
*/
}
catch (Exception e)
{
e.printStackTrace();
}


  API提供了两种方式来处理可写入的输出流,一种是直接生成本地文件,如果文件名不带全路径的话,缺省的文件会定位在当前目录,如果文件名带有全路径的话,则生成的Excel文件则会定位在相应的目录;另外一种是将Excel对象直接写入到输出流,例如:用户通过浏览器来访问Web服务器,如果HTTP头设置正确的话,浏览器自动调用客户端的Excel应用程序,来显示动态生成的Excel电子表格。

  接下来就是要创建工作表,创建工作表的方法与创建工作薄的方法几乎一样,同样是通过工厂模式方法获得相应的对象,该方法需要两个参数,一个是工作表的名称,另一个是工作表在工作薄中的位置,参考下面的代码片段:


//创建Excel工作表
jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);

"这锅也支好了,材料也准备齐全了,可以开始下锅了!",现在要做的只是实例化API所提供的Excel基本数据类型,并将它们添加到工作表中就可以了,参考下面的代码片段:
//1.添加Label对象
jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell");
ws.addCell(labelC);

//添加带有字型Formatting的对象
jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF);
ws.addCell(labelCF);

//添加带有字体颜色Formatting的对象
jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,
Underlinestyle.NO_UNDERLINE, jxl.format.Colour.RED);
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
jxl.write.Label labelCFC = new jxl.write.Label(1, 0, "This is a Label Cell", wcfFC);
ws.addCell(labelCF);

//2.添加Number对象
jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
ws.addCell(labelN);

//添加带有formatting的Number对象
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
ws.addCell(labelNF);

//3.添加Boolean对象
jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
ws.addCell(labelB);

//4.添加DateTime对象
jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
ws.addCell(labelDT);

//添加带有formatting的DateFormat对象
jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
ws.addCell(labelDTF);


  这里有两点大家要引起大家的注意。第一点,在构造单元格时,单元格在工作表中的位置就已经确定了。一旦创建后,单元格的位置是不能够变更的,尽管单元格的内容是可以改变的。第二点,单元格的定位是按照下面这样的规律(column, row),而且下标都是从0开始,例如,A1被存储在(0, 0),B1被存储在(1, 0)。

  最后,不要忘记关闭打开的Excel工作薄对象,以释放占用的内存,参见下面的代码片段:


//写入Exel工作表
wwb.write();

//关闭Excel工作薄对象
wwb.close();


  这可能与读取Excel文件的操作有少少不同,在关闭Excel对象之前,你必须要先调用write()方法,因为先前的操作都是存储在缓存中的,所以要通过该方法将操作的内容保存在文件中。如果你先关闭了Excel对象,那么只能得到一张空的工作薄了。

  3、拷贝、更新Excel工作薄

  接下来简要介绍一下如何更新一个已经存在的工作薄,主要是下面二步操作,第一步是构造只读的Excel工作薄,第二步是利用已经创建的Excel工作薄创建新的可写入的Excel工作薄,参考下面的代码片段:(完整代码见ExcelModifying.java)


//创建只读的Excel工作薄的对象
jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile));

//创建可写入的Excel工作薄对象
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile), rw);

//读取第一张工作表
jxl.write.WritableSheet ws = wwb.getSheet(0);

//获得第一个单元格对象
jxl.write.WritableCell wc = ws.getWritableCell(0, 0);

//判断单元格的类型, 做出相应的转化
if(wc.getType() == CellType.LABEL)
{
Label l = (Label)wc;
l.setString("The value has been modified.");
}

//写入Excel对象
wwb.write();

//关闭可写入的Excel对象
wwb.close();

//关闭只读的Excel对象
rw.close();


  之所以使用这种方式构建Excel对象,完全是因为效率的原因,因为上面的示例才是API的主要应用。为了提高性能,在读取工作表时,与数据相关的一些输出信息,所有的格式信息,如:字体、颜色等等,是不被处理的,因为我们的目的是获得行数据的值,既使没有了修饰,也不会对行数据的值产生什么影响。唯一的不利之处就是,在内存中会同时保存两个同样的工作表,这样当工作表体积比较大时,会占用相当大的内存,但现在好像内存的大小并不是什么关键因素了。

  一旦获得了可写入的工作表对象,我们就可以对单元格对象进行更新的操作了,在这里我们不必调用API提供的add()方法,因为单元格已经于工作表当中,所以我们只需要调用相应的setXXX()方法,就可以完成更新的操作了。

  尽单元格原有的格式化修饰是不能去掉的,我们还是可以将新的单元格修饰加上去,以使单元格的内容以不同的形式表现。

  新生成的工作表对象是可写入的,我们除了更新原有的单元格外,还可以添加新的单元格到工作表中,这与示例2的操作是完全一样的。

  最后,不要忘记调用write()方法,将更新的内容写入到文件中,然后关闭工作薄对象,这里有两个工作薄对象要关闭,一个是只读的,另外一个是可写入的。

Java中ThreadLocal的设计与使用
作者佚名 来源InterNet 加入时间:2005-1-28
早在Java 1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择。使用这个工具类可以很简洁地编写出优美的多线程程序,虽然ThreadLocal非常有用,但是似乎现在了解它、使用它的朋友还不多。

   ThreadLocal是什么

   ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。线程局部变量并不是Java的新发明,在其它的一些语言编译器实现(如IBM XL FORTRAN)中,它在语言的层次提供了直接的支持。因为Java中没有提供在语言层次的直接支持,而是提供了一个ThreadLocal的类来提供支持,所以,在Java中编写线程局部变量的代码相对比较笨拙,这也许是线程局部变量没有在Java中得到很好的普及的一个原因吧。

   ThreadLocal的设计

   首先看看ThreadLocal的接口:

    Object get() ; // 返回当前线程的线程局部变量副本 protected Object initialValue(); // 返回该线程局部变量的当前线程的初始值
    void set(Object value); // 设置当前线程的线程局部变量副本的值

   ThreadLocal有3个方法,其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行,并且仅执行1次。ThreadLocal中的确实实现直接返回一个null:


protected Object initialValue() { return null; }


  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现:


public class ThreadLocal
{
  private Map values = Collections.synchronizedMap(new HashMap());
  public Object get()
  {
   Thread curThread = Thread.currentThread();
   Object o = values.get(curThread);
   if (o == null && !values.containsKey(curThread))
   {
    o = initialValue();
    values.put(curThread, o);
   }
   return o;
  }

  public void set(Object newValue)
  {
   values.put(Thread.currentThread(), newValue);
  }

  public Object initialValue()
  {
   return null;
  }
}


  当然,这并不是一个工业强度的实现,但JDK中的ThreadLocal的实现总体思路也类似于此。

   ThreadLocal的使用

   如果希望线程局部变量初始化其它值,那么需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部匿名类对ThreadLocal进行子类化,比如下面的例子,SerialNum类为每一个类分配一个序号:


public class SerialNum
{
  // The next serial number to be assigned

  private static int nextSerialNum = 0;
  private static ThreadLocal serialNum = new ThreadLocal()
  {
   protected synchronized Object initialValue()
   {
    return new Integer(nextSerialNum++);
   }
  };

  public static int get()
  {
   return ((Integer) (serialNum.get())).intValue();
  }
}


  SerialNum类的使用将非常地简单,因为get()方法是static的,所以在需要获取当前线程的序号时,简单地调用:


int serial = SerialNum.get();


  即可。

   在线程是活动的并且ThreadLocal对象是可访问的时,该线程就持有一个到该线程局部变量副本的隐含引用,当该线程运行结束后,该线程拥有的所以线程局部变量的副本都将失效,并等待垃圾收集器收集。

   ThreadLocal与其它同步机制的比较

   ThreadLocal和其它同步机制相比有什么优势呢?ThreadLocal和其它所有的同步机制都是为了解决多线程中的对同一变量的访问冲突,在普通的同步机制中,是通过对象加锁来实现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的,使用这种同步机制需要很细致地分析在什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放该对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。ThreadLocal就从另一个角度来解决多线程的并发访问,ThreadLocal会为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的整个变量封装进ThreadLocal,或者把该对象的特定于线程的状态封装进ThreadLocal。

   由于ThreadLocal中可以持有任何类型的对象,所以使用ThreadLocal get当前线程的值是需要进行强制类型转换。但随着新的Java版本(1.5)将模版的引入,新的支持模版参数的ThreadLocal<T>类将从中受益。也可以减少强制类型转换,并将一些错误检查提前到了编译期,将一定程度地简化ThreadLocal的使用。

   总结

   当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。

2005年06月28日

Unicode:

unicode.org制定的编码机制, 要将全世界常用文字都函括进去.
在1.0中是16位编码, 由U+0000到U+FFFF. 每个2byte码对应一个字符; 在2.0开始抛弃了16位限制, 原来的16位作为基本位平面, 另外增加了16个位平面, 相当于20位编码, 编码范围0到0×10FFFF.

UCS:

ISO制定的ISO10646标准所定义的 Universal Character Set, 采用4byte编码.

Unicode与UCS的关系:

ISO与unicode.org是两个不同的组织, 因此最初制定了不同的标准; 但自从unicode2.0开始, unicode采用了与ISO 10646-1相同的字库和字码, ISO也承诺ISO10646将不会给超出0×10FFFF的UCS-4编码赋值, 使得两者保持一致.

UCS的编码方式:



UCS-2, 与unicode的2byte编码基本一样.

UCS-4, 4byte编码, 目前是在UCS-2前加上2个全零的byte.

UTF: Unicode/UCS Transformation Format

UTF-8, 8bit编码, ASCII不作变换, 其他字符做变长编码, 每个字符1-3 byte. 通常作为外码. 有以下优点:
* 与CPU字节顺序无关, 可以在不同平台之间交流
* 容错能力高, 任何一个字节损坏后, 最多只会导致一个编码码位损失, 不会链锁错误(如GB码错一个字节就会整行乱码)

UTF-16, 16bit编码, 是变长码, 大致相当于20位编码, 值在0到0×10FFFF之间, 基本上就是unicode编码的实现. 它是变长码, 与CPU字序有关, 但因为最省空间, 常作为网络传输的外码.
UTF-16是unicode的preferred encoding.

UTF-32, 仅使用了unicode范围(0到0×10FFFF)的32位编码, 相当于UCS-4的子集.

UTF与unicode的关系:

Unicode是一个字符集, 可以看作为内码.
而UTF是一种编码方式, 它的出现是因为unicode不适宜在某些场合直接传输和处理. UTF-16直接就是unicode编码, 没有变换, 但它包含了0×00在编码内, 头256字节码的第一个byte都是0×00, 在操作系统(C语言)中有特殊意义, 会引起问题. 采用UTF-8编码对unicode的直接编码作些变换可以避免这问题, 并带来一些优点.

中国国标编码:

GB 13000: 完全等同于ISO 10646-1/Unicode 2.1, 今后也将随ISO 10646/Unicode的标准更改而同步更改.

GBK: 对GB2312的扩充, 以容纳GB2312字符集范围以外的Unicode 2.1的统一汉字部分, 并且增加了部分unicode中没有的字符.

GB 18030-2000: 基于GB 13000, 作为Unicode 3.0的GBK扩展版本, 覆盖了所有unicode编码, 地位等同于UTF-8, UTF-16, 是一种unicode编码形式. 变长编码, 用单字节/双字节/4字节对字符编码. GB18030向下兼容GB2312/GBK.
GB 18030是中国所有非手持/嵌入式计算机系统的强制实施标准.

——————————-





什么是 UCS 和 ISO 10646?
国际标准 ISO 10646 定义了 通用字符集 (Universal Character Set, UCS). UCS 是所有其他字符集标准的一个超集. 它保证与其他字符集是双向兼容的. 就是说, 如果你将任何文本字符串翻译到 UCS格式, 然后再翻译回原编码, 你不会丢失任何信息.

UCS 包含了用于表达所有已知语言的字符. 不仅包括拉丁语,希腊语, 斯拉夫语,希伯来语,阿拉伯语,亚美尼亚语和乔治亚语的描述, 还包括中文, 日文和韩文这样的象形文字, 以及 平假名, 片假名, 孟加拉语, 旁遮普语果鲁穆奇字符(Gurmukhi), 泰米尔语, 印.埃纳德语(Kannada), Malayalam, 泰国语, 老挝语, 汉语拼音(Bopomofo), Hangul, Devangari, Gujarati, Oriya, Telugu 以及其他数也数不清的语. 对于还没有加入的语言, 由于正在研究怎样在计算机中最好地编码它们, 因而最终它们都将被加入. 这些语言包括 Tibetian, 高棉语, Runic(古代北欧文字), 埃塞俄比亚语, 其他象形文字, 以及各种各样的印-欧语系的语言, 还包括挑选出来的艺术语言比如 Tengwar, Cirth 和 克林贡语(Klingon). UCS 还包括大量的图形的, 印刷用的, 数学用的和科学用的符号, 包括所有由 TeX, Postscript, MS-DOS,MS-Windows, Macintosh, OCR 字体, 以及许多其他字处理和出版系统提供的字符.

ISO 10646 定义了一个 31 位的字符集. 然而, 在这巨大的编码空间中, 迄今为止只分配了前 65534 个码位 (0×0000 到 0xFFFD). 这个 UCS 的 16位子集称为 基本多语言面 (Basic Multilingual Plane, BMP). 将被编码在 16 位 BMP 以外的字符都属于非常特殊的字符(比如象形文字), 且只有专家在历史和科学领域里才会用到它们. 按当前的计划, 将来也许再也不会有字符被分配到从 0×000000 到 0×10FFFF 这个覆盖了超过 100 万个潜在的未来字符的 21 位的编码空间以外去了. ISO 10646-1 标准第一次发表于 1993 年, 定义了字符集与 BMP 中内容的架构. 定义 BMP 以外的字符编码的第二部分 ISO 10646-2 正在准备中, 但也许要过好几年才能完成. 新的字符仍源源不断地加入到 BMP 中, 但已经存在的字符是稳定的且不会再改变了.

UCS 不仅给每个字符分配一个代码, 而且赋予了一个正式的名字. 表示一个 UCS 或 Unicode 值的十六进制数, 通常在前面加上 "U+", 就象 U+0041 代表字符"拉丁大写字母A". UCS 字符 U+0000 到 U+007F 与 US-ASCII(ISO 646) 是一致的, U+0000 到 U+00FF 与 ISO 8859-1(Latin-1) 也是一致的. 从 U+E000 到 U+F8FF, 已经 BMP 以外的大范围的编码是为私用保留的.

什么是组合字符?
UCS里有些编码点分配给了 组合字符.它们类似于打字机上的无间隔重音键. 单个的组合字符不是一个完整的字符. 它是一个类似于重音符或其他指示标记, 加在前一个字符后面. 因而, 重音符可以加在任何字符后面. 那些最重要的被加重的字符, 就象普通语言的正字法(orthographies of common languages)里用到的那种, 在 UCS 里都有自己的位置, 以确保同老的字符集的向后兼容性. 既有自己的编码位置, 又可以表示为一个普通字符跟随一个组合字符的被加重字符, 被称为 预作字符(precomposed characters). UCS 里的预作字符是为了同没有预作字符的旧编码, 比如 ISO 8859, 保持向后兼容性而设的. 组合字符机制允许在任何字符后加上重音符或其他指示标记, 这在科学符号中特别有用, 比如数学方程式和国际音标字母, 可能会需要在一个基本字符后组合上一个或多个指示标记.

组合字符跟随着被修饰的字符. 比如, 德语中的元音变音字符 ("拉丁大写字母A 加上分音符"), 既可以表示为 UCS 码 U+00C4 的预作字符, 也可以表示成一个普通 "拉丁大写字母A" 跟着一个"组合分音符":U+0041 U+0308 这样的组合. 当需要堆叠多个重音符, 或在一个基本字符的上面和下面都要加上组合标记时, 可以使用多个组合字符. 比如在泰国文中, 一个基本字符最多可加上两个组合字符.

什么是 UCS 实现级别?
不是所有的系统都需要支持象组合字符这样的 UCS 里所有的先进机制. 因此 ISO 10646 指定了下列三种实现级别:

级别1
不支持组合字符和 Hangul Jamo 字符 (一种特别的, 更加复杂的韩国文的编码, 使用两个或三个子字符来编码一个韩文音节)
级别2
类似于级别1, 但在某些文字中, 允许一列固定的组合字符 (例如, 希伯来文, 阿拉伯文, Devangari, 孟加拉语, 果鲁穆奇语, Gujarati, Oriya, 泰米尔语, Telugo, 印.埃纳德语, Malayalam, 泰国语和老挝语). 如果没有这最起码的几个组合字符, UCS 就不能完整地表达这些语言.
级别3
支持所有的 UCS 字符, 例如数学家可以在任意一个字符上加上一个 tilde(颚化符号,西班牙语字母上面的~)或一个箭头(或两者都加).
什么是 Unicode?
历史上, 有两个独立的, 创立单一字符集的尝试. 一个是国际标准化组织(ISO)的 ISO 10646 项目, 另一个是由(一开始大多是美国的)多语言软件制造商组成的协会组织的 Unicode 项目. 幸运的是, 1991年前后, 两个项目的参与者都认识到, 世界不需要两个不同的单一字符集. 它们合并双方的工作成果, 并为创立一个单一编码表而协同工作. 两个项目仍都存在并独立地公布各自的标准, 但 Unicode 协会和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 标准的码表兼容, 并紧密地共同调整任何未来的扩展.

那么 Unicode 和 ISO 10646 不同在什么地方?
Unicode 协会公布的 Unicode 标准 严密地包含了 ISO 10646-1 实现级别3的基本多语言面. 在两个标准里所有的字符都在相同的位置并且有相同的名字.

Unicode 标准额外定义了许多与字符有关的语义符号学, 一般而言是对于实现高质量的印刷出版系统的更好的参考. Unicode 详细说明了绘制某些语言(比如阿拉伯语)表达形式的算法, 处理双向文字(比如拉丁与希伯来文混合文字)的算法和 排序与字符串比较 所需的算法, 以及其他许多东西.

另一方面, ISO 10646 标准, 就象广为人知的 ISO 8859 标准一样, 只不过是一个简单的字符集表. 它指定了一些与标准有关的术语, 定义了一些编码的别名, 并包括了规范说明, 指定了怎样使用 UCS 连接其他 ISO 标准的实现, 比如 ISO 6429 和 ISO 2022. 还有一些与 ISO 紧密相关的, 比如 ISO 14651 是关于 UCS 字符串排序的.

考虑到 Unicode 标准有一个易记的名字, 且在任何好的书店里的 Addison-Wesley 里有, 只花费 ISO 版本的一小部分, 且包括更多的辅助信息, 因而它成为使用广泛得多的参考也就不足为奇了. 然而, 一般认为, 用于打印 ISO 10646-1 标准的字体在某些方面的质量要高于用于打印 Unicode 2.0的. 专业字体设计者总是被建议说要两个标准都实现, 但一些提供的样例字形有显著的区别. ISO 10646-1 标准同样使用四种不同的风格变体来显示表意文字如中文, 日文和韩文 (CJK), 而 Unicode 2.0 的表里只有中文的变体. 这导致了普遍的认为 Unicode 对日本用户来说是不可接收的传说, 尽管是错误的.

什么是 UTF-8?
首先 UCS 和 Unicode 只是分配整数给字符的编码表. 现在存在好几种将一串字符表示为一串字节的方法. 最显而易见的两种方法是将 Unicode 文本存储为 2 个 或 4 个字节序列的串. 这两种方法的正式名称分别为 UCS-2 和 UCS-4. 除非另外指定, 否则大多数的字节都是这样的(Bigendian convention). 将一个 ASCII 或 Latin-1 的文件转换成 UCS-2 只需简单地在每个 ASCII 字节前插入 0×00. 如果要转换成 UCS-4, 则必须在每个 ASCII 字节前插入三个 0×00.

在 Unix 下使用 UCS-2 (或 UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 ‘\0′ 或 ‘/’, 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码.

在 ISO 10646-1 Annex R 和 RFC 2279 里定义的 UTF-8 编码没有这些问题. 它是在 Unix 风格的操作系统下使用 Unicode 的明显的方法.

UTF-8 有一下特性:

UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0×00 到 0×7F (ASCII 兼容). 这意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的.
所有 >U+007F 的 UCS 字符被编码为一个多个字节的串, 每个字节都有标记位集. 因此, ASCII 字节 (0×00-0×7F) 不可能作为任何其他字符的一部分.
表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里, 并指出这个字符包含多少个字节. 多字节串的其余字节都在 0×80 到 0xBF 范围里. 这使得重新同步非常容易, 并使编码无国界, 且很少受丢失字节的影响.
可以编入所有可能的 231个 UCS 代码
UTF-8 编码字符理论上可以最多到 6 个字节长, 然而 16 位 BMP 字符最多只用到 3 字节长.
Bigendian UCS-4 字节串的排列顺序是预定的.
字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到.
下列字节串用来表示一个字符. 用到哪个串取决于该字符在 Unicode 中的序号.

U-00000000 – U-0000007F: 0xxxxxxx
U-00000080 – U-000007FF: 110xxxxx 10xxxxxx
U-00000800 – U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 – U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 – U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 – U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

xxx 的位置由字符编码数的二进制表示的位填入. 越靠右的 x 具有越少的特殊意义. 只用最短的那个足够表达一个字符编码数的多字节串. 注意在多字节串中, 第一个字节的开头"1"的数目就是整个串中字节的数目.

例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:

11000010 10101001 = 0xC2 0xA9

而字符 U+2260 = 0010 0010 0110 0000 (不等于) 编码为:

11100010 10001001 10100000 = 0xE2 0×89 0xA0

这种编码的官方名字拼写为 UTF-8, 其中 UTF 代表 UCS Transformation Format. 请勿在任何文档中用其他名字 (比如 utf8 或 UTF_8) 来表示 UTF-8, 当然除非你指的是一个变量名而不是这种编码本身.

什么编程语言支持 Unicode?
在大约 1993 年之后开发的大多数现代编程语言都有一个特别的数据类型, 叫做 Unicode/ISO 10646-1 字符. 在 Ada95 中叫 Wide_Character, 在 Java 中叫 char.

ISO C 也详细说明了处理多字节编码和宽字符 (wide characters) 的机制, 1994 年 9 月 Amendment 1 to ISO C 发表时又加入了更多. 这些机制主要是为各类东亚编码而设计的, 它们比处理 UCS 所需的要健壮得多. UTF-8 是 ISO C 标准调用多字节字符串的编码的一个例子, wchar_t 类型可以用来存放 Unicode 字符.

2005年06月16日

ASP函数用法
Array()
函数返回一个数组
表达式 Array(list)
允许数据类型: 字符,数字均可

实例:

<%
Dim myArray()
For i = 1 to 7
Redim Preserve myArray(i)
myArray(i) = WeekdayName(i)
Next
%>

返回结果: 建立了一个包含7个元素的数组myArray

myArray("Sunday","Monday", … … "Saturday"

CInt()

函数将一个表达式转化为数字类型
表达式 CInt(expression)
允许数据类型: 任何有效的字符均可
实例:
<%
f = "234"
response.write cINT(f) + 2
%>
返回结果: 236
转化字符"234"为数字"234",如果字符串为空,则返回0值
CreateObject()
函数建立和返回一个已注册的ACTIVEX组件的实例。
表达式 CreateObject(objName)
允许数据类型: objName 是任何一个有效、已注册的ACTIVEX组件的名字.

实例: <%

Set con = Server.CreateObject("ADODB.Connection"

%>


CStr()

函数转化一个表达式为字符串.

表达式 CStr(expression)

允许数据类型: expression 是任何有效的表达式。

实例: <%

s = 3 + 2

response.write "The 返回结果 is: " & cStr(s)

%>

返回结果: 转化数字“5”为字符“5”。


Date()

函数返回当前系统日期.

表达式 Date()

允许数据类型: None.

实例: <%=Date%>

返回结果: 9/9/00


DateAdd()

函数返回一个被改变了的日期。

表达式 DateAdd(timeinterval,number,date)

允许数据类型:
timeinterval is the time interval to add;
number is amount of time intervals to add;
and date is the starting date.

实例: <%

currentDate = #9/9/00#

newDate = DateAdd("m",3,currentDate)

response.write newDate

%>


<%

currentDate = #12:34:45 PM#

newDate = DateAdd("h",3,currentDate)

response.write newDate

%>

返回结果: 9/9/00

3:34:45 PM

"m" = "month";

"d" = "day";

If currentDate is in time format then,

"h" = "hour";

"s" = "second";


DateDiff()

函数返回两个日期之间的差值 。

表达式 DateDiff(timeinterval,date1,date2 [, firstdayofweek [, firstweekofyear]])

允许数据类型: timeinterval 表示相隔时间的类型,如“M“表示“月”。

实例: <%

fromDate = #9/9/00#

toDate = #1/1/2000#

response.write "There are " & _

DateDiff("d",fromDate,toDate) & _

" days to millenium from 9/9/00."

%>

返回结果: 从9/9/00 到2000年还有 150 天.


Day()

函数返回一个月的第几日 .

表达式 Day(date)

允许数据类型: date 是任何有效的日期。

实例: <%=Day(#9/9/00#)%>

返回结果: 4


FormatCurrency()

函数返回表达式,此表达式已被格式化为货币值

表达式 FormatCurrency(Expression [, Digit [, LeadingDigit [, Paren [, GroupDigit]]]])

允许数据类型: Digit 指示小数点右侧显示位数的数值。默认值为 -1,指示使用的是计算机的区域设置; LeadingDigit 三态常数,指示是否显示小数值小数点前面的零。

实例: <%=FormatCurrency(34.3456)%>

返回结果: $34.35


FormatDateTime()

函数返回表达式,此表达式已被格式化为日期或时间

表达式 FormatDateTime(Date, [, NamedFormat])

允许数据类型: NamedFormat 指示所使用的日期/时间格式的数值,如果省略,则使用 vbGeneralDate.

实例: <%=FormatDateTime("09/9/00", vbLongDate)%>

返回结果: Sunday, September 09, 2000


FormatNumber()

函数返回表达式,此表达式已被格式化为数值.

表达式 FormatNumber(Expression [, Digit [, LeadingDigit [, Paren [, GroupDigit]]]])

允许数据类型: Digit 指示小数点右侧显示位数的数值。默认值为 -1,指示使用的是计算机的区域设置。; LeadingDigit i指示小数点右侧显示位数的数值。默认值为 -1,指示使用的是计算机的区域设置。; Paren 指示小数点右侧显示位数的数值。默认值为 -1,指示使用的是计算机的区域设置。; GroupDigit i指示小数点右侧显示位数的数值。默认值为 -1,指示使用的是计算机的区域设置。.

实例: <%=FormatNumber(45.324567, 3)%>

返回结果: 45.325


FormatPercent()

函数返回表达式,此表达式已被格式化为尾随有 % 符号的百分比(乘以 100 )。 (%)

表达式 FormatPercent(Expression [, Digit [, LeadingDigit [, Paren [, GroupDigit]]]])

允许数据类型: 同上.

实例: <%=FormatPercent(0.45267, 3)%>

返回结果: 45.267%


Hour()

函数以24时返回小时数.

表达式 Hour(time)

允许数据类型:

实例: <%=Hour(#4:45:34 PM#)%>

返回结果: 16

(Hour has been converted to 24-hour system)


Instr()

函数返回字符或字符串在另一个字符串中第一次出现的位置.

表达式 Instr([start, ] strToBeSearched, strSearchFor [, compare])

允许数据类型: Start为搜索的起始值,strToBeSearched接受搜索的字符串 strSearchFor要搜索的字符.compare比较方式(详细见ASP常数)

实例: <%

strText = "This is a test!!"

pos = Instr(strText, "a"

response.write pos

%>

返回结果: 9


InstrRev()

函数同上,只是从字符串的最后一个搜索起

表达式 InstrRev([start, ] strToBeSearched, strSearchFor [, compare])

允许数据类型: 同上.

实例: <%

strText = "This is a test!!"

pos = InstrRev(strText, "s"

response.write pos

%>

返回结果: 13


Int()

函数返回数值类型,不四舍五入。

表达式 Int(number)

允许数据类型:

实例: <%=INT(32.89)%>

返回结果: 32


IsArray()

函数判断一对象是否为数组,返回布尔值 .

表达式 IsArray(name)

实例: <%

strTest = "Test!"

response.write IsArray(strTest)

%>

返回结果: False


IsDate()

函数判断一对象是否为日期,返回布尔值

表达式 IsDate(expression)

实例: <%

strTest = "9/4/2000"

response.write IsDate(strTest)

%>

返回结果: True


IsEmpty()

函数判断一对象是否初始化,返回布尔值.

表达式 IsEmpty(expression)

实例: <%

Dim i

response.write IsEmpty(i)

%>

返回结果: True


IsNull()

函数判断一对象是否为空,返回布尔值.

表达式 IsNull(expression)

实例: <%

Dim i

response.write IsNull(i)

%>

返回结果: False


IsNumeric()

函数判断一对象是否为数字,返回布尔值.

表达式 IsNumeric(expression)

实例: <%

i = "345"

response.write IsNumeric(i)

%>

返回结果: True

就算数字加了引号,ASP还是认为它是数字。


IsObject()

函数判断一对象是否为对象,返回布尔值.

表达式 IsObject(expression)

实例: <%

Set con = Server.CreateObject("ADODB.Connection"

response.write IsObject(con)

%>

返回结果: True


LBound()

函数返回指定数组维的最小可用下标.

表达式 Lbound(arrayname [, dimension])

实例: <%

i = Array("Monday","Tuesday","Wednesday"

response.write LBound(i)

%>

返回结果: 0


LCase()

函数 返回字符串的小写形式

表达式 Lcase(string)

实例: <%

strTest = "This is a test!"

response.write LCase(strTest)

%>

返回结果: this is a test!


Left()

函数返回字符串左边第length个字符以前的字符(含第length个字符).

表达式 Left(string, length)

实例: <%

strTest = "This is a test!"

response.write Left(strTest, 3)

%>

返回结果: Thi


Len()

函数返回字符串的长度.

表达式 Len(string | varName)

实例: <%

strTest = "This is a test!"

response.write Len(strTest)

%>

返回结果: 15


LTrim()

函数去掉字符串左边的空格.

表达式 LTrim(string)

实例: <%

strTest = " This is a test!"

response.write LTrim(strTest)

%>

返回结果: This is a test!


Mid()

函数返回特定长度的字符串(从start开始,长度为length).

表达式 Mid(string, start [, length])

实例: <%

strTest = "This is a test! Today is Monday."

response.write Mid(strTest, 17, 5)

%>

返回结果: Today


Minute()

函数返回时间的分钟.

表达式 Minute(time)

实例: <%=Minute(#12:45:32 PM#)%>

返回结果: 45


Month()

函数返回日期.

表达式 Month(date)

实例: <%=Month(#08/04/99#)%>

返回结果: 8


MonthName()

函数返回指定月份

表达式 MonthName(month, [, Abb])

实例: <%=MonthName(Month(#08/04/99#))%>

返回结果: August


Now()

函数返回系统时间

表达式 Now()

实例: <%=Now%>

返回结果: 9/9/00 9:30:16 AM


Right()

函数返回字符串右边第length个字符以前的字符(含第length个字符).

表达式 Right(string, length)

实例: <%

strTest = "This is an test!"

response.write Right(strTest, 3)

%>

返回结果: st!


Rnd()

函数产生一个随机数.

表达式 Rnd [ (number) ]

实例: <%

Randomize()

response.write RND()

%>

返回结果: 任何一个在0 到 1 之间的数


Round()

函数返回按指定位数进行四舍五入的数值.

表达式 Round(expression [, numRight])

实例: <%

i = 32.45678

response.write Round(i)

%>

返回结果: 32


Rtrim()

函数去掉字符串右边的字符串.

表达式 Rtrim(string)

实例: <%

strTest = "This is a test!! "

response.write RTrim(strTest)

%>

返回结果: This is a test!!


Split()

函数将一个字符串分割并返回分割结果

表达式 Split (S[,d])

实例:<%V= Split(A,B,C)

For i = 0 To UBound(V)

Response.Write V(i)

Next

%>

返回结果: A B C


Second()

函数返回秒.

表达式 Second(time)

实例: <%=Second(#12:34:28 PM#)%>

返回结果: 28


StrReverse()

函数反排一字符串

表达式 StrReverse(string)

实例: <%

strTest = "This is a test!!"

response.write StrReverse(strTest)

%>

返回结果: !!tset a si sihT


Time()

函数返回系统时间.

表达式 Time()

实例: <%=Time%>

返回结果: 9:58:28 AM


Trim()

函数去掉字符串左右的空格.

表达式 Trim(string)

实例: <%

strTest = " This is a test!! "

response.write Trim(strTest)

%>

返回结果: This is a test!!


UBound()

函数返回指定数组维数的最大可用下标>.

表达式 Ubound(arrayname [, dimension])

实例: <%

i = Array("Monday","Tuesday","Wednesday"

response.write UBound(i)

%>

返回结果: 2


UCase()

函数返回字符串的大写形式.

表达式 UCase(string)

允许数据类型:

实例: <%

strTest = "This is a test!!"

response.write UCase(strTest)

%>

返回结果: THIS IS A TEST!!


VarType()

函数返回指示变量子类型的值

表达式 VarType(varName)

实例: <%

i = 3

response.write varType(i)

%>

返回结果: 2(数字)详见"asp常数"


WeekDay()

函数返回在一周的第几天.

表达式 WeekDay(date [, firstdayofweek])

实例: <%

d = #9/9/00#

response.write Weekday(d)

%>

返回结果: 4(星期三)


WeekDayName()

函数返回一周第几天的名字.

表达式 WeekDayName(weekday [, Abb [, firstdayofweek]])

实例: <%

d = #9/9/00#

response.write WeekdayName(Weekday(d))

%>

返回结果: Wednesday


Year()

函数返回当前的年份.

表达式 Year(date)

实例: <%=Year(#9/9/00#)%>

返回结果: 1999