2007年05月25日

在现代处理器设计中,Cache有效地解决了处理速度和存储速度之间的匹配问题,从而成为RISC技术成功的一项重要的支撑技术。现代计算机几乎没有不含Cache的。在很多机器中,还采用了多级Cache结构。可以说,Cache和流水线共同构成了RISC成功的两大支柱。本文将详细讨论Cache设计中的一些重要问题。

Cache设计中的四大基本问题

映射方式

相对于主存来说,Cache是一个小存储器。因此主存的块到Cache的行的映射是一种多到一映射。通常有三种映射策略:

a)直接映射

主存的一个块只能对应Cache的某一特定行。该方案中Cache被分为若干行,主存块号i对应的Cache行号为i对Cache行数的模。

b)全相连映射

主存的一个块可以存放在Cache中的任何行。

c)组相连映射

Cache行被分为M组,每组包含N行。主存的一个块和一个特定的组相对应,可存放在该组的任何一行。主存块号I映射成Cache的组号为I对Cache组数M的模。其中每组的行数N常被称为Cache的路数。

不难看到,直接映射和全相联可以看作是组相联方案的特殊形式。全相联命中率最高,但实现最复杂;直接映射实现最简单,但命中率较低。采用组相联是一种折衷方式,在总容量一定后,考虑组数M,路数N,及行中字节数L之间的配合关系。

映射机构

这是映射方式的实现问题。在Cache中为每个行设置了一个标志以指明该行对应的主存块地址。每个Cache行的标志中可包含一些特定信息,根据这些特定信息可以检测它们是否和来自CPU的块地址相匹配。由于速度对Cache至关重要,所以应该对所有可能的标志行并行查找。

另外,我们必需判断出Cache中的块中是否存放着有效信息。通用解决方法是在标志上增加一个有效位,以指明该标志是否包含有效地址。若有效位未设置,则不能对其进行匹配。由于每个Cache行都需要一个标志,所以增大行的大小对减小标志存储器占Cache总成本的比例是有益的。

替换策略

对于直接映射方式,在失效时只能对一个块进行替换,是最为简单的。但是对全相联和组相联方式,出现失效时需要在多个块中进行选择。这是我们有如下三种基本替换策略:

a)随机替换策略

这种策略就是随机的在候选块中选择一个进行替换。由于纯粹的随机选择会给硬件调试带来很大的困难,所以在一些系统中实际采用一种具有可再现能力的伪随机策略。

b)最近最少使用策略(LRU)

为了减少替换出最近可能使用的信息的机会,可以对块的访问情况进行记录。在发生Cache失效时,利用访问的时间局部性现象,替换出候选块中最近最少被访问的存储块。

c)先进先出策略(FIFO)

这种策略在Cache失效时,替换掉存放在Cache中时间最长的候选存储块。

试验数据证明随机替换策略一般比先进先出策略性能要好,而实现也更简单。所以我们只对随机替换策略和LRU策略进行比较分析。随机策略的一个重要性质就是易于硬件实现。随着可记录块数的增多,LRU策略的实现成本迅速增加,而性能改进一般并不十分明显。表1显示了LRU策略和随机策略在实效率方面的一些情况。可以看出,LRU在较小的Cache中比在较大的Cache中起着更重要的作用。

存储器的写策略

对Cache主要进行的是读操作,统计数据表明通常程序的读操作至少是写操作的两倍。但Amdahl定律提醒我们面向高性能的设计绝不能忽视写操作的速度。对于写命中情况,有两种基本的写策略:

a)写透策略

信息将被同时写到Cache行和低级存储器的相应存储块中。

b)写回策略

信息仅被写到Cache的相应行中,当被改变的行被替换出Cache时,其内容才被写回到主存相应的块。

这两种写策略各有利弊。对于写回策略,写操作能以Cache的速度来进行,而且在一个块内进行多次写操作仅需要一次对低级存储器的写动作,有益于降低对存储器的带宽要求,使其在多处理器系统中备受重视。对于写透策略,读失效不会导致必须对低级存储器进行写操作,而且写透操作更容易实现。写透策略的另一优点是主存中总是保存着最新数据,这对于多处理器系统和I/O设计都是非常重要的。

对于写失效情况,也有两种选择:

a)装入写

将块装入Cache,接着进行和命中情况相同的操作。

b)绕写

对低级存储器的相应内容进行改写,但不装入Cache。

上述两种写失效策略对写透和写回策略都适用,但对于写回方式,一般采用装入写策略,便于该存储块能被Cache继续利用;对于写透方式,由于后续对该存储块的写操作仍需写到主存中,故以绕写策略为好。

Cache的性能分析

之前本文介绍的各种技术的目的是减少平均访存时间。但是,设计Cache的最终目的是减少CPU的执行时间。CPU时间可以分为CPU执行时间和访存等待时间,即:

CPU时间=(CPU执行时钟数+访存等待时钟数)×时钟周期

由于Cache失效是访存等待的最重要原因,我们假设访存等待都是有Cache失

效产生的,以简化分析。

访存等待时钟数=(存储器访问数/程序)×失效率×失效损失

将指令总数IC提取出来单独考虑,可得:

CPU时间=IC×(CPI执行+(访存数/指令)×失效率×失效损失)×时钟周期长度

可见,对于越小CPI的处理器,设置Cache对CPI的影响程度越大。由于计算CPI时,Cache失效的损失以CPU时钟周期为单位进行衡量,所以较高的CPU时钟频率将导致较大的失效损失。

根据CPU时间公式,可得采用Cache会增大处理器的CPI,但它可以有效减少平均访存等待的周期数,所以它是有益于提高CPU性能的。对于CPI较小,特别是RISC处理器,Cache失效对CPI的影响很严重,在设计这种处理器的时候,必须降低Cache的失效率。

Cache失效原因的分析

导致Cache失效的主要原因有以下三种:

a)突发失效

对某存储块进行第一次访问时,由于该块不在Cache中,所以必须首先将存储块取到Cache中。这种情况又称为冷启失效。

b)容量失效

如果Cache不能容纳某一程序执行过程中的所有存储块,那么当程序又需使用某一曾在Cache中,但现在已替换出的存储块时,就会出现容量失效。

c)冲突失效

在采用组相联或直接映射替换策略的Cache中,许多块都必须映射到Cache中的某一块中,由于这种原因使得当程序又需要使用某一曾在Cache中,但现在已被替换出的存储块时,就会出现冲突失效。

全相联Cache中没有冲突失效,但增加相联度在意味着增加成本,而且可能延长访问时间,这样就会降低处理器的整体性能。要减少容量失效,就要增加Cache的容量。上层存储器容量太小,就会频繁产生抖动现象,这意味着机器将以接近低级存储器的速度运行。增加存储块的大小可以减小突发失效的数目,但这又会增加冲突失效的可能。这就需要在Cache的设计工作中,综合考虑三种失效情况,确定折衷的设计方案。

结束语

在现代处理器设计中,Cache有效地解决了处理速度和存储速度之间的匹配问题,从而成为现代处理器不可缺少的核心技术之一。在设计中应充分考虑各种条件的制约,权衡各种因素,才能充分提高系统的性能。

2007年04月11日

1.如何获得当前文件路径

常用:

字符串类型:System.getProperty("user.dir");

综合:

package com.zcjl.test.base;import java.io.File;public class Test {    public static void main(String[] args) throws Exception {
        System.out.println(            Thread.currentThread().getContextClassLoader().getResource(""));        System.out.println(Test.class.getClassLoader().getResource(""));        System.out.println

(ClassLoader.getSystemResource(""));        System.out.println(Test.class.getResource(""));        System.out.println(Test.class.getResource("/"));        System.out.println(new File("").getAbsolutePath());
        System.out.println(System.getProperty("user.dir"));    }}

2.Web服务中

(1).Weblogic

WebApplication的系统文件根目录是你的weblogic安装所在根目录。
例如:如果你的weblogic安装在c:\bea\weblogic700…..
那么,你的文件根路径就是c:\.
所以,有两种方式能够让你访问你的服务器端的文件:
a.使用绝对路径:
比如将你的参数文件放在c:\yourconfig\yourconf.properties,
直接使用 new FileInputStream("yourconfig/yourconf.properties");
b.使用相对路径:
相对路径的根目录就是你的webapplication的根路径,即WEB-INF的上一级目录,将你的参数文件放在yourwebapp\yourconfig\yourconf.properties,
这样使用:
new FileInputStream("./yourconfig/yourconf.properties");
这两种方式均可,自己选择。

(2).Tomcat

在类中输出System.getProperty("user.dir");显示的是%Tomcat_Home%/bin

(3).Resin

不是你的JSP放的相对路径,是JSP引擎执行这个JSP编译成SERVLET
的路径为根.比如用新建文件法测试File f = new File("a.htm");
这个a.htm在resin的安装目录下

(4).如何读相对路径哪?

在Java文件中getResource或getResourceAsStream均可

例:getClass().getResourceAsStream(filePath);//filePath可以是"/filename",这里的/代表web发布根路径下WEB-INF/classes

(5).获得文件真实路径

string  file_real_path=request.getRealPath("mypath/filename");  

通常使用request.getRealPath("/");  

3.文件操作的类

import java.io.*;import
java.net.*;import java.util.*;//import javax.swing.filechooser.*;//import org.jr.swing.filter.*;

/*** 此类中封装一些常用的文件操作。* 所有方法都是静态方法,不需要生成此类的实例,* 为避免生成此类的实例,构造方法被申明为private类型的。* @since  0.1

*/

public class FileUtil {  /**   * 私有构造方法,防止类的实例化,因为工具类不需要实例化。   */  private FileUtil() {

  }

  /**   * 修改文件的最后访问时间。   * 如果文件不存在则创建该文件。   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>   * @param file 需要修改最后访问时间的文件。   * @since  0.1   */  public static void touch(File file) {    long currentTime = System.currentTimeMillis();    if (!file.exists()) {      
System.err.println("file not found:" + file.getName());      System.err.println("Create a new file:" + file.getName());      try {        if (file.createNewFile()) {        //  System.out.println

("Succeeded!");        }        else {        //  System.err.println("Create file failed!");        }      }      catch (IOException e) {      //  System.err.println("Create file failed!");
        e.printStackTrace();      }    }    boolean result = file.setLastModified(currentTime);    if (!result) {    //  System.err.println("touch failed: " + file.getName());    }

  }

  /**   * 修改文件的最后访问时间。   * 如果文件不存在则创建该文件。   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>   * @param fileName 需要修改最后访问时间的文件的文件名。   * @since  0.1   */  public static void touch(String fileName) {
    File file = new File(fileName);    touch(file);  }

  /**   * 修改文件的最后访问时间。   * 如果文件不存在则创建该文件。   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>   * @param files 需要修改最后访问时间的文件数组。
   * @since  0.1   */  public static void touch(File[] files) {    for (int i = 0; i < files.length; i++) {      touch(files);    }  }

  /**   * 修改文件的最后访问时间。   * 如果文件不存在则创建该文件。
   * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

虑中。</b>   * @param fileNames 需要修改最后访问时间的文件名数组。   * @since  0.1   */  public static void touch(String[] fileNames) {    File[] files = new File[
fileNames.length];    for (int i = 0; i < fileNames.length; i++) {      files = new File(fileNames);    }    touch(files);  }

  /**   * 判断指定的文件是否存在。   * @param fileName 要判断的文件的文件名

   * @return 存在时返回true,否则返回false。   * @since  0.1   */  public static boolean isFileExist(String fileName) {    return new File(fileName).isFile();  }

  /**   * 创建指定的目录。   * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。
   * <b>注意:可能会在返回false的时候创建部分父目录。</b>   * @param file 要创建的目录   * @return 完全创建成功时返回true,否则返回false。   * @since  0.1   */  public static boolean makeDirectory(File file) {    File parent =
file.getParentFile();    if (parent != null) {      return parent.mkdirs();    }    return false;  }

  /**   * 创建指定的目录。   * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。   * <b>注意:可能会在返回false的时候创建部分父目录。</b>
   * @param fileName 要创建的目录的目录名   * @return 完全创建成功时返回true,否则返回false。   * @since  0.1   */  public static boolean makeDirectory(String fileName) {    File file = new File(fileName);    return makeDirectory(file);
  }

  /**   * 清空指定目录中的文件。   * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。   * 另外这个方法不会迭代删除,即不会删除子目录及其内容。   * @param directory 要清空的目录   * @return 目录下的所有文件都被成功删除时返回true,否则返回false.   * @since  
0.1   */  public static boolean emptyDirectory(File directory) {    boolean result = false;    File[] entries = directory.listFiles();    for (int i = 0; i < entries.length; i++) {      if (!entries.delete()) {
        result = false;      }    }    return true;  }

  /**   * 清空指定目录中的文件。   * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。   * 另外这个方法不会迭代删除,即不会删除子目录及其内容。   * @param directoryName 要清空的目录的目录名
   * @return 目录下的所有文件都被成功删除时返回true,否则返回false。   * @since  0.1   */  public static boolean emptyDirectory(String directoryName) {    File dir = new File(directoryName);    return emptyDirectory(dir);
  }

  /**   * 删除指定目录及其中的所有内容。   * @param dirName 要删除的目录的目录名   * @return 删除成功时返回true,否则返回false。   * @since  0.1   */  public static boolean deleteDirectory(String dirName) {    return deleteDirectory(new File(dirName));
  }

  /**   * 删除指定目录及其中的所有内容。   * @param dir 要删除的目录   * @return 删除成功时返回true,否则返回false。   * @since  0.1   */  public static boolean deleteDirectory(File dir) {    if ( (dir == null) || !dir.isDirectory()) {
      throw new IllegalArgumentException("Argument " + dir +                                         " is not a directory. ");    }

    File[] entries = dir.listFiles();    int sz =
entries.length;

    for (int i = 0; i < sz; i++) {      if (entries.isDirectory()) {        if (!deleteDirectory(entries)) {          return false;        }      }      else {        if (!entries.delete()) {
          return false;        }      }    }

    if (!dir.delete()) {      return false;    }    return true;  }

  /**   * 返回文件的URL地址。   * @param file 文件

   * @return 文件对应的的URL地址   * @throws MalformedURLException   * @since  0.4   * @deprecated 在实现的时候没有注意到File类本身带一个toURL方法将文件路径转换为URL。   *             请使用File.toURL方法。   */  public static URL getURL(File file) throws MalformedURLException {
    String fileURL = "file:/" + file.getAbsolutePath();    URL url = new URL(fileURL);    return url;  }

  /**   * 从文件路径得到文件名。   * @param filePath 文件的路径,可以是相对路径也可以是绝对路径   * @return 对应的文件名
   * @since  0.4   */  public static String getFileName(String filePath) {    File file = new File(filePath);    return file.getName();  }

  /**   * 从文件名得到文件绝对路径。   * @param fileName 文件名
   * @return 对应的文件路径   * @since  0.4   */  public static String getFilePath(String fileName) {    File file = new File(fileName);    return file.getAbsolutePath();  }

  /**   * 将DOS/Windows格式的路径转换为UNIX/Linux格式的路径。
   * 其实就是将路径中的"\"全部换为"/",因为在某些情况下我们转换为这种方式比较方便,   * 某中程度上说"/"比"\"更适合作为路径分隔符,而且DOS/Windows也将它当作路径分隔符。   * @param filePath 转换前的路径   * @return 转换后的路径   * @since  0.4

   */  public static String toUNIXpath(String filePath) {    return filePath.replace('\\', '/');  }

  /**   * 从文件名得到UNIX风格的文件绝对路径。   * @param fileName 文件名   * @return 对应的UNIX风格的文件路径
   * @since  0.4   * @see #toUNIXpath(String filePath) toUNIXpath   */  public static String getUNIXfilePath(String fileName) {    File file = new File(fileName);    return toUNIXpath(file.getAbsolutePath

());  }

  /**   * 得到文件的类型。   * 实际上就是得到文件名中最后一个"."后面的部分。   * @param fileName 文件名   * @return 文件名中的类型部分   * @since  0.5   */  public static String getTypePart(String fileName) {
    int point =
fileName.lastIndexOf('.');    int length = fileName.length();    if (point == -1 || point == length - 1) {      return "";    }    else {      return fileName.substring(point + 1, length);
    }  }

  /**   * 得到文件的类型。   * 实际上就是得到文件名中最后一个"."后面的部分。   * @param file 文件   * @return 文件名中的类型部分   * @since  0.5   */  public static String getFileType(File file) {
    return getTypePart(
file.getName());  }

  /**   * 得到文件的名字部分。   * 实际上就是路径中的最后一个路径分隔符后的部分。   * @param fileName 文件名   * @return 文件名中的名字部分   * @since  0.5   */  public static String getNamePart(String fileName) {
    int point = getPathLsatIndex(fileName);    int length = fileName.length();    if (point == -1) {      return fileName;    }    else if (point == length - 1) {      int secondPoint = getPathLsatIndex(fileName, point - 1);
      if (secondPoint == -1) {        if (length == 1) {          return fileName;        }        else {          return fileName.substring(0, point);        }      }      else {        return
fileName.substring(secondPoint + 1, point);      }    }    else {      return fileName.substring(point + 1);    }  }

  /**   * 得到文件名中的父路径部分。   * 对两种路径分隔符都有效。   * 不存在时返回""。
   * 如果文件名是以路径分隔符结尾的则不考虑该分隔符,例如"/path/"返回""。   * @param fileName 文件名   * @return 父路径,不存在或者已经是父目录时返回""   * @since  0.5   */  public static String getPathPart(String fileName) {
    int point = getPathLsatIndex(fileName);    int length = fileName.length();    if (point == -1) {      return "";    }    else if (point == length - 1) {      int secondPoint = getPathLsatIndex(fileName, point - 1);
      if (secondPoint == -1) {        return "";      }      else {        return fileName.substring(0, secondPoint);      }    }    else {      return fileName.substring(0, point);
    }  }

  /**   * 得到路径分隔符在文件路径中首次出现的位置。   * 对于DOS或者UNIX风格的分隔符都可以。   * @param fileName 文件路径   * @return 路径分隔符在路径中首次出现的位置,没有出现时返回-1。   * @since  0.5   */  public static int getPathIndex(String fileName) {
    int point = fileName.indexOf('/');    if (point == -1) {      point = fileName.indexOf('\\');    }    return point;  }

  /**   * 得到路径分隔符在文件路径中指定位置后首次出现的位置。   * 对于DOS或者UNIX风格的分隔符都可以。
   * @param fileName 文件路径   * @param fromIndex 开始查找的位置   * @return 路径分隔符在路径中指定位置后首次出现的位置,没有出现时返回-1。   * @since  0.5   */  public static int getPathIndex(String fileName, int fromIndex) {    int point =
fileName.indexOf('/', fromIndex);    if (point == -1) {      point = fileName.indexOf('\\', fromIndex);    }    return point;  }

  /**   * 得到路径分隔符在文件路径中最后出现的位置。   * 对于DOS或者UNIX风格的分隔符都可以。
   * @param fileName 文件路径   * @return 路径分隔符在路径中最后出现的位置,没有出现时返回-1。   * @since  0.5   */  public static int getPathLsatIndex(String fileName) {    int point = fileName.lastIndexOf('/');    if (point == -1) {
      point = fileName.lastIndexOf('\\');    }    return point;  }

  /**   * 得到路径分隔符在文件路径中指定位置前最后出现的位置。   * 对于DOS或者UNIX风格的分隔符都可以。   * @param fileName 文件路径   * @param fromIndex 开始查找的位置
   * @return 路径分隔符在路径中指定位置前最后出现的位置,没有出现时返回-1。   * @since  0.5   */  public static int getPathLsatIndex(String fileName, int fromIndex) {    int point = fileName.lastIndexOf('/', fromIndex);

    if (point == -1) {      point = fileName.lastIndexOf('\\', fromIndex);    }    return point;  }

  /**   * 将文件名中的类型部分去掉。   * @param filename 文件名   * @return 去掉类型部分的结果   * @since  
0.5   */  public static String trimType(String filename) {    int index = filename.lastIndexOf(".");    if (index != -1) {      return filename.substring(0, index);    }    else {

      return filename;    }  }  /**   * 得到相对路径。   * 文件名不是目录名的子节点时返回文件名。   * @param pathName 目录名   * @param fileName 文件名   * @return 得到文件名相对于目录名的相对路径,目录下不存在该文件时返回文件名   * @since  0.5
   */
  public static String getSubpath(String pathName,String fileName) {    int index = fileName.indexOf(pathName);    if (index != -1) {      return fileName.substring(index + pathName.length() + 1);    }
    else {      return fileName;    }  }

}

4.遗留问题

目前new FileInputStream()只会使用绝对路径,相对没用过,因为要相对于web服务器地址,比较麻烦

还不如写个配置文件来的快哪

5.按Java文件类型分类读取配置文件

配置文件是应用系统中不可缺少的,可以增加程序的灵活性。 java.util.Properties是从jdk1.2就有的类,一直到现在都支持load()方法,jdk1.4以后save(output,string) ->store(output,string)。如果只是单纯的读,根本不存在烦恼的问题。web层可以通过Thread.currentThread().getContextClassLoader().
getResourceAsStream("xx.properties ")获取;Application可以通过new FileInputStream("xx.properties");直接在classes一级获取。关键是有时我们需要通过web修改配置文件,我们不能将路径写死了。经过测试觉得有以下心得:

1.servlet中读写。如果运用Struts或者Servlet可以直接在初始化参数中配置,调用时根据servlet的getRealPath("/")获取真实路径,再根据String file = this.servlet.getInitParameter("abc");获取相对的WEB-INF的相对路径。
例:

InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream("abc.properties");Properties prop = new Properties();
prop.load(input);input.close();OutputStream out = new FileOutputStream(path);prop.setProperty("abc", "test");prop.store(out, "–test–");out.close();

2.直接在jsp中操作,通过jsp内置对象获取可操作的绝对地址。
例:

// jsp页面String path = pageContext.getServletContext().getRealPath("/");String realPath = path+"/WEB-INF/classes/abc.properties";

//java 程序InputStream in = getClass().getClassLoader().getResourceAsStream("
abc.properties"); // abc.properties放在webroot/WEB-INF/classes/目录下prop.load(in);in.close();

OutputStream out = new FileOutputStream(path); // path为通过页面传入的路径prop.setProperty("abc", "abcccccc");
prop.store(out, "–test–");out.close();

3.只通过Java程序操作资源文件

InputStream in = new FileInputStream("abc.properties"); // 放在classes同级

OutputStream out = new FileOutputStream("
abc.properties");
2006年08月28日

1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

3. Java中的数据类型有两种。

一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

int a = 3;
int b = 3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。 4. String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:

(1)先定义一个名为str的对String类的对象引用变量:String str;

(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。

(3)将str指向对象o的地址。

值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

我们再来更进一步,将以上代码改成:

String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false

这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。

再修改原来代码:

String str1 = "abc";
String str2 = "abc";

str1 = "bcd";

String str3 = str1;
System.out.println(str3); //bcd

String str4 = "bcd";
System.out.println(str1 == str4); //true

str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

我们再接着看以下的代码。

String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); //false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。 7. 结论与建议:

(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

2006年08月19日

程序员每天该做的事 
1、总结自己一天任务的完成情况 
最好的方式是写工作日志,把自己今天完成了什么事情,遇见了什么问题都记录下来,日后翻看好处多多 

2、考虑自己明天应该做的主要工作 
把明天要做的事情列出来,并按照优先级排列,第二天应该把自己效率最高的时间分配给最重要的工作 

3、考虑自己一天工作中失误的地方,并想出避免下一次再犯的方法 
出错不要紧,最重要的是不要重复犯相同的错误,那是愚蠢 

4、考虑自己一天工作完成的质量和效率能否还能提高 
一天只提高1%,365天你的效率就能提高多少倍你知道吗? (1+0.01)^365 = 37 倍 

5、看一个有用的新闻网站或读一张有用的报纸,了解业界动态 
闭门造车是不行的,了解一下别人都在做什么,对自己能带来很多启示 

6、记住一位同事的名字及其特点 
你认识公司的所有同事吗?你了解他们吗? 

7、清理自己的代码 
今天完成的代码,把中间的调试信息,测试代码清理掉,按照编码风格整理好,注释都写好了吗? 

8、清理自己的桌面 
当日事当日毕,保持清洁干劲的桌面才能让你工作时不分心,程序员特别要把电脑的桌面清理干净 

程序员每周该做的事 
1、向你的老板汇报一次工作 
让你的老板知道你在做什么,这很重要。可以口头、书面、邮件,看你老板的工作方式而定 

2、进行一次自我总结(非正式) 
这周之内自己表现得怎么样?该加分还是扣分? 

3、制定下周计划 
把下周要做的事情列出来,一样要分清楚优先级 

4、整理自己的文件夹、书柜和电脑文件 
把桌面以外的地方也要清理干净,电脑的文件夹,收到的邮件,把过时的垃圾全部清理掉 

5、与一个非公司的朋友沟通 
它山之石,可以攻玉 

6、看一本杂志 
找一本适合自己的专业杂志 

7、纠正自己或同事一个细节上的不正确做法 
《细节决定成败》看过了吗?没看过强烈建议先看看 

程序员每月该做的事 
1、至少和一个同事一起吃饭或喝茶 
不光了解自己工作伙伴的工作,还要了解他们的生活 

2、自我考核一次 
相对正式地考核自己一下,你对得起这个月的工资吗? 

3、对你的同事考核一次 
你的同事表现怎么样?哪些人值得学习,哪些人需要帮助? 

3、制定下月的计划,确定下月的工作重点 

4、总结自己工作质量改进状况 
自己的质量提高了多少? 

5、有针对性地对一项工作指标做深入地分析并得出改进的方案 
可以是对自己的,也可以是对公司的,一定要深入地分析后拿出自己的观点来。要想在老板面前说得上话,做的成事,工作上功夫要做足。 

6、与老板沟通一次 
最好是面对面地沟通,好好表现一下自己,虚心听取老板的意见,更重要的是要了解老板当前关心的重点 

程序员每年该做的事 
1、年终总结 
每个公司都会做的事情,但你真正认真地总结过自己吗? 

2、兑现给自己、给家人的承诺 
给老婆、儿子的新年礼物买了没有?给自己的呢? 

3、下年度工作规划 
好好想想自己明年的发展目标,争取升职/加薪、跳槽还是自己出来干? 

4、掌握一项新技术 
至少是一项,作为程序员一年要是一项新技术都学不到手,那就一定会被淘汰。 
掌握可不是看本书就行的,要真正懂得应用,最好你能够写一篇教程发表到你的blog 

5、推出一种新产品 
可以是一个真正的产品,也可以只是一个类库,只要是你创造的东西就行,让别人使用它,也为世界作点贡献。当然如果真的很有价值,收点注册费也是应该的 

6、与父母团聚一次 
常回家看看,常回家看看 

2006年07月14日

<%@   page   contentType="text/html;charset=GBK"%>  
  <%@   page   import="java.io.*"   %>  
  <%  
      //在Servlet与JSP中取得当前文件所在的相对路径与绝对路径  
       
      //JSP中  
   
      out.println("根目录所对应的绝对路径:"   +   request.getRequestURI()   +   "<br/>");  
       
      String   strPathFile   =   application.getRealPath(request.getRequestURI());  
      out.println("文件的绝对路径:"   +   strPathFile+   "<br/>");  
  out.println(application.getRealPath(request.getRequestURI()));  
      String   strDirPath   =   new   File(application.getRealPath(request.getRequestURI())).getParent();  
      out.println("目录的绝对路径:"   +   strDirPath   +   "<br/>");  
  %>  
   
      //Servlet中  
      //JSP中的application对象就是Servlet中的ServerContext,所以在Servlet中是如此获得  
      //import   java.io.File;  
   
  System.out.println("根目录所对应的绝对路径:"   +   request.getServletPath()   +   "<br/>");  
   
  String   strPathFile   =   request.getSession().getServletContext().getRealPath(request.getRequestURI());  
  System.out.println("文件的绝对路径:"   +   strPathFile   +   "<br/>");  
   
  String   strDirPath   =   new   File(request.getSession().getServletContext().getRealPath(request.getRequestURI())).getParent();  
  System.out.println("目录的绝对路径:"   +   strDirPath   +   "<br/>");  
   
      文件名不能包括以下字符:\/:*?"<>|

2006年07月13日

在设计模式中,Factory Method也是比较简单的一个,但应用非常广泛,EJB,RMI,COM,CORBA,Swing中都可以看到此模式的影子,它是最重要的模式之一.在很多地方我们都会看到xxxFactory这样命名的类,那么,什么是Factory Method,为什么要用这个模式,如何用Java语言来实现该模式,这就是本文想要带给大家的内容.

  基本概念

  Factory Method是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类.当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到Factory Method 模式了.简单说来,Factory Method可以根据不同的条件产生不同的实例,当然这些不同的实例通常是属于相同的类型,具有共同的父类.Factory Method把创建这些实例的具体过程封装起来了,简化了客户端的应用,也改善了程序的扩展性,使得将来可以做最小的改动就可以加入新的待创建的类. 通常我们将Factory Method作为一种标准的创建对象的方法,当发现需要更多的灵活性的时候,就开始考虑向其它创建型模式转化

  简单分析

  图1是Factory Method 模式的结构图,这里提供了一些术语,让我们可以进行更方便的描述:


图1: Factory Method 模式结构

  1.Product: 需要创建的产品的抽象类.
  2.ConcreteProduct: Product的子类,一系列具体的产品.
  3.Creator: 抽象创建器接口,声明返回Product类型对象的Factory Method.
  4.ConcreteCreator: 具体的创建器,重写Creator中的Factory Method,返回ConcreteProduct类型的实例.

  由此可以清楚的看出这样的平行对应关系: Product <====> Creator ; ConreteProduct <====> ConreteCreator

  抽象产品对应抽象创建器,具体产品对应具体创建器.这样做的好处是什么呢?为什么我们不直接用具体的产品和具体的创建器完成需求呢?实际上我们也可以这样做.但通过Factory Method模式来完成,客户(client)只需引用抽象的Product和Creater,对具体的ConcreteProduct和ConcreteCreator可以毫不关心,这样做我们可以获得额外的好处:

  首先客户端可以统一从抽象创建器获取产生的实例,Creator的作用将client和产品创建过程分离开来,客户不用操心返回的是那一个具体的产品,也不用关心这些产品是如何创建的.同时,ConcreteProduct也被隐藏在Product后面,ConreteProduct继承了Product的所有属性,并实现了Product中定义的抽象方法,按照Java中的对象造型(cast)原则,通过ConcreteCreator产生的ConcreteProduct可以自动的上溯造型成Product.这样一来,实质内容不同的ConcreteProduct就可以在形式上统一为Product,通过Creator提供给client来访问.

  其次,当我们添加一个新的ConcreteCreator时,由于Creator所提供的接口不变,客户端程序不会有丝毫的改动,不会带来动一发而牵全身的灾难, 这就是良好封装性的体现.但如果直接用ConcreteProduct和ConcreteCreator两个类是无论如何也做不到这点的. 优良的面向对象设计鼓励使用封装(encapsulation)和委托(delegation),而Factory Method模式就是使用了封装和委托的典型例子,这里封装是通过抽象创建器Creator来体现的,而委托则是通过抽象创建器把创建对象的责任完全交给具体创建器ConcreteCreator来体现的.

  现在,请再回头看看基本概念中的那段话,开始也许觉得生涩难懂,现在是不是已经明朗化了很多.

  下面让我们看看在 Java 中如何实现Factory Method模式,进一步加深对它的认识.

  具体实施

  先说明一点,用Factory Method模式创建对象并不一定会让我们的代码更短,实事上往往更长,我们也使用了更多的类,真正的目的在于这样可以灵活的,有弹性的创建不确定的对象.而且,代码的可重用性提高了,客户端的应用简化了,客户程序的代码会大大减少,变的更具可读性.

  标准实现: 这里我采用Bruce Eckel 用来描述OO思想的经典例子 Shape.这样大家会比较熟悉一些.我完全按照图1中所定义的结构写了下面的一段演示代码.这段代码的作用是创建不同的Shape实例,每个实例完成两个操作:draw和erase.具体的创建过程委托給ShapeFactory来完成.

  1.a 首先定义一个抽象类Shape,定义两个抽象的方法.

abstract class Shape {
  // 勾画shape
  public abstract void draw();
  // 擦去 shape
  public abstract void erase();

  public String name;
  public Shape(String aName){
    name = aName;
  }
}

  1.b 定义 Shape的两个子类: Circle, Square,实现Shape中定义的抽象方法

// 圆形子类
class Circle extends Shape {
  public void draw() {
    System.out.println("It will draw a circle.");
  }
  public void erase() {
    System.out.println("It will erase a circle.");
  }
  // 构造函数
  public Circle(String aName){
    super(aName);
  }
}

// 方形子类
class Square extends Shape {
  public void draw() {
    System.out.println("It will draw a square.");
  }
  public void erase() {
    System.out.println("It will erase a square.");
  }
  // 构造函数
  public Square(String aName){
    super(aName);
  }
}

  1.c 定义抽象的创建器,anOperation调用factoryMethod创建一个对象,并对该对象进行一系列操作.

abstract class ShapeFactory { 
  protected abstract Shape factoryMethod(String aName);
  // 在anOperation中定义Shape的一系列行为
public void anOperation(String aName){
    Shape s = factoryMethod(aName);
    System.out.println("The current shape is: " + s.name);
    s.draw();
    s.erase();
  }
}

  1.d 定义与circle和square相对应的两个具体创建器CircleFactory,SquareFactory,实现父类的methodFactory方法

// 定义返回 circle 实例的 CircleFactory
class CircleFactory extends ShapeFactory {
  // 重载factoryMethod方法,返回Circle对象
  protected Shape factoryMethod(String aName) {
    return new Circle(aName + " (created by CircleFactory)");
  }
}
 
// 定义返回 Square 实例的 SquareFactory
class SquareFactory extends ShapeFactory {
  // 重载factoryMethod方法,返回Square对象
protected Shape factoryMethod(String aName) {
    return new Square(aName + " (created by SquareFactory)");
  }
}

  1.e 测试类:请注意这个客户端程序多么简洁,既没有罗嗦的条件判断语句,也无需关心ConcreteProduct和ConcreteCreator的细节(因为这里我用anOperation封装了Product里的两个方法,所以连Product的影子也没看见,当然把Product里方法的具体调用放到客户程序中也是不错的).

class Main {
  public static void main(String[] args){
    ShapeFactory sf1 = new SquareFactory();
    ShapeFactory sf2 = new CircleFactory();
    sf1.anOperation("Shape one");
    sf2.anOperation("Shape two");
  }

  运行结果如下:
  The current shape is: Shape one (created by SquareFactory)
  It will draw a square.
  It will erase a square.
  The current shape is: Shape two (created by CircleFactory)
  It will draw a circle.
  It will erase a circle.

  参数化的Factory Method: 这种方式依靠指定的参数作为标志来创建对应的实例,这是很常见的一种办法.比如JFC中的BorderFactory就是个很不错的例子. 以下的这个例子是用字符串作为标记来进行判断的,如果参数的类型也不一样,那就可以用到过载函数来解决这个问题,定义一系列参数和方法体不同的同名函数,这里java.util.Calendar.getInstance()又是个极好的例子.参数化的创建方式克服了Factory Method模式一个最显著的缺陷,就是当具体产品比较多时,我们不得不也建立一系列与之对应的具体构造器. 但是在客户端我们必须指定参数来决定要创建哪一个类.

  2.a 我们在第一种方法的基础上进行修改,首先自定义一个的异常,这样当传入不正确的参数时可以得到更明显的报错信息.

class NoThisShape extends Exception {
  public NoThisShape(String aName) {
    super(aName);
  }
}

  2.b 去掉了ShapeFactory的两个子类,改为由ShapeFactory直接负责实例的创建. ShapeFactory自己变成一个具体的创建器,直接用参数化的方法实现factoryMethod返回多种对象.

abstract class ShapeFactory { 
  private static Shape s;
  private ShapeFactory() {}
   
  static Shape factoryMethod(String aName, String aType) throws NoThisShape{
    if (aType.compareTo("square")==0)
      return new Square(aName);
    else if (aType.compareTo("circle")==0)
      return new Circle(aName);
    else throw new NoThisShape(aType); 
  }
 
  // 在anOperation中定义Shape的一系列行为
  static void anOperation(String aName, String aType) throws NoThisShape{
    s = factoryMethod(aName, aType);
    System.out.println("The current shape is: " + s.name);
    s.draw();
    s.erase();
  }
}

  2.c 测试类:这里客户端必须指定参数来决定具体创建哪个类.这个例子里的anOperation是静态函数,可以直接引用.

class Main {
  public static void main(String[] args) throws NoThisShape{
    ShapeFactory.anOperation("Shape one","circle");
    ShapeFactory.anOperation("Shape two","square");
    ShapeFactory.anOperation("Shape three", "delta");
  }
}

  运行结果如下:
  The current shape is: Shape one
  It will draw a circle.
  It will erase a circle.
  The current shape is: Shape two
  It will draw a square.
  It will erase a square.
  Exception in thread "main" NoThisShape: delta
        at ShapeFactory.factoryMethod(ShapeFactory.java:10)
        at ShapeFactory.anOperation(ShapeFactory.java:15)
        at Main.main(Main.java:5)

  动态装载机制:

  有的时候我们会把ConcreteProduct的实例传给创建器作为参数,这种情况下,如果在创建器里完成创建过程,就必须判断参数的具体类型(用instanceof),然后才能产生相应的实例,那么比较好的做法是利用Java的动态装载机制来完成这件事.比如:

  我们得到一个Shape的子类s,但不知道具体是那个子类,就可以利用Class类自带的方法newInstance()得到实例

  return (Shape)s.getClass().newInstance();

  这种方法有兴趣得读者可以自己尝试,限于篇幅,不写具体代码出来了.

  后话:

  看完这篇文章后,相信读者对Factory Method模式有一个比较清楚的了解了.我想说的是,我们不仅应该关心一个具体的模式有什么作用,如何去实现这个模式,更应该透过现象看本质,不但知其然,还要知其所以然.要通过对模式的学习加深对面向对象思想的理解,让自己的认识得到升华.Factory Method模式看似简单,实则深刻.抽象,封装,继承,委托,多态,针对接口编程等面向对象中的概念都在这里得到了一一的体现.只有抓住了它的本质,我们才能够不拘于形式的灵活运用,而不是为了使用模式而使用模式.

2006年07月04日


不知道 JFreeChart 项目组出于什么目的,他们提供的 sample code 里面的例子给人感觉就是乱,同样的结果可以用不同方式、使用不的方法。再加上网上流传的 JFreeChart 旧的使用方法,使刚接触 JFreeChart 的朋友进到 JFreeChart 中不花一些时间很难理出一个头绪来。

前面已经介绍过 JFreeChart 生成饼状图,可能由于上面提到的原因,有些朋友来信和留言希望介绍 JFreeChart 如何生成柱状图。抄袭别人一句话:Help others as well as to help myself。这次我们介绍使用 JFreeChart 生成柱状图,首先从一个最简单的例子开始。

 

一 最简单的例子

为了降低门槛,让大家心理有个底,先介绍一个简单的不能再简单的例子,图片中的各类属性都采用默认值。

 

<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="org.jfree.chart.ChartFactory,
                 org.jfree.chart.JFreeChart,
                 org.jfree.chart.plot.PlotOrientation ,
                 org.jfree.chart.servlet.ServletUtilities,
                 org.jfree.data.DefaultCategoryDataset"%>
<%
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.addValue (300, "广州", "苹果");
dataset.addValue(200, "广州", "梨子");
dataset.addValue(500, "广州", "葡萄");
dataset.addValue(340, "广州", "芒果");
dataset.addValue (280, "广州", "荔枝");

JFreeChart chart = ChartFactory.createBarChart3D("水果销量统计图",
                  "水果",
                  "销量",
                  dataset,
                  PlotOrientation.VERTICAL,
                  false,
                  false,
                  false);

String filename = ServletUtilities.saveChartAsPNG(chart, 500, 300, null, session);
String graphURL = request.getContextPath() + "/servlet/DisplayChart?filename=" + filename;
%>
<img src="<%= graphURL %>" width=500 height=300 border=0 usemap="#<%= filename %>">

这个 JSP 程序运行的结果如下图

 


图 一

 

二 柱状图高级特性

上面的程序简单,但生成的柱状图也很简单。更多的时候,我们可能需要不同的效果。 org.jfree.chart.ChartFactory 这个工厂类有 createBarChart, createStackedBarChart, createBarChart3D, createStackedBarChart3D 这几个工厂方法创建不同类型的柱状图。关于这四个方法的 JFreeChart 的 Java Doc API 文档有详细说明,比较重要的是 PlotOrientation.VERTICAL 让平行柱垂直显示,而 PlotOrientation.HORIZONTAL 则让平行柱水平显示。

几个对柱状图影响较大的几个类,它们分别是:
org.jfree.chart.axis.CategoryAxis
org.jfree.chart.axis.ValueAxis
org.jfree.chart.renderer.BarRenderer
org.jfree.chart.renderer.BarRenderer3D

我们还是以实例来说明这几个类,先来假设一个需要统计的数据表:

  北京 上海 广州 成都 深圳
苹果 672 766 223 540 126
梨子 325 521 210 340 106
葡萄 332 256 523 240 526

根据上表数据,首先构造 CategoryDataset, 这里不再使用上面简单例子里面的 DefaultCategoryDataset 类,而是 DatasetUtilities 更有效的构造 CategoryDataset,如下列代码:

 

double[][] data = new double[][] {{672, 766, 223, 540, 126}, {325, 521, 210, 340, 106}, {332, 256, 523, 240, 526}};
String[] rowKeys = {"苹果","梨子","葡萄"};
String[] columnKeys = {"北京","上海","广州","成都","深圳"};
CategoryDataset dataset = DatasetUtilities.createCategoryDataset(rowKeys, columnKeys, data);

用上面的 dataset 生成的 3D 柱状图


图 二

org.jfree.chart.axis.CategoryAxis

 

CategoryAxis domainAxis = plot.getDomainAxis();
//设置 columnKey 是否垂直显示
domainAxis.setVerticalCategoryLabels(true);
//设置距离图片左端距离
domainAxis.setLowerMargin(0.1);
//设置距离图片右端距离
domainAxis.setUpperMargin (0.1);
//设置 columnKey 是否间隔显示
domainAxis.setSkipCategoryLabelsToFit(true);
plot.setDomainAxis(domainAxis);

上面代码产生的效果如下图,注意与图二的区别。


图 三

org.jfree.chart.axis.ValueAxis

 

ValueAxis rangeAxis = plot.getRangeAxis();
//设置最高的一个柱与图片顶端的距离
rangeAxis.setUpperMargin(0.15);
//设置最低的一个柱与图片底端的距离
//rangeAxis.setLowerMargin(0.15);
plot.setRangeAxis(rangeAxis);

上面代码产生的效果如下图,注意与图二的区别。


图 四

org.jfree.chart.renderer.BarRenderer3D

 

BarRenderer3D renderer = new BarRenderer3D();
renderer.setBaseOutlinePaint(Color.BLACK);
//设置 Wall 的颜色
renderer.setWallPaint(Color.gray);
//设置每种水果代表的柱的颜色
renderer.setSeriesPaint(0, new Color(0, 0, 255));
renderer.setSeriesPaint(1, new Color(0, 100, 255));
renderer.setSeriesPaint(2, Color.GREEN);
//设置每种水果代表的柱的 Outline 颜色
renderer.setSeriesOutlinePaint(0, Color.BLACK);
renderer.setSeriesOutlinePaint(1, Color.BLACK );
renderer.setSeriesOutlinePaint(2, Color.BLACK);
//设置每个地区所包含的平行柱的之间距离
renderer.setItemMargin(0.1);
//显示每个柱的数值,并修改该数值的字体属性
renderer.setItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setItemLabelFont (new Font("黑体",Font.PLAIN,12));
renderer.setItemLabelsVisible(true);
 

上面代码产生的效果如下图,注意与图二的区别。


图 五

补充两个有用的方法

补充 org.jfree.chart.plot.CategoryPlot 的两个方法,这两个方法对所有类型的图表都有作用,因为在前面没有介绍,这里补充一下。

 

//设置地区、销量的显示位置
plot.setDomainAxisLocation(AxisLocation.TOP_OR_RIGHT);
plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);

上面代码产生的效果如下图,注意与图二的区别。


图 六

三 完整范例

前面都是一些代码片段,现在把这些片段组合成一个完整范例。

 

<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="java.awt.Color,
                java.awt.Font,
                org.jfree.chart.ChartFactory,
                 org.jfree.chart.JFreeChart,
                org.jfree.chart.plot.PlotOrientation,
                org.jfree.chart.servlet.ServletUtilities,
                org.jfree.data.CategoryDataset,
                org.jfree.data.DatasetUtilities ,
                org.jfree.chart.plot.CategoryPlot,
                org.jfree.chart.axis.CategoryAxis,
                org.jfree.chart.axis.ValueAxis,
                org.jfree.chart.renderer.BarRenderer3D,
                org.jfree.chart.labels.StandardCategoryItemLabelGenerator,
                org.jfree.chart.axis.AxisLocation"%>
<%
double[][] data = new double[][] {{672, 766, 223, 540, 126},{325, 521, 210, 340, 106},{332, 256, 523, 240, 526}};
String[] rowKeys = {"苹果","梨子","葡萄"};
String[] columnKeys = {"北京","上海","广州","成都","深圳"};
CategoryDataset dataset = DatasetUtilities.createCategoryDataset (rowKeys, columnKeys, data);

JFreeChart chart = ChartFactory.createBarChart3D("水果销量图统计",
                  null,
                  null,
                  dataset,
                  PlotOrientation.VERTICAL ,
                  true,false,false);
chart.setBackgroundPaint(Color.WHITE);
CategoryPlot plot = chart.getCategoryPlot();

CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setVerticalCategoryLabels (false);
plot.setDomainAxis(domainAxis);

ValueAxis rangeAxis = plot.getRangeAxis();
//设置最高的一个 Item 与图片顶端的距离
rangeAxis.setUpperMargin(0.15);
//设置最低的一个 Item 与图片底端的距离
rangeAxis.setLowerMargin(0.15);
plot.setRangeAxis(rangeAxis);

BarRenderer3D renderer = new BarRenderer3D();
renderer.setBaseOutlinePaint(Color.BLACK);
//设置 Wall 的颜色
renderer.setWallPaint(Color.gray);
//设置每种水果代表的柱的颜色
renderer.setSeriesPaint (0, new Color(0, 0, 255));
renderer.setSeriesPaint(1, new Color(0, 100, 255));
renderer.setSeriesPaint(2, Color.GREEN);
//设置每个地区所包含的平行柱的之间距离
renderer.setItemMargin(0.1);
//显示每个柱的数值,并修改该数值的字体属性
renderer.setItemLabelGenerator (new StandardCategoryItemLabelGenerator());
renderer.setItemLabelsVisible(true);
plot.setRenderer(renderer);

//设置柱的透明度
plot.setForegroundAlpha(0.6f);
//设置地区、销量的显示位置
plot.setDomainAxisLocation(AxisLocation.TOP_OR_RIGHT );
plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);

String filename = ServletUtilities.saveChartAsPNG(chart, 500, 300, null, session);
String graphURL = request.getContextPath() + "/servlet/DisplayChart?filename=" + filename;
%s>
<img src="<%= graphURL %>" width=500 height=300 border=0 usemap="#<%= filename %>">
 

看看程序运行的结果吧:


图 七

 

三 总结

我只介绍了少量的方法,更多的请参考 JFreeChart 的 Java Doc API 文档和 Sample Code。文中的有些关于图表的术语不知道该如何正确的表达,如果你有任何关于本文的想法,可以与我联系 wayne@sentom.net.

2006年07月03日

  最近,自己开始学习JavaMail API,为了练习,于是写了一个发送邮件的程序,希望各位JAVA爱好者,能提一些宝贵的意见,下面就是我写的示例程序代码:
    

 /**  *=========================================================  * @Programing Name: SendMail.java  * @Author:          Wang weiPing  * @Program Date:    2004-02-02  * @E-Mail:          wangwp@mailer.com.cn  * @MobilePhone:     13810862095  * @Address:         北京市海淀区知春路23号863软件园903室  ********************************************  *include classes: *SendMail.class       *   *                 *MailAuthenticator.class *  *                 *MailSendHtml.class      *  *                 *MailSendText.class      *  ********************************************  *=========================================================  */
  1. package chundiwebmail;
  2. import java.util.Properties;
  3. import java.util.Date;
  4. import java.util.ArrayList;
  5. import javax.mail.*;
  6. import javax.mail.internet.*;
  7. import javax.activation.*;
  8. abstract class SendMail{
  9.     
  10.     protected BodyPart messageBodyPart = null;
  11.     protected Multipart multipart = null;
  12.     protected MimeMessage mailMessage = null;
  13.     protected Session mailSession = null;
  14.     protected Properties mailProperties = System.getProperties();
  15.     protected InternetAddress mailFromAddress = null;
  16.     protected InternetAddress mailToAddress = null;
  17.     protected MailAuthenticator authenticator = null;
  18.     protected String mailSubject ="";
  19.     protected Date mailSendDate = null;
  20.     
  21.     public SendMail(String smtpHost,String username,String password){
  22.         mailProperties.put("mail.smtp.host",smtpHost);
  23.         mailProperties.put("mail.smtp.auth","true"); //设置smtp认证,很关键的一句
  24.         authenticator = new MailAuthenticator(username,password);
  25.         mailSession = Session.getDefaultInstance(mailProperties,authenticator);
  26.         mailMessage = new MimeMessage(mailSession);
  27.         messageBodyPart = new MimeBodyPart();
  28.     }
  29.     //设置邮件主题
  30.     public void setSubject(String mailSubject)throws MessagingException{
  31.         this.mailSubject = mailSubject;
  32.         mailMessage.setSubject(mailSubject);
  33.     }
  34.     //所有子类都需要实现的抽象方法,为了支持不同的邮件类型
  35.     protected abstract void setMailContent(String mailContent)throws MessagingException;
  36.     
  37.     //设置邮件发送日期
  38.     public void setSendDate(Date sendDate)throws MessagingException{
  39.         this.mailSendDate = sendDate;
  40.         mailMessage.setSentDate(sendDate);
  41.     }
  42.     //设置邮件发送附件
  43.     public void setAttachments(String attachmentName)throws MessagingException{
  44.         messageBodyPart = new MimeBodyPart();
  45.         DataSource source = new FileDataSource(attachmentName);
  46.         messageBodyPart.setDataHandler(new DataHandler(source));
  47.         int index = attachmentName.lastIndexOf('\\');
  48.         String attachmentRealName = attachmentName.substring(index+1);
  49.         messageBodyPart.setFileName(attachmentRealName);
  50.         multipart.addBodyPart(messageBodyPart);
  51.     }
  52.     
  53.     //设置发件人地址
  54.     public void setMailFrom(String mailFrom)throws MessagingException{
  55.         mailFromAddress = new InternetAddress(mailFrom);
  56.         mailMessage.setFrom(mailFromAddress);
  57.     }
  58.     
  59.     //设置收件人地址,收件人类型为to,cc,bcc(大小写不限)
  60.     public void setMailTo(String[] mailTo,String mailType)throws Exception{
  61.         for(int i=0;i<mailTo.length;i++){
  62.             mailToAddress = new InternetAddress(mailTo[i]);
  63.             if(mailType.equalsIgnoreCase("to")){
  64.                 mailMessage.addRecipient(Message.RecipientType.TO,mailToAddress);
  65.             }
  66.             else if(mailType.equalsIgnoreCase("cc")){
  67.                 mailMessage.addRecipient(Message.RecipientType.CC,mailToAddress);
  68.             }
  69.             else if(mailType.equalsIgnoreCase("bcc")){
  70.                 mailMessage.addRecipient(Message.RecipientType.BCC,mailToAddress);
  71.             }
  72.             else{
  73.                 throw new Exception("Unknown mailType: "+mailType+"!");
  74.             }
  75.         }
  76.     }
  77.     
  78.     //开始投递邮件
  79.     public void sendMail()throws MessagingException,SendFailedException{
  80.         if(mailToAddress == null){
  81.             System.out.println("请你必须你填写收件人地址!");
  82.             System.exit(1);
  83.         }
  84.         else{
  85.             mailMessage.setContent(multipart);
  86.             System.out.println("正在发送邮件,请稍候.......");
  87.             Transport.send(mailMessage);
  88.             System.out.println("恭喜你,邮件已经成功发送!");
  89.         }
  90.     }
  91. }
  92. //现在的大部分的邮件服务器都要求有身份验证,所以需要此类实现验证功能
  93. class MailAuthenticator extends Authenticator{
  94.     private String username = null;
  95.     private String userpasswd = null;
  96.     public MailAuthenticator(){}
  97.     public MailAuthenticator(String username,String userpasswd){
  98.         this.username = username;
  99.         this.userpasswd = userpasswd;
  100.     }
  101.     
  102.     public void setUserName(String username){
  103.         this.username = username;
  104.     }
  105.     public void setPassword(String password){
  106.         this.userpasswd = password;
  107.     }
  108.     public PasswordAuthentication getPasswordAuthentication(){
  109.         return new PasswordAuthentication(username,userpasswd);
  110.     }
  111. }
  112. //为了使此邮件发送程序能够支持可扩展性,把发送邮件的类型放到子类中来,
  113. //以便支持更多的邮件类型,比如语音邮件,视频邮件等......
  114. //1.发送纯文本文件的的子类MailSendText.class
  115. class MailSendText extends SendMail{
  116.     public MailSendText(String smtpHost,String username,String password){
  117.         super(smtpHost,username,password);
  118.         multipart = new MimeMultipart();
  119.     }
  120.     public void setMailContent(String mailContent)throws MessagingException{
  121.         messageBodyPart.setText(mailContent);
  122.         multipart.addBodyPart(messageBodyPart);
  123.     }
  124. }
  125. //2.发送html格式的子类MailSendHtml.class
  126. class MailSendHtml extends SendMail{
  127.     private ArrayList arrayList1 = new ArrayList();
  128.     private ArrayList arrayList2 = new ArrayList();
  129.     public MailSendHtml(String smtpHost,String username,String password){
  130.         super(smtpHost,username,password);
  131.         multipart = new MimeMultipart("related");
  132.     }
  133.     public void setMailContent(String mailContent)throws MessagingException{
  134.         String htmlContent = getContent("<img src=",mailContent);
  135.         System.out.println(htmlContent);//1
  136.         messageBodyPart.setContent(htmlContent,"text/html");
  137.         multipart.addBodyPart(messageBodyPart);
  138.         //调用处理html文件中的图片方法
  139.         processHtmlImage(mailContent);
  140.     }
  141.     //处理html页面上的图片方法如下:
  142.     private void processHtmlImage(String mailContent)throws MessagingException{
  143.          for(int i=0;i<arrayList1.size();i++){
  144.              messageBodyPart = new MimeBodyPart();
  145.              DataSource source = new FileDataSource((String)arrayList1.get(i));
  146.              messageBodyPart.setDataHandler(new DataHandler(source));
  147.              String contentId = "<"+(String)arrayList2.get(i)+">";
  148.              System.out.println(contentId);
  149.              messageBodyPart.setHeader("Content-ID",contentId);
  150.              messageBodyPart.setFileName((String)arrayList1.get(i));
  151.              multipart.addBodyPart(messageBodyPart);
  152.          }
  153.     }
  154.     //处理要发送的html文件,主要是针对html文件中的图片
  155.     private String getContent(String searchString,String mailContent){
  156.         String afterReplaceStr = "";
  157.         for(int i=0;i<mailContent.length();i++){
  158.             for(int j=i+1;j<mailContent.length();j++){
  159.                 String searResult = mailContent.substring(i,j);
  160.                 if(searResult.equalsIgnoreCase(searchString)){
  161.                     String subString = mailContent.substring(j);
  162.                     int flagIndex = subString.indexOf('>');
  163.                     String replaceStr = subString.substring(1,flagIndex-1);
  164.                     if(replaceStr.indexOf("http://") != -1){
  165.                         System.out.println(replaceStr);
  166.                         System.out.println("不需要处理图片!");
  167.                     }
  168.                     else{
  169.                         arrayList1.add(replaceStr);
  170.                     }
  171.                 }
  172.             }
  173.         }
  174.         //在html文件中用"cid:"+Content-ID来替换原来的图片链接
  175.         for(int m=0;m<arrayList1.size();m++){
  176.             arrayList2.add(createRandomStr());
  177.             String addString = "cid:"+(String)arrayList2.get(m);
  178.             afterReplaceStr = mailContent.replaceAll((String)arrayList1.get(m),addString);
  179.         }
  180.         return afterReplaceStr;
  181.     }
  182.     //产生一个随机字符串,为了给图片设定Content-ID值
  183.     private String createRandomStr(){
  184.         char []randomChar = new char[8];
  185.         for(int i=0;i<8;i++){
  186.             randomChar[i]=(char)(Math.random()*26+'a');
  187.         }
  188.         String replaceStr = new String(randomChar);
  189.         return replaceStr;
  190.     }
  191. }
  192. /*=============================================================================
  193. 下面是此电子邮件发送程序的简单测试程序:
  194.  */
  195. package chundiwebmail;
  196. import java.util.Date;
  197. public class SendMailTest{
  198.     public static void main(String args[]){
  199.         String []toAddress = {"wwp_1124@yahoo.com.cn"};
  200.         SendMail sendmail = new MailSendHtml("smtp.mail.yahoo.com.cn","wwp_1124","3061643");
  201.         try{
  202.             sendmail.setSubject("HelloWorld");
  203.             sendmail.setSendDate(new Date());
  204.             //String plainText = "Welcome to use this Mail-Send program!";
  205.             String htmlText = "<H1>HelloWorld</H1>" +
  206.                     "<img src=\"image.jpg\">"+
  207.                     "<img src=\"http://www.yahoo.com.cn/image1.jpg\">";
  208.             //sendmail.setMailContent(plainText);
  209.             sendmail.setMailContent(htmlText);
  210.             //sendmail.setAttachments("D:\\wwpdev\\attach.jsp");
  211.             sendmail.setMailFrom("wangwp@mailer.com.cn");
  212.             sendmail.setMailTo(toAddress,"to");
  213.             sendmail.setMailTo(toAddress,"cc");
  214.             sendmail.sendMail();
  215.         }
  216.         catch(Exception ex){ ex.printStackTrace();}
  217.     }
  218. }
系统需求:
数据库:sql server 2000 sp3
驱动程序:sql server driver for jdbc
java版本:jdk1.2以上

1:我们首先使用jdbc-odbc桥来实现数据库的连接,这个相对简单
首先使用sql server 企业管理其建立一个数据库test,并建立一个简单的表 first_table
建立odbc数据源 ,各步骤采用默认操作即可

编写一个简单的测试程序,该程序主要实现数据库的连接,以及一个简单的sql操作 ,代码如下:

/*********************************************** /* /*DbTest.java /* /******************************************* */

 import java.sql.*;

 public class DbTest {        Connection con;    Statement  sta;    ResultSet  rs;        String driver;    String url;    String user;    String pwd;
    public DbTest()    {        driver = "sun.jdbc.odbc.JdbcOdbcDriver";        url    = "jdbc:odbc:store_manager";        user   = "share";        pwd    = "share";
        init();    }    public void init()    {       try{           Class.forName(driver);           System.out.println("driver is ok");           con = DriverManager.getConnection

(url,user,pwd);           System.out.println("conection is ok");           sta = con.createStatement();           rs  = sta.executeQuery("select * from room");           while(rs.next())
            System.out.println(rs.getInt("roomNum"));        }catch(Exception e)        {           e.printStackTrace();        }    }        public static void main(String args  [])//自己替换[]
    {       new DbTest();    } }

运行结果如下:

driver is ok
conection is ok
1001
1002
1003
1004
1005
1006
Press any key to continue…

顺利通过测试

2,我们这次不通过odbc桥来操作数据库,我们采用sql server driver 来实现对sqlserver数据库的操作, 这将是我们这篇文章的重点,因为jdbc-odbc桥是一种常见的操作windows系统数据库的常用方法,但它存在的缺点很多,所以现在很多开发者都侧重于使用sqlserver driver来操作,在这里我们通过一步步的调试,来加深读者对这种连接的理解

在通常的理解下,只要我们装了sqlserver driver for jdbc 我们便可进行数据库编程,事实则不然,首先我们看下边的代码:

/*********************************************** /* /*DbTest.java /* /******************************************* */

 import java.sql.*;

 public class DbTest {        Connection con;    Statement  sta;    ResultSet  rs;        String driver;    String url;    String user;    String pwd;
    public DbTest()    {        driver = "com.microsoft.jdbc.sqlserver.SQLServerDriver";;        url    = "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName
 =StoreManager";        user   = "sa";        pwd    = "potsmart10";        init();    }    public void init()    {       try{          Class.forName(driver);

          System.out.println("driver is ok");          con = DriverManager.getConnection(url,user,pwd);          System.out.println("conection is ok");           sta = con.createStatement();
           rs  = sta.executeQuery("select * from room");           while(rs.next())            System.out.println(rs.getInt("roomNum"));        }catch(Exception e)        {           
e.printStackTrace();        }    }        public static void main(String args [])//自己替换[]    {       new DbTest();    } }

这段代码跟上变得代码是一样的,差别在于驱动,还有url,这是在使用sqlserver driver for jdbc 中遇到的困惑

按道理讲,上边这段代码应该没错,可首先我们来看一下,如果sqlser服务器没有升级到sp3(在使用jdbc时,如果系统是xp或者2003务必要把sqlserver 升级到sp3,往上到处都有下的),我们看看运行结果

driver is ok
java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC]Error establis
hing socket.
at com.microsoft.jdbc.base.BaseExceptions.createException(Unknown Source
)
at com.microsoft.jdbc.base.BaseExceptions.getException(Unknown Source)
at com.microsoft.jdbc.base.BaseExceptions.getException (Unknown Source)
at com.microsoft.jdbc.sqlserver.tds.TDSConnection.<init>(Unknown Source)

at com.microsoft.jdbc.sqlserver.SQLServerImplConnection.open(Unknown Sou
rce)
at com.microsoft.jdbc.base.BaseConnection.getNewImplConnection (Unknown S
ource)
at com.microsoft.jdbc.base.BaseConnection.open(Unknown Source)
at com.microsoft.jdbc.base.BaseDriver.connect(Unknown Source)
at java.sql.DriverManager.getConnection(DriverManager.java:523)
at java.sql.DriverManager.getConnection(DriverManager.java:171)
at DbTest.init(DbTest.java:32)
at DbTest.<init>(DbTest.java:25)
at DbTest.main(DbTest.java:46)
Press any key to continue…

出现上边错误的主要原因是默认的数据库服务器端口 1433没有打开,无法直接连接 。

如果升级到sp3则这个问题可以结决,我们再来看看升级之后,程序运行的结果

driver is ok
conection is ok
java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC][SQLServer]对
象名 ‘room’ 无效。
at com.microsoft.jdbc.base.BaseExceptions.createException (Unknown Source
)
at com.microsoft.jdbc.base.BaseExceptions.getException(Unknown Source)
at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processErrorToken(Unknown
Source)
at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processReplyToken (Unknown
Source)
at com.microsoft.jdbc.sqlserver.tds.TDSExecuteRequest.processReplyToken(
Unknown Source)
at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processReply(Unknown Sour
ce)
at com.microsoft.jdbc.sqlserver.SQLServerImplStatement.getNextResultType
(Unknown Source)
at com.microsoft.jdbc.base.BaseStatement.commonTransitionToState(Unknown
Source)
at com.microsoft.jdbc.base.BaseStatement.postImplExecu
te(Unknown Source)

at com.microsoft.jdbc.base.BaseStatement.commonExecute (Unknown Source)
at com.microsoft.jdbc.base.BaseStatement.executeQueryInternal(Unknown So
urce)
at com.microsoft.jdbc.base.BaseStatement.executeQuery(Unknown Source)
at DbTest.init(DbTest.java:35)
at DbTest.<init>( DbTest.java:25)
at DbTest.main(DbTest.java:46)
Press any key to continue…

在这儿,用户已经登陆上去,但是却不能访问里边的数据表,出现这个问题的原因在于sa用户为系统用户
它虽然能够登陆数据库,但是storeManager数据库里边却没有这个用户的访问权限,所以,我们现在为这个数据库重新建立一个用户
share ,建立过程如下:在storeManager数据库中选重用户 —〉新建用户 — 〉名称选择(这一步中有两个关键点 1:身份验证选sql身份验证,默认数据库选StoreManager)-〉建立新教色share ,此时更改程序,将用户登陆名和密码修改一下,重新运行程序

driver is ok
conection is ok
1001
1002
1003
1004
1005
1006
Press any key to continue…

这次顺利通过测试

总结:sqlserve 和jdbc 的融合问题,关键涉及到sp3补丁(端口开放)还有用户问题,解决这两个问题之后,剩余的便是sqlserver 操作问题了,还有一点在远程操作的时候,要把sqlserver 组设置一下,在安全性里边亦将身份验证更改为sqlserve 验证即可

Eclipse 运行命令行参数大全  
  包括英文版本和中文版本两种的说明, 特别需要值得一提的是那个 -nl 参数, 可以指定程序启动时所使用的语言. 例如:
eclipse -nl en_US
将启动英文语言, 这个特性在安装了国际化语言包以后特别有用, 可以方便的切换各个语言的版本. 注意 IBM WSAD v5.1 也支持这个功能. 

运行 Eclipse
将 Eclipse 驱动程序安装(解压缩)到某个目录(例如,c:\eclipse)中之后,通过运行顶级安装目录中的 Eclipse 可执行文件来启动"工作台"。在 Windows 系统上,该可执行文件称为  eclipse.exe,而在 Linux 系统上称为 eclipse。注意:下列讨论描述 Windows 系统上的设置。Linux 上的设置是相似的。

如果您没有另行指定,则平台将缺省工作区目录创建为可执行文件的兄弟目录(例如 c:\eclipse\workspace)。此工作区目录用作项目的缺省内容区,还用于保存任何必需的元数据。要进行共享安装或多工作区安装,应明确指出工作区的位置而不是使用缺省值。有两种控制工作区位置的方法:使用当前工作目录或使用 -data 命令行自变量。

将工作区位置设置为在当前工作目录内
在此方案中,工作区位置将是当前工作目录中称为 workspace 的目录。

实现此目的最容易的方法可能是使用下列步骤来创建快捷方式:

导航到 Windows 资源管理器中的 eclipse.exe 并使用右键拖动来创建 eclipse.exe 的快捷方式。 
编辑快捷方式的属性,以使启动位置:字段标识工作区位置的父目录(例如,c:\users\robert)。 
关闭属性对话框并双击快捷方式(如果提供的目录为 c:\users\robert,则工作区位置将为 c:\users\robert\workspace)。 
当然,您也可以使用命令提示符(通过将目录切换为工作区父目录然后运行 eclipse.exe)来获得同样的效果。

使用 -data 设置工作区的特定位置
要使用 -data 命令行自变量,只要将 -data your_workspace_location(例如,-data c:\users\robert\myworkspace)添加至快捷方式属性中的目标字段或显式地将它包括在命令行上

使用 -vm 设置 java VM
建议显式指定在运行 Eclipse 时要使用哪个 Java VM。使用 -vm 命令行自变量(例如,-vm c:\jre\bin\javaw.exe)可以实现此目的。如果不使用 -vm,则 Eclipse 将使用在 O/S 路径上找到的一个 Java VM。当安装其它产品时,它们可更改您的路径,导致在下一次启动 Eclipse 时使用另一 Java VM。

运行 Eclipse 中的高级主题
Eclipse 可执行文件及平台本身提供了人们感兴趣的开发或调试 Eclipse 各部件的许多执行选项。运行 Eclipse 可执行文件的一般格式是:

eclipse [platform options] [-vmargs [Java VM arguments]]
Eclipse 启动参数  命令 描述  原因 
-arch architecture
 定义 Eclipse 平台在其上运行的处理器体系结构。Eclipse 平台通常使用 Java  os.arch 属性的常用值来计算最佳设置。如果在此处指定该项,则这是 Eclipse 平台使用的值。此处指定的值可作为 BootLoader.getOSArch() 用于插件。示例值有:"x86"、"sparc"、"PA-RISC"和"ppc"。 2.0 
-application applicationId
 要运行的应用程序。应用程序由向 org.eclipse.core.runtime.applications  扩展点提供扩展的插件来声明。通常不需要此自变量。如果指定了此项,则该值会覆盖配置提供的值。如果不指定此项,则会运行"Eclipse 工作台"。 1.0 
-boot bootJarURL
 (建议不使用;用 -configuration 代替;支持 1.0 兼容)。Eclipse 平台的引导插件代码(boot.jar)的位置,表示为 URL。如果指定此项,则会用它来为装入 Eclipse 平台引导程序类装入器的类装入器设置类路径。仅当更改  startup.jar 和 boot.jar 的相对位置时才需要它。注意,不允许使用相对 URL。  *1.0 
-classloaderproperties [file]
 如果指定的话,则使用给定位置处的类装入器属性文件来激活平台类类装入器增强。文件自变量可以是文件路径或 URL。注意,不允许使用相对 URL。单击此处以获得更多详细信息。 2.0.2 
-configuration configurationFileURL
 Eclipse 平台配置文件的位置,表示为 URL。配置文件确定 Eclipse 平台、可用插件集和主要功能部件的位置。注意,不允许使用相对 URL。当安装或更新 Eclipse 平台时配置文件被写至此位置。  2.0 
-consolelog
 将 Eclipse 平台的错误日志镜像到用来运行 Eclipse 的控制台。与 -debug 组合时很方便使用。 1.0 
-data workspacePath
 要运行 Eclipse 平台的工作区的路径。工作区位置也是项目的缺省位置。相对于从中启动 eclipse 的目录来解释相对路径。 1.0 
-debug [optionsFile]
 将平台置于调试方式,并从给定位置处的文件装入调试选项(如果指定的话)。此文件指示哪些调试点可用于插件以及是否已启用它们。如果未给出文件位置,则平台在启动 eclipse 的目录中查找称为".options"的文件。URL 和文件系统路径都可作为文件位置。  1.0 
-dev [classpathEntries]
 将平台置于开发方式。将可选类路径条目(用逗号分隔的列表)添加至每个插件的运行时类路径。例如,当工作区包含要开发的插件时,指定 -dev bin 会为每个插件项目的名为 bin 的目录添加类路径条目,允许在其中存储最新生成的类文件。除去了冗余或不存在的类路径条目。 1.0 
-endsplash params
 用于在 Eclipse 平台启动并运行时关闭闪屏的内部选项。此选项在闪屏处理链中不同的位置有不同的语法和语义。  2.0 
-feature featureId
 主要功能部件的标识。主要功能部件为 Eclipse 的已启动实例提供了产品个性,并确定使用的产品定制信息。 2.0 
-keyring keyringFilePath
 磁盘上授权数据库(或"密钥环"文件)的位置。此自变量必须与 -password 选项配合使用。相对于从中启动 eclipse 的目录来解释相对路径。 1.0 
-nl locale
 定义 Eclipse 平台在其上运行的语言环境的名称。Eclipse 平台通常自动计算最佳设置。如果在此处指定该项,则这是 Eclipse 平台使用的值。此处指定的值可作为 BootLoader.getNL() 用于插件。示例值有:"en_US"和"fr_FR_EURO"。 2.0 
-nolazyregistrycacheloading
 取消激活装入优化的平台插件注册表高速缓存。缺省情况下,仅当需要时才从注册表高速缓存(可用时)中装入扩展的配置元素,以减少内存占用。此选项将在启动时强制完全装入注册表高速缓存。  2.1 
-noregistrycache
 绕过读写内部插件注册表高速缓存文件。 2.0 
-nosplash
 运行平台而不显示闪屏。 1.0 
-os operatingSystem
 定义 Eclipse 平台在其上运行的操作系统。Eclipse 平台通常使用 Java os.name 属性的常用值来计算最佳设置。如果在此处指定该项,则这是 Eclipse 平台使用的值。此处指定的值可作为  BootLoader.getOS() 用于插件,并用于解析插件清单文件中提及的路径中 $os$ 变量的出现。示例值有:"win32"、"linux"、"hpux"、"solaris"和"aix"。 1.0 
-password password
 授权数据库的密码。与 -keyring 选项配合使用。 1.0 
-perspective perspectiveId
 启动时要在活动工作台窗口中打开的透视图。如果没有指定该参数,则将打开关闭时活动的透视图。 1.0 
-plugincustomization   propertiesFile
 包含插件首选项缺省设置的属性文件的位置。这些缺省设置覆盖在主要功能部件中指定的缺省设置。相对于从中启动 eclipse 的目录来解释相对路径。 2.0 
-plugins pluginsFileURL
 (建议不使用;用 -configuration 代替;支持  1.0 兼容)。 指定 Eclipse 平台查找插件的文件的位置,表示为 URL。该文件为属性文件格式,其中键是任意用户定义名称,值是指向 plugin.xml 文件的显式路径或指向包含插件的目录的路径的用逗号分隔的列表。注意,不允许使用相对 URL。如果指定此项,则此选项会导致创建适当的临时配置。 *1.0 
-refresh 
 启动时执行工作区的全局刷新的选项。这将使从上次平台运行以来在文件系统中所做的任何更改一致。 1.0  
-showlocation 
 用于在窗口标题栏中显示工作区的位置的选项。在发行版 2.0 中,此选项仅与 -data 命令行自变量一起使用。 2.0 
-showsplash params
 用于显示闪屏(由可执行的 Eclipse 平台启动器执行)的内部选项。此选项在闪屏处理链中不同的位置有不同的语法和语义。 2.0 
-vm vmPath
 要用来运行 Eclipse 平台的"Java 运行时环境"(JRE)的位置。如果不指定此项,则 JRE 位于 jre(它是 Eclipse 可执行文件的兄弟目录)。相对于从中启动 eclipse 的目录来解释相对路径。  1.0 
-ws windowSystem
 定义 Eclipse 平台在其上运行的 Windows 系统。Eclipse 平台通常使用 Java os.name 属性的常用值来计算最佳设置。如果在此处指定该项,则这是 Eclipse 平台使用的值。此处指定的值可作为 BootLoader.getWS() 用于插件、用于配置 SWT 以及用于解析插件清单文件中提及的路径中 $ws$ 变量的出现。示例值有:"win32"、"motif"和"gtk"。  1.0 

将 -vmargs 条目后面的所有自变量(但不包括 -vmargs)作为虚拟机自变量(即,在要运行的类的前面)直接传递到所指示的 Java VM。注意:如果 Eclipse 启动在 Java vm 自变量(-vmargs)之后提供的自变量(例如,-data),则 Eclipse 将不会启动并且您将接收到"JVM 已终止。出口代码为 1"的错误。

在不同的 VM 上运行 
在 J9 上运行 Eclipse
当在 J9 版本 1.5 上运行 Eclipse 时,建议使用以下 VM 选项: 

eclipse.exe [eclipse arguments] -vm path_to_j9w.exe             -vmargs -ms:32 -mm:2048 -mo:32768 -moi:32768 -mca:32 -mco:128 -mx:2000000
当在 J9 版本 2.0 上运行 Eclipse 时,J9W 选择的缺省自变量应为合适的选项。但是,要覆盖 Eclipse 可执行文件以内部方式自动设置的参数,必须指定 -vmargs 不带任何参数,如下所示: 

eclipse.exe [eclipse arguments] -vm path_to_j9w.exe -vmargs
有关进一步信息,参考 J9 VM 文档和帮助。

在 IBM Developer Kit, Java(TM) Technology Edition VM 上运行 Eclipse
IBM Developer Kit, Java(TM) Technology Edition 1.3 Linux 的缺省 VM 设置适合进行初期研究工作,但在进行大型开发时是不够的。对于大型开发,应修改 VM 自变量以使有更多的堆可用。例如,下列设置将允许 Java 堆增大为 256MB: