posts - 73,  comments - 55,  trackbacks - 0
JAVA中的传递都是值传递吗?有没有引用传递呢?

在回答这两个问题前,让我们首先来看一段代码:
Java代码 复制代码
  1. public class ParamTest {   
  2.     // 初始值为0   
  3.     protected int num = 0;   
  4.   
  5.     // 为方法参数重新赋值   
  6.     public void change(int i) {   
  7.          i = 5;   
  8.      }   
  9.   
  10.     // 为方法参数重新赋值   
  11.     public void change(ParamTest t) {   
  12.          ParamTest tmp = new ParamTest();   
  13.          tmp.num = 9;   
  14.          t = tmp;   
  15.      }   
  16.   
  17.     // 改变方法参数的值   
  18.     public void add(int i) {   
  19.          i += 10;   
  20.      }   
  21.   
  22.     // 改变方法参数属性的值   
  23.     public void add(ParamTest pt) {   
  24.          pt.num += 20;   
  25.      }   
  26.   
  27.     public static void main(String[] args) {   
  28.          ParamTest t = new ParamTest();   
  29.   
  30.          System.out.println("参数--基本类型");   
  31.          System.out.println("原有的值:" + t.num);   
  32.         // 为基本类型参数重新赋值   
  33.          t.change(t.num);   
  34.          System.out.println("赋值之后:" + t.num);   
  35.         // 为引用型参数重新赋值   
  36.          t.change(t);   
  37.          System.out.println("运算之后:" + t.num);   
  38.   
  39.          System.out.println();   
  40.   
  41.          t = new ParamTest();   
  42.          System.out.println("参数--引用类型");   
  43.          System.out.println("原有的值:" + t.num);   
  44.         // 改变基本类型参数的值   
  45.          t.add(t.num);   
  46.          System.out.println("赋引用后:" + t.num);   
  47.         // 改变引用类型参数所指向对象的属性值   
  48.          t.add(t);   
  49.          System.out.println("改属性后:" + t.num);   
  50.      }   
  51. }  

这段代码的运行结果如下:
  1. 参数--基本类型
  2. 原有的值:0
  3. 赋值之后:0
  4. 运算之后:0

  5. 参数--引用类型
  6. 原有的值:0
  7. 赋引用后:0
  8. 改属性后:20

从上面这个直观的结果中我们很容易得出如下结论:
  1. 对于基本类型,在方法体内对方法参数进行重新赋值,并不会改变原有变量的值。
  2. 对于引用类型,在方法体内对方法参数进行重新赋予引用,并不会改变原有变量所持有的引用。
  3. 方法体内对参数进行运算,不影响原有变量的值。
  4. 方法体内对参数所指向对象的属性进行运算,将改变原有变量所指向对象的属性值。

上面总结出来的不过是我们所看到的表面现象。那么,为什么会出现这样的现象呢?这就要说到值传递和引用传递的概念了。这个问题向来是颇有争议的。

大家都知道,在JAVA中变量有以下两种:
  1. 基本类型变量,包括char、byte、short、int、long、float、double、boolean。
  2. 引用类型变量,包括类、接口、数组(基本类型数组和对象数组)。

当基本类型的变量被当作参数传递给方法时,JAVA虚拟机所做的工作是把这个值拷贝了一份,然后把拷贝后的值传递到了方法的内部。因此在上面的例子中,我们回头来看看这个方法:
Java代码 复制代码
  1. // 为方法参数重新赋值   
  2. public void change(int i) {   
  3.      i = 5;   
  4. }  

在这个方法被调用时,变量i和ParamTest型对象t的属性num具有相同的值,却是两个不同变量。变量i是由JAVA虚拟机创建的作用域在 change(int i)方法内的局部变量,在这个方法执行完毕后,它的生命周期就结束了。在JAVA虚拟机中,它们是以类似如下的方式存储的:

很明显,在基本类型被作为参数传递给方式时,是值传递,在整个过程中根本没有牵扯到引用这个概念。这也是大家所公认的。对于布尔型变量当然也是如此,请看下面的例子:
Java代码 复制代码
  1. public class BooleanTest {   
  2.     // 布尔型值   
  3.     boolean bool = true;   
  4.   
  5.     // 为布尔型参数重新赋值   
  6.     public void change(boolean b) {   
  7.          b = false;   
  8.      }   
  9.   
  10.     // 对布尔型参数进行运算   
  11.     public void calculate(boolean b) {   
  12.          b = b && false;   
  13.         // 为了方便对比,将运算结果输出   
  14.          System.out.println("b运算后的值:" + b);   
  15.      }   
  16.   
  17.     public static void main(String[] args) {   
  18.          BooleanTest t = new BooleanTest();   
  19.   
  20.          System.out.println("参数--布尔型");   
  21.          System.out.println("原有的值:" + t.bool);   
  22.         // 为布尔型参数重新赋值   
  23.          t.change(t.bool);   
  24.          System.out.println("赋值之后:" + t.bool);   
  25.   
  26.         // 改变布尔型参数的值   
  27.          t.calculate(t.bool);   
  28.          System.out.println("运算之后:" + t.bool);   
  29.      }   
  30. }  

输出结果如下:
  1. 参数--布尔型
  2. 原有的值:true
  3. 赋值之后:true
  4. b运算后的值:false
  5. 运算之后:true

那么当引用型变量被当作参数传递给方法时JAVA虚拟机又是怎样处理的呢?同样,它会拷贝一份这个变量所持有的引用,然后把它传递给JAVA虚拟机为方法 创建的局部变量,从而这两个变量指向了同一个对象。在篇首所举的示例中,ParamTest类型变量t和局部变量pt在JAVA虚拟机中是以如下的方式存 储的:

有一种说法是当一个对象或引用类型变量被当作参数传递时,也是值传递,这个值就是对象的引用,因此JAVA中只有值传递,没有引用传递。还有一种说法是引 用可以看作是对象的别名,当对象被当作参数传递给方法时,传递的是对象的引用,因此是引用传递。这两种观点各有支持者,但是前一种观点被绝大多数人所接 受,其中有《Core Java》一书的作者,以及JAVA的创造者James Gosling,而《Thinking in Java》一书的作者Bruce Eckel则站在了中立的立场上。

我个人认为值传递中的值指的是基本类型的数值,即使对于布尔型,虽然它的表现形式为true和false,但是在栈中,它仍然是以数值形式保存的,即0表 示false,其它数值表示true。而引用是我们用来操作对象的工具,它包含了对象在堆中保存地址的信息。即使在被作为参数传递给方法时,实际上传递的 是它的拷贝,但那仍是引用。因此,用引用传递来区别与值传递,概念上更加清晰。

最后我们得出如下的结论:
  1. 基本类型和基本类型变量被当作参数传递给方法时,是值传递。在方法实体中,无法给原变量重新赋值,也无法改变它的值。
  2. 对象和引用型变量被当作参数传递给方法时,在方法实体中,无法给原变量重新赋值,但是可以改变它所指向对象的属性。至于到底它是值传递还是引用传递,这并不重要,重要的是我们要清楚当一个引用被作为参数传递给一个方法时,在这个方法体内会发生什么。

什么叫引用?只因为这个变量的值和其它的不一样.


首先理解:都是变量
int i;
ArrayList b;
i和b都是变量.
但i是基本变量,也叫原始变量.
其它的就叫引用变量,因为它的值是一个内存地址值.引用对象的.但记住:它们都是有一个值的!i是一个数字,而b是一个内存地址值(简单的说是一个十六进 制的值).除了基本变量之外的变量都是引用变量.Vector a;这里的a也是一个变量.它也是有值的,它的值是一个十六进制的值.

变量的赋值:
int i=10;
int j=i;
//这里把i的值10给了j,所以j的值也是10

ArrayList b=new ArrayList();
ArrayList c=b;
//首先,b是一个引用变量,它的"值":是一个内存地址值!!! new ArrayList()要分配一段内存保存它们,怎么样找到这段内存?那就是通过b里的值了.b的值就是new ArrayList()所占内存的首地址.然后c也是一个引用变量,它的值(地址值)和b是一样的.也就是new ArrayList()所占内存的首地址.所以当通过b或者c进行操作时,它们都是操作同一个对象的.

在方法调用的时候,方法的参数实际也就是一个变量.如果是基本类型变量的时候,假设有方法method(int aa);
int j=10;
method(j);
这里边,int aa实际也是定义了一个变量,调用的时候把j的值:10也给了aa.所以aa也是10,改变了aa的值并不会改变j的值.

如果是引用变量的时候,假设有方法methodA(ArrayList aa);
ArrayList b = new ArrayList();
methodA(b);
//方法定义了变量aa,调用的时候把b的值(地址值!!!!!)给了aa,所以aa与b有一样的值(地址值!!!!),在方法里通过aa去操作的时候,b所引用的对象也就被改变了,因为它们引用同一个对象.

纸 a = new 银行帐户();//开一个银行帐户,返回一个卡号给你,写在你的纸a里边.

用一张纸(引用变量),把你的银行卡号写在上边,然后调用我的时候,我用另外一张纸(引用变量---方法的形数),把你的号码抄过来.然后我通过这个卡号,去到银行找到你的帐号,给你存点钱.

然后你用你的纸(引用变量)上的卡号 <没变,还是那个卡号>再去查询银行帐号的时候就会发现了多了一些钱了.....

说说我对值传递和引用传递的看法:
首先我认为,大家对Java传递参数的行为是清楚的,这个争论只是一个语义上的争论。
也就是我们是否需要区分值传递和应用传递呢?或者说这样的区分有没有意义?是否合理?

博主认为存在引用传递的关键点在于,传递的对象地址值,本质上它是一个引用,无论它是否被copy过。
认为只有值传递的关键点在于,传递的对象地址值,它是一个值的copy,这个值代表的意义无所谓。

引用是c++里的概念,由于java跟c++是有一定关系的,这里把引用迁移过来,如果合理未尝不可。
c++中关于引用的解释一般喜欢说是看作“别名”,我查了几本书,大部分提到引用并不会分配内存空间,也有一本书提到,某些编译器会分配存储空间来存储被引用对象的地址。
那么还是回到语义上来,c++里的这个引用,语义上是“别名”的意思,我的理解是,一组指向同一个对象的别名应该只存储一份内存地址。当然具体实现可能会 把引用当做一个不可变的指针来处理(每个别名都存储自己的对象地址)。但是请注意,我们应该关注于它的语义,即:它没有任何值的copy,即使是一个地 址,只是另外一个名字而已。

但是java里面没有这样的概念,所有的地址传递其行为是值的传递方式,语义上统一成值传递更为清晰,我们只需要考虑这个值具体是什么,无非两种,要么是基本类型值,要么是个地址。
所以我认为这个“引用”的概念放到java中并不合适。只有值传递的说法更合理。

posted @ 2008-09-12 10:25 保尔任 阅读(3403) | 评论 (1)编辑 收藏
Linux 发展到今天,可用的软件已经非常多了。这样自然会有一些软件的功能大致上相同。例如,同样是编辑器,就有 nvi、vim、emacs、nano,而且我说的这些还只是一部分。大多数情况下,这样的功能相似的软件都是同时安装在系统里的,可以用它们的名称来执 行。例如,要执行 vim,只要在终端下输入 vim 并按回车就可以了。不过,有些情况下我们需要用一个相对固定的命令调用这些程序中的一个。例如,当我们写一个脚本程序时,只要写下 editor,而不希望要为“编辑器是哪个”而操心。Debian 提供了一种机制来解决这个问题,而 update-alternatives 就是用来实现这种机制的。

在说明 update-alternatives 的详细内容之间,先让我们看看系统中已有的例子。打开终端,执行下面的命令:

herbert@natsu:~$ ls -l /usr/bin/editor
lrwxrwxrwx 1 root root 24 2004-09-26 08:48 /usr/bin/editor -> /etc/alternatives/editor
herbert@natsu:~$ ls -l /etc/alternatives/editor
lrwxrwxrwx 1 root root 12 2004-10-27 16:24 /etc/alternatives/editor -> /usr/bin/vim
herbert@natsu:~$

我 们看到,editor 这个可执行命令实际上是个符号链接,它指向 /etc/alternatives/editor;而 /etc/alternatives/editor 也是个符号链接,它指向 /usr/bin/vim。这样,当我输入 editor 并回车时,将执行 vim。之所以要在 /usr/bin 和 /etc/alternatives 中费心建立这样两个链接,就是要实现上面说到的特性:方便脚本
程序的编写和系统的管理。

下面我们就来看看 update-alternatives 的功能。当然,如果你觉得我说得不详细,可以看看这个命令的 manpage:UPDATE-ALTERNATIVES(8)。

首先要介绍的参数是 --display。它使我们可以看到一个命令的所有可选命令。执行

natsu:/home/herbert# update-alternatives --display editor
editor - status is auto.
 link currently points to /usr/bin/vim
/bin/ed - priority -100
 slave editor.1.gz: /usr/share/man/man1/ed.1.gz
/usr/bin/nvi - priority 19
 slave editor.1.gz: /usr/share/man/man1/nvi.1.gz
/bin/nano - priority 40
 slave editor.1.gz: /usr/share/man/man1/nano.1.gz
/usr/bin/vim - priority 120
 slave editor.1.gz: /usr/share/man/man1/vim.1.gz
/usr/bin/emacs21 - priority 0
 slave editor.1.gz: /usr/share/man/man1/emacs.1emacs21.gz
Current `best' version is /usr/bin/vim.
natsu:/home/herbert#

你可以看到我的机器上的所有可以用来被 editor 链接的命令。

下面说说 --config。这个选项使我们可以选择其中一个命令:

natsu:/home/herbert# update-alternatives --config editor

There are 5 alternatives which provide `editor'.

  Selection Alternative
-----------------------------------------------
      1 /bin/ed
      2 /usr/bin/nvi
      3 /bin/nano
*+    4 /usr/bin/vim
      5 /usr/bin/emacs21

Press enter to keep the default[*], or type selection number: 4
Using `/usr/bin/vim' to provide `editor'.
natsu:/home/herbert#

我并没有修改它,因为我还是比较喜欢 vim 的。当然,你可以选择别的程序。

说 到这里我们就要介绍一些概念了。首先,update-alternatives 在一般情况下是由 postinst 和 prerm 这样的安装脚本自动调用的,所以一个 alternative 的状态有两种:自动和手动。每个 alternative 的初始状态都是自动。如果系统发现管理员手动修改了一个 alternative,它的状态就从自动变成了手动,这样安装脚本就不会更新它了。如果你希望将一个 alternative 变回自动,只要执行

update-alternatives --auto editor

就可以了。你注意到了吗?我们说到了“名字”。该怎样写名字呢?这就是我们要介绍的第二个概念:
general name -- 这是指一系列功能相似的程序的“公用”名字(包括绝对路径),比如 /usr/bin/editor。
link -- 这是指一个 alternative 在 /etc/alternative 中的名字,比如 editor。
alternative -- 顾名思义,这是指一个可选的程序所在的路径(包括绝对路径),比如 /usr/bin/vim。
-- auto,--display 和 --config 跟的都是 link。我们要说的第三个概念是优先级。这个比较简单,当然优先级越高的程序越好啦(在大多数情况下,我不想争论)最后一个概念是主和从的 alternative。想想看,你将 /usr/bin/editor 链接到了 vim,可是当你执行 man editor 时看到的却是 emacs 的 manpage,你会做何感想呢?这就引出了主和从 alternative 的概念了:当更新主的 alternative 时,从的 alternative 也会被更新。

说完这四个重要的概念后,我们介绍另外两个选项。至于其他的。。。。我相信你会去看手册页的,对吗?

第一个是 --install。它的格式是:

update-alternatives --install gen link alt pri [--slave sgen slink salt] ...

gen, link,alt,pri 分别是我们上面说过的。如果需要从的 alternative,你可以用 --slave 加在后面。如果你在向一个已经存在的 alternative 组中添加新的 alternatives,该命令会把这些 alternatives 加入到这个已经存在的 alternative 组的
列表中,并用新的可选命令作为新的命令;否则,将会建立一个新的自动的 alternative 组。

呜呼!我加入了一个错误的 alternative。我不想要这个 alternative 了。在这种情况 下,可以执行下面的命令:

update-alternatives --remove name path

name 是一个在 /etc/alternatives 中的名字,也就是上面的 link,而 path 是希望删除的可选程序名的绝对路径名(放心,这样只是从列表中删除了这个程序,并不会真的从硬盘上删除程序的可执行文件)。如果从一个 alternative 组中删除了一个正在被链接的程序并且这个组仍然没有变成空的,update-alternatives 会自动用一个具有其他优先级的可选程序代替原来的程序。如果这个组变成空的了,那么连这个 alternative 组都会被移除。如果删除的程序没有被链接,则只有有关这个程序的信息会被移除。

说个例子吧。我下载了 Eclipse,并且安装了 gcj 和 gij。可是我发现 GNU 的 java 工具还不足以运行 Eclipse。我只好到 Sun 公司的网页上下载了它的 java 工具 jdk。因为是自己安装的,我将它们安装在 /usr/local 上,以便将来重新安装 Linux 系统时这些程序仍然可以使用。于是我要做的就是用这个 jdk 中的 java 和 javac 来代替系统原来的。执行

natsu:/home/herbert# update-alternatives --display java
java - status is auto.
 link currently points to /usr/local/j2sdk1.4.2_06/bin/java
/usr/bin/gij-wrapper-3.3 - priority 33
 slave java.1.gz: /usr/share/man/man1/gij-wrapper-3.3.1.gz
/usr/local/j2sdk1.4.2_06/bin/java - priority 100
 slave java.1.gz: /usr/local/j2sdk1.4.2_06/man/man1/java.1
Current `best' version is /usr/local/j2sdk1.4.2_06/bin/java.
natsu:/home/herbert# update-alternatives --display javac
javac - status is auto.
 link currently points to /usr/local/j2sdk1.4.2_06/bin/javac
/usr/bin/gcj-wrapper-3.3 - priority 33
 slave javah: /usr/bin/gcjh-wrapper-3.3
 slave javac.1.gz: /usr/share/man/man1/gcj-wrapper-3.3.1.gz
 slave javah.1.gz: /usr/share/man/man1/gcjh-wrapper-3.3.1.gz
/usr/bin/gcj-wrapper-3.4 - priority 33
 slave javah: /usr/bin/gcjh-wrapper-3.4
 slave javac.1.gz: /usr/share/man/man1/gcj-wrapper-3.4.1.gz
 slave javah.1.gz: /usr/share/man/man1/gcjh-wrapper-3.4.1.gz
/usr/local/j2sdk1.4.2_06/bin/javac - priority 100
 slave javah: /usr/local/j2sdk1.4.2_06/bin/javah
 slave javac.1.gz: /usr/local/j2sdk1.4.2_06/man/man1/javac.1
 slave javah.1.gz: /usr/local/j2sdk1.4.2_06/man/man1/javah.1
Current `best' version is /usr/local/j2sdk1.4.2_06/bin/javac.
natsu:/home/herbert#

(你看到的是我更新以后的)就可以得到关于要更新哪些 alternatives 的信息。我是这么更新的:

update-alternatives --install /usr/bin/javac javac /usr/local/j2sdk1.4.2_06/bin/javac 100 --slave /usr/bin/javah javah /usr/local/j2sdk1.4.2_06/bin/javah --slave /usr/share/man/man1/javac.1.gz javac.1.gz /usr/local/j2sdk1.4.2_06/man/man1/javac.1 --slave /usr/share/man/man1/javah.1.gz javah.1.gz /usr/local/j2sdk1.4.2_06/man/man1/javah.1
update-alternatives --install /usr/bin/java java /usr/local/j2sdk1.4.2_06/bin/java 100 --slave /usr/share/man/man1/java.1.gz java.1.gz /usr/local/j2sdk1.4.2_06/man/man1/java.1
posted @ 2008-02-13 10:08 保尔任 阅读(2555) | 评论 (0)编辑 收藏
1, insert Ubuntu 7.10 CD
a, format disc(primary 10G ext3; extend 59G ext3; swap 1G)

b, install(timezone shanghai; en_US; "prepare disc space" manual, or the system will partition autoly)

c, auto restart, go on install system(remenber cut off the net line except the netwidth is large, or it will cost long time to download from far away)

2, config
a, sources list
sudo vim /etc/apt/sources.list
# add "deb http://debian.exoweb.net/debian.cn99.com/debian etch main" into it
sudo apt-get update
sudo apt-get upgrade

b, vedio card driver
在ubuntu7.10下装nvidia 7 series显卡并配置双屏显示:

一,显卡驱动 + 双显示器
(修改X配置命令:sudo dpkg-reconfigure xserver-xorg)

1,到nvidia网站下载7系列显卡的最新驱动

2,ensure that the linux-restricted-modules or linux-restricted-modules-common packages have been uninstalled. Alternatively, you can edit the /etc/default/linux-restricted-modules or /etc/default/linux-restricted-modules-common configuration file and disable the NVIDIA linux-restricted kernel modules (nvidia, nvidia_legacy) via:

DISABLED_MODULES="nv nvidia_new"

3,
sudo apt-get remove --purge nvidia-glx nvidia-glx-new
sudo rm /etc/init.d/nvidia-glx /etc/init.d/nvidia-kernel /lib/linux-restricted-modules/.nvidia_new_installed

4,然后ctrl+alt+1进入tty1
sudo /etc/init.d/gdm stop
sudo sh NVIDIA-Linux-x86-100.14.23-pkg1.run
(这时会出现错误提示,说少了“libc header file...libc development package”)
sudo apt-get install sudo apt-get install build-essential xorg-dev pkg-config linux-headers-$(uname -r), libc6-dev
sudo sh NVIDIA-Linux-x86-100.14.23-pkg1.run
sudo /etc/init.d/gdm start

用application -> system tools里的nvidia工具去配置双显示器

c, multi-language
System -> Administration -> Language support: install English and Chinese
check "input method"

d, Wen Quan Yi font
browse http://wenq.org/, and download 文泉驿点阵宋体 and 文泉驿矢量正黑, then install them
System -> Preference -> Appearance -> Fonts 前四项选择:点阵宋体(WenQuanYi Bitmap Song), 第五项不改(Monospace)
sudo fc-cache -f -v (刷新字体缓存,每次修改字体都要这样,不然Xorg会很慢)

e, stardict                   
sudo apt-get install stardict
(http://stardict.sourceforge.net/Dictionaries_zh_CN.php 下载朗道英汉,汉英字典)
tar -xjvf *** --directory /usr/share/stardict/dic/

f, pidgin internet messager
sudo apt-get install gaim-guifications
config: Tools -> Plugins -> (check) Guifications; then, config it to uncheck on "Chat message"

3, install and config Software
sudo apt-get install postgresql-8.1 python2.4 ipython vim-gnome sun-java5-jdk eclipse subversion build-essential ssh build-essential meld kompare

a, postgresql
sudo su - postgres (for user postgres has Null password, so you can't just "su - postgres", or you can sudo "sudo passwd postgres" to set password for postgres, then "su - postgres")
createuser (enter username and password.)
config postgresql as below:
In /etc/postgresql/8.1/main/postgresql.conf, Change listen_addresses to '*' and change datestyle to 'ISO,European' and uncomment them.
In /etc/postgresql/8.1/main/pg_hba.conf, 最后加入一行“host        all    justin        127.0.0.1/16        trust”

b, eclipse
sudo eclipse, exit, eclipse

c, ssh
When other mathines want to ssh or scp your mathine which is new OS, it should "rm ~/.ssh/known_hosts" to reload the new Cert.

d, kompare
add a file svndiff in src with context
"""
if [ $1 ] ; then
    svn up -r $1
    svn st -q
    svn log -r $1
    PRE=`expr $1 - 1`
    svn diff --diff-cmd=diff -x "-U 10000" -r$PRE:$1 > /tmp/$1.diff
    cat /tmp/$1.diff | kompare -
else
    svn up
    svn st
    svn diff --diff-cmd=diff -x "-U 10000" | kompare -
fi
"""
then, in src, ./svndiff 9827 will show diff about r9827

e, firefox add-ons
firebug, flashblock

3, chroot
a,
sudo apt-get install debootstrap
sudo debootstrap --arch i386 etch /home/etch http://debian.exoweb.net/debian.cn99.com/debian/
(if in 64 bit system, use --arch amd64)
sudo chroot /home/etch
#in etch as root
apt-get install locales
dpkg-reconfigure locales #(choose en_us UTF8 as before)
apt-get install vim vim-gnome xbase-clients less sudo postgresql-client subversion
echo "etch" > /etc/debian-chroot
visudo (add user justin to sudo)
adduser justin (删除的命令是userdel justin)

在ubuntu的/usr/bin/etch加入:
sudo cp /etc/passwd /home/etch/etc/
sudo cp /etc/shadow /home/etch/etc/
sudo cp /etc/group /home/etch/etc/
sudo cp /etc/sudoers /home/etch/etc/
sudo cp /etc/resolv.conf /home/etch/etc/
sudo cp /etc/hosts /home/etch/etc/

在/etc/fstab加入:
/home   /home/etch/home    none    bind 0 0
/tmp    /home/etch/tmp     none    bind 0 0
/dev    /home/etch/dev     none    bind 0 0
/proc   /home/etch/proc    none    bind 0 0
sudo chroot /home/etch/  su - justin

现在就可一享受chroot的双系统了

b, run X in etch 3 steps
b1, (etch)mkdir /tmp/.X11-unix
(ubuntu)sudo echo "/tmp/.X11-unix/x0 /home/justin/etch/tmp/.X11-unix/x0 none bind 0 0" >> /etc/fstab
# another way is write it in to /etc/fstab, or sudo mount --bind /tmp/*** /home/justin/etch/tmp/***
b2, (etch)vim ~/.bashrc # add "export DISPLAY=:0.0"
b3, (ubuntu) cp ~/.Xauthority ~/etch/home/justin/ (其实这步不需要,因为上面已经把/home mount到了/home/etch/home了)

c, install java
#download jdk-1_5_0_14-linux-i586.bin to /opt/, and into etch/opt/
sudo chmod +x jdk-1_5_0_14-linux-i586.bin
sudo ./jdk-1_5_0_14-linux-i586.bin
vim ~/.bashrc
#add below in the end of .bashrc
#export JAVA_HOME=/opt/jdk1.5.0_14
#export CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
#export PATH=$JAVA_HOME/bin:$PATH

java -version
#java version "1.5.0_14"
#Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_14-b03)
#Java HotSpot(TM) Client VM (build 1.5.0_14-b03, mixed mode, sharing)
配置默认Java使用哪个 sudo update-alternatives --config java
posted @ 2007-12-19 17:29 保尔任 阅读(2758) | 评论 (0)编辑 收藏
一,两个数的最大公约数:

1、欧几里德算法


欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。其计算原理依赖于下面的定理:

定理:gcd(a,b) = gcd(b,a mod b)

证明:a可以表示成a = kb + r,则r = a mod b
假设d是a,b的一个公约数,则有
d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公约数

假设d 是(b,a mod b)的公约数,则
d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公约数

因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证

欧几里德算法就是根据这个原理来做的,其算法用C++语言描述为:

void swap(int & a, int & b){
     int c = a;
       a = b;
       b = c;
}

int gcd(int a,int b){
     if(0 == a ){
         return b;
     }
     if( 0 == b){
         return a;
     }
     if(a > b){
         swap(a,b);
     }
     int c;
     for(c = a % b ; c > 0 ; c = a % b){
           a = b;
           b = c;
     }
     return b;
}

2、Stein算法
欧几里德算法是计算两个数最大公约数的传统算法,它无论从理论还是从效率上都是很好的。但是有一个致命的缺陷,这个缺陷只有在大素数时才会显现出来。

考虑现在的硬件平台,一般整数最多也就是64位,对于这样的整数,计算两个数之间的模是很简单的。对于字长为32位的平台,计算两个不超过32位的整数的 模,只需要一个指令周期,而计算64位以下的整数模,也不过几个周期而已。但是对于更大的素数,这样的计算过程就不得不由用户来设计,为了计算两个超过 64位的整数的模,用户也许不得不采用类似于多位数除法手算过程中的试商法,这个过程不但复杂,而且消耗了很多CPU时间。对于现代密码算法,要求计算 128位以上的素数的情况比比皆是,设计这样的程序迫切希望能够抛弃除法和取模。

Stein算法由J. Stein 1961年提出,这个方法也是计算两个数的最大公约数。和欧几里德算法 算法不同的是,Stein算法只有整数的移位和加减法,这对于程序设计者是一个福音。

为了说明Stein算法的正确性,首先必须注意到以下结论:

gcd(a,a) = a,也就是一个数和它自身的公约数是其自身
gcd(ka,kb) = k gcd(a,b),也就是最大公约数运算和倍乘运算可以交换,特殊的,当k=2时,说明两个偶数的最大公约数必然能被2整除

C++/java 实现

// c++/java stein 算法
int gcd(int a,int b){
     if(a<b){
//arrange so that a>b
         int temp = a;
           a = b;
           b=temp;
     }
     if(0==b)
//the base case
        return a;
     if(a%2==0 && b%2 ==0)
//a and b are even
         return 2*gcd(a/2,b/2);
     if ( a%2 == 0)
// only a is even
         return gcd(a/2,b);
     if ( b%2==0 )
// only b is even
         return gcd(a,b/2);
     return gcd((a+b)/2,(a-b)/2);
// a and b are odd
}

二,多个数的最大公约数:(python实现:取出数组a中最小的,从2到最小的循环,找出其中最大的能被数组中所有数整除的那个数,就是最大公约数)
def gcd(a):
    a.sort()
    min = a[0]
    result = 1
    for i in range(2, min+1):
        flag = True
        for j in a:
            if j % i != 0:
                flag = False
        if flag == True:
            result = i
    return result
posted @ 2007-12-15 15:40 保尔任 阅读(4662) | 评论 (2)编辑 收藏
Catalan数:(for http://acm.pku.edu.cn/JudgeOnline/problem?id=2084)

C_n = ΣC_i*C_(n-i),其中0≤i<n;
C_n = C(2n,n) / (n+1); 其中C(2n, n) 表示组合数,公式为:C(n, k) = n! / (k!(n-k)!)
C_n=C_(n-1)*(4n-2)/(n+1)。

它的意义有很多,例如:n+1边形用对角线划分成 三角形的方法数;n个+1和n个-1满足所有部分和不小于零的排列数;具有n个节点的二叉树的数量……

(详细说明参考:http://hi.baidu.com/kikoqiu/blog/item/81d792015ab13e01738da51d.html)
posted @ 2007-11-16 18:07 保尔任 阅读(1444) | 评论 (0)编辑 收藏
指令語法

crontab [ -u user ] file
crontab [ -u user ] { -l | -r | -e }

指令說明

crontab 提供我們在固定的間隔時間執行自訂的程式、系統指令或 shell secrip。時間間隔的單位可以是分鐘、小時、日、週、月及以上的任意組合。允許使用者離線執行,並且可以將執行結果以 email 通知使用者。因此,非常設合對週期性的管理分析或資料備份等工作。

基本上,crontab 的指令格式分為六個部分,前五個為時間間隔,最後則是執行的指令。每一個部分用空格來區隔。

分 -- 0-59
時 -- 0-23
日 -- 1-31
月 -- 1-12 或使用英文名稱
星期 -- 0-7 或使用英文名稱
工作命令 -- 指令,shell script,程式....(建議使用絕對路徑)
以上是 crontab 的基本格式。

選項說明

-u user
以指定的使用者身份,執行 crontab 工作。此選項僅供 root 使用。


-l
顯示使用者現行的 crontab 檔。

-r
移除現行的 crontab 檔。

-e
進入 vi 編輯 crontab 檔(如有設定 VISUAL 或 EDITOR 環境變數,怎使用該環境變數所設定的編輯器來編輯)。在使用者退出編輯器後,會自動將所編輯 crontab 檔,置入 crontab 執行。
相關檔案

/etc/cron.allow
/etc/cron.deny

實例說明

# crontab -l
# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/tmp/crontab.3672 installed on Thu Jan 1 15:55:18 2004)
# (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
0 0-23/6 * * * /usr/bin/webalizer
30 3 * * * /root/fbin/bak-web
#

先前曾提到,crontab 的格式分成六個部分,前五個是時間參數。在上例中你會發現除了數字與英文名稱,有使用到符號"*",這個符號代表每一單位的意思,譬如 30 3 * * * 既代表 30分 3點 每日 每月 星期的每天。

時間的指定,可以是單一的數字,或幾個數字用逗號來連接。看下例

30 3,12 * * * /root/fbin/bak-web

其中的第二項為 3,12,這代表 3 以及 12 小時的意思。再來看下例

30 */6 * * * /root/fbin/bak-web

我把第二項改成 */6 這代表每 6 小時,也相當於 6,12,18,24 的作用。此外還有一個區段的做法

30 8-18/2 * * * /root/fbin/bak-web

我把第二項改成 8-18/2 這代表在 8 小時到 18 小時之間每 2 小時,也相當於 8,10,12,14,16,18 的作用。

在認知的以上介紹各項時間用法後,你可以視實際的需要自行組合。使用上的彈性是相當自由的。這篇暫時到此。


posted @ 2007-11-02 16:56 保尔任 阅读(646) | 评论 (0)编辑 收藏
(转自:http://blog.chinaunix.net/u/24474/showart_217098.html)

diff和patch是一对工具,在数学上来说,diff是对两个集合的差运算,patch是对两个集合的和运算。
diff比较两个文件或文件集合的差异,并记录下来,生成一个diff文件,这也是我们常说的patch文件,即补丁文件。
patch能将diff文件运用于 原来的两个集合之一,从而得到另一个集合。举个例子来说文件A和文件B,经过diff之后生成了补丁文件C,那么着个过程相当于 A -B = C ,那么patch的过程就是B+C = A 或A-C =B。
因此我们只要能得到A, B, C三个文件中的任何两个,就能用diff和patch这对工具生成另外一个文件。

这就是diff和patch的妙处。下面分别介绍一下两个工具的用法:

1. diff的用法

diff后面可以接两个文件名或两个目录名。 如果是一个目录名加一个文件名,那么只作用在那么个目录下的同名文件。

如果是两个目录的话,作用于该目录下的所有文件,不递归。如果我们希望递归执行,需要使用-r参数。

命令diff A B > C ,一般A是原始文件,B是修改后的文件,C称为A的补丁文件。
不加任何参数生成的diff文件格式是一种简单的格式,这种格式只标出了不一样的行数和内容。我们需要一种更详细的格式,可以标识出不同之处的上下文环境,这样更有利于提高patch命令的识别能力。这个时候可以用-c开关。


2. patch的用法

patch用于根据原文件和补丁文件生成目标文件。还是拿上个例子来说

patch A C 就能得到B, 这一步叫做对A打上了B的名字为C的补丁。

之一步之后,你的文件A就变成了文件B。如果你打完补丁之后想恢复到A怎么办呢?

patch -R B C 就可以重新还原到A了。

所以不用担心会失去A的问题。

其实patch在具体使用的时候是不用指定原文件的,因为补丁文件中都已经记载了原文件的路径和名称。patch足够聪明可以认出来。但是有时候会有点小 问题。比如一般对两个目录diff的时候可能已经包含了原目录的名字,但是我们打补丁的时候会进入到目录中再使用patch,着个时候就需要你告诉 patch命令怎么处理补丁文件中的路径。可以利用-pn开关,告诉patch命令忽略的路径分隔符的个数。举例如下:

A文件在 DIR_A下,修改后的B文件在DIR_B下,一般DIR_A和DIR_B在同一级目录。我们为了对整个目录下的所有文件一次性diff,我们一般会到DIR_A和DIR_B的父目录下执行以下命令

diff -rc DIR_A DIR_B > C

这个时候补丁文件C中会记录了原始文件的路径为 DIR_A/A

现在另一个用户得到了A文件和C文件,其中A文件所在的目录也是DIR_A。 一般,他会比较喜欢在DIR_A目录下面进行patch操作,它会执行

patch < C

但是这个时候patch分析C文件中的记录,认为原始文件是./DIR_A/A,但实际上是./A,此时patch会找不到原始文件。为了避免这种情况我们可以使用-p1参数如下

patch -p1 < C

此时,patch会忽略掉第1个”/”之前的内容,认为原始文件是 ./A,这样就正确了。
使用patch

patch附带有一个很好的帮助,其中罗列了很多选项,但是99%的时间只要两个选项就能满足我们的需要:

patch -p1 < [patchfile]

patch -R < [patchfile] (used to undo a patch)

-p1选项代表patchfile中      文件名左边目录的层数,顶层目录在不同的机器上有所不同。要使用这个选项,就要把你的patch放在要被打补丁的目录下,然后在这个目录中运行path -p1 < [patchfile]。
posted @ 2007-10-25 10:22 保尔任 阅读(1353) | 评论 (0)编辑 收藏
断言概述

编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设
可以将断言看作是异常处理的一种高级形式

断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真

可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。

使用断言可以创建更稳定,品质更好且易于除错的代码

当需要在一个值为FALSE时中断当前操作的话,可以使用断言

单元测试必须使用断言(Junit/JunitX

除了类型检查和单元测试外,断言还提供了一种确定个种特性是否在程序中得到维护的极好的方法

使用断言使我们向按契约式设计更近了一步



常见的断言特性


前置条件断言:代码执行之前必须具备的特性

后置条件断言:代码执行之后必须具备的特性

前后不变断言:代码执行前后不能变化的特性



断言使用方式


断言可以有两种形式

1.assert Expression1
2.assert Expression1:Expression2
其中Expression1应该总是一个布尔值,Expression2是断言失败时输出的失败消息的字符串。如果Expression1为假,则抛出一个 AssertionError,这是一个错误,而不是一个异常,也就是说是一个不可控制异常(unchecked Exception),AssertionError由于是错误,所以可以不捕获,但不推荐这样做,因为那样会使你的系统进入不稳定状态。



起用断言


断言在默认情况下是关闭的,要在编译时启用断言,需要使用source1.4标记javac source1.4 Test.java ,在运行时启用断言需要使用 -ea参数。要在系统类中启用和禁用断言可以使用 -esa -dsa参数。


例如:

public >  public AssertExampleOne(){}
  public static void main(String args[]){
    int x=10;
    System.out.println("Testing Assertion that x==100");
    assert x=100;"Out assertion failed!";
    System.out.println("Test passed!");
  }
}

如果编译时未加 -source1.4,则编译通不过

在执行时未加 -ea 时输出为

Testing Assertion that x==100
Test passed
jre
忽略了断言的就代码,而使用了该参数就会输出为

Testing Assertion that x==100
Exception in thread "main" java.lang.AssertionError: Out assertion failed!
at AssertExampleOne.main(AssertExampleOne.java:6)


断言的副作用


由于程序员的问题,断言的使用可能会带来副作用,例如:

boolean isEnable=false;
//...
assert isEnable=true;
这个断言的副作用是因为它修改了程序中变量的值并且未抛出错误,这样的错误如果不细心的检查是很难发现的。但是同时我们可以根据以上的副作用得到一个有用的特性,根据它来测试断言是否打开。


public >
  public static void main(String args[]){
    boolean isEnable=false;
    //...
    assert isEnable=true;
    if(isEnable==false){
      throw new RuntimeException("Assertion shoule be enable!");
    }
  }
}


何时需要使用断言


1.
可以在预计正常情况下程序不会到达的地方放置断言
assert false
2.
断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)

3.
使用断言测试方法执行的前置条件和后置条件

4.
使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)



什么地方不要使用断言


断言语句不是永远会执行,可以屏蔽也可以启用

因此:

1.
不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行

2.
断言语句不可以有任何边界效应,不要使用断言语句去修改变量和改变方法的返回值

下边是介绍断言的用法
:

assert是在J2SE1.4中引入的新特性,assertion就是在代码中包括的布尔型状态,程序员认为这个状态是true。一般来说assert在开发的时候是检查程序的安全性的,在发布的时候通常都不使用assert。在1.4中添加了assert关键字和java.lang.AssertError类的支持。
首先,我们有必要从一个例子说起
assert

public >  public static void main(String[] args) {
    AssertTest at = new AssertTest();
    at.assertMe(true);
    at.assertMe(false);
  } 
  private void assertMe(boolean boo) {
    assert boo?true:false;
    System.out.println("true condition");
  }
}
程序中包含了assert的话,你要用javac -source 1.4 xxx.java来编译,否则编译器会报错的。要想让assert得部分运行的话,要使用java -ea xxx来运行,否则包含assert得行会被忽略。下面我们运行

javac -source 1.4 AssertTest.java
java -ea AssertTest
看看结果的输出是:


true condition
Exception in thread "main" java.lang.AssertionError
at AssertTest.assertMe(AssertTest.java:13)
at AssertTest.main(AssertTest.java:7)

当我们运行at.assertMe(true)得时候,由于assert boo?true:false相当于 assert true;因此没有任何问题,程序往下执行打印出true condition,但是执行at.assertMe(false)的时候相当于assert false,这个时候解释器就会抛出AssertionError了,程序就终止了。大家必须清楚AssertionError是继承自Error得,因此你可以不再程序中catch它的,当然你也可以在程序中catch它然后程序可以继续执行。例如:

public >  public static void main(String[] args) {
    AssertTest at = new AssertTest();
    try {
      at.assertMe(true);
      at.assertMe(false);
    } catch(AssertionError ae) {
      System.out.println("AsseriontError catched");
    }
    System.out.println("go on");
  }
  private void assertMe(boolean boo) {
    assert boo?true:false;
    System.out.println("true condition");
  }
}

assert
还有另外一种表达的方式,就是assert exp1:exp2;其中exp1是个boolean返回值得表达式,而exp2可以是原始的数据类型或者对象都可以例如:

boolean boo = true;
String str = null;
assert boo = false
str="error";

我们刚开始讲得assert exp1得形式,当exp1false得时候,AssertionError得默认构造器会被调用,但是assert exp1:exp2这样的形式,当exp1true的时候后面exp2被或略,如果false的话,后面的表达式的结果会被计算出来并作为AssertionError得构造器参数。看下面的例子:

public >  public static void main(String[] args) {
    AssertTest at = new AssertTest();
    at.assertMe(true);
    at.assertMe(false);
  }
  private void assertMe(boolean boo) {
    String s = null;
    assert boo?true:false:s = "hello world";
    System.out.println("true condition");
  }
}


运行的时候会得到这样的结果:

true condition
Exception in thread "main" java.lang.AssertionError: hello world
at AssertTest.assertMe(AssertTest.java:14)
at AssertTest.main(AssertTest.java:7)

Assert
最好不要滥用,原因是assert并不一定都是enable的,下面两种情况就不应该用
assert

不要在public的方法里面检查参数是不是为null之类的操作,
例如

public int get(String s) {
  assert s != null;
}
如果需要检查也最好通过if s = null 抛出NullPointerException来检查


不要用
assert来检查方法操作的返回值来判断方法操作的结果,
例如

assert list.removeAll();

这样看起来好像没有问题
但是想想如果assert disable呢,那样他就不会被执行了所以removeAll()操作就没有被执行可以这样代替

boolean boo = list.removeAl();
assert boo;
posted @ 2007-10-12 13:16 保尔任 阅读(927) | 评论 (0)编辑 收藏
     摘要: Python基础篇 整理:Jims of 肥肥世家 <jims.yang@gmail.com> Copyright © 2004,2005,2006 本文遵从GNU 的自由文档许可证(Free Document License)的条款,欢迎转载、修改、散布。 发布时间:2004年07月10日 更新时间:20...  阅读全文
posted @ 2007-09-02 16:18 保尔任 阅读(5065) | 评论 (0)编辑 收藏
/etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置.

/etc/bashrc:为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取.

~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件.

~/.bashrc:该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该该文件被读取.

~/.bash_logout:当每次退出系统(退出bash shell)时,执行该文件.

另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系.

~/.bash_profile 是交互式、login 方式进入 bash 运行的

~/.bashrc 是交互式 non-login 方式进入 bash 运行的

通常二者设置大致相同,所以通常前者会调用后者。
posted @ 2007-07-16 10:52 保尔任 阅读(389) | 评论 (0)编辑 收藏
一,安装jdk:

(这里的方法是用于ubuntu或debian的,把下载的jdk构建成deb包,我觉得是为了便于包管理,否则删除的时候都不知道删除哪些文件,很麻烦。)
1. 获取JDK
可以选择从Java官方下载: ::URL::http://java.sun.com 或者从其它网站下载.我用的版本是:jdk-1_5_0-linux-i586.bin

2. 构建打包环境
Debian专门提供了SDK 的DEB包构建工具: java-package,而Ubuntu是基于Debian的,所以
# apt-get install -u java-package fakeroot

在apt-get之前最好update一下

3. 创建.deb 软件包

这一步要以普通用户运行,如果以Root运行是不允许的.会有下面的提示:

You are real root -- unfortunately, some Java distributions have
install scripts that directly manipulate /etc, and may cause some
inconsistencies on your system. Instead, you should become a
non-root user and run:

fakeroot make-jpkg jdk-1_5_0-linux-i586.bin

which will allow no damage to be done to your system files and
still permit the Java distribution to successfully extract.

Aborting.

以普通用户执行:
$ fakeroot make-jpkg jdk-1_5_0_06-linux-i586.bin
接下来做一些必要的选择.几分钟后,就应当出现软件包创建成功的提示.你在当前目录下会发现类似:
sun-j2sdk1.5_1.5.0+update00_i386.deb的软件包

4. 安装
切换回root执行以下命令:
# dpkg -i sun-j2sdk1.5_1.5.0+update06_i386.deb

5.配置环境

在 ~/.bashrc脚本文件中加入类似如下内容

PATH=$PATH:/usr/lib/j2sdk1.5-sun/bin:/usr/lib/j2sdk1.5-sun/jre/bin
JAVA_HOME=/usr/lib/j2sdk1.5-sun
JRE_HOME=/usr/lib/j2sdk1.5-sun/jre
CLASSPATH=.:/usr/lib/j2sdk1.5-sun/lib/tools.jar:/usr/lib/j2sdk1.5-sun/lib/dt.jar
export PATH
export JRE_HOME
export JAVA_HOME
export CLASSPATH

6. 测试
创建一个简单的java程序(Hello.java)
public class Hello
{
public Hello()
{
}

public static void main(String[] args)
{
System.out.println("Hello World!";
}

}
然后
$javac Hello.java
检查当前目录会生成一个Hello.class的文件, 然后运行
$java Hello
Hello World!
OK,测试成功!

7. 中文化安装中文字体:
在 $JAVA_HOME/jre/lib/fonts/ 目录下创建一个fallback目录.
复制中文字体(例如:simsun.ttf 至此目录.

8. 安装插件
对于此种方法安装的Java环境, 浏览器插件文件位置应当位于:
/usr/lib/j2sdk1.5-sun/jre/plugin/i386/ns7/libjavaplugin_oji.so

以 firefox1.5.0.1为例:
# cd /usr/lib/mozilla-firefox/plugins
# ln -s \
/usr/lib/j2sdk1.5-sun/jre/plugin/i386/ns7/libjavaplugin_oji.so

卸载JDK:
# apt-get remove --purge sun-j2sdk1.5
卸载插件, 直接删除符号链接:
# rm /usr/lib/mozilla-firefox/plugins/libjavaplugin_oji.so

二,安装jython:

1,http://www.jython.org/Project/installation.html下载jython安装文件,运行命令“java -jar jython_installer-2.2rc2.jar”,jython即安装成功。比如安装在/home/justin/java/jython2.2目录下

2,把jython包加入classpath,即把上面的classpath改为:CLASSPATH=.:/usr/lib/j2sdk1.5-sun/lib/tools.jar:/usr/lib/j2sdk1.5-sun/lib/dt.jar:/home/justin/java/jython2.2/jython.jar
此后就可以在java文件中加入python库了,例如:
import org.python.util.PythonInterpreter; 

import org.python.core.*

public class SimpleEmbedded { 

    
public static void main(String []args)

        
throws PyException

    { 

        PythonInterpreter interp 
=

            
new PythonInterpreter();

 

        System.out.println(
"Hello, brave new world");

        interp.exec(
"import sys");

        interp.exec(
"print sys");

        interp.set(
"a"new PyInteger(42));

        interp.exec(
"print a");

        interp.exec(
"x = 2+2");

        PyObject x 
= interp.get("x");

 

        System.out.println(
"x: "+x);

        System.out.println(
"Goodbye, cruel world");

    }
}

3,将选择的/home/justin/java/jython2.2/jython安装路径添加到 PATH 环境变量。现在只要输入“jython”就可以运行交互式 PATH :
$ jython
Jython 2.1 on java1.4.0_01 (JIT: null)
Type "copyright", "credits" or "license" for more information.
>>># 通过 Jython 访问标准 Java 库
>>> from java.util import Random
>>> rng = Random()
>>> i = rng.nextBoolean()
>>> print i

jython 解释器对于快速检查和作提示都很方便,但您不必在这其中完成所有工作 ― Jython 还允许您在源文件中编写代码,并随后运行该代码(
from java.util import Random
rng = Random()
#This is a comment in Jython
print "Flipping a coin..."
if rng.nextBoolean():
    print "Came up heads"
else:
    print "Came up tails"
用jython运行该文件,即可
posted @ 2007-07-13 15:42 保尔任 阅读(588) | 评论 (0)编辑 收藏
一,网络时间服务:

1. 与一个已知的时间服务器同步
公司配置:
#synchronize time with fw.exoweb.net
00 0 1 * * root rdate -s fw.exoweb.net

2. 配置网络时间协议(ntp)


1. 让linux自动同步时间

vi /etc/crontab
加上一句:
00 0 1 * * root rdate -s time.nist.gov

time.nist.gov 是一个时间服务器.

2. 时间服务器配置(192.168.10.1)

1). # rpm -ivh ntp-4.1.2-4.EL3.1.i386.rpm
2). # vi /etc/ntp.conf
注释一行
restrict default ignore
加入一行
restrict 192.168.10.0 mask 255.255.255.0 notrust nomodify notrap
3). # vi /etc/ntp/step-tickers
加入一行
pool.ntp.org
这样每次ntpd启动时,会自动连接该国际标准时间服务器;
4). # service ntpd start
5). # netstat -an |grep 123
确保该端口以udp方式开放

时间客户端配置(192.168.10.2)
1). # ntpdate 192.168.10.2
应该显示同步成功
2). # crond -e
加入
0-59/10 * * * * /usr/sbin/ntpdate 192.168.10.1
表示每隔10分钟同步一次时间


二, 出现  must be setuid root 错误
解决办法:

ls -l  /usr/bin/sudo
chown root:root /usr/bin/sudo
chmod 4755 /usr/bin/sudo
reboot

三,用nohup命令让Linux下程序永远在后台执行

Unix/Linux下一般想让某个程序在后台运行,很多都是使用 & 在程序结尾来让程序自动运行。比如我们要运行mysql在后台:

         /usr/local/mysql/bin/mysqld_safe --user=mysql &

但是我们很多程序并不象mysqld一样可以做成守护进程,可能我们的程序只是普通程序而已,一般这种程序即使使用 & 结尾,如果终端关闭,那么程序也会被关闭。为了能够后台运行,我们需要使用nohup这个命令,比如我们有个start.sh需要在后台运行,并且希望在 后台能够一直运行,那么就使用nohup:

            nohup /root/start.sh &

四, python反编译工具
decompyle

五,rpm包转deb包工具: fakeroot and alien
fakeroot alien -d *.rpm


六, 保存ftest信息并查看
nohup ./nordicbetsite ftest -v2 >ftest_result 2>&1 &
tail -f ftest_result

七, ip信息
ifconfig

八, dpkg命令
查看python2.5是否安装: dpkg -l python2.5
查看名称含有python的所有软件: dpkg -l | grep python
查看python2.5软件包的位置: dpkg -L python2.5

九, 分区情况
查看所有分区情况: df -h
查看某个软件在哪个分区: df -h ***


posted @ 2007-05-08 16:47 保尔任 阅读(599) | 评论 (0)编辑 收藏
命令行下载工具 ,转自:http://blog.chinaunix.net/u/9465/showart.php?id=186155,方便在虚拟机上开发,不用再从外面拷贝到虚拟机上了。

   对于喜欢命令行操作及追求高效率、高速度下载的朋友,推荐使用命令行下载工具。命令行工具不但使用方便,而且大多具有很高的下载速度及下载效率,尤其适合 于大批量下载文件。下面就为大家详细介绍一下这些工具。

    Wget     Wget是一个十分常用命令行下载工具,多数Linux发行版本都默认包含这个工具。如果没有安装可在http://www.gnu.org/software/wget/wget.html下 载最新版本,并使用如下命令编译安装:
    #tar zxvf wget-1.9.1.tar.gz
    #cd wget-1.9.1 #./configure
    #make #make install
它的用法很简单,Wget使用格式如下: #wget [选项] [下载地址] 1.Wget常用参数 ◆-b:后台下载,Wget默认的是把文件下载到当前目录。 ◆-O:将文件下载到指定的目录中。 ◆-P:保存文件之前先创建指定名称的目录。 ◆-t:尝试连接次数,当Wget无法与服务器建立连接时,尝试连接多少次。 ◆-c:断点续传,如果下载中断,那么连接恢复时会从上次断点开始下载。     除了上述常用功能,Wget还支持HTTP和FTP代理功能,编辑其配置文件“/etc/wgetrc”即可。具体方法是使用VI编辑器打开上述文件,将 “http_proxy”和“ftp_proxoy”前的#去掉,然后在这两项后输入相应的代理服务器的地址,保存退出即可。此外,Wget还可下载整个 网站,如下载http://man.chinaunix.net整个Man手册中心。只需输入如下命令即可: #wget -r -p -np -k http://man.chinaunix.net 其中-r参数是指使用递归下载,-p是指下载所有显示完整网页所以需要的文件,如图片等,-np是指不搜索上层目录,-k则是指将绝对链接转换为相对链 接。

     Prozilla     Prozilla也是一个十分流行的命令行下载工具,支持多线程下载和断点续传功能。可到http://prozilla.genesys.ro/下 载最新的1.3.7.4安装包,下载安装包后使用如下命令进行安装:
    #tar zxvf prozilla-1.3.7.4.tar.gz
    #cd prozilla-1.3.7.4
    #./configure #make
    #make install
Prozilla命令格式如下: #proz [参数] [下载地址] 常用的选项有: ◆-k=n :设置n个线程下载。不加此参数指定线程数,Prozilla默认为4线程下载。 ◆-P, --directory-prefix=DIR:指定将下载的文件保存在DIR/目录。 ◆-r, --resume:继续下载未完成的文件。如果要指定线程数下载可用如下命令: #proz -k=5 http://64.12.204.21/pub/mozilla.org/firefox/releases/1.0/linux-i686/zh-CN/firefox-1.0.installer.tar.gz 这样便以5线程进行文件的下载,并将文件保存到当前目录。和Wget一样,Prozilla也提供了续传功能,下载中断后,重新输入上述命令,就会出现提 示续传,按R键就可继续下载了。

     MyGet     MyGet目标设计成一个可扩展的,拥有丰富界面的多线程下载工具,它支持HTTP、FTP、HTTPS、MMS、RTSP等协议。在http://myget.sourceforge.net/release/myget-0.1.0.tar.bz2下 载其最新版本0.1.0,下载后使用如下命令安装:
     #tar jxvf myget-0.1.0.tar.bz2
    #cd myget-0.1.0 #./configure
    #make
    #make install
MyGet命令格式如下: #mytget [选项] [下载地址] 常用的选项: ◆-d [目录]:指定下载到的文件在本地存放的位置,默认当前目录。 ◆-f [文件]:指定下载文件名称。 ◆-h:帮助选项。 ◆-n [线程数]:下载线程数量,默认为4个。 ◆-x [代理服务器地址]:设置代理服务器地址,如“-x http://user:password@host:port”。 MyGet常用的形式如下: #mytget -d /root/ -n 10 http://lumaqq.linuxsir.org/download/patch/lumaqq_2004t_patch_2005.07.21.00.00.zip        

    Linuxdown     Linuxdown是一个命令行多线程下载工具,最多可支持30线程的下载。在https://gro.clinux.org/frs/download.php/1015/linuxdown-1.0.0.tar.gz下 载最新的1.1.0版本。然后使用如下命令进行编译安装:
    #tar zxvf linuxdown-1.1.0.tar.gz
    #cd dandelion/
    #make
    #make install
linuxdown格式为: #linuxdown [下载地址] [选项] [线程数]     需要注意的是下载地址和选项都需要西文引号括起来,线程数不可超过30个。一个典型的下载如下: #linuxdown "http://lumaqq.linuxsir.org/download/patch/lumaqq_2004t_patch_2005.07.21.00.00.zip" 30

    Curl     Curl也是Linux下不错的命令行下载工具,小巧、高速,唯一的缺点是不支持多线程下载。在http://curl.haxx.se/download/curl-7.14.0.tar.gz下 载最新版本。下载后便可使用如下命令编译安装:         #tar zxvf curl-7.14.0.tar.gz
    #cd curl-7.14.0/
    #./configure
    #make
    #make test
    #make install
Curl使用格式如下: #curl [选项][下载地址] Curl典型下载如下: #curl -O http://10.1.27.10/~kennycx/tools/lumaqq_2004-linux_gtk2_x86_with_jre.tar.gz     使用Curl下载一个文件并保存到当前目录。此外,Curl虽然不支持多线程下载,但它可同时下载多个文件或下载文件的某一部分,可使用如下命令实现: #curl -r 0-199 http://www.netscape.com/ 获得文件的前200 bytes。     对于常用的代理下载Curl也可轻松实现,具体操作如下: #curl -x 10.1.27.10:1022 ftp://ftp.funet.fi/README 使用代理地址为10.1.27.10端口为1022的代理服务器下载一个文件。 #curl -U user:passwd -x 10.1.27.10:1022 ftp://ftp.funet.fi/README 如果代理服务器需要特别的验证,则需要在user:passwd处输入合法的帐号和密码。

    Axel     Axel是命令行下的多线程下载工具,支持断点续传,速度通常情况下是Wget的几倍。可在http://www.linuxfans.org/nuke/modules.php?name=Site_Downloads&op=mydown&did=1697下 载。下载后使用如下命令编译安装:
    #tar zxvf axel-1.0a.tar.gz
    #cd axel-1.0a/
    #./configure
    #make
    #make install
基本的用法如下: #axel [选项] [下载目录] [下载地址] 一个典型下载如下: #alex -n 10 -o /home/kennycx/ http://10.1.27.10/~kennycx/tools/lumaqq_2004-linux_gtk2_x86_with_jre.tar.gz 用10线程将指定路径的文件下载到/home/kennycx/这个目录下。     本文详细介绍了Linux中常用的下载工具,这些下载工具功能上各有千秋,使用上都比较简单,所以无论是初学者还是Linux高手总有一款适合你。
posted @ 2007-04-25 10:03 保尔任 阅读(399) | 评论 (0)编辑 收藏

Hashtable和HashMap类有三个重要的不同之处。第一个不同主要是历史原因。Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现。

也许最重要的不同是Hashtable的方法是同步的,而HashMap的方法不是。这就意味着,虽然你可以不用采取任何特殊的行为就可以在一个多线程的应用程序中用一个Hashtable,但你必须同样地为一个HashMap提供外同步。一个方便的方法就是利用Collections类的静态的synchronizedMap()方法,它创建一个线程安全的Map对象,并把它作为一个封装的对象来返回。这个对象的方法可以让你同步访问潜在的HashMap。这么做的结果就是当你不需要同步时,你不能切断Hashtable中的同步(比如在一个单线程的应用程序中),而且同步增加了很多处理费用。

第三点不同是,只有HashMap可以让你将空值作为一个表的条目的key或value。HashMap中只有一条记录可以是一个空的key,但任意数量的条目可以是空的value。这就是说,如果在表中没有发现搜索键,或者如果发现了搜索键,但它是一个空的值,那么get()将返回null。如果有必要,用containKey()方法来区别这两种情况。

一些资料建议,当需要同步时,用Hashtable,反之用HashMap。但是,因为在需要时,HashMap可以被同步,HashMap的功能比Hashtable的功能更多,而且它不是基于一个陈旧的类的,所以有人认为,在各种情况下,HashMap都优先于Hashtable。

关于Properties
有时侯,你可能想用一个hashtable来映射key的字符串到value的字符串。DOS、Windows和Unix中的环境字符串就有一些例子,如key的字符串PATH被映射到value的字符串C:\WINDOWS;C:\WINDOWS\SYSTEM。Hashtables是表示这些的一个简单的方法,但Java提供了另外一种方法。

Java.util.Properties类是Hashtable的一个子类,设计用于String keys和values。Properties对象的用法同Hashtable的用法相象,但是类增加了两个节省时间的方法,你应该知道。

Store()方法把一个Properties对象的内容以一种可读的形式保存到一个文件中。Load()方法正好相反,用来读取文件,并设定Properties对象来包含keys和values。

注意,因为Properties扩展了Hashtable,你可以用超类的put()方法来添加不是String对象的keys和values。这是不可取的。另外,如果你将store()用于一个不包含String对象的Properties对象,store()将失败。作为put()和get()的替代,你应该用setProperty()和getProperty(),它们用String参数。

好了,我希望你现在可以知道如何用hashtables来加速你的处理了。

 

 

下面再转一篇关于两个类的区别,比较简单的过一下

最近同学找工作,经常被问到这个问题rt,所以。。。。。。
 
HashTable的应用非常广泛,HashMap是新框架中用来代替HashTable的类,也就是说建议使用HashMap,不要使用HashTable
 
这里简单分析他们的区别。 
1.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。(最主要的区别)

2.HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以,只容许有一个null值的key,可以有多个null值的value)。

3.HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。

4.HashTable使用Enumeration,HashMap使用Iterator。

以上只是表面的不同,它们的实现也有很大的不同。

5.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

6.哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap重新计算hash值,而且用代替求模:
int hash = hash(k);
int i = indexFor(hash, table.length);
static int hash(Object x) {
   int h = x.hashCode();

   h += ~(h << 9);
   h ^= (h >>> 14);
   h += (h << 4);
   h ^= (h >>> 10);
   return h;
}
static int indexFor(int h, int length) {
   return h & (length-1);
}
以上只是一些比较突出的区别,当然他们的实现上还是有很多不同的,比如
HashMap对null的操作。
posted @ 2007-03-29 20:32 保尔任 阅读(368) | 评论 (0)编辑 收藏

java.math.Math类常用的常量和方法:

Math.PI 记录的圆周率
Math.E记录e的常量
Math.abs 求绝对值
Math.sin 正弦函数 Math.asin 反正弦函数
Math.cos 余弦函数 Math.acos 反余弦函数
Math.tan 正切函数 Math.atan 反正切函数&nbsp;Math.atan2 商的反正切函数
Math.toDegrees 弧度转化为角度 Math.toRadians 角度转化为弧度
Math.ceil 得到不小于某数的最大整数
Math.floor 得到不大于某数的最大整数
Math.IEEEremainder 求余
Math.max 求两数中最大
Math.min 求两数中最小
Math.sqrt 求开方
Math.pow 求某数的任意次方, 抛出ArithmeticException处理溢出异常
Math.exp 求e的任意次方
Math.log10 以10为底的对数
Math.log 自然对数
Math.rint 求距离某数最近的整数(可能比某数大,也可能比它小)
Math.round 同上,返回int型或者long型(上一个函数返回double型)
Math.random 返回0,1之间的一个随机数

java.math.BigInteger(大整数):
BigInteger bi1=new BigInteger("1234567890123456890");
BigInteger bi2=BigInteger.valueOf(123L);
bi1=bi1.add(bi2);//b1+b2
bi1=bi1.multiply(bi2);//b1*b2
bi1=bi1.subtract(bi2);//b1-b2
bi1=bi1.divide(bi2);// b1/b2

java.math.BigDecimal(大浮点数):
BigDecimal bd = new BigDecimal("3.1415926");
bd = bd.setScale(2,BigDecimal.ROUND_DOWN);//取3.1415926小数点后面二位

posted @ 2007-03-16 15:54 保尔任 阅读(4467) | 评论 (1)编辑 收藏

1、classpath不用再定义.;***\lib\tools.jar;***\lib\rt.jar,因为jre会自动寻找lib目录

2、如果想要用jdk5.0编译出jdk1.4可运行的class文件需要带-source和-target两个参数
eg: javac -source 1.4 -target 1.4 Hello.java

3、命令行读入int i = System.in.read();//读入输入字符串的第一个字符的int值;
读整个字符串时:
public class Test{
 public static void main(String[] args){
  byte[] a = new byte[100];
  try {
   System.in.read(a);
  } catch (IOException e) {
   e.printStackTrace();
  }
  System.out.println(new String(a));
 }
}

jdk5.0中命令行读入的方法更好,可以读成不同类型的数据:
//Scanner取得输入的依据是:空格键、Tab键或Enter键
import java.util.Scanner;

public class ScannerDemo{
 public static void main(String[] args){
  Scanner scanner = new Scanner(System.in);
  System.out.print("请输入姓名");
  System.out.printf("您好%s!\n", scanner.next());
  System.out.print("请输入年龄");
  System.out.printf("您好%d!\n", scanner.nextInt());
  //还有scanner.nextFloat(),scanner.nextBoolean();
 }
}

//BufferReader取得输入的依据是:Enter键
import java.io.*;

public class BufferReaderDemo{
 public static void main(String[] args){
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
  System.out.print("请输入一系列文字");
  String text = bufferedReader.readLine();
  System.out.print("您输入的是:" + text);
 }
 }
}

4、aotuboxing和unboxing,jdk5.0可以自动对基本类型和它们的包装类型自动转换。

5、数组
数组的索引值:由0开始的原因:索引值表示偏移量,第一个值的偏移为0.

数组的初始化:byte/short/int = 0; long = ol; float = o.0f; double = 0.0d; char = \u0000; boolean = false; Objective = null;

一维数组:
法一:int[] i = {1,2,3};
法二:int[] i = new int[]{1,2,3};
法三:int[] i = new int[3]; i[0] = 1; i[1] = 2; i[2] = 3;

多维数组:
法一:int[][] i = {{...},...,{...}};
法二:int[][] i = int[][]{{...},...,{...}};
法三:int[][] i = int[3][]; i[0] = {1,2,3}; i[0] = {1,2,3}; i[0] = {1,2,3};
法四:int[][] i = int[3][3];

不规则数组:行列不等

数组的常用方法:都是java.util.Arrays类的方法
sort()//制定数组快速排序
binarySearch()//对已排序的数组搜索,找到返回索引,否则返回负值
fill()//根据数组的数据类型填默认值
equals()//比较两数组
jdk1.5中新增:
deepEquals()//深度比较
deepToString()//深度输出

foreach与数组:
String[] a = {"asd","efge","efg"};
for(String s : a)
 System.out.println(s);

5、字符串
java.lang.StringBuilder是jdk5.0新增的类,它与StringBuffer具有相同接口,只是单机非多线程情况下用StringBuilder效率较高,因为StringBuilder没处理同步问题;多线程下用StringBuffer好。

字符串分离:
 String s = "23/twomen/tlai/t jeje";
 String[] a = s.split("/t");
 for(int i = 0; i < a.length; i++){
  System.out.print(a[i] + " ");
 }
输出结构:23 women lai  jeje

由于工作关系学习jdk5.0的步伐暂时停止,以后有机会继续看《jdk5.0学习笔记》,回来写我的总结。

posted @ 2007-03-08 15:06 保尔任 阅读(449) | 评论 (0)编辑 收藏
/*
 * 题目:
 * 编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。 
 * 
 * 解释:
 * 此处的编码方式应该是操作系统默认的GB编码,即汉字占2个字节且第一个字节的最高位是1,
 * 如果理解为有符号数的话,就是负数;而英文占1个字节,符合ASC2码。
 
*/

class  SplitString 
{
 
private  String str;
 
private   int  byteNum;

 
public  SplitString() {}

 
public  SplitString(String str, int  byteNum)
 
{
  
this .str = str;
  
this .byteNum = byteNum;

 }

 
 
public   void  splitIt()
 
{

  
byte  bt[] = str.getBytes();
  System.out.println(
" Length of this String ===> " + bt.length);
  
if (byteNum >= 1 )
  
{
   
if (bt[byteNum] < 0 )
   
{
    String substrx
= new  String(bt, 0 , -- byteNum);
    System.out.println(substrx);
   }
else
   
{
    String substrex
= new  String(bt, 0 ,byteNum);
    System.out.println(substrex);
   }

  }
else
  

   System.out.println(
" 输入错误!!!请输入大于零的整数: " );
  }

 }

}


public   class  TestSplitString
{
 
public   static   void  main(String args[])
 
{
  String str
= " 我ABC汉DEF " ;
  
int  num = 6 ;
  SplitString sptstr 
=   new  SplitString(str,num);
  sptstr.splitIt();
 }

}
posted @ 2007-03-06 17:17 保尔任 阅读(1688) | 评论 (1)编辑 收藏
/*
 求两个字符串的最大公共子串
 String s1 = "abcdefghigj";
 String s2 = "xyzabcdeigj";
 则输出abcde
*/
 
public   class  Test
{
  
public  String search(String s1,String s2)
  
{
  String max 
=   "" ;
  
for ( int  i  =   0 ; i  <  s1.length(); i ++ )
  
{
    
for ( int  j  =  i + 1 ; j  <=  s1.length(); j ++ )
    
{
      String sub 
=  s1.substring(i,j);
      
if ((s2.indexOf(sub) !=   - 1 ) &&  sub.length()  >  max.length())
      
{
        max 
=  sub;
      }

    }

  }
  
  
return  max;
  }

  
  
public   static   void  main(String[] args)
  
{
    String s1 
=   " abedafghigj " ;
    String s2 
=   " xyzabfddfigj " ;
    String output 
=   new  Test().search(s1,s2);
    System.out.println(output);
  }

}
posted @ 2007-03-05 15:50 保尔任 阅读(900) | 评论 (0)编辑 收藏

1 术语定义

在字符串匹配问题中,我们期待察看串T中是否含有串P。
其中串T被称为目标串,串S被称为模式串。

2 朴素匹配算法

进行字符串匹配,最简单的一个想法是:

public   class  SimpleMatch  {
  
public   int  StringMatch(String target,String patten)  {
      
int  tl  =  target.length();
      
int  pl  =  patten.length();
      
int  i  =   0 ;
      
int  j  =   0 ;
      
while (i  <  tl  -  pl  &&  j  <  pl)  {
          
if (patten.charAt(j)  ==  target.charAt(i + j))
              j
++ ;
          
else   {
              j 
=   0 ;
              i
++ ;
          }

      }

      
if (j  ==  pl)
          
return  i;
      
return   - 1 ;
  }

  
  
public   static   void  main(String[] args) {
      String t 
=   " 123456789 " ;
      String p 
=   " 456 " ;
      SimpleMatch sm 
=   new  SimpleMatch();
      System.out.println(sm.StringMatch(t, p));
  }

}

可以看见,这个算法(假定m>>n)的复杂度是O(mn),其中m是T的长度,n是P的长度。这种算法的缺陷是匹配过程中带有回溯——准确地说是T串存在回溯,也就是当匹配不成功的时候,之前进行的匹配完全变为无用功,所有的比较需要重新开始。

3 KMP算法

KMP算法是D.E.Knuth、J.H.Morris和V.R.Pratt提出的无回溯的字符串匹配算法,算法的核心思想就是设法在匹配失败的时候,尽量利用之前的匹配结果,消除T串的回溯问题。那么如何消除回溯呢?请看下面的例子:

假设P=abacd,如果T=abax...,当从头开始匹配到字符c时,若c=x,显然,匹配过程继续;当c≠x时,按照朴素的匹配算法,T串会发生回溯,之后T串会从第2个字符b开始重新匹配,而不是从匹配失败的字符x开始继续。但是显然,对于上述的匹配过程,T串不需要从b开始重新匹配,它只需要从x开始和P的b字符继续匹配即可。如下:
匹配过程:
P=abacd
T=abax....
     ^----比较到此处时发生匹配失败
朴素匹配算法:
P= abacd
T=abax...
   ^----回溯到b,重新开始和P的匹配
KMP算法:
P=  abacd
T=abax...
     ^----T串不回溯,从x处继续匹配

现在的问题是,按照KMP算法,匹配失败的时候,P串需要重新调整位置,但是调整的依据是什么?Knuth等人发现,P调整位置的依据和P的构造有关,和T无关。具体来说,定义失效函数:f(j)=k,其中0<=k<=j,且k是使得p0p1...pk-1 = pj-k+1pj-k+2...pj成立的最大整数。建立失效函数的算法如下:
public void Build() {
 if(pattern == null)
  throw new Exception("KMP Exception : null pattern");
 array = new int[pattern.Length];
 int i = 0, s = pattern.Length;
 if(s > 1)
  array[0] = 0;
 for(i = 1; i < s; i++) {
  if(pattern[i] == pattern[array[i - 1]])
   array[i] = array[i - 1] + 1;
  else
   array[i] = 0;
 }
}

匹配过程如下:
public int Match(String target, int start) {
 if(array == null || pattern == null || target == null)
  return -1;
 int target_index = start;
 int pattern_index = 0;
 int token_length = target.Length;
 int pattern_length = pattern.Length;
 while(target_index < token_length && pattern_index < pattern_length) {
  if(target[target_index] == pattern[pattern_index]) {
   target_index++;
   pattern_index++;
  } else {
   if(pattern_index == begin)
    target_index++;
   else
    pattern_index = array[pattern_index - 1];
  }
 }
 if(pattern_index == pattern_length)
  return target_index - pattern_length;
 return -1;
}

4 支持通配符?和*的KMP算法

KMP算法虽然能够进行字符串匹配,但是,在实践中字符串匹配往往还要支持通配符,MS系统中最常见的通配符是?和*。其中,?可以代表一个字符(不能没有),*可以代表任意多个字符(可以为空)。经典的KMP算法针对通配符是无能为力的,但是经过简单的改造,KMP算法也可以识别通配符。

首先是?,根据?的功能,?表示任意字符,也就是说在匹配过程中,?永远匹配成功。因此对匹配函数的修改十分简单:
...
 while(target_index < token_length && pattern_index < pattern_length) {
  if(target[target_index] == pattern[pattern_index]|| pattern[pattern_index] == '?') {
   target_index++;
   pattern_index++;
  } else {
...
建立失效函数的过程和匹配过程类似,修改如下:
...
 for(i = 1; i < s; i++) {
  if(pattern[i] == pattern[array[i - 1]]|| pattern[i] == '?' || pattern[array[i - 1]] == '?')
   array[i] = array[i - 1] + 1;
...

本质上,?并没有修改算法,而仅仅修改了匹配规则——遇到?则一定匹配。然而*与此不同,*的作用是匹配任意多个字符,显然我们不能简单的修改匹配过程而满足要求。如果我们重新思考*的作用,我们会发现*的另一个作用就是分割P串,即如果P=P1*P2,那么与其说*代表匹配任意多个字符,不如说P的匹配条件是在匹配P1子串后再匹配P2子串。

现在回顾失效函数的作用,如果当匹配到P的j+1位时匹配失败,那么重新开始匹配的时候,P串的位置调整到f(j)位,直到P串的位置调整到0,则匹配重新开始。但当P=P1*P2,假如P1已经匹配成功,而在P2中发生匹配失败,那么P串要需要调整位置,但P串无论如何调整,此时也不应该调整到0,最多调整到P2的开始处,因为P1已经匹配,只需匹配P2即可。假如P=abcab*abcab,失效函数应该是(注意之前提到*的作用):
a b c a b * a b c a b
0 0 0 1 2 - 6 6 6 7 8

因此,要想让KMP支持*,那么关键是要重新设计失效函数的建立算法,如下:
public void Build() {
 if(pattern == null)
  throw new Exception("KMP Exception : null pattern");
 array = new int[pattern.Length];
 int i = 0, s = pattern.Length;
 if(s > 1)
  array[0] = 0;
 int begin = 0;
 for(i = 1; i < s; i++) {
  if(pattern[i] == '*') {
   array[i] = i;
   begin = i + 1;
  } else if(pattern[i] == pattern[array[i - 1]] || pattern[i] == '?' || pattern[array[i - 1]] == '?')
   array[i] = array[i - 1] + 1;
  else
   array[i] = begin;
 }

算法中begin表示每段字符串的开始位置。此外,匹配过程也应该进行相应的修改,因为字符*对于匹配没有任何帮助,它属于占位符,因此需要跳过,匹配算法如下:
public int Match(String target, int start) {
 if(array == null || pattern == null || target == null)
  return -1;
 int target_index = start;
 int pattern_index = 0;
 int token_length = target.Length;
 int pattern_length = pattern.Length;
 int begin = 0;
 while(target_index < token_length && pattern_index < pattern_length) {
  if(pattern[pattern_index] == '*') {
   begin = pattern_index + 1;
   pattern_index++;
  } else if(target[target_index] == pattern[pattern_index] || pattern[pattern_index] == '?') {
   target_index++;
   pattern_index++;
  } else {
   if(pattern_index == begin)
    target_index++;
   else
    pattern_index = array[pattern_index - 1];
  }
 }
 if(pattern_index == pattern_length)
  return target_index - pattern_length + begin;
 return -1;
}

5 正则语言和确定状态自动机

一个数字逻辑的问题:设计一个识别11011的电路,解这个问题的关键就是设计出这个电路的DFA,如下:

仔细看看这个状态机,是不是和KMP的算法有几分类似呢?这并不是巧合,因为KMP算法中的失效函数总可以等价的转化为一个DFA。当然KMP的DFA远比识别11011的DFA要复杂,原因在于KMP接受的输入是全体字符集合,识别11011的DFA只接受0和1这两个输入。我们知道,一个正则语言和一个DFA是等价的,而KMP计算失效函数的算法,实际上等价于求DFA的过程,f(j)的值实际上表明状态j+1接受到不正确的字符时应该回溯到的状态(注意此时输入流并没有前进)。普通的字符串都能看成是一个正则语言,含有通配符?和*的字符串也可以等价的转换为一个正则表达式。但是,正则语言的集合远比KMP算法所能支持的模式集合的更大,期间原因还是刚才提过的输入问题。试想P=p1p2...pn,当匹配到pj的时候,如果下一个输入字符正是pj,那么状态机进入下一个状态,如果不是pj,那么状态机按照实效函数的指示转移到状态f(j-1),也就是说KMP状态机的每个状态只能根据输入是否为pj来进行转移。而正则表达式所对应的状态机则有所不同,如果正则语言L=l1l2...ln,假设这些都是字母,当匹配到lj位的时候,如果下一个输入字符正是lj,那么状态机进入下一个状态,否则它还可以根据输入的值进行转移,例如lj=c1时转换到状态x,lj=c2时状态转换到y等等。

6 结语

字符串匹配问题是老问题了,并没有太多新意可言,只不过虽然KMP算法十分简单,但它的内在含义还是十分深刻的。横向比较KMP、DFA和正则语言、正则表达式我们会发现,它们之间存在很多的关联,而这种比较也有利于我们更好的理解这些算法,或者改进这些算法。最后说一句,试图利用目前的框架使得KMP算法支持全部种类的通配符(对应于正则表达式就是x?、x*、x+、{m,n}等等)是不可能,而我们也不需要这么做,因为我们还有正则表达式嘛。

posted @ 2007-03-05 15:29 保尔任 阅读(5700) | 评论 (2)编辑 收藏
/*
 * 整形数组平衡点问题:平衡点指左边的整数和等于右边的整数和,
 * 求出平衡点位置,要求输入的数组可能是GB级
 * 
 * 本题要求找出整型数组的一个平衡点(如果要找出所有平衡点的话,按此方法需要把每一个平衡点都存起来)
 
*/


public   class  Test  {

    
public   int  findBalanceableNod( int [] a) {
        
if (a  ==   null ) {
            
return   - 1 ;
        }

        
long  sum  =   0l ;
        
long  subSum  =   0l ;
        
for ( int  i  =   0 ; i  <  a.length; i ++ ) {
            sum 
+=  a[i];
        }

        
for ( int  i  =   0 ; i  <  a.length; i ++ ) {
            
if (subSum  ==  sum  -  subSum  -  a[i]) {
                
return  i;
            }
else {
                subSum 
+=  a[i];
            }

        }

        
return   - 1 ;
    }

    
    
public   static   void  main(String[] args)  {
        
// 测试用例:平衡点为0位,为n-1位,为中间位,a的每个为存了Integer.MAX_VALUE(所以用sum,subSum用long型)
         int [] a  =   { - 1 } ;
        Test t 
=   new  Test();
        System.out.println(t.findBalanceableNod(a));
    }

}
posted @ 2007-03-05 10:40 保尔任 阅读(1148) | 评论 (0)编辑 收藏
/*
 * 原题如下:用1、2、2、3、4、6这六个数字,用java写一个main函数,打印出所有不同的排列,
 * 如:612234、412346等,要求:"4"不能在第三位,"3"与"6"不能相连. 
 * 
 * 1 把问题归结为图结构的遍历问题。实际上6个数字就是六个结点,把六个结点连接成无向连通图,对于每一个结点求这个图形的遍历路径,
 * 所有结点的遍历路径就是最后对这6个数字的排列组合结果集。 
 * 2 显然这个结果集还未达到题目的要求。从以下几个方面考虑: 
 * 1. 3,6不能相连:实际要求这个连通图的结点3,5之间不能连通, 可在构造图结构时就满足改条件,然后再遍历图。 
 * 2. 不能有重复: 考虑到有两个2,明显会存在重复结果,可以把结果集放在TreeSet中过滤重复结果 
 * 3. 4不能在第三位: 仍旧在结果集中去除满足此条件的结果。
 
*/


import  java.util.Iterator;
import  java.util.TreeSet;

public   class  Test  {

 
private  String[] b  =   new  String[]  " 1 " " 2 " " 2 " " 3 " " 4 " " 6 "  } ;

 
private   int  n  =  b.length;

 
private   boolean [] visited  =   new   boolean [n];

 
private   int [][] a  =   new   int [n][n];

 
private  String result  =   "" ;

 
private  TreeSet set  =   new  TreeSet();

 
public   static   void  main(String[] args)  {
  
new  Test().start();
 }


 
private   void  start()  {

  
//  Initial the map a[][]
   for  ( int  i  =   0 ; i  <  n; i ++ {
   
for  ( int  j  =   0 ; j  <  n; j ++ {
    
if  (i  ==  j)  {
     a[i][j] 
=   0 ;
    }
  else   {
     a[i][j] 
=   1 ;
    }

   }

  }


  
//  3 and 5 can not be the neighbor.
  a[ 3 ][ 5 =   0 ;
  a[
5 ][ 3 =   0 ;

  
//  Begin to depth search.
   for  ( int  i  =   0 ; i  <  n; i ++ {
   
this .depthFirstSearch(i);
  }


  
//  Print result treeset.
  Iterator it  =  set.iterator();
  
while  (it.hasNext())  {
   String string 
=  (String) it.next();
   System.out.println(string);
  }

 }


 
private   void  depthFirstSearch( int  startIndex)  {
  visited[startIndex] 
=   true ;
  result 
=  result  +  b[startIndex];
  
if  (result.length()  ==  n)  {
//    "4" can not be the third position.
    if  (result.indexOf( " 4 " !=   2 {
//     Filt the duplicate value.
    set.add(result);
   }

  }

  
for  ( int  j  =   0 ; j  <  n; j ++ {
   
if  (a[startIndex][j]  ==   1   &&  visited[j]  ==   false {
    depthFirstSearch(j);
   }

  }


  
//  restore the result value and visited value after listing a node.
  result  =  result.substring( 0 , result.length()  -   1 );
  visited[startIndex] 
=   false ;
 }

}


只要这样定义图,根本不用在代码中写IF ELSE语句。
实际上基于图的算法好处在于,只要你能定义好满足题目要求的图结构,遍历的结果就是你要的结果,不用任何对遍历结果做任何处理。包括本题中的:4不能在第三位置,3,5不能相连,唯一性要求,其实都可以在体现在构造的图形结构里,然后直接遍历图取得自己要的结果。而不用再次处理结果集。只是说这里实际上对其它要求要体现在图结构里有困难(理论上是可以的),但起码3,5不能相接是很好构造的,就是上面的代码段来解释的。

关于图形数据结构建议先看看数据结构的书,主要是将如何利用二维数组描述图结构,再看看图的深度遍历实现原理。最后再应用到这个问题上来,自然就不难明白了。

posted @ 2007-03-02 17:37 保尔任 阅读(2370) | 评论 (0)编辑 收藏

(转自:http://chinavery.100steps.net/chengxuyuan/4759.html
动态规划是本书介绍的五种算法设计方法中难度最大的一种,它建立在最优原则的基础上。采用动态规划方法,可以优雅而高效地解决许多用贪婪算法或分而治之算法无法解决的问题。在介绍动态规划的原理之后,本章将分别考察动态规划方法在解决背包问题、图象压缩、矩阵乘法链、最短路径、无交叉子集和元件折叠等方面的应用。

3.1 算法思想

和贪婪算法一样,在动态规划中,可将一个问题的解决方案视为一系列决策的结果。不同的是,在贪婪算法中,每采用一次贪婪准则便做出一个不可撤回的决策,而在动态规划中,还要考察每个最优决策序列中是否包含一个最优子序列。

例3-1 [最短路经] 考察图1 2 - 2中的有向图。假设要寻找一条从源节点s= 1到目的节点d= 5的最短路径,即选择此路径所经过的各个节点。第一步可选择节点2,3或4。假设选择了节点3,则此时所要求解的问题变成:选择一条从3到5的最短路径。如果3到5的路径不是最短的,则从1开始经过3和5的路径也不会是最短的。例如,若选择的子路径(非最短路径)是3,2,5 (耗费为9 ),则1到5的路径为1,3,2,5 (耗费为11 ),这比选择最短子路径3,4,5而得到的1到5的路径1,3,4,5 (耗费为9) 耗费更大。

所以在最短路径问题中,假如在的第一次决策时到达了某个节点v,那么不管v 是怎样确定的,此后选择从v 到d 的路径时,都必须采用最优策略。

例3-2 [0/1背包问题] 考察1 3 . 4节的0 / 1背包问题。如前所述,在该问题中需要决定x1 .. xn的值。假设按i = 1,2,.,n 的次序来确定xi 的值。如果置x1 = 0,则问题转变为相对于其余物品(即物品2,3,.,n),背包容量仍为c 的背包问题。若置x1 = 1,问题就变为关于最大背包容量为c-w1 的问题。现设r?{c,c-w1 } 为剩余的背包容量。

在第一次决策之后,剩下的问题便是考虑背包容量为r 时的决策。不管x1 是0或是1,[x2 ,.,xn ] 必须是第一次决策之后的一个最优方案,如果不是,则会有一个更好的方案[y2,.,yn ],因而[x1,y2,.,yn ]是一个更好的方案。

假设n=3, w=[100,14,10], p=[20,18,15], c= 11 6。若设x1 = 1,则在本次决策之后,可用的背包容量为r= 116-100=16 。[x2,x3 ]=[0,1] 符合容量限制的条件,所得值为1 5,但因为[x2,x3 ]= [1,0] 同样符合容量条件且所得值为1 8,因此[x2,x3 ] = [ 0,1] 并非最优策略。即x= [ 1,0,1] 可改进为x= [ 1,1,0 ]。若设x1 = 0,则对于剩下的两种物品而言,容量限制条件为11 6。总之,如果子问题的结果[x2,x3 ]不是剩余情况下的一个最优解,则[x1,x2,x3 ]也不会是总体的最优解。

例3-3 [航费] 某航线价格表为:从亚特兰大到纽约或芝加哥,或从洛杉矶到亚特兰大的费用为$ 1 0 0;从芝加哥到纽约票价$ 2 0;而对于路经亚特兰大的旅客,从亚特兰大到芝加哥的费用仅为$ 2 0。从洛杉矶到纽约的航线涉及到对中转机场的选择。如果问题状态的形式为(起点,终点),那么在选择从洛杉矶到亚特兰大后,问题的状态变为(亚特兰大,纽约)。从亚特兰大到纽约的最便宜航线是从亚特兰大直飞纽约,票价$ 1 0 0。而使用直飞方式时,从洛杉矶到纽约的花费为$ 2 0 0。不过,从洛杉矶到纽约的最便宜航线为洛杉矶-亚特兰大-芝加哥-纽约,其总花费为$ 1 4 0(在处理局部最优路径亚特兰大到纽约过程中选择了最低花费的路径:亚特兰大-芝加哥-纽约)。

如果用三维数组(t a g,起点,终点)表示问题状态,其中t a g为0表示转飞, t a g为1表示其他情形,那么在到达亚特兰大后,状态的三维数组将变为( 0,亚特兰大,纽约),它对应的最优路径是经由芝加哥的那条路径。

当最优决策序列中包含最优决策子序列时,可建立动态规划递归方程( d y n a m i c -programming recurrence equation),它可以帮助我们高效地解决问题。

例3-4 [0/1背包] 在例3 - 2的0 / 1背包问题中,最优决策序列由最优决策子序列组成。假设f (i,y) 表示例1 5 - 2中剩余容量为y,剩余物品为i,i + 1,.,n 时的最优解的值,即:和利用最优序列由最优子序列构成的结论,可得到f 的递归式。f ( 1 ,c) 是初始时背包问题的最优解。可使用( 1 5 - 2)式通过递归或迭代来求解f ( 1 ,c)。从f (n, * )开始迭式, f (n, * )由(1 5 - 1)式得出,然后由( 1 5 - 2)式递归计算f (i,*) ( i=n- 1,n- 2,., 2 ),最后由( 1 5 - 2)式得出f ( 1 ,c)。

对于例1 5 - 2,若0≤y<1 0,则f ( 3 ,y) = 0;若y≥1 0,f ( 3 ,y) = 1 5。利用递归式(1 5 - 2),可得f (2, y) = 0 ( 0≤y<10 );f(2,y)= 1 5(1 0≤y<1 4);f(2,y)= 1 8(1 4≤y<2 4)和f(2,y)= 3 3(y≥2 4)。因此最优解f ( 1 , 11 6 ) = m a x {f(2,11 6),f(2,11 6 - w1)+ p1} = m a x {f(2,11 6),f(2,1 6)+ 2 0 } = m a x { 3 3,3 8 } = 3 8。

现在计算xi 值,步骤如下:若f ( 1 ,c) =f ( 2 ,c),则x1 = 0,否则x1 = 1。接下来需从剩余容量c-w1中寻求最优解,用f (2, c-w1) 表示最优解。依此类推,可得到所有的xi (i= 1.n) 值。

在该例中,可得出f ( 2 , 11 6 ) = 3 3≠f ( 1 , 11 6 ),所以x1 = 1。接着利用返回值3 8 -p1=18 计算x2 及x3,此时r = 11 6 -w1 = 1 6,又由f ( 2 , 1 6 ) = 1 8,得f ( 3 , 1 6 ) = 1 4≠f ( 2 , 1 6 ),因此x2 = 1,此时r= 1 6 -w2 = 2,所以f (3,2) =0,即得x3 = 0。

动态规划方法采用最优原则( principle of optimality)来建立用于计算最优解的递归式。所谓最优原则即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次决策产生)的最优决策。由于对于有些问题的某些递归式来说并不一定能保证最优原则,因此在求解问题时有必要对它进行验证。若不能保持最优原则,则不可应用动态规划方法。在得到最优解的递归式之后,需要执行回溯(t r a c e b a c k)以构造最优解。

编写一个简单的递归程序来求解动态规划递归方程是一件很诱人的事。然而,正如我们将在下文看到的,如果不努力地去避免重复计算,递归程序的复杂性将非常可观。如果在递归程序设计中解决了重复计算问题时,复杂性将急剧下降。动态规划递归方程也可用迭代方式来求解,这时很自然地避免了重复计算。尽管迭代程序与避免重复计算的递归程序有相同的复杂性,但迭代程序不需要附加的递归栈空间,因此将比避免重复计算的递归程序更快。


3.2 应用

3.2.1 0/1背包问题

1. 递归策略

在例3 - 4中已建立了背包问题的动态规划递归方程,求解递归式( 1 5 - 2)的一个很自然的方法便是使用程序1 5 - 1中的递归算法。该模块假设p、w 和n 为输入,且p 为整型,F(1,c) 返回f ( 1 ,c) 值。

程序15-1 背包问题的递归函数

int F(int i, int y)

{// 返回f ( i , y ) .

if (i == n) return (y < w[n]) ? 0 : p[n];

if (y < w[i]) return F(i+1,y);

return max(F(i+1,y), F(i+1,y-w[i]) + p[i]);

}

程序1 5 - 1的时间复杂性t (n)满足:t ( 1 ) =a;t(n)≤2t(n- 1)+b(n>1),其中a、b 为常数。通过求解可得t (n) =O( 2n)。

例3-5 设n= 5,p= [ 6 , 3 , 5 , 4 , 6 ],w=[2,2,6,5,4] 且c= 1 0 ,求f ( 1 , 1 0 )。为了确定f ( 1 , 1 0 ),调用函数F ( 1 , 1 0 )。递归调用的关系如图1 5 - 1的树型结构所示。每个节点用y值来标记。对于第j层的节点有i=j,因此根节点表示F ( 1 , 1 0 ),而它有左孩子和右孩子,分别对应F ( 2 , 1 0 )和F ( 2 , 8 )。总共执行了2 8次递归调用。但我们注意到,其中可能含有重复前面工作的节点,如f ( 3 , 8 )计算过两次,相同情况的还有f ( 4 , 8 )、f ( 4 , 6 )、f ( 4 , 2 )、f ( 5 , 8 )、f ( 5 , 6 )、f ( 5 , 3 )、f (5,2) 和f ( 5 , 1 )。如果保留以前的计算结果,则可将节点数减至1 9,因为可以丢弃图中的阴影节点。

正如在例3 - 5中所看到的,程序1 5 - 1做了一些不必要的工作。为了避免f (i,y)的重复计算,必须定义一个用于保留已被计算出的f (i,y)值的表格L,该表格的元素是三元组(i,y,f (i,y) )。在计算每一个f (i,y)之前,应检查表L中是否已包含一个三元组(i,y, * ),其中*表示任意值。如果已包含,则从该表中取出f (i,y)的值,否则,对f (i,y)进行计算并将计算所得的三元组(i,y,f (i,y) )加入表L。L既可以用散列(见7 . 4节)的形式存储,也可用二叉搜索树(见11章)的形式存储。

2. 权为整数的迭代方法

当权为整数时,可设计一个相当简单的算法(见程序1 5 - 2)来求解f ( 1 ,c)。该算法基于例3 - 4所给出的策略,因此每个f (i,y) 只计算一次。程序1 5 - 2用二维数组f [ ][ ]来保存各f 的值。而回溯函数Tr a c e b a c k用于确定由程序1 5 - 2所产生的xi 值。函数K n a p s a c k的复杂性为( n c),而Tr a c e b a c k的复杂性为( n )。

程序15-2 f 和x 的迭代计算

template<class T>

void Knapsack(T p[], int w[], int c, int n, T** f)

{// 对于所有i和y计算f [ i ] [ y ]

// 初始化f [ n ] [ ]

for (int y = 0; y <= yMax; y++)

f[n][y] = 0;

for (int y = w[n]; y <= c; y++)

f[n][y] = p[n];

// 计算剩下的f

for (int i = n - 1; i > 1; i--) {

for (int y = 0; y <= yMax; y++)

f[i][y] = f[i+1][y];

for (int y = w[i]; y <= c; y++)

f[i][y] = max(f[i+1][y], f[i+1][y-w[i]] + p[i]);

}

f[1][c] = f[2][c];

if (c >= w[1])

f[1][c] = max(f[1][c], f[2][c-w[1]] + p[1]);

}

template<class T>

void Traceback(T **f, int w[], int c, int n, int x[])

{// 计算x

for (int i = 1; i < n; i++)

if (f[i][c] == f[i+1][c]) x[i] = 0;

else {x[i] = 1;

c -= w[i];}

x[n] = (f[n][c]) ? 1 : 0;

}

3. 元组方法( 选读)

程序1 5 - 2有两个缺点:1) 要求权为整数;2) 当背包容量c 很大时,程序1 5 - 2的速度慢于程序1 5 - 1。一般情况下,若c>2n,程序1 5 - 2的复杂性为W (n2n )。可利用元组的方法来克服上述两个缺点。在元组方法中,对于每个i,f (i, y) 都以数对(y, f (i, y)) 的形式按y的递增次序存储于表P(i)中。同时,由于f (i, y) 是y 的非递减函数,因此P(i) 中各数对(y, f (i, y)) 也是按f (i, y) 的递增次序排列的。

例3-6 条件同例3 - 5。对f 的计算如图1 5 - 2所示。当i= 5时,f 由数对集合P( 5 ) = [ ( 0 , 0 ) , ( 4 , 6 ) ]表示。而P( 4 )、P( 3 )和P( 2 )分别为[ ( 0 , 0 ) , ( 4 , 6 ) , ( 9 , 1 0 ) ]、[ ( 0 , 0 ) ( 4 , 6 ) , ( 9 , 1 0 ) , ( 1 0 , 11)] 和[ ( 0 , 0 ) ( 2 , 3 ) ( 4 , 6 ) ( 6 , 9 ) ( 9 , 1 0 ) ( 1 0 , 11 ) ]。

为求f ( 1 , 1 0 ),利用式(1 5 - 2)得f ( 1 , 1 0 ) = m a x{f ( 2 , 1 0 ),f ( 2 , 8 ) + p 1}。由P( 2 )得f ( 2 , 1 0 ) = 11、f (2,8)=9 (f ( 2 , 8 ) = 9来自数对( 6,9 ) ),因此f ( 1 , 1 0 ) = m a x{11 , 1 5}= 1 5。现在来求xi 的值,因为f ( 1 , 1 0 ) =f ( 2 , 6 ) +p1,所以x1 = 1;由f ( 2 , 6 ) =f ( 3 , 6 - w 2 ) +p2 =f ( 3 , 4 ) +p2,得x2 = 1;由f ( 3 , 4 ) =f ( 4 , 4 ) =f ( 5 , 4 )得x3=x4 = 0;最后,因f ( 5 , 4 )≠0得x5= 1。

检查每个P(i) 中的数对,可以发现每对(y,f (i,y)) 对应于变量xi , ., xn 的0/1 赋值的不同组合。设(a,b)和(c,d)是对应于两组不同xi , ., xn 的0 / 1赋值,若a≥c且b<d,则(a, b) 受(b, c) 支配。被支配者不必加入P(i)中。若在相同的数对中有两个或更多的赋值,则只有一个放入P(i)。假设wn≤C,P(n)=[(0,0), (wn , pn ) ],P(n)中对应于xn 的两个数对分别等于0和1。对于每个i,P(i)可由P(i+ 1 )得出。首先,要计算数对的有序集合Q,使得当且仅当wi≤s≤c且(s-wi , t-pi )为P(i+1) 中的一个数对时,(s,t)为Q中的一个数对。现在Q中包含xi = 1时的数对集,而P(i+ 1 )对应于xi = 0的数对集。接下来,合并Q和P(i+ 1 )并删除受支配者和重复值即可得到P(i)。

例3-7 各数据同例1 5 - 6。P(5)=[(0,0),(4,6)], 因此Q= [ ( 5 , 4 ) , ( 9 , 1 0 ) ]。现在要将P( 5 )和Q合并得到P( 4 )。因( 5 , 4 )受( 4 , 6 )支配,可删除( 5 , 4 ),所以P(4)=[(0,0), (4,6), (9,10)]。接着计算P( 3 ),首先由P( 4 )得Q=[(6,5), (10,11 ) ],然后又由合并方法得P(3)=[(0,0), (4,6), (9,10), (10,11 ) ]。最后计算P( 2 ):由P( 3 )得Q= [ ( 2 , 3 ),( 6 , 9 ) ],P( 3 )与Q合并得P(2)=[(0,0), (2,3), (4,6), (6,9), (9,10). (10,11 ) ]。因为每个P(i) 中的数对对应于xi , ., xn 的不同0 / 1赋值,因此P(i) 中的数对不会超过2n-i+ 1个。计算P(i) 时,计算Q需消耗( |P(i+ 1 ) |)的时间,合并P(i+1) 和Q同样需要( |P(i+ 1 ) | )的时间。计算所有P(i) 时所需要的总时间为: (n ?i=2|P(i + 1)|= O ( 2n )。当权为整数时,|P(i) |≤c+1, 此时复杂性为O ( m i n {n c, 2n } )。

如6 . 4 . 3节定义的,数字化图像是m×m的像素阵列。假定每个像素有一个0 ~ 2 5 5的灰度值。因此存储一个像素至多需8位。若每个像素存储都用最大位8位,则总的存储空间为8m2 位。为了减少存储空间,我们将采用变长模式( variable bit scheme),即不同像素用不同位数来存储。像素值为0和1时只需1位存储空间;值2、3各需2位;值4,5,6和7各需3位;以此类推,使用变长模式的步骤如下:

1) 图像线性化根据图15-3a 中的折线将m×m维图像转换为1×m2 维矩阵。

2) 分段将像素组分成若干个段,分段原则是:每段中的像素位数相同。每个段是相邻像素的集合且每段最多含2 5 6个像素,因此,若相同位数的像素超过2 5 6个的话,则用两个以上的段表示。

3) 创建文件创建三个文件:S e g m e n t L e n g t h, BitsPerPixel 和P i x e l s。第一个文件包含在2 )中所建的段的长度(减1 ),文件中各项均为8位长。文件BitsPerPixel 给出了各段中每个像素的存储位数(减1),文件中各项均为3位。文件Pixels 则是以变长格式存储的像素的二进制串。

4) 压缩文件压缩在3) 中所建立的文件,以减少空间需求。

上述压缩方法的效率(用所得压缩率表示)很大程度上取决于长段的出现频率。

例3-8 考察图15-3b 的4×4图像。按照蛇形的行主次序,灰度值依次为1 0,9,1 2,4 0,5 0,3 5,1 5,1 2,8,1 0,9,1 5,11,1 3 0,1 6 0和2 4 0。各像素所需的位数分别为4,4,4,6,6,6,4,4,4,4,4,4,4,8,8和8,按等长的条件将像素分段,可以得到4个段[ 1 0,9,1 2 ]、[ 4 0,5 0,3 5 ]、[15, 12, 8, 10, 9, 15, 11] 和[130, 160, 240]。因此,文件SegmentLength 为2,2,6,2;文件BitsPerSegment 的内容为3,5,3,7;文件P i x e l s包含了按蛇形行主次序排列的1 6个灰度值,其中头三个各用4位存储,接下来三个各用6位,再接下来的七个各用4位,最后三个各用8位存储。因此存储单元中前3 0位存储了前六个像素:

1010 1001 1100 111000 110010 100011

这三个文件需要的存储空间分别为:文件SegmentLength 需3 2位;BitsPerSegment 需1 2位;Pixels 需8 2位,共需1 2 6位。而如果每个像素都用8位存储,则存储空间需8×1 6 = 1 2 8位,因而在本例图像中,节省了2位的空间。

假设在2) 之后,产生了n 个段。段标题(segment header)用于存储段的长度以及该段中每个像素所占用的位数。每个段标题需11位。现假设li 和bi 分别表示第i 段的段长和该段每个像素的长度,则存储第i 段像素所需要的空间为li *bi 。在2) 中所得的三个文件的总存储空间为11 n+n ?i = 1li bi。可通过将某些相邻段合并的方式来减少空间消耗。如当段i 和i+ 1被合并时,合并后的段长应为li +li + 1。此时每个像素的存储位数为m a x {bi,bi +1 } 位。尽管这种技术增加了文件P i x e l s的空间消耗,但同时也减少了一个段标题的空间。

例3-9 如果将例1 5 - 8中的第1段和第2段合并,合并后,文件S e g m e n t L e n g t h变为5,6,2,BitsPerSegment 变为5,3,7。而文件Pixels 的前3 6位存储的是合并后的第一段:001010 001001 001100 111000 110010 100011其余的像素(例1 5 - 8第3段)没有改变。因为减少了1个段标题,文件S e g m e n t L e n g t h和BitsPerPixel 的空间消耗共减少了11位,而文件Pixels 的空间增加6位,因此总共节约的空间为5位,空间总消耗为1 2 1位。

我们希望能设计一种算法,使得在产生n 个段之后,能对相邻段进行合并,以便产生一个具有最小空间需求的新的段集合。在合并相邻段之后,可利用诸如L Z W法(见7 . 5节)和霍夫曼编码(见9 . 5 . 3节)等其他技术来进一步压缩这三个文件。

令sq 为前q 个段的最优合并所需要的空间。定义s0 = 0。考虑第i 段(i>0 ),假如在最优合并C中,第i 段与第i- 1,i- 2,.,i-r+1 段相合并,而不包括第i-r 段。合并C所需要的空间消耗等于:第1段到第i-r 段所需空间+ l s u m (i-r+ 1 ,i) * b m a x (i-r+ 1 ,i) + 11

其中l s u m(a, b)=b ?j =a

lj,bmax (a, b)= m a x {ba , ..., bb }。假如在C中第1段到第i-r 段的合并不是最优合并,那么需要对合并进行修改,以使其具有更小的空间需求。因此还必须对段1到段i-r 进行最优合并,也即保证最优原则得以维持。故C的空间消耗为:

si = si-r +l s u m(i-r+1, i)*b m a x(i-r+1, i)+ 11

r 的值介于1到i 之间,其中要求l s u m不超过2 5 6 (因为段长限制在2 5 6之内)。尽管我们不知道如何选择r,但我们知道,由于C具有最小的空间需求,因此在所有选择中, r 必须产生最小的空间需求。

假定k a yi 表示取得最小值时k 的值,sn 为n 段的最优合并所需要的空间,因而一个最优合并可用kay 的值构造出来。

例3-10 假定在2) 中得到五个段,它们的长度为[ 6,3,1 0,2,3 ],像素位数为[ 1,2,3,2,1 ],要用公式(1 5 - 3)计算sn,必须先求出sn-1,.,s0 的值。s0 为0,现计算s1:s1 =s0 +l1 *b1+ 11 = 1 7k a y1 = 1s2 由下式得出:

s2 = m i n {s1 +l2 b2 , s0 + (l1 +l2 ) * m a x {b1 , b2} } + 11 = m i n { 1 7 + 6 , 0 + 9 * 2 } + 11 = 2 9

k a y2 = 2

以此类推,可得s1.s5 = [ 1 7,2 9,6 7,7 3,82] ,k a y1.k a y5 = [ 1,2,2,3,4 ]。因为s5 = 8 2,所以最优空间合并需8 2位的空间。可由k a y5 导出本合并的方式,过程如下:因为k a y5 = 4,所以s5 是由公式(1 5 - 3)在k=4 时取得的,因而最优合并包括:段1到段( 5 - 4 ) = 1的最优合并以及段2,3,4和5的合并。最后仅剩下两个段:段1以及段2到段5的合并段。

1. 递归方法

用递归式(1 5 - 3)可以递归地算出si 和k a yi。程序1 5 - 3为递归式的计算代码。l,b,和k a y是一维的全局整型数组,L是段长限制( 2 5 6),h e a d e r为段标题所需的空间( 11 )。调用S ( n )返回sn 的值且同时得出k a y值。调用Tr a c e b a c k ( k a y, n )可得到最优合并。

现讨论程序1 5 - 3的复杂性。t( 0 ) =c(c 为一个常数): (n>0),因此利用递归的方法可得t (n) = O ( 2n )。Tr a c e b a c k的复杂性为(n)。

程序15-3 递归计算s , k a y及最优合并

int S(int i)

{ / /返回S ( i )并计算k a y [ i ]

if (i == 0 ) return 0;

//k = 1时, 根据公式( 1 5 - 3)计算最小值

int lsum = l[i],bmax = b[i];

int s = S(i-1) + lsum * bmax;

kay[i] = 1;

/ /对其余的k计算最小值并求取最小值

for (int k = 2; k <= i && lsum+l[i-k+1] <= L; k++) {

lsum += l[i-k+1];

if (bmax < b[i-k+1]) bmax = b[i-k+1];

int t = S(i-k);

if (s > t + lsum * bmax) {

s = t + lsum * bmax;

kay[i] = k;}

}

return s + header;

}

void Traceback(int kay[], int n)

{// 合并段

if (n == 0) return;

Tr a c e b a c k ( k a y, n-kay[n]);

cout << "New segment begins at " << (n - kay[n] + 1) << endl;

}

2. 无重复计算的递归方法

通过避免重复计算si,可将函数S的复杂性减少到(n)。注意这里只有n个不同的si。

例3 - 11 再考察例1 5 - 1 0中五个段的例子。当计算s5 时,先通过递归调用来计算s4,.,s0。计算s4 时,通过递归调用计算s3,.,s0,因此s4 只计算了一次,而s3 计算了两次,每一次计算s3要计算一次s2,因此s2 共计算了四次,而s1 重复计算了1 6次!可利用一个数组s 来保存先前计算过的si 以避免重复计算。改进后的代码见程序1 5 - 4,其中s为初值为0的全局整型数组。

程序15-4 避免重复计算的递归算法

int S(int i)

{ / /计算S ( i )和k a y [ i ]

/ /避免重复计算

if (i == 0) return 0;

if (s[i] > 0) return s[i]; //已计算完

/ /计算s [ i ]

/ /首先根据公式(1 5 - 3)计算k = 1时最小值

int lsum = l[i], bmax = b[i];

s[i] =S(i-1) + lsum * bmax;

kay[i] = 1;

/ /对其余的k计算最小值并更新

for (int k = 2; k <= i && lsum+l[i-k+1] <= L; k++) {

lsum += l[i-k+1];

if (bmax < b[i-k+1]) bmax = b[i-k+1];

int t = S(i-k);

if (s[i] > t + lsum * bmax) {

s[i] = t + lsum * bmax;

kay[i] = k;}

}

s[i] += header;

return s[i];

}

为了确定程序1 5 - 4的时间复杂性,我们将使用分期计算模式( amortization scheme)。在该模式中,总时间被分解为若干个不同项,通过计算各项的时间然后求和来获得总时间。当计算si 时,若sj 还未算出,则把调用S(j) 的消耗计入sj ;若sj 已算出,则把S(j) 的消耗计入si (这里sj依次把计算新sq 的消耗转移至每个sq )。程序1 5 - 4的其他消耗也被计入si。因为L是2 5 6之内的常数且每个li 至少为1,所以程序1 5 - 4的其他消耗为( 1 ),即计入每个si 的量是一个常数,且si 数目为n,因而总工作量为(n)。

3. 迭代方法

倘若用式(1 5 - 3)依序计算s1 , ., sn,便可得到一个复杂性为(n)的迭代方法。在该方法中,在si 计算之前, sj 必须已计算好。该方法的代码见程序1 5 - 5,其中仍利用函数Tr a c e b a c k(见程序1 5 - 3)来获得最优合并。

程序15-5 迭代计算s和k a y

void Vbits (int l[], int b[], int n, int s[], int kay[])

{ / /计算s [ i ]和k a y [ i ]

int L = 256, header = 11 ;

s[0] = 0;

/ /根据式(1 5 - 3)计算s [ i ]

for (int i = 1; i <= n; i++) {

// k = 1时,计算最小值

int lsum = l,

bmax = b[i];

s[i] = s[i-1] + lsum * bmax;

kay[i] = 1;

/ /对其余的k计算最小值并更新

for (int k=2; k<= i && lsum+l[i-k+1]<= L; k++) {

lsum += l[i-k+1];

if (bmax < b[i-k+1]) bmax = b[i-k+1];

if (s[i] > s[i-k] + lsum * bmax){

s[i] = s[i-k] + lsum * bmax;

kay[i] = k; }

}

s[i] += header;

}

}


3.2.3 矩阵乘法链

m×n矩阵A与n×p矩阵B相乘需耗费(m n p)的时间(见第2章练习1 6)。我们把m n p作为两个矩阵相乘所需时间的测量值。现假定要计算三个矩阵A、B和C的乘积,有两种方式计算此乘积。在第一种方式中,先用A乘以B得到矩阵D,然后D乘以C得到最终结果,这种乘法的顺序可写为(A*B) *C。第二种方式写为A* (B*C) ,道理同上。尽管这两种不同的计算顺序所得的结果相同,但时间消耗会有很大的差距。

例3-12 假定A为1 0 0×1矩阵,B为1×1 0 0矩阵,C为1 0 0×1矩阵,则A*B的时间耗费为10 0 0 0,得到的结果D为1 0 0×1 0 0矩阵,再与C相乘所需的时间耗费为1 000 000,因此计算(A*B) *C的总时间为1 010 000。B*C的时间耗费为10 000,得到的中间矩阵为1×1矩阵,再与A相乘的时间消耗为1 0 0,因而计算A*(B*C)的时间耗费竟只有10 100!而且,计算( A*B)*C时,还需10 000个单元来存储A*B,而A*(B*C)计算过程中,只需用1个单元来存储B*C。

下面举一个得益于选择合适秩序计算A*B*C矩阵的实例:考虑两个3维图像的匹配。图像匹配问题的要求是,确定一个图像需旋转、平移和缩放多少次才能逼近另一个图像。实现匹配的方法之一便是执行约1 0 0次迭代计算,每次迭代需计算1 2×1个向量T:

T=?A(x, y, z) *B(x, y, z)*C(x, y, z )

其中A,B和C分别为1 2×3,3×3和3×1矩阵。(x , y, z) 为矩阵中向量的坐标。设t 表示计算A(x , y, z) *B(x , y, z) *C(x , y, z)的计算量。假定此图像含2 5 6×2 5 6×2 5 6个向量,在此条件中,这1 0 0个迭代所需的总计算量近似为1 0 0 * 2 5 63 * t≈1 . 7 * 1 09 t。若三个矩阵是按由左向右的顺序相乘的,则t = 1 2 * 3 * 3 + 1 2 * 3 *1= 1 4 4;但如果从右向左相乘, t = 3 * 3 * 1 + 1 2 * 3 * 1 = 4 5。由左至右计算约需2 . 4 * 1 011个操作,而由右至左计算大概只需7 . 5 * 1 01 0个操作。假如使用一个每秒可执行1亿次操作的计算机,由左至右需4 0分钟,而由右至左只需1 2 . 5分钟。

在计算矩阵运算A*B*C时,仅有两种乘法顺序(由左至右或由右至左),所以可以很容易算出每种顺序所需要的操作数,并选择操作数比较少的那种乘法顺序。但对于更多矩阵相乘来说,情况要复杂得多。如计算矩阵乘积M1×M2×.×Mq,其中Mi 是一个ri×ri + 1 矩阵( 1≤i≤q)。不妨考虑q=4 的情况,此时矩阵运算A*B*C*D可按以下方式(顺序)计算:

A* ( (B*C) *D) A* (B* (C*D)) (A*B) * (C*D) (A* (B*C) ) *D

不难看出计算的方法数会随q 以指数级增加。因此,对于很大的q 来说,考虑每一种计算顺序并选择最优者已是不切实际的。

现在要介绍一种采用动态规划方法获得矩阵乘法次序的最优策略。这种方法可将算法的时间消耗降为(q3 )。用Mi j 表示链Mi×.×Mj (i≤j)的乘积。设c(i,j) 为用最优法计算Mi j 的消耗,k a y(i, j) 为用最优法计算Mi j 的最后一步Mi k×Mk+1, j 的消耗。因此Mij 的最优算法包括如何用最优算法计算Mik 和Mkj 以及计算Mik×Mkj 。根据最优原理,可得到如下的动态规划递归式:k a y(i,i+s)= 获得上述最小值的k. 以上求c 的递归式可用递归或迭代的方法来求解。c( 1,q) 为用最优法计算矩阵链的消耗,k a y( 1 ,q) 为最后一步的消耗。其余的乘积可由k a y值来确定。

1. 递归方法

与求解0 / 1背包及图像压缩问题一样,本递归方法也须避免重复计算c (i, j) 和k a y(i, j),否则算法的复杂性将会非常高。

例3-13 设q= 5和r =(1 0 , 5 , 1 , 1 0 , 2 , 1 0),式中待求的c 中有四个c的s= 0或1,因此用动态规划方法可立即求得它们的值: c( 1 , 1 ) =c( 5 , 5 ) = 0 ;c(1,2)=50; c( 4 , 5 ) = 2 0 0。现计算C( 2,5 ):c( 2 , 5 ) = m i n {c( 2 , 2 ) +c(3,5)+50, c( 2 , 3 ) +c(4,5)+500, c( 2 , 4 ) +c( 5 , 5 ) + 1 0 0 } (1 5 - 5)其中c( 2 , 2 ) =c( 5 , 5 ) = 0;c( 2 , 3 ) = 5 0;c(4,5)=200 。再用递归式计算c( 3 , 5 )及c( 2 , 4 ) :c( 3 , 5 ) = m i n {c( 3 , 3 ) +c(4,5)+100, c( 3 , 4 ) +c( 5 , 5 ) + 2 0 } = m i n { 0 + 2 0 0 + 1 0 0 , 2 0 + 0 + 2 0 } = 4 0c( 2 , 4 ) = m i n {c( 2 , 2 ) +c( 3 , 4 ) + 1 0 ,c( 2 , 3 ) +c( 4 , 4 ) + 1 0 0 } = m i n { 0 + 2 0 + 1 0 , 5 0 + 1 0 + 2 0 } = 3 0由以上计算还可得k a y( 3 , 5 ) = 4,k ay( 2 , 4 ) = 2。现在,计算c(2,5) 所需的所有中间值都已求得,将它们代入式(1 5 - 5)得:

c(2,5)=min{0+40+50, 50+200+500, 30+0+100}=90且k a y( 2 , 5 ) = 2

再用式(1 5 - 4)计算c( 1 , 5 ),在此之前必须算出c( 3 , 5 )、c(1,3) 和c( 1 , 4 )。同上述过程,亦可计算出它们的值分别为4 0、1 5 0和9 0,相应的k a y 值分别为4、2和2。代入式(1 5 - 4)得:

c(1,5)=min{0+90+500, 50+40+100, 150+200+1000, 90+0+200}=190且k a y( 1 , 5 ) = 2

此最优乘法算法的消耗为1 9 0,由k a y(1,5) 值可推出该算法的最后一步, k a y(1,5) 等于2,因此最后一步为M1 2×M3 5,而M12 和M35 都是用最优法计算而来。由k a y( 1 , 2 ) = 1知M12 等于M11×M2 2,同理由k a y( 3 , 5) = 4得知M35 由M3 4×M55 算出。依此类推,M34 由M3 3×M44 得出。因而此最优乘法算法的步骤为:

M11×M2 2 = M1 2

M3 3×M4 4 = M3 4

M3 4×M5 5 = M3 5

M1 2×M3 5 = M1 5

计算c(i, j) 和k a y (i, j) 的递归代码见程序1 5 - 6。在函数C中,r 为全局一维数组变量, k a y是全局二维数组变量,函数C返回c(i j) 之值且置k a y [a] [b] =k ay (a , b) (对于任何a , b),其中c(a , b)在计算c(i,j) 时皆已算出。函数Traceback 利用函数C中已算出的k a y值来推导出最优乘法算法的步骤。

设t(q)为函数C的复杂性,其中q=j-i+ 1(即Mij 是q个矩阵运算的结果)。当q为1或2时,t(q) =d,其中d 为一常数;而q> 2时,t (q)=2q-1?k = 1t (k ) +e q,其中e 是一个常量。因此当q>2时,t(q)>2t (q- 1 ) +e,所以t (q)= W ( 2q)。函数Traceback 的复杂性为(q)。

程序15-6 递归计算c (i, j) 和kay (i, j)

int C(int i, int j)

{ / /返回c(i,j) 且计算k(i,j) = kay[i][j]

if (i==j) return 0; //一个矩阵的情形

if (i == j-1) { //两个矩阵的情形

kay[i][i+1] = i;

return r[i]*r[i+1]*r[r+2];}

/ /多于两个矩阵的情形

/ /设u为k = i 时的最小值

int u = C(i,i) + C(i+1,j) + r[i]*r[i+1]*r[j+1];

kay[i][j] = i;

/ /计算其余的最小值并更新u

for (int k = i+1; k < j; k++) {

int t = C(i,k) + C(k+1,j) + r[i]*r[k+1]*r[j+1];

if (r < u) {//小于最小值的情形

u = t;

kay[i][j] = k;

}

return u;

}

void Traceback (int i, int j ,int **kay)

{ / /输出计算Mi j 的最优方法

if ( i == j) return;

Traceback(i, kay[i][j], kay);

Traceback(kay[i][j]+1, j, kay);

cout << "Multiply M" << i << ", "<< kay[i][j];

cout << " and M " << (kay[i][j]+1) << ", " << j << end1;

}

2. 无重复计算的递归方法

若避免再次计算前面已经计算过的c(及相应的k a y),可将复杂性降低到(q3)。而为了避免重复计算,需用一个全局数组c[ ][ ]存储c(i, j) 值,该数组初始值为0。函数C的新代码见程序1 5 - 7:

程序15-7 无重复计算的c (i, j) 计算方法

int C(int i,int j)

{ / /返回c(i,j) 并计算k a y ( i , j ) = k a y [ I ] [ j ]

/ /避免重复计算

/ /检查是否已计算过

if (c[i][j] >) return c[i][j];

/ /若未计算,则进行计算

if(i==j) return 0; //一个矩阵的情形

i f ( i = = j - 1 ) { / /两个矩阵的情形

kay[i][i+1]=i;

c [ i ] [ j ] = r [ i ] * r [ i + 1 ] * r [ i + 2 ] ;

return c[i][j];}

/ /多于两个矩阵的情形

/ /设u为k = i 时的最小值

int u=C(i,i)+C(i+1,j)+r[i]*r[i+1]*r[j+1];

k a y [ i ] [ j ] = i ;

/ /计算其余的最小值并更新u

for (int k==i+1; k<j;k++){

int t=C(i,k)+C(k+1,j)+r[i]*r[k+1]*r[j+1];

if (t<u) {// 比最小值还小

u = t ;

k a y [ i ] [ j ] = k ; }

}

c [ i ] [ j ] = u ;

return u;

}

为分析改进后函数C 的复杂性,再次使用分期计算方法。注意到调用C(1, q) 时每个c (i, j)(1≤i≤j≤q)仅被计算一次。要计算尚未计算过的c(a,b),需附加的工作量s =j-i>1。将s 计入第一次计算c (a, b) 时的工作量中。在依次计算c(a, b) 时,这个s 会转计到每个c (a, b) 的第一次计算时间c 中,因此每个c (i, i) 均被计入s。对于每个s,有q-s+ 1个c(i, j) 需要计算,因此总的工作消耗为q-1 ?s=1(q-s+ 1) = (q3 )。

3. 迭代方法

c 的动态规划递归式可用迭代的方法来求解。若按s = 2,3,.,q-1 的顺序计算c (i, i+s),每个c 和kay 仅需计算一次。

例3-14 考察例3 - 1 3中五个矩阵的情况。先初始化c (i, i) (0≤i≤5) 为0,然后对于i=1, ., 4分别计算c (i, i+ 1 )。c (1, 2)= r1 r2 r3 = 5 0,c (2, 3)= 5 0,c ( 3,4)=20 和c (4, 5) = 2 0 0。相应的k ay 值分别为1,2,3和4。

当s= 2时,可得:

c( 1 , 3 ) = m i n {c( 1 , 1 ) +c(2,3)+ r1 r2 r4 , c( 1 , 2 ) +c( 3 ,3 )+r1r3r4 }=min

=150

且k a y( 1 , 3 ) = 2。用相同方法可求得c( 2 , 4 )和c( 3 , 5 )分别为3 0和4 0,相应k a y值分别为2和3。

当s= 3时,需计算c(1,4) 和c( 2 , 5 )。计算c(2,5) 所需要的所有中间值均已知(见( 1 5 - 5 )式),代入计算公式后可得c( 2 , 5 ) = 9 0,k a y( 2 , 5 ) = 2。c( 1 , 4 )可用同样的公式计算。最后,当s= 4时,可直接用(1 5 - 4)式来计算c( 1 , 5 ),因为该式右边所有项都已知。

计算c 和kay 的迭代程序见函数M a t r i x C h a i n(见程序1 5 - 8),该函数的复杂性为(q3 )。计算出kay 后同样可用程序1 5 - 6中的Traceback 函数推算出相应的最优乘法计算过程。

程序15-8 c 和kay 的迭代计算

void MatrixChain(int r[], int q, int **c, int **kay)

{// 为所有的Mij 计算耗费和k a y

// 初始化c[i][i], c[i][i+1]和k a y [ i ] [ i + 1 ]

for (int i = 1; i < q; i++) {

c[i][i] = 0;

c[i][i+1] = r[i]*r[i+1]*r[i+2];

kay[i][i+1] = i;

}

c[q][q] = 0;

/ /计算余下的c和k a y

for (int s = 2; s < q; s++)

for (int i = 1; i <= q - s; i++) {

// k = i时的最小项

c[i][i+s] = c[i][i] + c[i+1][i+s] + r[i]*r[i+1]*r[i+s+1];

kay[i][i+s] = i;

// 余下的最小项

for (int k = i+1; k < i + s; k++) {

int t = c[i][k] + c[k+1][i+s] + r[i]*r[k+1]*r[i+s+1];

if (t < c[i][i+s]) {// 更小的最小项

c[i][i+s] = t;

kay[i][i+s] = k;}

}

}

}

3.2.4 最短路径

假设G为有向图,其中每条边都有一个长度(或耗费),图中每条有向路径的长度等于该路径中各边的长度之和。对于每对顶点(i, j),在顶点i 与j 之间可能有多条路径,各路径的长度可能各不相同。我们定义从i 到j 的所有路径中,具有最小长度的路径为从i 到j 的最短路径。

例3-15 如图1 5 - 4所示。从顶点1到顶点3的路径有

1) 1,2,5,3

2) 1,4,3

3) 1,2,5,8,6,3

4) 1,4,6,3

由该图可知,各路径相应的长度为1 0、2 8、9、2 7,因而路径3) 是该图中顶点1到顶点3的最短路径。

在所有点对最短路径问题( a l l - p a i r sshorest-paths problem)中,要寻找有向图G中每对顶点之间的最短路径。也就是说,对于每对顶点(i, j),需要寻找从i到j 的最短路径及从j 到i 的最短路径。因此对于一个n 个顶点的图来说,需寻找p =n(n-1) 条最短路径。假定图G中不含有长度为负数的环路,只有在这种假设下才可保证G中每对顶点(i, j) 之间总有一条不含环路的最短路径。当有向图中存在长度小于0的环路时,可能得到长度为-∞的更短路径,因为包含该环路的最短路径往往可无限多次地加上此负长度的环路。

设图G中n 个顶点的编号为1到n。令c (i, j, k)表示从i 到j 的最短路径的长度,其中k 表示该路径中的最大顶点。因此,如果G中包含边<i, j>,则c(i, j, 0) =边<i, j> 的长度;若i= j ,则c(i,j, 0)=0;如果G中不包含边<i, j>,则c (i, j, 0)= +∞。c(i, j, n) 则是从i 到j 的最短路径的长度。

例3-16 考察图1 5 - 4。若k=0, 1, 2, 3,则c (1, 3, k)= ∞;c (1, 3, 4)= 2 8;若k = 5, 6, 7,则c (1, 3,k) = 1 0;若k=8, 9, 10,则c (1, 3, k) = 9。因此1到3的最短路径长度为9。对于任意k>0,如何确定c (i, j, k) 呢?中间顶点不超过k 的i 到j 的最短路径有两种可能:该路径含或不含中间顶点k。若不含,则该路径长度应为c(i, j, k- 1 ),否则长度为c(i, k, k- 1) +c (k, j, k- 1 )。c(i, j, k) 可取两者中的最小值。因此可得到如下递归式:

c( i, j, k)= m i n {c(i, j, k-1), c (i, k, k- 1) +c (k, j, k- 1 ) },k>0

以上的递归公式将一个k 级运算转化为多个k-1 级运算,而多个k-1 级运算应比一个k 级运算简单。如果用递归方法求解上式,则计算最终结果的复杂性将无法估量。令t (k) 为递归求解c (i, j, k) 的时间。根据递归式可以看出t(k) = 2t(k- 1 ) +c。利用替代方法可得t(n) = ( 2n )。因此得到所有c (i, j, n) 的时间为(n2 2n )。

当注意到某些c (i, j, k-1) 值可能被使用多次时,可以更高效地求解c (i, j, n)。利用避免重复计算c(i, j, k) 的方法,可将计算c 值的时间减少到(n3 )。这可通过递归方式(见程序1 5 - 7矩阵链问题)或迭代方式来实现。出迭代算法的伪代码如图1 5 - 5所示。

 

/ /寻找最短路径的长度

/ /初始化c(i,j,1)

for (int i=1; i < = n ; i + +)

for (int j=1; j<=n; j+ + )

c ( i ,j, 0 ) = a ( i ,j); // a 是长度邻接矩阵

/ /计算c ( i ,j, k ) ( 0 < k < = n )

for(int k=1;k<=n;k++)

for (int i=1;i<=n;i++)

for (int j= 1 ;j< = n ;j+ + )

if (c(i,k,k-1)+c(k,j, k - 1 ) < c ( i ,j, k - 1 ) )

c ( i ,j, k ) = c ( i , k , k - 1 ) + c ( k ,j, k - 1 ) ;

else c(i,j, k ) = c ( i ,j, k - 1 ) ;

图15-5 最短路径算法的伪代码

 

注意到对于任意i,c(i,k,k) =c(i,k,k- 1 )且c(k,i,k) =c(k,i,k- 1 ),因而,若用c(i,j)代替图1 5 - 5的c(i,j,k),最后所得的c(i,j) 之值将等于c(i,j,n) 值。此时图1 5 - 5可改写成程序1 5 - 9的C + +代码。程序1 5 - 9中还利用了程序1 2 - 1中定义的AdjacencyWDigraph 类。函数AllPairs 在c 中返回最短路径的长度。若i 到j 无通路,则c [i] [j]被赋值为N o E d g e。函数AllPairs 同时计算了k a y [ i ] [ j ],其中kay[i][j] 表示从i 到j 的最短路径中最大的k 值。在后面将看到如何根据kay 值来推断出从一个顶点到另一顶点的最短路径(见程序1 5 - 1 0中的函数O u t p u t P a t h)。

程序1 5 - 9的时间复杂性为(n3 ),其中输出一条最短路径的实际时间为O (n)。

程序15-9 c 和kay 的计算

template<class T>

void AdjacencyWDigraph<T>::Allpairs(T **c, int **kay)

{ / /所有点对的最短路径

/ /对于所有i和j,计算c [ i ] [ j ]和k a y [ i ] [ j ]

/ /初始化c [ i ] [ j ] = c(i,j,0)

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++) {

c[i][j] = a[i][j];

kay[i][j] = 0;

}

for (i = 1; i <= n; i++)

c[i][i] = 0;

// 计算c[i][j] = c(i,j,k)

for (int k = 1; k <= n; k++)

for (int i = 1; i <= n; i++)

for (int j = 1; j <= n; j++) {

T t1 = c[i][k];

T t2 = c[k][j];

T t3 = c[i][j];

if (t1 != NoEdge && t2 != NoEdge && (t3 == NoEdge || t1 + t2 < t3)) {

c[i][j] = t1 + t2;

kay[i][j] = k;}

}

}

程序15-10 输出最短路径

void outputPath(int **kay, int i, int j)

{// 输出i 到j 的路径的实际代码

if (i == j) return;

if (kay[i][j] == 0) cout << j << ' ';

else {outputPath(kay, i, kay[i][j]);

o u t p u t P a t h ( k a y, kay[i][j], j);}

}

template<class T>

void OutputPath(T **c, int **kay, T NoEdge, int i, int j)

{// 输出从i 到j的最短路径

if (c[i][j] == NoEdge) {

cout << "There is no path from " << i << " to " << j << endl;

r e t u r n ; }

cout << "The path is" << endl;

cout << i << ' ';

o u t p u t P a t h ( k a y, i , j ) ;

cout << endl;

}

例3-17 图15-6a 给出某图的长度矩阵a,15-6b 给出由程序1 5 - 9所计算出的c 矩阵,15-6c 为对应的k a y值。根据15-6c 中的kay 值,可知从1到5的最短路径是从1到k a y [ 1 ] [ 5 ] = 4的最短路径再加上从4到5的最短路径,因为k a y [ 4 ] [ 5 ] = 0,所以从4到5的最短路径无中间顶点。从1到4的最短路径经过k a y [ 1 ] [ 4 ] = 3。重复以上过程,最后可得1到5的最短路径为:1,2,3,4,5。

3.2.5 网络的无交叉子集

在11 . 5 . 3节的交叉分布问题中,给定一个每边带n 个针脚的布线通道和一个排列C。顶部的针脚i 与底部的针脚Ci 相连,其中1≤i≤n,数对(i, Ci ) 称为网组。总共有n 个网组需连接或连通。假设有两个或更多的布线层,其中有一个为优先层,在优先层中可以使用更细的连线,其电阻也可能比其他层要小得多。布线时应尽可能在优先层中布设更多的网组。而剩下的其他网组将布设在其他层。当且仅当两个网组之间不交叉时,它们可布设在同一层。我们的任务是寻找一个最大无交叉子集(Maximum Noncrossing Su b s e t,M N S )。在该集中,任意两个网组都不交叉。因(i, Ci ) 完全由i 决定,因此可用i 来指定(i, Ci )。

例3-18 考察图1 5 - 7(对应于图1 0 - 1 7)。( 1 , 8 )和( 2 , 7 )(也即1号网组和2号网组)交叉,因而不能布设在同一层中。而( 1 , 8 ),(7,9) 和(9,10) 未交叉,因此可布设在同一层。但这3个网组并不能构成一个M N S,因为还有更大的不交叉子集。图1 0 - 1 7中给出的例子中,集合{ ( 4 , 2 ) ,( 5 , 5 ) , ( 7 , 9 ) , ( 9 , 1 0 )}是一个含4个网组的M N S。

设M N S(i, j) 代表一个M N S,其中所有的(u, Cu ) 满足u≤i,Cu≤j。令s i z e(i,j) 表示M N S(i,j)的大小(即网组的数目)。显然M N S(n,n)是对应于给定输入的M N S,而s i z e(n,n)是它的大小。

例3-19 对于图1 0 - 1 7中的例子,M N S( 1 0 , 1 0 )是我们要找的最终结果。如例3 - 1 8中所指出的,s i z e( 1 0 , 1 0 ) = 4,因为( 1 , 8 ),( 2 , 7 ),( 7 , 9 ),( 8 , 3 ),( 9 , 1 0 )和( 1 0 , 6 )中要么顶部针脚编号比7大,要么底部针脚编号比6大,因此它们都不属于M N S( 7 , 6 )。因此只需考察剩下的4个网组是否属于M N S( 7 , 6 ),如图1 5 - 8所示。子集{( 3 , 4 ) , ( 5 , 5 )}是大小为2的无交叉子集。没有大小为3的无交叉子集,因此s i z e( 7 , 6) = 2。

当i= 1时,( 1 ,C1) 是M N S( 1 ,j) 的唯一候选。仅当j≥C1 时,这个网组才会是M N S( 1 ,j) 的一个成员.

下一步,考虑i>1时的情况。若j<Ci,则(i,Ci ) 不可能是M N S( i,j) 的成员,所有属于M N S(i,j) 的(u, Cu ) 都需满足u<i且Cu<j,因此:s i z e(i,j) =s i z e(i- 1 ,j), j<Ci (1 5 - 7)

若j≥Ci,则(i,Ci ) 可能在也可能不在M N S(i,j) 内。若(i,Ci ) 在M N S(i,j) 内,则在M N S(i,j)中不会有这样的(u,Cu ):u<i且Cu>Ci,因为这个网组必与(i, Ci ) 相交。因此M N S(i,j) 中的其他所有成员都必须满足条件u<i且Cu<Ci。在M N S(i,j) 中这样的网组共有Mi- 1 , Ci- 1 个。若(i,Ci ) 不在M N S(i,j)中,则M N S(i,j) 中的所有(u, Cu ) 必须满足u<i;因此s i z e(i,j)=s i z e(i- 1 ,j)。虽然不能确定(i, Ci )是否在M N S(i,j) 中,但我们可以根据获取更大M N S的原则来作出选择。因此:s i z e(i,j) = m a x {s i z e(i-1 ,j), s i z e(i- 1 ,Ci-1)+1}, j≥Ci (1 5 - 8)

虽然从(1 5 - 6)式到( 1 5 - 8)式可用递归法求解,但从前面的例子可以看出,即使避免了重复计算,动态规划递归算法的效率也不够高,因此只考虑迭代方法。在迭代过程中先用式(1 5 - 6)计算出s i ze ( 1 ,j),然后再用式(1 5 - 7)和(1 5 - 8)按i=2, 3, ., n 的顺序计算s i ze (i,j),最后再用Traceback 来得到M N S(n, n) 中的所有网组。

例3-20 图1 5 - 9给出了图1 5 - 7对应的s i z e(i,j) 值。因s i z e( 1 0 , 1 0) = 4,可知M N S含4个网组。为求得这4个网组,先从s i ze ( 1 0 , 1 0 )入手。可用(1 5 - 8)式算出s i z e( 1 0 , 1 0 )。根据式(1 5 - 8)时的产生原因可知s i ze ( 1 0 , 1 0)=s i z e( 9 , 1 0 ),因此现在要求M NS ( 9 , 1 0 )。由于M NS ( 1 0 , 1 0 )≠s i z e( 8 , 1 0 ),因此M NS (9,10) 中必包含9号网组。M N S(9,10) 中剩下的网组组成M NS ( 8 , C9- 1)=M N S( 8 , 9 )。由M N S( 8 , 9 ) =M NS (7,9) 知,8号网组可以被排除。接下来要求M N S( 7 , 9 ),因为s i z e( 7 , 9 )≠s i z e( 6 , 9 ),所以M N S中必含7号网组。M NS (7,9) 中余下的网组组成M NS ( 6 , C7- 1 ) =M N S( 6 , 8 )。根据s i z e( 6 , 8 ) =s i z e( 5 , 8 )可排除6号网组。按同样的方法, 5号网组,3号网组加入M N S中,而4号网组等其他网组被排除。因此回溯过程所得到的大小为4的M N S为{ 3 , 5 , 7 , 9 }。

注意到在回溯过程中未用到s i z e( 1 0 ,j) (j≠1 0 ),因此不必计算这些值。

程序1 5 - 11给出了计算s i z e ( i , j ) 的迭代代码和输出M N S的代码。函数M N S用来计算s i ze (i,j) 的值,计算结果用一个二维数组M N来存储。size[i][j] 表示s i z e(i,j),其中i=j= n 或1≤i<n,0≤j≤n,计算过程的时间复杂性为(n2 )。函数Traceback 在N et[0 : m - 1]中输出所得到的M N S,其时间复杂性为(n)。因此求解M M S问题的动态规划算法的总的时间复杂性

为(n2 )。

程序1 5 - 11 寻找最大无交叉子集

void MNS(int C[], int n, int **size)

{ / /对于所有的i 和j,计算s i z e [ i ] [ j ]

/ /初始化s i z e [ 1 ] [ * ]

for (int j = 0; j < C[1]; j++)

size[1][j] = 0;

for (j = C[1]; j <= n; j++)

size[1][j] = 1;

// 计算size[i][*], 1 < i < n

for (int i = 2; i < n; i++) {

for (int j = 0; j < C[i]; j++)

size[i][j] = size[i-1][j];

for (j = C[i]; j <= n; j++)

size[i][j] = max(size[i-1][j], size[i-1][C[i]-1]+1);

}

size[n][n] = max(size[n-1][n], size[n-1][C[n]-1]+1);

}

void Traceback(int C[], int **size, int n, int Net[], int& m)

{// 在N e t [ 0 : m - 1 ]中返回M M S

int j = n; // 所允许的底部最大针脚编号

m = 0; // 网组的游标

for (int i = n; i > 1; i--)

// i 号n e t在M N S中?

if (size[i][j] != size[i-1][j]){// 在M N S中

Net[m++] = i;

j = C[i] - 1;}

// 1号网组在M N S中?

if (j >= C[1])

Net[m++] = 1; // 在M N S中

}

3.2.6 元件折叠

在设计电路的过程中,工程师们会采取多种不同的设计风格。其中的两种为位-片设计(bit-slice design)和标准单元设计(standard-cell design)。在前一种方法中,电路首先被设计为一个元件栈(如图15-10a 所示)。每个元件Ci 宽为wi ,高为hi ,而元件宽度用片数来表示。图15-10a 给出了一个四片的设计。线路是按片来连接各元件的,即连线可能连接元件Ci 的第j片到元件Ci+1 的第j 片。如果某些元件的宽度不足j 片,则这些元件之间不存在片j 的连线。当图1 5 -10a 的位-片设计作为某一大系统的一部分时,则在V L SI ( Very Large Scale Integrated) 芯片上为它分配一定数量的空间单元。分配是按空间宽度或高度的限制来完成的。现在的问题便是如何将元件栈折叠到分配空间中去,以便尽量减小未受限制的尺度(如,若高度限制为H时,必须折叠栈以尽量减小宽度W)。由于其他尺度不变,因此缩小一个尺度(如W)等价于缩小面积。可用折线方式来折叠元件栈,在每一折叠点,元件旋转1 8 0°。在图15-10b 的例子中,一个1 2元件的栈折叠成四个垂直栈,折叠点为C6 , C9 和C1 0。折叠栈的宽度是宽度最大的元件所需的片数。在图15-10b 中,栈宽各为4,3,2和4。折叠栈的高度等于各栈所有元件高度之和的最大值。在图15-10b 中栈1的元件高度之和最大,该栈的高度决定了包围所有栈的矩形高度。

实际上,在元件折叠问题中,还需考虑连接两个栈的线路所需的附加空间。如,在图1 5 -10b 中C5 和C6 间的线路因C6 为折叠点而弯曲。这些线路要求在C5 和C6 之下留有垂直空间,以便能从栈1连到栈2。令ri 为Ci 是折叠点时所需的高度。栈1所需的高度为5 ?i =1hi +r6,栈2所需高度为8 ?i=6hi +r6+r9。

在标准单元设计中,电路首先被设计成为具有相同高度的符合线性顺序的元件排列。假设此线性顺序中的元件为C1,.,Cn,下一步元件被折叠成如图1 5 - 11所示的相同宽度的行。在此图中, 1 2个标准单元折叠成四个等宽行。折叠点是C4,C6 和C11。在相邻标准单元行之间,使用布线通道来连接不同的行。折叠点决定了所需布线通道的高度。设li 表示当Ci 为折叠点时所需的通道高度。在图1 5 - 11的例子中,布线通道1的高度为l4,通道2的高度为l6,通道3的高度为l11。位-片栈折叠和标准单元折叠都会引出一系列的问题,这些问题可用动态规划方法来解决。

1. 等宽位-片元件折叠

定义r1 = rn+1 =0。由元件Ci 至Cj 构成的栈的高度要求为j ?k= ilk+ ri+ rj + 1。设一个位-片设计中所有元件有相同宽度W。首先考察在折叠矩形的高度H给定的情况下,如何缩小其宽度。设Wi

为将元件Ci 到Cn 折叠到高为H的矩形时的最小宽度。若折叠不能实现(如当ri +hi>H时),取Wi =∞。注意到W1 可能是所有n 个元件的最佳折叠宽度。

当折叠Ci 到Cn 时,需要确定折叠点。现假定折叠点是按栈左到栈右的顺序来取定的。若第一点定为Ck+ 1,则Ci 到Ck 在第一个栈中。为了得到最小宽度,从Ck+1 到Cn 的折叠必须用最优化方法,因此又将用到最优原理,可用动态规划方法来解决此问题。当第一个折叠点k+ 1已知时,可得到以下公式:

Wi =w+ Wk + 1 (1 5 - 9)

由于不知道第一个折叠点,因此需要尝试所有可行的折叠点,并选择满足( 1 5 - 9)式的折叠点。令h s u m(i,k)=k ?j = ihj。因k+ 1是一个可行的折叠点,因此h s u m(i, k) +ri +rk+1 一定不会超过H。

根据上述分析,可得到以下动态规划递归式:

这里Wn+1 =0,且在无最优折叠点k+ 1时Wi 为∞。利用递归式(1 5 - 1 0),可通过递归计算Wn , Wn- 1., W2 , W1 来计算Wi。Wi 的计算需要至多检查n-i+ 1个Wk+ 1,耗时为O (n-k)。因此计算所有Wi 的时间为O (n2 )。通过保留式(1 5 - 1 0)每次所得的k 值,可回溯地计算出各个最优的折叠点,其时间耗费为O (n)。

现在来考察另外一个有关等宽元件的折叠问题:折叠后矩形的宽度W已知,需要尽量减小其高度。因每个折叠矩形宽为w,因此折叠后栈的最大数量为s=W / w。令Hi, j 为Ci , ., Cn 折叠成一宽度为jw 的矩形后的最小高度, H1, s 则是所有元件折叠后的最小高度。当j= 1时,不允许任何折叠,因此:Hi,1 =h s u m(i,n) +ri , 1≤i≤n

另外,当i=n 时,仅有一个元件,也不可能折叠,因此:Hn ,j=hn+rn , 1≤j≤s

在其他情况下,都可以进行元件折叠。如果第一个折叠点为k+ 1,则第一个栈的高度为

h s u m(i,k) +ri +rk+ 1。其他元件必须以至多(j- 1 ) *w 的宽度折叠。为保证该折叠的最优性,其他元件也需以最小高度进行折叠.

因为第一个折叠点未知,因此必须尝试所有可能的折叠点,然后从中找出一个使式(1 5 - 11)的右侧取最小值的点,该点成为第一个折叠点。

可用迭代法来求解Hi, j ( 1≤i≤n, 1≤j≤s),求解的顺序为:先计算j=2 时的H i, j,再算j= 3,.,以此类推。对应每个j 的Hi, j 的计算时间为O (n2 ),所以计算所有H i, j 的时间为O(s n2 )。通过保存由( 1 5 - 1 2)式计算出的每个k 值,可以采用复杂性为O (n) 的回溯过程来确定各个最优的折叠点。

2. 变宽位-片元件的折叠

首先考察折叠矩形的高度H已定,欲求最小的折叠宽度的情况。令Wi 如式(1 5 - 1 0)所示,按照与(1 5 - 1 0)式相同的推导过程,可得:

Wi = m i n {w m i n(i, k) +Wk+1 | h s u m(i,k)+ ri +rk+ 1≤H, i≤k≤n} (1 5 - 1 3)

其中Wn+1=0且w m i n(i,k)= m ini≤j≤k{wj }。可用与(1 5 - 1 0)式一样的方法求解(1 5 - 1 3)式,所需时间为O(n2 )。

当折叠宽度W给定时,最小高度折叠可用折半搜索方法对超过O(n2 )个可能值进行搜索来实现,可能的高度值为h(i,j)+ri +rj + 1。在检测每个高度时,也可用( 1 5 - 1 3)式来确定该折叠的宽度是否小于等于W。这种情况下总的时间消耗为O (n2 l o gn)。

3. 标准单元折叠

用wi 定义单元Ci 的宽度。每个单元的高度为h。当标准单元行的宽度W 固定不变时,通过减少折叠高度,可以相应地减少折叠面积。考察Ci 到Cn 的最小高度折叠。设第一个折叠点是Cs+ 1。从元件Cs+1 到Cn 的折叠必须使用最小高度,否则,可使用更小的高度来折叠Cs+1 到Cn,从而得到更小的折叠高度。所以这里仍可使用最优原理和动态规划方法。

令Hi , s 为Ci 到Cn 折叠成宽为W的矩形时的最小高度,其中第一个折叠点为Cs+ 1。令w s u m(i, s)=s ?j = iwj。可假定没有宽度超过W的元件,否则不可能进行折叠。对于Hn,n 因为只有一个元件,不存在连线问题,因此Hn, n =h。对于H i, s(1≤i<s≤n)注意到如果w s u m(i, s )>W,不可能实现折叠。若w s u m(i,s)≤W,元件Ci 和C j + 1 在相同的标准单元行中,该行下方布线通道的高度为ls+ 1(定义ln+1 = 0)。因而:Hi, s = Hi+1, k (1 5 - 1 4)

当i=s<n 时,第一个标准单元行只包含Ci 。该行的高度为h 且该行下方布线通道的高度为li+ 1。因Ci+ 1 到Cn 单元的折叠是最优的.

为了寻找最小高度折叠,首先使用式( 1 5 - 1 4)和(1 5 - 1 5)来确定Hi, s (1≤i≤s≤n)。最小高度折叠的高度为m in{H1 , s}。可以使用回溯过程来确定最小高度折叠中的折叠点。

posted @ 2007-02-27 17:10 保尔任 阅读(1505) | 评论 (0)编辑 收藏
// 建立二叉树并先根遍历的代码
public   class  BinaryTreeTest  {
 
public   static   void  main(String args[])  { // 主方法
  BinaryTreeTest b  =   new  BinaryTreeTest();
  
int  data[]  =   12 11 34 45 67 89 56 43 22 98  } ;
  BinaryTree root 
=   new  BinaryTree(data[ 0 ]);

  System.out.print(
" 二叉树的中的数据:   " );
// 建立二叉树
   for  ( int  i  =   1 ; i  <  data.length; i ++ {
   root.insertTree(root, data[i]);
   System.out.print(data[i 
-   1 +   " ; " );
  }


  System.out.println(data[data.length 
-   1 ]);

  
int  key  =  Integer.parseInt(args[ 0 ]);

  
if  (b.searchkey(root, key))  {
   System.out.println(
" 找到了: "   +  key);
  }
  else   {
   System.out.println(
" 没有找到: "   +  key);
  }

 }


 
public   boolean  searchkey(BinaryTree root,  int  key)  { // 查询
   boolean  bl  =   false ;
  
if  (root  ==   null {
   bl 
=   false ;
   
return  bl;
  }
  else   if  (root.data  ==  key)  {
   bl 
=   true ;
   
return  bl;
  }
  else   if  (key  >=  root.data)  {
   
return  searchkey(root.rightpoiter, key);
  }

  
return  searchkey(root.leftpoiter, key);
 }

}


class  BinaryTree  { // 二叉树类
  int  data;

 BinaryTree leftpoiter;

 BinaryTree rightpoiter;

 BinaryTree(
int  data)  {
  
this .data  =  data;
  leftpoiter 
=   null ;
  rightpoiter 
=   null ;
 }


 
public   void  insertTree(BinaryTree root,  int  data)  { // 插入节点
   if  (data  >=  root.data)  {
   
if  (root.rightpoiter  ==   null {
    root.rightpoiter 
=   new  BinaryTree(data);
   }
  else   {
    insertTree(root.rightpoiter, data);
   }

  }
  else   {
   
if  (root.leftpoiter  ==   null {
    root.leftpoiter 
=   new  BinaryTree(data);
   }
  else   {
    insertTree(root.leftpoiter, data);
   }

  }

 }

}

//  end

讲解:一个寻找关键字--searchkey
另一个是插入一个结点:insertTree
另外这是一个完全的先序遍历二叉树的语法。先根结点,再左结点,如无再右结点,如些加归至搜索完毕。  

posted @ 2007-02-27 11:41 保尔任 阅读(402) | 评论 (0)编辑 收藏

10, 增加eclipse内存:
更改ECLIPSE文件夹下的ECLIPSE.INI文件内容如下:
-vmargs
-Xms128m
-Xmx512m
-XX:PermSize=128m
-XX:MaxPermSize=256m

或者:

在eclipse目录下建个批处理文件eclipse.bat,用文本编辑器打开,写入如下内容:
eclipse.exe -vmargs -Xms128m -Xmx512m -XX:PermSize=128m -XX:PermSize=256m

然后保存.以后运行eclipse的时候就执行这个批处理就行了.
解释下参数的意思:
-vmargs                                   说明后面的参数都是java虚拟机(vm)的参数
-Xms128m                               虚拟机占用系统的最小内存
-Xmx512m                               虚拟机占用系统的最大内存
-XX:PermSize=64m              最小堆大小.一般报内存不足时,都是说这个太小,堆空间剩余小于5%                             就会警告,建议把这个稍微设大一点,不过要视自己机器内存大小来设置

-XX:PermSize=128m             最大堆大小.这个也适当变大些

在快捷方式中设置也可:
eclipse.exe -vmargs -Xverify:none -XX:+UseParallelGC -XX:PermSize=20M -Xms64M -Xmx512M


9,代码风格设置
可以通过Window->Perferences->Java->Code Style->Code Formatter来设定代码的编写格式,然后只要用快捷键Ctrl+Shift+F就可以将不标准的代码自动转化成所设定的标准格式。

8,显示行号,显示限制列的线(默认80列)

window -> Preferences -> General -> Editors -> Text Editors: Show line numbers; Show print margin

7.打包成jar文件时,需要根据自定义的文件生成MANIFEST.MF,其中每行的冒号后面都有一个空格,否则出错。例:Manifest-Version: 1.0(1.0前有空格,其他行也是如此)

6.由数据库中的表自动建立.java和.hbm.xml文件
a.建立项目:打开带HibernateTools插件的eclipse,建立一个名为“test”的java project,内部新建一个名字为src的source folder。
b.建立hibernate配置文件:新建“hibernate configuration file”,输出路径选择“test项目的src目录”,然后的对话框填写配置文件(包括database dialect,driver class,connection url,username,password,creat a console configuration),下一个对话框先填写name(即console configuration name),再点“add external jars”,选择数据库驱动的jar文件,看到src中有“hibernate.cfg.xml”就是配置文件建立成功。
c.建立目标文件:点工具栏hibernate图标,选择“hibernate code generation...”,在弹出的对话框中点击左侧“新建”,把名字改为“test”,console configuration选刚才建立的console configuration name,package填想生成的包结构,点reveng.xml的“setup”,接下来对话框选择test的src目录,然后导入需要的数据库表(有关联的就要导入,即外键的表也要导入),然后点“finish”;选择main右边的exporters,选中generate domain code,generate mappings三项,run,刷新项目,看到包中生成的.java和.hbm.xml文件,成功,把它们拷入myeclipse的相应项目里。
d.删除Console Configuration:打开Hibernate Console的透视图(perspective),在左侧Hibernate Configuration的视图(view)中右键单击,就可以删除。
删除Hivernate Code Generation:点击工具栏Hibernate图标,左侧即可删除。

5.*.service.spring包中的*ServiceImpl.java文件中有dao对象属性,必须包括这个对象的get/set方法,否则出错。

4.从一个.jsp文件转到另一个包含有form表单.jsp文件时,出错信息为form表单的action找不到mapping,在两个页面之间加一个action即可找到。

3.eclipse与tomcat代码不同步的问题
搜索tomcat中有此项目名的所有文件,全部删除。在实验应该会成功。

2.字符集框手动输入
我把“eclipse 的window-->prefrences -->general -->content type”设为了UTF8,是为了不让每个项目再选一遍UTF8,结果单个项目选择时就没有GBK选项了。解决办法就是在单个项目让你选字符集的地方手动输入GBK,就ok了!!!

1.debug工具条灰色
debug模式下,eclipse用debug透视图打开断点页面,但debug工具条却显示灰色,应该转到其他透视图在转会来就可以了。比如debug -> java -> debug


 

posted @ 2007-02-13 11:21 保尔任 阅读(456) | 评论 (0)编辑 收藏
作者一记录:
?
编辑
作用域  功能  快捷键
全局  查找并替换 Ctrl+F
文本编辑器 查找上一个 Ctrl+Shift+K
文本编辑器 查找下一个 Ctrl+K
全局  撤销  Ctrl+Z
全局  重做  Ctrl+Y
全局  复制  Ctrl+C
全局  剪切  Ctrl+X
全局  粘贴  Ctrl+V
全局  全部选中 Ctrl+A
全局  删除  Delete
全局  快速修正 Ctrl+1
全局  内容辅助 Alt+/
全局  上下文信息 Alt+? Alt+Shift+? Ctrl+Shift+Space
Java编辑器 显示工具提示描述 F2
全局 恢复上一个选择 Alt+Shift+↓
Java编辑器 选择封装元素 Alt+Shift+↑
Java编辑器 选择上一个元素 Alt+Shift+←
Java编辑器 选择下一个元素 Alt+Shift+→
文本编辑器 增量查找 Ctrl+J
文本编辑器 增量逆向查找 Ctrl+Shift+J
文本编辑器 改写切换 Insert
文本编辑器 上滚行 Ctrl+↑
文本编辑器 下滚行 Ctrl+↓

?
查看
作用域 功能 快捷键
全局 放大 Ctrl+=
全局 缩小 Ctrl+-
?
窗口
作用域 功能 快捷键
全局 激活编辑器 F12
全局 切换编辑器 Ctrl+Shift+W
全局 上一个编辑器 Ctrl+Shift+F6
全局 上一个视图 Ctrl+Shift+F7
全局 上一个透视图 Ctrl+Shift+F8
全局 下一个编辑器 Ctrl+F6
全局 下一个视图 Ctrl+F7
全局 下一个透视图 Ctrl+F8
文本编辑器 显示标尺上下文菜单 Ctrl+W
全局 显示视图菜单 Ctrl+F10
全局 显示系统菜单 Alt+-
?
导航
作用域 功能 快捷键
Java编辑器 打开结构 Ctrl+F3
全局 打开类型 Ctrl+Shift+T
全局 打开类型层次结构 F4
全局 打开声明 F3
全局 打开外部javadoc Shift+F2
全局 打开资源 Ctrl+Shift+R
全局 后退历史记录 Alt+←
全局 前进历史记录 Alt+→
全局 上一个 Ctrl+,
全局 下一个 Ctrl+.
Java编辑器 显示大纲 Ctrl+O
全局 在层次结构中打开类型 Ctrl+Shift+H
全局 转至匹配的括号 Ctrl+Shift+P
全局 转至上一个编辑位置 Ctrl+Q
Java编辑器 转至上一个成员 Ctrl+Shift+↑
Java编辑器 转至下一个成员 Ctrl+Shift+↓
文本编辑器 转至行 Ctrl+L

搜索
作用域 功能 快捷键
全局 出现在文件中 Ctrl+Shift+U
全局 打开搜索对话框 Ctrl+H
全局 工作区中的声明 Ctrl+G
全局 工作区中的引用 Ctrl+Shift+G

?
文件
作用域 功能 快捷键
全局 保存 Ctrl+S
全局 打印 Ctrl+P
全局 关闭 Ctrl+F4
全局 全部保存 Ctrl+Shift+S
全局 全部关闭 Ctrl+Shift+F4
全局 属性 Alt+Enter
全局 新建 Ctrl+N

项目
作用域 功能 快捷键
全局 全部构建 Ctrl+B

源代码
作用域 功能 快捷键
Java编辑器 格式化 Ctrl+Shift+F
Java编辑器 取消注释 Ctrl+\
Java编辑器 注释 Ctrl+/
Java编辑器 添加导入 Ctrl+Shift+M
Java编辑器 组织导入 Ctrl+Shift+O
Java编辑器 使用try/catch块来包围未设置,太常用了,所以在这里列出,建议自己设置。
也可以使用Ctrl+1自动修正。
?
运行
作用域 功能 快捷键
全局 单步返回 F7
全局 单步跳过 F6
全局 单步跳入 F5
全局 单步跳入选择 Ctrl+F5
全局 调试上次启动 F11
全局 继续 F8
全局 使用过滤器单步执行 Shift+F5
全局 添加/去除断点 Ctrl+Shift+B
全局 显示 Ctrl+D
全局 运行上次启动 Ctrl+F11
全局 运行至行 Ctrl+R
全局 执行 Ctrl+U

重构
作用域 功能 快捷键
全局 撤销重构 Alt+Shift+Z
全局 抽取方法 Alt+Shift+M
全局 抽取局部变量 Alt+Shift+L
全局 内联 Alt+Shift+I
全局 移动 Alt+Shift+V
全局 重命名 Alt+Shift+R
全局 重做 Alt+Shift+Y

作者二记录:
让我们按照使用频率来看看我最爱用的一些热键组合。(注:以下内容在Eclipse3.02及一上版本通过测试)
1. Control-Shift-T: 打开类型(Open type)。如果你不是有意磨洋工,还是忘记通过源码树(source tree)打开的方式吧。
2. Control-Shift-R: 打开资源(不只是用来寻找Java文件)。小提示:利用Navigator视图的黄色双向箭头按钮让你的编辑窗口和导航器相关联。这会让你打开的文件对应显示在导航器的层级结构中,这样便于组织信息。如果这影响了速度,就关掉它。
3. F3: 打开申明(Open declaration)。或者,利用Declaration Tab(在Java视图模式下,选择Windows --> Show View -- > Declaration)。当你选中代码中的一个方法,然后按这个按键,它会把整个方法在申明方框里显示出来。
4. Alt-left arrow: 在导航历史记录(Navigation History)中后退。就像Web浏览器的后退按钮一样,在利用F3跳转之后,特别有用。(用来返回原先编译的地方)
5. Alt-right arrow: 导航历史记录中向前。
6. Control-Q: 回到最后依次编辑的地方。这个快捷键也是当你在代码中跳转后用的。特别是当你钻的过深,忘记你最初在做什么的时候。
7. Control-Shift-G: 在workspace中搜索引用(reference)。这是重构的前提。对于方法,这个热键的作用和F3恰好相反。它使你在方法的栈中,向上找出一个方法的所有调用者。一个与此相关的功能是开启“标记”功能(occurrence marking) 。选择Windows->Preferences->Java-> Editor-> Mark Occurrences,勾选选项。这时,当你单击一个元素的时候,代码中所有该元素存在的地方都会被高亮显示。我个人只使用“标记本地变量”(Mark Local Variables)。注意:太多的高亮显示会拖慢Eclipse。
8. Control-Shift-F: 根据代码风格设定重新格式化代码。我们的团队有统一的代码格式,我们把它放在我们的wiki上。要这么做,我们打开Eclipse,选择Window?Preferences?Java?Code Style,然后设置Code Formatter,Code Style和Organize Imports。利用导出(Export)功能来生成配置文件。我们把这些配置文件放在wiki上,然后团队里的每个人都导入到自己的Eclipse中。
9. Control-O: 快速概要(quick outline)。通过这个快捷键,你可以迅速的跳到一个方法或者属性,只需要输入名字的头几个字母。
10. Control-/: 对一行注释或取消注释。对于多行也同样适用。
11. Control-Alt-down arrow: 复制高亮显示的一行或多行。
12. Alt-down arrow: 将一行或多行向下移动。Alt-up arrow会向上移动。
其他的热键在菜单里有。你可以通过按下Control-Shift-L(从3.1版本开始),看到所有快捷键的列表。按下Control-Shift-L两次,会显示热键对话框(Keys Preferences dialog),你可以在这里自己设置热键。我欢迎你在Talkback部分发表你的Eclipse提示。
其他的Eclipse窍门
我总结了几个相关的小窍门:
锁定命令行窗口:在命令行视图中(Window ? Show View ? Other ? Basic ? Console),试试看用滚动锁定按钮来锁定控制台输出不要滚屏。
使用Ant视图:在我的Java或Debug模式下,我喜欢显示出Ant视图,这样我就可以迅速的运行Ant任务。通过Window ? Show View ? Other ? Ant可以找到该视图。把Ant视图放在屏幕的一角,通过“添加编译文件(Add Buildfiles)”按钮来添加build.xml文件。在3.1版本中,甚至支持Ant调试脚本语言。
自动遍历一个集合:for + Control-Space: 如果你还不知道,那么你应该记住Control-Space是自动完成功能。在Eclipse中,你还可以自动完成结构。在一个数组或集合范围内,试试看输入“for”然后按下Control-Space键。Eclipse会问你你想要遍历哪一个集合然后自动完成循环代码。
使用分级布局:在包浏览视图(Package Explorer view)中默认的布局(扁平式)方式让我困惑,它把包的全名显示在导航树(navigation tree)中。我更喜欢我源码的包和文件系统视图,在Eclipse中叫做分级布局(Hierarchical Layout)。要切换到这种模式,点击包浏览视图中向下的按钮,选择布局(Layout),然后选择分级(Hierarchial)。
一次显示多个文件:你可以一次浏览多个文件。把不在激活状态的编辑窗口拖到激活窗口的底部或侧边的滚动条上,就可以打开该编辑窗口。这是我能描述该窍门的最好方式了。
同时打开两个Eclipse:要将改动从一个CVS分支上合并到另外一个上,我喜欢通过同时打开两个工作目录(Workspace)不同Eclipse来实现。这样我可以通过比较CVS上的最新版本看到所有的变化(右键单击工程,然后选择Compare with ? Lastest from HEAD)然后把每一个变化都合并到另外一个CVS分支上。启动多个Eclipse的最简单的方法是利用Eclipse Launcher。
Implementors插件:安装一个能够跳到一个接口的实现的插件。如果你是个dependency injection 粉丝,或者正在基于编写优良的接口工作,那么你需要一个这样的插件来加速代码导航。你可以在SourceForge找到这个插件。
posted @ 2007-02-13 11:20 保尔任 阅读(721) | 评论 (0)编辑 收藏
   可以【方法一】简单地把plugin放到eclipse SDK本身的features和plugins目录下来进行plugin的安装,但是这种方法并不利于plugin的管理:
  • 虽然可以通过【方法二】eclipse SDK的update功能来升级自身,然而因为速度的原因我们一般还是会选择完全下载新版本,这样就需要把后来安装到eclipse SDK目录下的plugin都挑选出来并拷贝到新版本的eclipse SDK目录下,如果这样的plugin比较多的话将会有些麻烦。
  • 有时候会共存多个版本的eclipse SDK,显然我们并不想把这些plugin拷贝到每个版本的eclipse SDK里
  【方法三】eclipse platform是支持把plugin安装到其他目录的,不过它对这些目录是有要求的:该目录必须有一个名为eclipse的子目录,eclipse子目录下必须有一个.eclipseextension文件,plugin本身放在eclipse子目录下的features和plugins目录下。这样的一个位置就是一个eclipse extension,.eclipseextension文件描述了这个eclipse extension,包括三项name、id和version;可以有多个eclipse extension,具体创建几个eclipse extension,每个eclipse extension包含哪些plugin,完全视情况而定,比如可以把关系比较密切的几个plugin放在一个eclipse extension中。

  显然我们必须告诉eclipse platform这些eclipse extension的位置才行,这有两种方法:
  • 当eclipse启动后用,打开Help->Software Updates/Manager Configuration,用Add an Extension Location来添加eclipse extesnion,指定的位置将会被存放到当前的configuration里
  • 在eclipse platform所在的eclipse目录下建一个links目录,多个插件可以定义一个***.link,一个path=location一行;或者定义多个***.link文件,每个包含一个path=location。路径分隔符为正斜杠,如果用反斜杠必须用两个以转义
  第一种方法是把eclipse extension的位置保存在当前configuration中,因此用这种方法指定的eclipse extension是特定于configuration的,不同的configuration可以具有不同的eclipse extension配置,可以在启动时用-configuration选项来选择一个configuration,但是似乎当添加完eclipse extension后是不能删除的,只能disable,而且多个configuration也带来了管理的负担;第二种方法比较明了,但它是 configuration insensitive的,不管以哪个configuration运行这些eclipse extension都是可见的,这里不用担心内存的占用问题,因为eclipse的plugin都是lazy loading的,用不到的plugin是并不会占用内存空间的,不过可能会有plugin冲突问题,比如两个插件在同一个extension point处扩展,而对extension point的处理又是不可配置的,比如选择extension的策略是找到的第一个extension,而此时如果我们希望运行的extension恰好排在第二位,那么就有问题了,这时可能就需要两种方法都用到了。

  配置好eclipse extension后,这些eclipse extension中的plugin就和eclipse platform/sdk中的plugin,按照extension和extension point的关系,共同形成了一个插件网络,这时各个plugin的位置已经没有区别了,你甚至可以指定运行位于eclipse extension中的product。

  一般的plugin包都会把eclipse目录打进去,这样只要把该包直接解压到选定的 eclipse extension目录中即可,不过如前所述,要成为真正的eclipse extension目录,还需要一个.eclipseextension文件,除了手工建立外,当从update site安装plugin时还可以让eclipse来建立它,只要在安装对话框弹出时选择change location指定一个目录即可。
posted @ 2007-02-13 11:18 保尔任 阅读(303) | 评论 (0)编辑 收藏
-1 subversion开发
subclipse: http://subclipse.tigris.org/update_1.2.x
按照提示安装完毕后,我们就可以打开Subversion的资源库了。选择Eclipse菜单Window->Show View->Other…,选择SVN->SVN Repository,然后添加一个新的资源库,例如http://livebookstore.googlecode.com/svn/trunk

0  python开发
pydev:http://www.fabioz.com/pydev/updates

1  Eclipse下载
EMF,GEF - Graphical Editor Framework,UML2,VE - Visual Editor都在这里下载
http://www.eclipse.org/downloads/index.php

2  lomboz J2EE插件,开发JSP,EJB
http://forge.objectweb.org/projects/lomboz

3  MyEclipse J2EE开发插件,支持SERVLET/JSP/EJB/数据库操纵等
http://www.myeclipseide.com/

4  Properties Editor  编辑java的属性文件,并可以自动存盘为Unicode格式
http://propedit.sourceforge.jp/index_en.html

5  Colorer Take  为上百种类型的文件按语法着色
http://colorer.sourceforge.net/

6  XMLBuddy 编辑xml文件
http://www.xmlbuddy.com/

7  Code Folding  加入多种代码折叠功能(比eclipse自带的更多)
http://www.coffee-bytes.com/servlet/PlatformSupport

8  Easy Explorer  从eclipse中访问选定文件、目录所在的文件夹
http://easystruts.sourceforge.net/

9  Fat Jar 打包插件,可以方便的完成各种打包任务,可以包含外部的包等
http://fjep.sourceforge.net/

10  RegEx Test 测试正则表达式
http://brosinski.com/stephan/archives/000028.php

11  JasperAssistant 报表插件(要钱的哦~)
http://www.jasperassistant.com/

12  Jigloo GUI Builder JAVA的GUI编辑插件
http://cloudgarden.com/jigloo/

13  Profiler 性能跟踪、测量工具,能跟踪、测量B/S程序
http://sourceforge.net/projects/eclipsecolorer/

14  AdvanQas 提供对if/else等条件语句的提示和快捷帮助(自动更改结构等)
http://eclipsecolorer.sourceforge.net/advanqas/index.html

15  Log4E Log4j插件,提供各种和Log4j相关的任务,如为方法、类添加一个logger等
http://log4e.jayefem.de/index.php/Main_Page

16  VSSPlugin VSS插件
http://sourceforge.net/projects/vssplugin

17  Implementors 提供跳转到一个方法的实现类,而不是接口的功能(实用!)
http://eclipse-tools.sourceforge.net/implementors/

18  Call Hierarchy 显示一个方法的调用层次(被哪些方法调,调了哪些方法)
http://eclipse-tools.sourceforge.net/call-hierarchy/index.html

19  EclipseTidy 检查和格式化HTML/XML文件
http://eclipsetidy.sourceforge.net/

20  Checkclipse 检查代码的风格、写法是否符合规范
http://www.mvmsoft.de/content/plugins/checkclipse/checkclipse.htm

21  Hibernate Synchronizer Hibernate插件,自动映射等
http://www.binamics.com/hibernatesync/

22  VeloEclipse  Velocity插件
http://propsorter.sourceforge.net/ 
 
23  EditorList 方便的列出所有打开的Editor
http://editorlist.sourceforge.net/ 
 
24  MemoryManager 内存占用率的监视
http://cloudgarden.com/memorymanager/ 
 
25  swt-designer java的GUI插件
http://www.swt-designer.com/
 
26  TomcatPlugin 支持Tomcat插件
http://www.sysdeo.com/eclipse/tomcatPlugin.html
 
27  XML Viewer
http://tabaquismo.freehosting.net/ignacio/eclipse/xmlview/index.html
 
28  quantum 数据库插件
http://quantum.sourceforge.net/
 
29  Dbedit 数据库插件
http://sourceforge.net/projects/dbedit
 
30  clay.core 可视化的数据库插件
http://www.azzurri.jp/en/software/index.jsp
http://www.azzurri.jp/eclipse/plugins
 
31  hiberclipse hibernate插件
http://hiberclipse.sourceforge.net/
http://www.binamics.com/hibernatesync
 
32  struts-console Struts插件
http://www.jamesholmes.com/struts/console/
 
33  easystruts Struts插件
http://easystruts.sourceforge.net/ 
 
34  veloedit Velocity插件
http://veloedit.sourceforge.net/
 
35  jalopy 代码整理插件
http://jalopy.sourceforge.net/
 
36  JDepend 包关系分析
http://andrei.gmxhome.de/jdepend4eclipse/links.html
 
37  Spring IDE Spring插件
http://springide-eclip.sourceforge.net/updatesite/
 
38  doclipse 可以产生xdoclet 的代码提示
http://beust.com/doclipse/

39  SQLExplorer,在Eclipse 中连接各种数据库进行操作使用
http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=124&threadID=31124

===========================================================\

以下为7月13日转贴更新

JSEclipse

插件主页:http://www.interaktonline.com/Products/Eclipse/JSEclipse/Overview/
插件介绍:JSEclipse是个Eclipse下的免费Javascript脚本编辑器

subversion
版本控制,相当于CVS
安装:http://subclipse.tigris.org/install.html
Name: Subclipse
URL:  http://subclipse.tigris.org/update_1.0.x


CSS Editor for Eclipse
http://csseditor.sourceforge.net/

FacesIDE
FacesIDE是一个用于开发JSF的Eclispe插件.它可以可视化编辑faces-config.xml文件并且提供代码编辑与校验,预览JSF的JSP文件.FacesIDE包含MyFaces来作为JSF的实现
http://amateras.sourceforge.jp/cgi-bin/fswiki_en/wiki.cgi?page=FacesIDE

Eclipse SQLExplorer plugin
一个数据库管理插件
http://sourceforge.net/projects/eclipsesql

Poperties Editor
一个在编辑完成后可以将资源文件中的中文编码格式转换为unicode编码的插件,在开发国际化应用程序的时候非常有用
http://propedit.sourceforge.jp/eclipse/updates/

eclipseME
 
http://eclipseme.org/updates/

Eclipse加速插件KeepResident
http://suif.stanford.edu/pub/keepresident/

 
MyEclipse  J2EE开发插件,支持SERVLET/JSP/EJB/数据库操纵等
www.myeclipseide.com
 
Properties Editor  编辑java的属性文件,并可以自动存盘为Unicode格式
http://propedit.sourceforge.jp/index_en.html
http://propedit.sourceforge.jp/eclipse/updates/
 
Colorer Take  为上百种类型的文件按语法着色
http://colorer.sourceforge.net/
 
XMLBuddy 编辑xml文件
www.xmlbuddy.com
 
Code Folding  加入多种代码折叠功能(比eclipse自带的更多)
http://www.coffee-bytes.com/servlet/PlatformSupport
 
Easy Explorer  从eclipse中访问选定文件、目录所在的文件夹
http://easystruts.sourceforge.net/
 
Fat Jar 打包插件,可以方便的完成各种打包任务,可以包含外部的包等
http://fjep.sourceforge.net/
 
RegEx Test 测试正则表达式
http://brosinski.com/stephan/archives/000028.php
 
JasperAssistant 报表插件(强,要钱的)
http://www.jasperassistant.com/
 
Jigloo GUI Builder JAVA的GUI编辑插件
http://cloudgarden.com/jigloo/
 
Profiler 性能跟踪、测量工具,能跟踪、测量BS程序
http://sourceforge.net/projects/eclipsecolorer/
 
AdvanQas 提供对if/else等条件语句的提示和快捷帮助(自动更改结构等)
http://eclipsecolorer.sourceforge.net/advanqas/index.html
 
Log4E     Log4j插件,提供各种和Log4j相关的任务,如为方法、类添加一个logger等
http://log4e.jayefem.de/index.php/Main_Page
 
VSSPlugin VSS插件
http://sourceforge.net/projects/vssplugin
 
Implementors   提供跳转到一个方法的实现类,而不是接中的功能(实用!)
http://eclipse-tools.sourceforge.net/implementors/
 
Call Hierarchy 显示一个方法的调用层次(被哪些方法调,调了哪些方法)
http://eclipse-tools.sourceforge.net/call-hierarchy/index.html
 
EclipseTidy 检查和格式化HTML/XML文件
http://eclipsetidy.sourceforge.net/
 
Checkclipse 检查代码的风格、写法是否符合规范
http://www.mvmsoft.de/content/plugins/checkclipse/checkclipse.htm
 
Hibernate Synchronizer Hibernate插件,自动映射等
http://www.binamics.com/hibernatesync/
 
spring updatesite 插件
http://springide.org/updatesite/

VeloEclipse  Velocity插件
http://propsorter.sourceforge.net/
 
EditorList   方便的列出所有打开的Editor
http://editorlist.sourceforge.net/
 
MemoryManager 内存占用率的监视
http://cloudgarden.com/memorymanager/

Eclipse的游戏插件
http://eclipse-games.sourceforge.net/

JBoss-IDE
http://jboss.sourceforge.net/jbosside/updates/

自动反编译class,安装后要设定class文件缺省关联到jode
http://www.technoetic.com/eclipse/update


jigloo swing/sw设计工具,里面自带的form/anchor布局很好用!
http://cloudgarden.soft-gems.net/update-site/

jinto的资源文件编辑工具,同时编辑多种语言,而且自动转换成iso8859-1编码。很好用!
http://www.guh-software.de/eclipse/

posted @ 2007-02-13 11:17 保尔任 阅读(671) | 评论 (0)编辑 收藏
 一、exe4j

说明:exe4j可以将Jar文件制作成exe文件,但需jre支持,也可将Jar文件放在外面。

软件性质:共享软件

下载地址:http://www.ej-technologies.com/products/exe4j/overview.html

二、JBuilder

说明:新版本的JBuilder可以直接把工程制作成各系统的可执行文件,包括Windows系统。

软件性质:商业软件

下载地址:略。我是从eMule下载的。

三、NativeJ

说明:与exe4j功能类似。

软件性质:共享软件

下载地址:http://www.dobysoft.com/products/nativej/download.html

四、Excelsior JET

说明:可以直接将Java类文件制作成exe文件,除AWT和Swing及第三方图形接口外可不需jre支持(Java5.0不行)。

软件性质:共享软件

下载地址:http://excelsior-usa.com/home.html

五、jshrink

说明:可将Jar文件打包进exe文件。同时具有混淆功能(这才是它的主要功能)。

软件性质:共享软件

下载地址:http://www.e-t.com/jshrink.html

六、InstallAnywhere

说明:打包工具,对Java打包最好用。可打包成各操作系统运行包。包括Windows系统。

软件性质:商业软件。

下载地址:http://www.zerog.com/

七、InstallShieldX

说明:与InstallAnywhere类似,但比InstallAnywhere功能强大。相对的,比较复杂,不易上手,我现在还没学会。
posted @ 2007-02-13 11:16 保尔任 阅读(288) | 评论 (0)编辑 收藏

一、代码转换工具:
native2ascii -encoding gb2312 application_temp.properties application_zh_CN.properties
注释:-encoding gb2312 表示读application_temp.properties 的编码方式,application_temp.properties 存的是中文资源文件,application_zh_CN.properties
存的是转成ascii码后的资源文件。

二、反编译工具jad.exe:
 以下假设jad.exe在c:\java目录下
1、基本用法
Usage:    jad [option(s)] <filename(s)>
直接输入类文件名,且支持通配符,如下所示。
c:\java\>jad example1.class
c:\java\>jad *.class
结果是将example1.class反编译为example1.jad。将example1.jad改为example1.java即得源文件。
2、Option -o
不提示,覆盖源文件
3、Option -s
c:\java\>jad -sjava example1.class
反编译结果以.java为扩展名。
4、Option -p
将反编译结果输出到屏幕
c:\java\>jad -p example1.class
将反编译结果重定向到文件
c:\java\>jad -p example1.class>example1.java
5、Option -d
指定反编译的输出文件目录
c:\java\>jad -o -dtest -sjava *.class

三、文档生成工具javadoc.exe
  大家都知道,J2SE5中的Javadoc.exe的命令行可选参数多达五十余个,其复杂性可想而知,是不是看着头都大了呢?但通常情况下,我们不想那么麻烦!
假设源代码在 C:\src 目录下,其中 com.liigo 是主包,其下可能有数十个子包,数百(千)个Java文件。目录结构大约是这样的:
- C:\
      | src\
          | com\
              | liigo\
                  | ***
怎么才能以最简捷的方式生成所有的API文档呢?
c:\>
c:\>cd src
c:\src>javadoc -d doc -subpackages com.liigo
这样就搞定了,最终生成的API文档位于 c:\src\doc 目录(该目录是由javadoc.exe自动生成的)。
上面的用法利用了“当前目录”和“相对路径”,当然也可以用绝对路径:
...>javadoc -d c:\doc -sourcepath c:\src -subpackages com.liigo
最终生成的API文档位于 c:\doc 目录(该目录同样是由javadoc.exe自动生成的)。

总结一下:
我们只用到了javadoc的三个参数: -d,-subpackages,-sourcepath,其中:
 参数  说明 
 -d  指定API文档的输出目录,默认是当前目录。建议总是指定该参数。
 -sourcepath 指定源代码路径,默认是当前目录。 此参数通常是必须的。
 -subpackages  以递归的方式处理各子包。关键参数!如果不使用本参数,每次只能处理一个子包(或需手工列出所有子包)。

四、运行jvm时改变内存或堆的大小
-Xms<size>                 set   initial   Java   heap   size  
-Xmx<size>                 set   maximum   Java   heap   size  
-Xss<size>                 set   java   thread   stack   size  
   
比如:java   -Xmx512M  HelloWorld.class,让jvm使用512Mheap内存.

posted @ 2007-02-13 11:16 保尔任 阅读(295) | 评论 (0)编辑 收藏

   作者:江南白衣,原文出处: http://blog.csdn.net/calvinxiu/archive/2007/01/27/1495778.aspx,转载请保留出处。

    Unix系统永远只会越来越多,开发人员就没必要特意学习它们的安装、配置和管理了,就全部交给集成人员吧。
    但开发人员行走于Unix之间,依然有四样东西要熟练。

    一、VI

    虽然Unix上的文本编辑器已经越来越好用,但不在Console前面,网速也不够连XWindows的时候,还是要依赖VI。
    回想VI的时代背景,发现VI对开发人员已经周到得离谱了,热键多到你双手不离键盘就能完成大半编辑工作。
    建议自己制作一张自己认为有用,但又经常忘记的命令的sheet,拿出考试的力气把它背熟。

    二、文本处理

       开发人员在Unix下干得最多的除了Make和除Bug外,大概就是处理日志文件、业务文件进行查错和统计了。
     只会more和grep是不够的,开发老手会把awk,sed,grep,sort,uniq,wc,head,tail这些文本处理命令,通过管道玩具式的拆卸拼装,最后完成一件原本以为非编写大段代码不可的工作。周到的参数设定,让人再一次感叹那个简单的年代,这样复杂到极致的设计.......怪不得《Unix 编程艺术》的作者有那么骄傲的自觉。

     比如车东的每月访问TOP10 统计脚本:

awk -F ' t' '{ print   $ 4 }' 2004_2 . txt| grep chedong . com / tech / | uniq -c| sort  -rn|head - 10  
  • awk -F '\t' 将2004_2.txt访问纪录文件,用TAB分割,打印第4列
  • grep chedong.com/tech 只列出chedong.com/tech笔记目录下的文档
  • uniq -c 汇总计数
  • sort -rn 按数值排序
  • head -10 TOP 10

    三、Bash Shell 编程

    编程是开发人员的天赋本能,不论什么语言,看看参考手册应该就能上手。

    见Bash新手指南中文版,一份写给新手看的包含很多老手知识的指南。

    四、Make与AutoMake

    用过Java的Ant后,想起Make就觉得很烦,很厌倦。总归还是会的,见GNU Make 3.8.0 中文手册    

     不过即使make已经精通到变态,每个人写出来的MakeFile还是千奇百怪,再看看开源项目们个个都是automake+autoconf了,我们自己也长进一点吧。手工编写MakeFile.am,让auotomake变成MakeFile.in,再让用户./configure 生成最终的MakeFile。
    
    生成的MakeFile既能跨越平台,又是标准的写法,最重要的是,编写MakeFile.am的工作量比MakeFile少多了,只要简单的定义目标文件,先要处理的子目录,需要的源文件,头文件与库文件就可以了。如果看完下面两篇还是不懂,直接看ACE里的Makefile.am就懂了。

    入门文章:使用AutoMake轻松生成Makefile 
    进阶文章:IBM DW:解 autoconf 和 automake 生成 Makefile 文件
    完整的免费电子书: GNU Autoconf, Automake and Libtool

    另外,ACE里还贡献了一个更厉害的MPC(Makefile, Project, and Workspace Creator ),  自动的生成了MakeFile.am或者VC的项目文件。

    附录A:我的VI易忘命令手册

    上下左右:
    ctrl+u/d 上下半屏,ctrl+f/b,上下一屏
    H/G屏幕头/文章末 ,0/$ 行首行末
   
    增删改:
    yy/dd 复制/删除 一行,p/P:将yy/dd的内容paste出来
    I/A 在行首/末添加, o/O 开新行,d0/d$ 删除到行首,行末
    u:undo

    查:
    ? 向前查找, n/N 重复上一次查找

附录B: 文本处理命令小结

   awk:处理结构化的文本(每行以固定符号分成若干列),提取打印某些字段,如:
    ls -l|awk '{print $1}'  --将ls-l结果的第一列打印出来
    awk -F":" '{print $1"  "$6}' /etc/passwd ,将以:分割的/etc/passwd文件的第1,6列打印出来,中间以空格分开
    详见IBM DW中国的AWK实例(共3篇) 或 Bash新手指南中文版第6章

    grep:过滤,大家用得最多的命令,支持正则表达式。参数有:
    -i忽略大小写,-n显示line number,-c 统计在每个文件的出现次数,-l只显示符合的文件的名字。

    sed:流编辑器,主要用于替换,如:
    sed -e '1,10s/foo/bar/g' myfile2.txt 将1到10行的文本中的foo 替换成bar,s代表替换,g代表全局替换
    支持正则的替换字符串,可以只替换某个范围内的内容。
    用法不算简单,详见IBM DW中国的Sed实例(共3篇)或 Bash新手指南中文版第5章
    
    sort:排序,参数有:
    -r逆序, -n 数字比较 , -M 日历比较 Feb,Dec, -f 忽略大小写
    同样支持结构化文件,如
    sort -t : -k 1,1 /etc/passwd,以: 分割,只按第1列排序
    sort -t : -k 1,1 -k2.2,3.4 /etc/passwd ,以:分割,先按第1列排序,再按第2列的第二个字符到第3列的第4个字符排序。

    uniq:去除重复行。
    除了正常用法外,还有-c统计重复次数,和-u (唯一)和 -d (重复)两个参数,只显示唯一的和重复的行。

    wc: 统计。
    -l 行,-m 字符,-w 单词

posted @ 2007-01-29 17:31 保尔任 阅读(310) | 评论 (0)编辑 收藏
URL(Uniform Resoure Locator,统一资源定位器)是Internet中对资源进行统一定位和管理的标志。
一个完整的URL包括如下内容:
1.         应用协议名称,包括http,ftp,file等标志
2.         资源定位,是由(.)分割等网络路径
3.         端口号,按照规定,http应用端口是80,telnet协议应用端口是23。
4.         服务器中的文件路径
5.         文件中的编码位置
一个完整的URL如下:
http://SomeUser:mypassword@www.some_server.com:8080/path/file.html
 URL无法显示某些特殊符号,这个时候就要使用编码了。编码的格式为:一个百分号,后面跟对应字符的ASCII(16进制)码值。例如 空格的编码值是"%20"。(ASCII参考)
有些字符在URL中具有特殊含义,基本编码规则如下:
特殊含义                                                            十六进制值
1.+ 表示空格(在 URL 中不能使用空格)          %20
2./ 分隔目录和子目录                                              %2F
3.? 分隔实际的 URL 和参数                                   %3F
4.% 指定特殊字符                                                    %25
5.# 表示书签                                                             %23
6.& URL 中指定的参数间的分隔符                        %26
java中URL 的编码和解码函数
java.net.URLEncoder.encode(String s)和java.net.URLDecoder.decode(String s);
在javascript 中URL 的编码和解码函数
escape(String s)和unescape(String s) ;
posted @ 2007-01-12 21:14 保尔任 阅读(10299) | 评论 (0)编辑 收藏
(转自:http://blog.csdn.net/nomads/archive/2006/09/05/1178867.aspx)
Java提供了一套机制来动态执行方法和构造方法,以及数组操作等,这套机制就叫——反射。反射机制是如今很多流行框架的实现基础,其中包括Spring、Hibernate等。原理性的问题不是本文的重点,接下来让我们在实例中学习这套精彩的机制。

1. 得到某个对象的属性

1 public Object getProperty(Object owner, String fieldName) throws Exception {
2     Class ownerClass = owner.getClass();
3 
4     Field field = ownerClass.getField(fieldName);
5 
6     Object property = field.get(owner);
7 
8     return property;
9 }

Class ownerClass = owner.getClass():得到该对象的Class。

Field field = ownerClass.getField(fieldName):通过Class得到类声明的属性。

Object property = field.get(owner):通过对象得到该属性的实例,如果这个属性是非公有的,这里会报IllegalAccessException。



2. 得到某个类的静态属性

 1 public Object getStaticProperty(String className, String fieldName)
 2             throws Exception {
 3     Class ownerClass = Class.forName(className);
 4 
 5     Field field = ownerClass.getField(fieldName);
 6 
 7     Object property = field.get(ownerClass);
 8 
 9     return property;
10 }


Class ownerClass = Class.forName(className) :首先得到这个类的Class。

Field field = ownerClass.getField(fieldName):和上面一样,通过Class得到类声明的属性。

Object property = field.get(ownerClass) :这里和上面有些不同,因为该属性是静态的,所以直接从类的Class里取。


3. 执行某对象的方法

 1 public Object invokeMethod(Object owner, String methodName, Object[] args) throws Exception {
 2 
 3     Class ownerClass = owner.getClass();
 4 
 5     Class[] argsClass = new Class[args.length];
 6 
 7     for (int i = 0, j = args.length; i < j; i++) {
 8         argsClass[i] = args[i].getClass();
 9     }
10 
11     Method method = ownerClass.getMethod(methodName, argsClass);
12 
13     return method.invoke(owner, args);
14 }

Class owner_class = owner.getClass() :首先还是必须得到这个对象的Class。

5~9行:配置参数的Class数组,作为寻找Method的条件。

Method method = ownerClass.getMethod(methodName, argsClass):通过Method名和参数的Class数组得到要执行的Method。

method.invoke(owner, args):执行该Method,invoke方法的参数是执行这个方法的对象,和参数数组。返回值是Object,也既是该方法的返回值。


4. 执行某个类的静态方法

 1 public Object invokeStaticMethod(String className, String methodName,
 2             Object[] args) throws Exception {
 3     Class ownerClass = Class.forName(className);
 4 
 5     Class[] argsClass = new Class[args.length];
 6 
 7     for (int i = 0, j = args.length; i < j; i++) {
 8         argsClass[i] = args[i].getClass();
 9     }
10 
11     Method method = ownerClass.getMethod(methodName, argsClass);
12 
13     return method.invoke(null, args);
14 }


基本的原理和实例3相同,不同点是最后一行,invoke的一个参数是null,因为这是静态方法,不需要借助实例运行。



5. 新建实例
 1 
 2 public Object newInstance(String className, Object[] args) throws Exception {
 3     Class newoneClass = Class.forName(className);
 4 
 5     Class[] argsClass = new Class[args.length];
 6 
 7     for (int i = 0, j = args.length; i < j; i++) {
 8         argsClass[i] = args[i].getClass();
 9     }
10 
11     Constructor cons = newoneClass.getConstructor(argsClass);
12 
13     return cons.newInstance(args);
14 
15 }


这里说的方法是执行带参数的构造函数来新建实例的方法。如果不需要参数,可以直接使用newoneClass.newInstance()来实现。

Class newoneClass = Class.forName(className):第一步,得到要构造的实例的Class。

第5~第9行:得到参数的Class数组。

Constructor cons = newoneClass.getConstructor(argsClass):得到构造子。

cons.newInstance(args):新建实例。


6. 判断是否为某个类的实例

1 public boolean isInstance(Object obj, Class cls) {
2     return cls.isInstance(obj);
3 }



7. 得到数组中的某个元素
1 public Object getByArray(Object array, int index) {
2     return Array.get(array,index);
3 }



附完整源码:

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;


/**
 * Java Reflection Cookbook
 *
 * 
@author Michael Lee
 * 
@since 2006-8-23
 * 
@version 0.1a
 
*/

public class Reflection {
    
/**
     * 得到某个对象的公共属性
     *
     * 
@param owner, fieldName
     * 
@return 该属性对象
     * 
@throws Exception
     *
     
*/
    
public Object getProperty(Object owner, String fieldName) throws Exception {
        Class ownerClass 
= owner.getClass();

        Field field 
= ownerClass.getField(fieldName);

        Object property 
= field.get(owner);

        
return property;
    }

    
/**
     * 得到某类的静态公共属性
     *
     * 
@param className   类名
     * 
@param fieldName   属性名
     * 
@return 该属性对象
     * 
@throws Exception
     
*/
    
public Object getStaticProperty(String className, String fieldName)
            
throws Exception {
        Class ownerClass 
= Class.forName(className);

        Field field 
= ownerClass.getField(fieldName);

        Object property 
= field.get(ownerClass);

        
return property;
    }


    
/**
     * 执行某对象方法
     *
     * 
@param owner
     *            对象
     * 
@param methodName
     *            方法名
     * 
@param args
     *            参数
     * 
@return 方法返回值
     * 
@throws Exception
     
*/
    
public Object invokeMethod(Object owner, String methodName, Object[] args)
            
throws Exception {

        Class ownerClass 
= owner.getClass();

        Class[] argsClass 
= new Class[args.length];

        
for (int i = 0, j = args.length; i < j; i++) {
            argsClass[i] 
= args[i].getClass();
        }

        Method method 
= ownerClass.getMethod(methodName, argsClass);

        
return method.invoke(owner, args);
    }


      
/**
     * 执行某类的静态方法
     *
     * 
@param className
     *            类名
     * 
@param methodName
     *            方法名
     * 
@param args
     *            参数数组
     * 
@return 执行方法返回的结果
     * 
@throws Exception
     
*/
    
public Object invokeStaticMethod(String className, String methodName,
            Object[] args) 
throws Exception {
        Class ownerClass 
= Class.forName(className);

        Class[] argsClass 
= new Class[args.length];

        
for (int i = 0, j = args.length; i < j; i++) {
            argsClass[i] 
= args[i].getClass();
        }

        Method method 
= ownerClass.getMethod(methodName, argsClass);

        
return method.invoke(null, args);
    }



    
/**
     * 新建实例
     *
     * 
@param className
     *            类名
     * 
@param args
     *            构造函数的参数
     * 
@return 新建的实例
     * 
@throws Exception
     
*/
    
public Object newInstance(String className, Object[] args) throws Exception {
        Class newoneClass 
= Class.forName(className);

        Class[] argsClass 
= new Class[args.length];

        
for (int i = 0, j = args.length; i < j; i++) {
            argsClass[i] 
= args[i].getClass();
        }

        Constructor cons 
= newoneClass.getConstructor(argsClass);

        
return cons.newInstance(args);

    }


    
    
/**
     * 是不是某个类的实例
     * 
@param obj 实例
     * 
@param cls 类
     * 
@return 如果 obj 是此类的实例,则返回 true
     
*/
    
public boolean isInstance(Object obj, Class cls) {
        
return cls.isInstance(obj);
    }
    
    
/**
     * 得到数组中的某个元素
     * 
@param array 数组
     * 
@param index 索引
     * 
@return 返回指定数组对象中索引组件的值
     
*/
    
public Object getByArray(Object array, int index) {
        
return Array.get(array,index);
    }
}
 
posted @ 2007-01-12 10:03 保尔任 阅读(514) | 评论 (2)编辑 收藏

面试经验 综合/算法 C/C++/VC MS-SQL Server Java .NET技术 Oracle 其他
[微软系列] [INTEL] [网易] [中兴] [华为] [Google12] [DELL] [朗讯] [神州数码] [方正] [SUN]
 
[面试经验]
高薪是怎么跳出来的?(转载) (wjf4856)
最近几天招聘程序员的感想,另外附一份面试题,大家批批(jyk)
机会是给有准备的人的,我今天去上海微创面试的体会(sanjie88)
面试经历分享:北京文思创新(ghz)
面试的苦恼(palts520)
面试阿里巴巴(boona)
我的面试经历,一个字难!!!!!!!!!!(zxs3543)
如何面试软件工程师?(zenhan)
在网上看了不少公司面试笔试的文章,郁闷了(BLGT)
前天面试失败,特来此作检讨.从今以后开始要好好学习.(utmost100)
要到珠海优特公司面试,请教该做些什么准备?(Builder_Soft)
美工面试一般都会问什么问题(qs_25)
请问面试VB程序员要注意些什么?(chinayokel)
没有面试经验,请大家指导一下(chinayokel)
Asp.net面试一般问什么(zhukuanliang)
急请大家帮忙一会要去面试,听说是上机。一般都出什么题呀(tt5201)
紧急求助:面试时,企业考察编程能力一般会出怎样的题目,特别是VC++!(ab2)
问个问题(视频聊天)和写写4次面试的烦恼(jianjian8410)
[新手求助]电话面试都要准备那些方面?(OpenHero)
为什么每次面试之后都没有结果啊(fangza)
在华为地下室餐厅面试的来聊聊(xubai)
过些天去面试, 软件测试方面, 请各位大虾请提些建议,非常感谢(Ripple_wang)
江湖救急!马上要去面试一个跟物流有关的技术职位,有做物流开发经验的吗?(givemecool)
上周五去长沙大唐先一科技面试,十分郁闷,散分散心!(lingfeng0626)
关于面试(mystones)
做嵌入式开发,一般都面试哪方面的知识?(werqqq)
这次面试太丢脸了,郁闷啊!(feng13555)
继续那个:从东软面试回来郁闷中(转)(mysticality)
NEC公司明天面试,向去过或在这个公司的兄弟请教,顶者勿入(ATaoo)
面试5个月后收到华为录取通知,faint(gomydream)
在面试的时候,自我介绍应该说些什么?(luobo525)
 
[综合/算法]
101道经典面试题 - 01  23456789 (china_cooooooooooder)
一个年薪10万美金的面试题(ayungood)
面试考题,愁啊!!!(mysticality)
[调查]去西安交大的一个软件公司面试,面试官问了一个问题,看有多少人能回答来,顺便散分(bgqy2000)
Google面试的20题,知道的麻烦给点提示 .(hansin)
据说是微软面试题,有答案了,但不知道为什么会是这样!(DaChu)
月薪3万的一道面试题(看看你的IQ )(cgtsea)
SZSM面试题:写一个可以返回任意两个string串的最大公串的函数,语言不限(redbirdli)
分享一下:今天我们公司招聘产品设计师的面试题目……(KiteGirl)
面试时一道超难的算法问题!!!(justrun2005)
一道真正难倒亿人的智力题,这是微软的面试题(66766960)
据说是一道微软月薪5w职位的面试题(cjjfam)
DELL的英文面试题(硬件部分和操作系统),兄弟们直接写下答案,切磋以下!(zh050317)
从网上搜集到的网易笔试题,大家一起讨论一下.
 

[C/C++/VC]

再帖 几道面试题(据说是INTEL公司的)(zdl1016)
[转帖]大家来试试GOOGLE面试题!!(laiwusheng)
据说是朗训面试题(flyiner)
我的中兴面试题,求达人给个答案啊(flyabcd)
求面试题~南京联创的(110120119)
求教一面试题:编写一个函数比较两个整数大小,但不能使用任何比较操作符(neoadane)
C语言面试题大汇总,个人觉得还是比较全地!!!(free131)
前天的一道面试题,前辈帮看看!(67676373)
微软经典面试题(bastenf)
问个面试题(zjbirdman)
面试问题之反转字符串中单词的顺序(ugg)
求助:关于局部变量的一道面试题(neoadane)
昨天朋友面试碰到的一面试题,请教大家(petertangpei)
一个面试题(xiaojun19830916)
两道有趣的面试题目.(yangnix)
各位大侠请帮小弟作道面试题,先谢了!(open_ocean)
面试问题,求助,送分(OneAudollar)
1道面试题 大家帮忙看看(gggaaakkk)
面试题1(yangxudongseu)
 面试题目,求助!(OneAudollar)
又有一个面试题,大家看看(yifongzhou)
面试题,有点难,欢迎高手指点(xnkjdx1998)
大家看看面试题!(yanjun885)
求解几个面试题,今天面的,郁闷死我了(paskaa)
昨天的面试题,分享一下(jianyachu)
一道面试纠错题,大家看看吧(nysst_hxl)
一个关于指针的面试题,会者请进。(fansgq)
软件外企C++面试题,大家试试看?(OneAudollar)
两道C面试题,不小心你未必能做对哦(ilelf)
[高手进]一个面试题,希望高手帮我解答,图形方面!(yangc_83)
[要求置顶]开源软件的面试题——各位大哥们给点思路(dream2013)
面试时被人问到两个问题(yjukh)
求助:面试一些题目,高手们过来帮忙解答一下~(yjukh)
今天的面试题(自学C果然够呛,望大家多多提示)(ppsdog1956)
 
[MS-SQL Server]
sql server 面试时 的考题!!(整理)(2344095)
神州数码11道面试题求解(jinder22)
3棵面试题,大家来看看...................(hanchi8008)
关于事务的问题(面试题)(zyq_10_25)
sql server 面试时 的考题!!(2344095)
昨天看了一个朋友的面试题,大家也来看看(THE_ROCK)
这SQL语句我真的不会写?面试题目(hzchl219)
棘手的(SQL)面试题(songyutou)
面试题!(zyq_10_25)
我在面试时遇到的sql语句,请高人指点,跪求!!!!(polo_van)
面试问题,请帮忙解答!!(songcan)
这个sql语句该怎么写?(面试题)(csdn02)
这边还有40分.求面试题:关于存储过程和触发器的,并附答案呀!!!!!!!!!!!!!!(fairan)
面试问题 大表、小表 ,内联查询,大表放在前面还是后面查询速度更快(jietuan)
 
[Oracle]
DBA常见面试题征集答案,并希望达人补充,希望此贴让所有想做DBA的人有点方向。(pegtop)
面试题(zyq_10_25)
我今天面试的SQL题目,欢迎刚毕业的同学来试一试!!!(kong361)
 
[Java]
共享一些面试题(wts173)
方正的面试题目(jmh0525)
一个很妖的问题(sun公司的面试题)(CCJHJ)
高分求救!面试的时候,考官这样问我,我要怎么回答!! 谢谢!!定给高分!
外企的一道面试题.还请高手多多指点指点(急)!谢了!!(ayorange)
Java面试题(x_hong)
一公司面试题,做一翻译字典,要把不同的字典放在不同的服务器,高手帮忙啊(totti1110)
请教一道面试题?(javaf1)
本人自己遇到的面试题(急!急!急!)(caiheng520)
一道面试题:这么确定a是不是B类的一个实例。谢谢(luofengjava)
一个Hibernate 的面试题, 现场分析问题(this_is_alan)
面试题!在线等!(mousefog)
面试题目!谁能告诉我答案啊!(yinlei920)
面试题(Hmilyl)
感兴趣的话来看一下这道面试题(chenfive)
高分求教!!!一道面试题(url链接传中文参数值乱码问题)(tooker)
面试题60分!请大家帮忙(zyq_10_25)
面试题求答案(zhyhongyuan)
共享J2ME面试题目,欢迎大家积极参与(Mailbomb)
今天的面试题(Hmilyl)
一道java面试题(今天刚面试回来)(cuiter)
面试关于JSP+oracl的问题求救(coolcat_1981)
 
[.NET技术]  
在网上看到的面试题大全,有兴趣的朋友可以来试试看(greatbag)
dotNet高级软件工程师面试题(flyinsky333)
.net面试题集(liuxingjin)
闲来无事,做了一道面试题:100位数字相乘(czhenq)
**大型软件公司.net面试题!一定的看!(aspjsp)
我们公司面试基本完成,现公开部分面试题,感兴趣的来看看,同时也是给出标准答案供面试者参考。(Ivony)
昨天面试微软,只有这一题不会做。(amingo)
一道真正难倒亿人的智力题,这是微软的面试题(66766960)
【MS的一道面试题】Session问题(cxyppppp)
我们公司的一道面试题(jobenc)
有点麻烦的面试题,请给个核心算法(iacpdj)
一道.net面试题???(never_give_up520)
一道面试题,大家解答!(tigerlgf)
今天面试的两个问题,没能回答好,郁闷!(zhanghaif)
面试题大家都来看看 在线等急~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(willyer)
一道面试题,怎么也没看懂,大家帮忙看看(iacpdj)
同学去面试的两个面试题!(longshaoye)
面试题(karso)
不知道是垃圾面试题还是好的面试题?大家看看吧!(rex1984)
面试遇到这种题,晕了!求各位施予援手!(songcan)
一道面试题,没做出来!(cloud_1981)
面试题目:如何在客户端新建一个项目到服务器端(evila_love)
一道外企机试面试--------高手请进(getbyling)
 
[其他]
网络安全方面的面试题(vagrantisme)
软件测试/质量评估(QA)面试的题目,大家帮忙想想(qiuzhizhe)
[汇编]来者有分,面试题,贴出来一起讨论,超难!!!!!(crtd024)
出了套面试题,大家看看,这让没经验值的人做合不合适(wojiudaofen)
急问一道关于测试的面试题(xjh_Love_paopao)
一道关于路由器的面试题,高手请进!!(Gamehal)
[Linux/Unix社区]今天面试的时候的问题没有能够回答出来的题,郁闷!各位帮忙看看
[Linux/Unix社区]请教面试题一则(ty263)
[Windows]昨天面试,考官问我平时用户端的数据怎么做备份的,大家来谈谈!(ballatong)
[Windows]求面试题(qiuyinggxnn)
[ASP]下午去面试遇上了一个很有挑战的问题,有没高手路过?(me121121)
[VB]VB面试过后不过,问问大家这几题该怎么回答(xiebird)
 
[微软系列]
[转贴]微软面试题系列(1) 2 3 4 5(fire_dragon)
一道真正难倒亿人的智力题,这是微软的面试题(66766960)
据说是一道微软月薪5w职位的面试题(cjjfam)
据说是微软面试题,有答案了,但不知道为什么会是这样!(DaChu)
微软经典面试题(bastenf)
昨天面试微软,只有这一题不会做。(amingo)
一道真正难倒亿人的智力题,这是微软的面试题(66766960)
【MS的一道面试题】Session问题(cxyppppp)

posted @ 2006-12-31 16:09 保尔任 阅读(5671) | 评论 (0)编辑 收藏

URI(Uniform Resoure Identifier:统一资源标识符),URL(Uniform Resoure Location:统一资源定位器),URN(Uniform Resource Name统一资源

名),URC(Uniform Resource Citation统一资源引用符)

URI、URL和URN是识别、定位和命名互联网上的资源的标准途径; URL,URN是URI的子集.

     URI不能定位或读取/写入资源。这是统一的资源定位器(URL)的任务。URL是一种URI,但是它的大纲组件是已知的网络协议(简称协议

),并且它把URI组件与某种协议处理程序(一种资源定位器和根据协议建立的约束规则与资源通讯的读/写机制)。

  URI一般不能为资源提供持久不便的名称。这是统一的资源命名(URN)的任务。URN也是一种URI,但是全球唯一的、持久不便的,即使资

源不在存在或不再使用。

      web上地址的基本形式是URI,它代表统一资源标识符。有两种形式:
      URL:目前URI的最普遍形式就是无处不在的URL或统一资源定位器。
      URN:URL的一种更新形式,统一资源名称(URN, Uniform Resource Name)不依赖于位置,并且有可能减少失效连接的个数。但是其流行

还需假以时日,因为它需要更精密软件的支持。

体系中的URI、URL和URN是彼此关联的。URI的范畴位于体系的顶层,URL和URN的范畴位于体系的底层。这种排列显示URL和URN都是URI的子范畴

, URI表示的是统一的资源标识,它是以某种统一的(标准化的)方式标识资源的简单字符串。典型情况下,这种字符串以scheme(命名URI

的名字空间的标识符--一组相关的名称)开头,语法如下:

[scheme:] scheme-specific-part

URI以scheme和冒号开头。Scheme用大写/小写字母开头,后面为空或者跟着更多的大写/小写字母、数字、加号、减号和点号。冒号把scheme与

scheme-specific-part分开了,并且scheme-specific-part的语法和语义(意思)由URI的名字空间决定。  

其中一个例子是http://www.cnn.com,其中http是scheme,//www.cnn.com是 scheme-specific-part,并且它的scheme与scheme-specific-

part被冒号分开了。

我们可以把URI按照绝对的或相对的分类。绝对的URI指以scheme(后面跟着冒号)开头的URI。前面提到的http://www.cnn.com就是绝对的URI

的一个例子,其它的例子还有mailto:jeff@javajeff.comnews:comp.lang.java.help和xyz://whatever。你可以把绝对的URI看作是以某种方

式引用某种资源,而这种方式对标识符出现的环境没有依赖。   

如果使用文件系统作类比,绝对的URI类似于从根目录开始的某个文件的路径。与绝对的URI不同的,相对的URI不是以scheme(后面跟着冒号)

开始的URI。

它的一个例子是articles/articles.html。你可以把相对的URI看作是以某种方式引用某种资源,而这种方式依赖于标识符出现的环境。如果用

文件系统作类比,相对的URI类似于从当前目录开始的文件路径。

URI:

Web上可用的每种资源 - HTML文档、图像、视频片段、程序等 - 由一个通过通用资源标志符(Universal Resource Identifier, 简称"URI")

进行定位。
URI一般由三部分组成:
1. 访问资源的命名机制。
2. 存放资源的主机名。
3. 资源自身的名称,由路径表示。
注:大多数人可能熟悉"URL",而不是URI。URL是URI命名机制的一个子集。

URL:

URL是Uniform Resource Location的缩写,译为"统一资源定位符"。通俗地说,URL是Internet上用来描述信息资源的字符串,主要用在各种

WWW客户程序和服务器程序上,特别是著名的Mosaic。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等


URL的格式
URL的格式由下列三部分组成:
第一部分是协议(或称为服务方式);
第二部分是存有该资源的主机IP地址(有时也包括端口号);
第三部分是主机资源的具体地址。,如目录和文件名等。
第一部分和第二部分之间用"://"符号隔开,第二部分和第三部分用"/"符号隔开。第一部分和第二部分是不可缺少的,第三部分有时可以省略


URL的缺点:
当信息资源的存放地点发生变化时,必须对URL作相应的改变。因此人们正在研究新的信息资源表示方法,例如:URI(Universal Resource

Identifier)即"通用资源标识"(参见RFC 1630)、URN(Uniform Resource Name)即"统一资源名"和URC(Uniform Resource Citation)即"

统一资源引用符"等。

 

posted @ 2006-12-31 15:27 保尔任 阅读(1114) | 评论 (0)编辑 收藏
 
   Abstract:本文深入分析了Java程序设计中Java编译器对java源文件和JVM对class类文件的编码/解码过程,通过此过程的解析透视出了Java编程中中文问题产生的根本原因,最后给出了建议的最优化的解决Java中文问题的方法。 

  1、中文问题的来源

    计算机最初的操作系统支持的编码是单字节的字符编码,于是,在计算机中一切处理程序最初都是以单字节编码的英文为准进行处理。随着计算机的发展,为了适应世界其它民族的语言(当然包括我们的汉字),人们提出了UNICODE编码,它采用双字节编码,兼容英文字符和其它民族的双字节字符编码,所以,目前,大多数国际***的软件内部均采用UNICODE编码,在软件运行时,它获得本地支持系统(多数时间是操作系统)默认支持的编码格式,然后再将软件内部的 UNICODE转化为本地系统默认支持的格式显示出来。Java的JDK和JVM即是如此,我这里说的JDK是指国际版的JDK,我们大多数程序员使用的是国际化的JDK版本,以下所有的JDK均指国际化的JDK版本。我们的汉字是双字节编码语言,为了能让计算机处理中文,我们自己制定的gb2312、 GBK、GBK2K等标准以适应计算机处理的需求。所以,大部分的操作系统为了适应我们处理中文的需求,均定制有中文操作系统,它们采用的是GBK, GB2312编码格式以正确显示我们的汉字。如:中文Win2K默认采用的是GBK编码显示,在中文WIN2k中保存文件时默认采用的保存文件的编码格式也是GBK的,即,所有在中文WIN2K中保存的文件它的内部编码默认均采用GBK编码,注意:GBK是在GB2312基础上扩充来的。

    由于Java语言内部采用UNICODE编码,所以在JAVA程序运行时,就存在着一个从UNICODE编码和对应的操作系统及浏览器支持的编码格式转换输入、输出的问题,这个转换过程有着一系列的步骤,如果其中任何一步出错,则显示出来的汉字就会出是乱码,这就是我们常见的JAVA中文问题。

    同时,Java是一个跨平台的编程语言,也即我们编写的程序不仅能在中文windows上运行,也能在中文Linux等系统上运行,同时也要求能在英文等系统上运行(我们经常看到有人把在中文win2k上编写的JAVA程序,移植到英文Linux上运行)。这种移植操作也会带来中文问题。

    还有,有人使用英文的操作系统和英文的IE等浏览器,来运行带中文字符的程序和浏览中文网页,它们本身就不支持中文,也会带来中文问题。

    几乎所有的浏览器默认在传递参数时都是以UTF-8编码格式来传递,而不是按中文编码传递,所以,传递中文参数时也会有问题,从而带来乱码现象。

    总之,以上几个方面是JAVA中的中文问题的主要来源,我们把以上原因造成的程序不能正确运行而产生的问题称作:JAVA中文问题。

  2、JAVA编码转换的详细过程

    我们常见的JAVA程序包括以下类别:
     *直接在console上运行的类(包括可视化界面的类)
     *JSP代码类(注:JSP是Servlets类的变型)
     *Servelets类
     *EJB类
     *其它不可以直接运行的支持类

    这些类文件中,都有可能含有中文字符串,并且我们常用前三类JAVA程序和用户直接交互,用于输出和输入字符,如:我们在JSP和Servlet中得到客户端送来的字符,这些字符也包括中文字符。无论这些JAVA类的作用如何,这些JAVA程序的生命周期都是这样的:

    *编程人员在一定的操作系统上选择一个合适的编辑软件来实现源程序代码并以.java扩展名保存在操作系统中,例如我们在中文win2k中用记事本编辑一个java源程序;
     *编程人员用JDK中的javac.exe来编译这些源代码,形成.class类(JSP文件是由容器调用JDK来编译的);
     *直接运行这些类或将这些类布署到WEB容器中去运行,并输出结果。
    那么,在这些过程中,JDK和JVM是如何将这些文件如何编码和解码并运行的呢?

这里,我们以中文win2k操作系统为例说明JAVA类是如何来编码和被解码的。

    第一步,我们在中文win2k中用编辑软件如记事本编写一个Java源程序文件(包括以上五类JAVA 程序),程序文件在保存时默认采用了操作系统默认支持GBK编码格式(操作系统默认支持的格式为file.encoding格式)形成了一个.java文件,也即,java程序在被编译前,我们的JAVA源程序文件是采用操作系统默认支持的file.encoding编码格式保存的,java源程序中含有中文信息字符和英文程序代码;要查看系统的file.encoding参数,可以用以下代码:
  public class ShowSystemDefaultEncoding {
  public static void main(String[] args) {
  String encoding = System.getProperty("file.encoding");
  System.out.println(encoding);
  }}

    第二步,我们用JDK的javac.exe文件编译我们的Java源程序,由于JDK是国际版的,在编译的时候,如果我们没有用-encoding参数指定我们的 JAVA源程序的编码格式,则javac.exe首先获得我们操作系统默认采用的编码格式,也即在编译java程序时,若我们不指定源程序文件的编码格式,JDK首先获得操作系统的file.encoding参数(它保存的就是操作系统默认的编码格式,如WIN2k,它的值为GBK),然后JDK就把我们的java源程序从file.encoding编码格式转化为JAVA内部默认的 UNICODE格式放入内存中。然后,javac把转换后的unicode格式的文件进行编译成.class类文件,此时.class文件是 UNICODE编码的,它暂放在内存中,紧接着,JDK将此以UNICODE编码的编译后的class文件保存到我们的操作系统中形成我们见到的. class文件。对我们来说,我们最终获得的.class文件是内容以UNICODE编码格式保存的类文件,它内部包含我们源程序中的中文字符串,只不过此时它己经由file.encoding格式转化为UNICODE格式了。

    这一步中,对于JSP源程序文件是不同的,对于JSP,这个过程是这样的:即WEB容器调用JSP编译器,JSP编译器先查看JSP文件中是否设置有文件编码格式,如果JSP文件中没有设置JSP文件的编码格式,则JSP编译器调用JDK先把JSP文件用JVM默认的字符编码格式(也即WEB容器所在的操作系统的默认的file.encoding)转化为临时的Servlet类,然后再把它编译成UNICODE格式的class类,并保存在临时文件夹中。如:在中文win2k上,WEB容器就把JSP文件从GBK编码格式转化为UNICODE格式,然后编译成临时保存的Servlet类,以响应用户的请求。

    第三步,运行第二步编译出来的类,分为三种情况:

    A、 直接在console上运行的类
    B、 EJB类和不可以直接运行的支持类(如JavaBean类)
    C、 JSP代码和Servlet类
    D、 JAVA程序和数据库之间
    下面我们分这四种情况来看。
    A、直接在console上运行的类

    这种情况,运行该类首先需要JVM支持,即操作系统中必须安装有JRE。运行过程是这样的:首先java启动JVM,此时JVM读出操作系统中保存的 class文件并把内容读入内存中,此时内存中为UNICODE格式的class类,然后JVM运行它,如果此时此类需要接收用户输入,则类会默认用 file.encoding编码格式对用户输入的串进行编码并转化为unicode保存入内存(用户可以设置输入流的编码格式)。程序运行后,产生的字符串(UNICODE编码的)再回交给JVM,最后JRE把此字符串再转化为file.encoding格式(用户可以设置输出流的编码格式)传递给操作系统显示接口并输出到界面上。

    对于这种直接在console上运行的类,它的转化过程可用图1更加明确的表示出来:

图1

以上每一步的转化都需要正确的编码格式转化,才能最终不出现乱码现象。

    B、EJB类和不可以直接运行的支持类(如JavaBean类)

    由于EJB类和不可以直接运行的支持类,它们一般不与用户直接交互输入和输出,它们常常与其它的类进行交互输入和输出,所以它们在第二步被编译后,就形成了内容是UNICODE编码的类保存在操作系统中了,以后只要它与其它的类之间的交互在参数传递过程中没有丢失,则它就会正确的运行。
这种EJB类和不可以直接运行的支持类, 它的转化过程可用图2更加明确的表示出来:

图2


    C、JSP代码和Servlet类

    经过第二步后,JSP文件也被转化为Servlets类文件,只不过它不像标准的Servlets一校存在于classes目录中,它存在于WEB容器的临时目录中,故这一步中我们也把它做为Servlets来看。

    对于Servlets,客户端请求它时,WEB容器调用它的JVM来运行Servlet,首先,JVM把Servlet的class类从系统中读出并装入内存中,内存中是以UNICODE编码的Servlet类的代码,然后JVM在内存中运行该Servlet类,如果Servlet在运行的过程中,需要接受从客户端传来的字符如:表单输入的值和URL中传入的值,此时如果程序中没有设定接受参数时采用的编码格式,则WEB容器会默认采用ISO-8859- 1编码格式来接受传入的值并在JVM中转化为UNICODE格式的保存在WEB容器的内存中。Servlet运行后生成输出,输出的字符串是 UNICODE格式的,紧接着,容器将Servlet运行产生的UNICODE格式的串(如html语法,用户输出的串等)直接发送到客户端浏览器上并输出给用户,如果此时指定了发送时输出的编码格式,则按指定的编码格式输出到浏览器上,如果没有指定,则默认按ISO-8859-1编码发送到客户的浏览器上。这种JSP代码和Servlet类,它的转化过程可用图3更加明确地表示出来:

图3

D、Java程序和数据库之间

    对于几乎所有数据库的JDBC驱动程序,默认的在JAVA程序和数据库之间传递数据都是以ISO-8859-1为默认编码格式的,所以,我们的程序在向数据库内存储包含中文的数据时,JDBC首先是把程序内部的UNICODE编码格式的数据转化为ISO-8859-1的格式,然后传递到数据库中,在数据库保存数据时,它默认即以ISO-8859-1保存,所以,这是为什么我们常常在数据库中读出的中文数据是乱码。
    对于JAVA程序和数据库之间的数据传递,我们可以用图4清晰地表示出来

图4


    3、分析常见的JAVA中文问题几个必须清楚的原则

    首先,经过上面的详细分析,我们可以清晰地看到,任何JAVA程序的生命期中,其编码转换的关键过程是在于:最初编译成class文件的转码和最终向用户输出的转码过程。
    其次,我们必须了解JAVA在编译时支持的、常用的编码格式有以下几种:
    *ISO-8859-1,8-bit, 同8859_1,ISO-8859-1,ISO_8859_1等编码
    *Cp1252,美国英语编码,同ANSI标准编码
    *UTF-8,同unicode编码
    *GB2312,同gb2312-80,gb2312-1980等编码
    *GBK , 同MS936,它是gb2312的扩充
    及其它的编码,如韩文、日文、繁体中文等。同时,我们要注意这些编码间的兼容关体系如下:
    unicode和UTF-8编码是一一对应的关系。GB2312可以认为是GBK的子集,即GBK编码是在gb2312上扩展来的。同时,GBK编码包含了20902个汉字,编码范围为:0x8140-0xfefe,所有的字符可以一一对应到UNICODE2.0中来。

    再次,对于放在操作系统中的.java源程序文件,在编译时,我们可以指定它内容的编码格式,具体来说用-encoding来指定。注意:如果源程序中含有中文字符,而你用-encoding指定为其它的编码字符,显然是要出错的。用-encoding指定源文件的编码方式为GBK或gb2312,无论我们在什么系统上编译含有中文字符的JAVA源程序都不会有问题,它都会正确地将中文转化为UNICODE存储在class文件中。
    
    然后,我们必须清楚,几乎所有的WEB容器在其内部默认的字符编码格式都是以ISO-8859-1为默认值的,同时,几乎所有的浏览器在传递参数时都是默认以UTF-8的方式来传递参数的。所以,虽然我们的Java源文件在出入口的地方指定了正确的编码方式,但其在容器内部运行时还是以ISO-8859- 1来处理的。


 4、中文问题的分类及其建议最优解决办法

    了解以上JAVA处理文件的原理之后,我们就可以提出了一套建议最优的解决汉字问题的办法。
    我们的目标是:我们在中文系统中编辑的含有中文字符串或进行中文处理的JAVA源程序经编译后可以移值到任何其它的操作系统中正确运行,或拿到其它操作系统中编译后能正确运行,能正确地传递中文和英文参数,能正确地和数据库交流中英文字符串。
    我们的具体思路是:在JAVA程序转码的入口和出口及JAVA程序同用户有输入输出转换的地方限制编码方法使之正确即可。

    具体解决办法如下:

    1、 针对直接在console上运行的类
    对于这种情况,我们建议在程序编写时,如果需要从用户端接收用户的可能含有中文的输入或含有中文的输出,程序中应该采用字符流来处理输入和输出,具体来说,应用以下面向字符型节点流类型:
    对文件:FileReader,FileWrieter
        其字节型节点流类型为:FileInputStream,FileOutputStream
    对内存(数组):CharArrayReader,CharArrayWriter
        其字节型节点流类型为:ByteArrayInputStream,ByteArrayOutputStream
    对内存(字符串):StringReader,StringWriter
    对管道:PipedReader,PipedWriter
        其字节型节点流类型为:PipedInputStream,PipedOutputStream
    同时,应该用以下面向字符型处理流来处理输入和输出:
    BufferedWriter,BufferedReader
        其字节型的处理流为:BufferedInputeStream,BufferedOutputStream
    InputStreamReader,OutputStreamWriter
    其字节型的处理流为:DataInputStream,DataOutputStream
    其中InputStreamReader和InputStreamWriter用于将字节流按照指定的字符编码集转换到字符流,如:
    InputStreamReader in = new InputStreamReader(System.in,"GB2312");
    OutputStreamWriter out = new OutputStreamWriter (System.out,"GB2312");
    例如:采用如下的示例JAVA编码就达到了要求:

    //Read.java
    import java.io.*;
    public class Read {
    public static void main(String[] args) throws IOException {
    String str = "\n中文测试,这是内部硬编码的串"+"\ntest english character";
    String strin= "";
    BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in,"gb2312")); //设置输入接口按中文编码
    BufferedWriter stdout = new BufferedWriter(new OutputStreamWriter(System.out,"gb2312")); //设置输出接口按中文编码
    stdout.write("请输入:");
    stdout.flush();
    strin = stdin.readLine();
    stdout.write("这是从用户输入的串:"+strin);
    stdout.write(str);
    stdout.flush();
    }}
    同时,在编译程序时,我们用以下方式来进行:
    javac -encoding gb2312 Read.java
    其运行结果如图5所示:

    图5
2、 针对EJB类和不可以直接运行的支持类(如JavaBean类)

    由于这种类它们本身被其它的类调用,不直接与用户交互,故对这种类来说,我们的建议的处理方式是内部程序中应该采用字符流来处理程序内部的中文字符串(具体如上面一节中一样),同时,在编译类时用-encoding gb2312参数指示源文件是中文格式编码的即可。


    3、 针对Servlet类

    针对Servlet,我们建议用以下方法:(我建议将.java文件设置为UTF8编码的。当然如果是用Eclipse的话,只要设置下就是了。对于数据库,我以为编码最好设置为UTF8,便于国际化 。尽可能的使用UTF8码,  千里草)

    在编译Servlet类的源程序时,用-encoding指定编码为GBK或GB2312,且在向用户输出时的编码部分用response对象的 setContentType("text/html;charset=GBK");或gb2312来设置输出编码格式,同样在接收用户输入时,我们用 request.setCharacterEncoding("GB2312");这样无论我们的servlet类移植到什么操作系统中,只有客户端的浏览器支持中文显示,就可以正确显示。如下是一个正确的示例:

    //HelloWorld.java
    package hello;
    import java.io.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    public class HelloWorld extends HttpServlet
    {
    public void init() throws ServletException { }
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
    {
    request.setCharacterEncoding("GB2312"); //设置输入编码格式
    response.setContentType("text/html;charset=GB2312"); //设置输出编码格式
    PrintWriter out = response.getWriter(); //建议使用PrintWriter输出
    out.println("<hr>");
    out.println("Hello World! This is created by Servlet!测试中文!");
    out.println("<hr>");
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
    {
    request.setCharacterEncoding("GB2312"); //设置输入编码格式
    response.setContentType("text/html;charset=GB2312"); //设置输出编码格式
    String name = request.getParameter("name");
    String id = request.getParameter("id");
    if(name==null) name="";
    if(id==null) id="";
    PrintWriter out = response.getWriter(); //建议使用PrintWriter输出
    out.println("<hr>");
    out.println("你传入的中文字串是:" + name);
    out.println("<hr>你输入的id是:" + id);
    out.println("<hr>");
    }
    public void destroy() { }
    }
        请用javac -encoding gb2312 HelloWorld.java来编译此程序。
        测试此Servlet的程序如下所示:
    <%@page contentType="text/html; charset=gb2312"%>
    <%request.setCharacterEncoding("GB2312");%>
    <html><head><title></title>
    <Script language="JavaScript">
    function Submit() {
    //通过URL传递中文字符串值给Servlet
    document.base.action = "./HelloWorld?name=中文";
    document.base.method = "POST";
    document.base.submit();
    }
    </Script>
    </head>

<body bgcolor="#FFFFFF" text="#000000" topmargin="5">
    <form name="base" method = "POST" target="_self">
    <input name="id" type="text" value="" size="30">
    <a href = "JavaScript:Submit()">传给Servlet</a>
    </form></body></html>
    其运行结果如图6所示:

    图6
    4、 JAVA程序和数据库之间

    为避免JAVA程序和数据库之间数据传递出现乱码现象,我们建议采用以下最优方法来处理:
    1、 对于JAVA程序的处理方法按我们指定的方法处理。
    2、 把数据库默认支持的编码格式改为GBK或GB2312的。

    如:在mysql中,我们可以在配置文件my.ini中加入以下语句实现:
    在[mysqld]区增加:
    default-character-set=gbk
    并增加:
    [client]
    default-character-set=gbk
    在SQL Server2K中,我们可以将数据库默认的语言设置为Simplified Chinese来达到目的。

    5、 针对JSP代码

    由于JSP是在运行时,由WEB容器进行动态编译的,如果我们没有指定JSP源文件的编码格式,则JSP编译器会获得服务器操作系统的 file.encoding值来对JSP文件编译的,它在移植时最容易出问题,如在中文win2k中可以很好运行的jsp文件拿到英文linux中就不行,尽管客户端都是一样的,那是因为容器在编译JSP文件时获取的操作系统的编码不同造成的(在中文wink中的file.encoding和在英文 Linux中file.encoding是不同的,且英文Linux的file.encoding对中文不支持,所以编译出来的JSP类就会有问题)。网络上讨论的大多数是此类问题,多是因为JSP文件移植平台时不能正确显示的问题,对于这类问题,我们了解了JAVA中程序编码转换的原理,解决起来就容易多了。我们建议的解决办法如下:

    1、我们要保证JSP向客户端输出时是采用中文编码方式输出的,即无论如何我们首先在我们的JSP源代编中加入以下一行:

    <%@page contentType="text/html; charset=gb2312"%>
    2、为了让JSP能正确获得传入的参数,我们在JSP源文件头加入下面一句:
    <%request.setCharacterEncoding("GB2312");%>
    3、为了让JSP编译器能正确地解码我们的含有中文字符的JSP文件,我们需要在JSP源文件中指定我们的JSP源文件的编码格式,具体来说,我们在JSP源文件头上加入下面的一句即可:
    <%@page pageEncoding="GB2312"%>或<%@page pageEncoding="GBK"%>
    这是JSP规范2.0新增加的指令。
    我们建议使用此方法来解JSP文件中的中文问题,下面的代码是一个正确做法的JSP文件的测试程序:

//testchinese.jsp
    <%@page pageEncoding="GB2312"%>
    <%@page contentType="text/html; charset=gb2312"%>
    <%request.setCharacterEncoding("GB2312");%>
    <%
    String action = request.getParameter("ACTION");
    String name = "";
    String str = "";
    if(action!=null && action.equals("SENT"))
    {
    name = request.getParameter("name");
    str = request.getParameter("str");
    }
    %>
    <html>
    <head>
    <title></title>
    <Script language="JavaScript">
    function Submit()
    {
    document.base.action = "?ACTION=SENT&str=传入的中文";
    document.base.method = "POST";
    document.base.submit();
    }
    </Script>
    </head>
    <body bgcolor="#FFFFFF" text="#000000" topmargin="5">
    <form name="base" method = "POST" target="_self">
    <input type="text" name="name" value="" size="30">
    <a href = "JavaScript:Submit()">提交</a>
    </form>
    <%
    if(action!=null && action.equals("SENT"))
    {
    out.println("<br>你输入的字符为:"+name);
    out.println("<br>你通过URL传入的字符为:"+str);
    }
    %>
    </body>
    </html>
    如图7是此程序运行的结果示意图:

    图7

    5、总结

    在上面的详细分析中,我们清晰地给出了JAVA在处理源程序过程中的详细转换过程,为我们正确解决JAVA编程中的中文问题提供了基础。同时,我们给出了认为是最优的解决JAVA中文问题的办法。

我的补充(需要特别注意): 在表单(form )提交时,如果提交的方法为get,那么request.setCharacterEncoding() 是不起作用的。此时在服务器端得到的字符编码仍然是ISO8859-1,而不是你设置的编码。所以一般我们在提交数据时,尽量使用post方法,此时 request.setCharacterEncoding()方法起效。

 如果非要使用get方法传form则要转换一下才行: 
  -----  
  <%@   page   contentType="text/html;charset=gb2312"%>  
  <%!  
          public   String   getStr(String   str){  
  try{  
  String   temp_p=str;  
  byte[]   temp_t=temp_p.getBytes("ISO8859-1");  
  String   temp=new   String(temp_t);  
  return   temp;  
  }  
  catch(Exception   e){  
  }  
  return   "null";  
    }  
    %>  
  然后把String   userId=request.getParameter("userId");改成  
  String   userId=getStr(request.getParameter("userId"));  
--------------------------------------------
我来说一下tomcat如何实现JSP的你就明白了。
预备知识:
 1.字节和unicode
  Java内核是unicode的,就连class文件也是,但是很多媒体,包括文件/流的保存方式
  是使用字节流的。 因此Java要对这些字节流经行转化。char是unicode的,而byte是字节.
  Java中byte/char互转的函数在sun.io的包中间有。其中ByteToCharConverter类是中调度,
  可以用来告诉你,你用的Convertor。其中两个很常用的静态函数是
   public static ByteToCharConverter getDefault() ;
   public static ByteToCharConverter getConverter(String encoding);
  如果你不指定converter,则系统会自动使用当前的Encoding,GB平台上用GBK,EN平台上用
  8859_1
  
  我们来就一个简单的例子:
     "你"的gb码是:0xC4E3 ,unicode是0x4F60
     你用:
     --encoding="gb2312";
     --byte b[]={(byte)'\u00c4',(byte)'\u00E3'};
     --convertor=ByteToCharConverter.getConverter(encoding);
     --char [] c=converter.convertAll(b);
     --for(int i=0;i<c.length;c++)
     --{
     -- System.out.println(Integer.toHexString(c[i]));
     --}
     --打印出来是0x4F60
     --但是如果使用8859_1的编码,打印出来是
     --0x00C4,0x00E3
     ----例1
     反过来:
      --encoding="gb2312";
      --char c[]={'\u4F60'};
     --convertor=ByteToCharConverter.getConverter(encoding);
     --byte [] b=converter.convertAll(c);
     --for(int i=0;i<b.length;c++)
     --{
     -- System.out.println(Integer.toHexString(b[i]));
     --}
      --打印出来是:0xC4,0xE3
      ----例2
      --如果用8859_1就是0x3F,?号,表示无法转化      --
      很多中文问题就是从这两个最简单的类派生出来的。而却有很多类  
  不直接支持把Encoding输入,这给我们带来诸多不便。很多程序难得用encoding
  了,直接用default的encoding,这就给我们移植带来了很多困难
  --
  2.UTF-8
  --UTF-8是和Unicode一一对应的,其实现很简单
  --
  -- 7位的Unicode: 0 _ _ _ _ _ _ _
  --11位的Unicode: 1 1 0 _ _ _ _ _ 1 0 _ _ _ _ _ _
  --16位的Unicode: 1 1 1 0 _ _ _ _ 1 0 _ _ _ _ _ _ 1 0 _ _ _ _ _ _
  --21位的Unicode: 1 1 1 1 0 _ _ _ 1 0 _ _ _ _ _ _ 1 0 _ _ _ _ _ _ 1 0 _ _ _ _ _ _
  --大多数情况是只使用到16位以下的Unicode:
  --"你"的gb码是:0xC4E3 ,unicode是0x4F60
  --我们还是用上面的例子
  --  --例1:0xC4E3的二进制:
  --  --    1 1 0 0 0 1 0 0 1 1 1 0 0 0 1 1
  --  --    由于只有两位我们按照两位的编码来排,但是我们发现这行不通,
  --  --    因为第7位不是0因此,返回"?"
  --  --   
  --  --例2:0x4F60的二进制:
  --  --    0 1 0 0 1 1 1 1 0 1 1 0 0 0 0 0
  --  --    我们用UTF-8补齐,变成:
  --  --    11100100 10111101 10100000
  --  --    E4--BD-- A0
  --  --    于是返回0xE4,0xBD,0xA0
  --  --
  3.String和byte[]
  --String其实核心是char[],然而要把byte转化成String,必须经过编码。
  --String.length()其实就是char数组的长度,如果使用不同的编码,很可
  --能会错分,造成散字和乱码。
  --例:
  ----byte [] b={(byte)'\u00c4',(byte)'\u00e3'};
  ----String str=new String(b,encoding);  ----
  ----如果encoding=8859_1,会有两个字,但是encoding=gb2312只有一个字  ----
  --这个问题在处理分页是经常发生
  4.Reader,Writer/InputStream,OutputStream
  --Reader和Writer核心是char,InputStream和OutputStream核心是byte。
  --但是Reader和Writer的主要目的是要把Char读/写InputStream/OutputStream
--一个reader的例子:
--文件test.txt只有一个"你"字,0xC4,0xE3--
--String encoding=;
--InputStreamReader reader=new InputStreamReader(
----new FileInputStream("text.txt"),encoding);
--char []c=new char[10];
--int length=reader.read(c);
--for(int i=0;i<c.length;i++)
----System.out.println(c[i]);
  --如果encoding是gb2312,则只有一个字符,如果encoding=8859_1,则有两个字符
  --------
--
--
  
   ----
 2.我们要对Java的编译器有所了解:
 --javac -encoding
  我们常常没有用到ENCODING这个参数。其实Encoding这个参数对于跨平台的操作是很重要的。
  如果没有指定Encoding,则按照系统的默认Encoding,gb平台上是gb2312,英文平台上是ISO8859_1。 
 --Java的编译器实际上是调用sun.tools.javac.Main的类,对文件进行编译,这个类 --
 有compile函数中间有一个encoding的变量,-encoding的参数其实直接传给encoding变量。
 编译器就是根据这个变量来读取java文件的,然后把用UTF-8形式编译成class文件。
 一个例子:
 --public void test()
 --{
 ----String str="你";
 ----FileWriter write=new FileWriter("test.txt");
 ----write.write(str);
 ----write.close();
 --}
 ----例3
--如果用gb2312编译,你会找到E4 BD A0的字段
--
--如果用8859_1编译,
--00C4 00E3的二进制:
--00000000 11000100 00000000 11100011--
--因为每个字符都大于7位,因此用11位编码:
--11000001 10000100 11000011 10100011
--C1-- 84-- C3--  A3
--你会找到C1 84 C3 A3 --
    
  但是我们往往忽略掉这个参数,因此这样往往会有跨平台的问题:
  --  例3在中文平台上编译,生成ZhClass
  --  例3在英文平台上编译,输出EnClass
  --1.  ZhClass在中文平台上执行OK,但是在英文平台上不行
  --2.  EnClass在英文平台上执行OK,但是在中文平台上不行
  原因:
 --1.在中文平台上编译后,其实str在运行态的char[]是0x4F60, ----
 --在中文平台上运行,FileWriter的缺省编码是gb2312,因此
 --CharToByteConverter会自动用调用gb2312的converter,把str转化
 --成byte输入到FileOutputStream中,于是0xC4,0xE3放进了文件。
 --但是如果是在英文平台下,CharToByteConverter的缺省值是8859_1,
 --FileWriter会自动调用8859_1去转化str,但是他无法解释,因此他会
 --输出"?" ----
 --2. 在英文平台上编译后,其实str在运行态的char[]是0x00C4 0x00E3, ----
 --在中文平台上运行,中文无法识别,因此会出现??
 --  在英文平台上,0x00C4-->0xC4,0x00E3->0xE3,因此0xC4,0xE3被放进了
 --文件
----
1.对于JSP正文的解释:
--Tomcat首先看一下你的叶面中有没有"<%@page include的符号。有,则在相同
--地方设定response.setContentType(..);按照encoding的来读,没有他按照8859_1
--读取文件,然后用UTF-8写成.java文件,然后用sun.tools.Main去读取这个文件,
--(当然它使用UTF-8去读),然后编译成class文件
--setContentType改变的是out的属性,out变量缺省的encoding是8859_1
2.对Parameter的解释
--很不幸Parameter只有ISO8859_1的解释,这个质料可以在servlet的实现代码中找到。
3.对include的解释
格式的,但是很不幸,由于那个写"org.apache.jasper.compiler.Parser"的人
在数组JspUtil.ValidAttribute[]忘记加了一个参数:encoding,因此导致不支
持这种方式。你完全可以编译源代码,加上对encoding的支持
总结:
如果你在NT底下,最简单的方法就是欺骗java,不加任何Encoding变量:
<html>
你好<%=request.getParameter("value")%>
</html>
http://localhost/test/test.jsp?value=你
结果:你好你
但这种方法局限性较大,比如对上传的文章分段,这样的做法是死定的,最好的
解决方案是用这种方案:
<%@ page contentType="text/html;charset=gb2312" %>
<html>
你好<%=new String(request.getParameter("value").getBytes("8859_1"),"gb2312")%>
</html>

<select name="account.accountId" >
    <OPTION value="<%=account.getAccountId()%>">我的日志</OPTION>
    <OPTION value="">所有日志</OPTION>
    <OPTION <%=s%> value="<%=a.getAccountId()%>"><%=a.getAccountName()%></OPTION>
   </select>
posted @ 2006-12-31 10:13 保尔任 阅读(314) | 评论 (0)编辑 收藏
 
  J2EE学习者越来越多,J2EE本身技术不断在发展,涌现出各种概念,本文章试图从一种容易理解的角度对这些概念向初学者进行解释,以便掌握学习J2EE学习方向。
  首先我们需要知道Java和J2EE是两个不同概念,Java不只是指一种语言,已经代表与微软不同的另外一个巨大阵营,所以Java有时是指一种软件系统的流派,当然目前主要是.NET和Java两大主流体系。
  J2EE可以说指Java在数据库信息系统上实现,数据库信息系统从早期的dBase、到Delphi/VB等C/S结构,发展到B/S(Browser浏览器/Server服务器)结构,而J2EE主要是指B/S结构的实现。
  J2EE又是一种框架和标准,框架类似API、库的概念,但是要超出它们。如果需要详细了解框架,可先从设计模式开始学习。
  J2EE是一个虚的大的概念,J2EE标准主要有三种子技术标准:WEB技术、EJB技术和JMS,谈到J2EE应该说最终要落实到这三个子概念上。
  这三种技术的每个技术在应用时都涉及两个部分:容器部分和应用部分,Web容器也是指Jsp/Servlet容器,你如果要开发一个Web应用,无论是编译或运行,都必须要有Jsp/Servlet库或API支持(除了JDK/J2SE以外)。
  Web技术中除了Jsp/Servlet技术外,还需要JavaBeans或Java Class实现一些功能或者包装携带数据,所以Web技术最初裸体简称为Jsp/Servlet+JavaBeans系统。
  谈到JavaBeans技术,就涉及到组件构件技术(component),这是Java的核心基础部分,很多软件设计概念(设计模式)都是通过JavaBeans实现的。
  JavaBeans不属于J2EE概念范畴中,如果一个JavaBeans对象被Web技术(也就是Jsp/Servlet)调用,那么JavaBeans就运行在J2EE的Web容器中;如果它被EJB调用,它就运行在EJB容器中。
  EJB(企业JavaBeans)是普通JavaBeans的一种提升和规范,因为企业信息系统开发中需要一个可伸缩的性能和事务、安全机制,这样能保证企业系统平滑发展,而不是发展到一种规模重新更换一套软件系统。
  至此,JavaBeans组件发展到EJB后,并不是说以前的那种JavaBeans形式就消失了,这就自然形成了两种JavaBeans技术:EJB 和POJO,POJO完全不同于EJB概念,指的是普通JavaBeans,而且这个JavaBeans不依附某种框架,或者干脆可以说:这个 JavaBeans是你为这个应用程序单独开发创建的。
  J2EE应用系统开发工具有很多:如JBuilder、 Eclipse等,这些IDE首先是Java开发工具,也就是说,它们首要基本功能是可以开发出JavaBeans或Java class,但是如果要开发出J2EE系统,就要落实到要么是Web技术或EJB技术,那么就有可能要一些专门模块功能(如eclipse需要 lomboz插件),最重要的是,因为J2EE系统区分为容器和应用两个部分,所以,在任何开发工具中开发J2EE都需要指定J2EE容器。
  J2EE容器分为WEB容器和EJB容器,Tomcat/Resin是Web容器;JBoss是EJB容器+Web容器等,其中Web容器直接使用 Tomcat实现的。所以你开发的Web应用程序可以在上面两种容器运行,而你开发的Web+EJB应用则只可以在JBoss服务器上运行,商业产品 Websphere/Weblogic等和JBoss属于同一种性质。
  J2EE容器也称为J2EE服务器,大部分时它们概念是一致的。
  如果你的J2EE应用系统的数据库连接是通过JNDI获得,也就是说是从容器中获得,那么你的J2EE应用系统基本与数据库无关,如果你在你的J2EE 应用系统耦合了数据库JDBC驱动的配置,那么你的J2EE应用系统就有数据库概念色彩,作为一个成熟需要推广的J2EE应用系统,不推荐和具体数据库耦合,当然这其中如何保证J2EE应用系统运行性能又是体现你的设计水平了。
  衡量J2EE应用系统设计开发水平高低的标准就是:解耦性;你的应用系统各个功能是否能够彻底脱离?是否不相互依赖,也只有这样,才能体现可维护性、可拓展性的软件设计目标。
  为了达到这个目的,诞生各种框架概念,J2EE框架标准将一个系统划分为WEB和EJB主要部分,当然我们有时不是以这个具体技术区分,而是从设计上抽象为表现层、服务层和持久层,这三个层次从一个高度将J2EE分离开来,实现解耦目的。
  因此,我们实际编程中,也要将自己的功能向这三个层次上靠,做到大方向清楚,泾渭分明,但是没有技术上约束限制要做到这点是很不容易的,因此我们还是必须借助J2EE具体技术来实现,这时,你可以使用EJB规范实现服务层和持久层,Web技术实现表现层;
  EJB为什么能将服务层从Jsp/Servlet手中分离出来,因为它对JavaBeans编码有强制的约束,现在有一种对JavaBeans弱约束,使用Ioc模式实现的(当然EJB 3.0也采取这种方式),在Ioc模式诞生前,一般都是通过工厂模式来对JavaBeans约束,形成一个服务层,这也是是Jive这样开源论坛设计原理之一。
  由此,将服务层从表现层中分离出来目前有两种可选架构选择:管理普通JavaBeans(POJO)框架(如 Spring、JdonFramework)以及管理EJB的EJB框架,因为EJB不只是框架,还是标准,而标准可以扩展发展,所以,这两种区别将来是可能模糊,被纳入同一个标准了。 但是,个人认为:标准制定是为某个目的服务的,总要牺牲一些换取另外一些,所以,这两种架构会长时间并存。
  这两种架构分歧也曾经诞生一个新名词:完全POJO的系统也称为轻量级系统(lightweight),其实这个名词本身就没有一个严格定义,更多是一个吸引人的招牌,轻量是指容易学习容易使用吗?按照这个定义,其实轻量Spring等系统并不容易学习;而且EJB 3.0(依然叫EJB)以后的系统是否可称为轻量级了呢?
  前面谈了服务层框架,使用服务层框架可以将 JavaBeans从Jsp/Servlet中分离出来,而使用表现层框架则可以将Jsp中剩余的JavaBeans完全分离,这部分JavaBeans 主要负责显示相关,一般是通过标签库(taglib)实现,不同框架有不同自己的标签库,Struts是应用比较广泛的一种表现层框架。
  这样,表现层和服务层的分离是通过两种框架达到目的,剩余的就是持久层框架了,通过持久层的框架将数据库存储从服务层中分离出来是其目的,持久层框架有两种方向:直接自己编写JDBC等SQL语句(如iBatis);使用O/R Mapping技术实现的Hibernate和JDO技术;当然还有EJB中的实体Bean技术。
  持久层框架目前呈现百花齐放,各有优缺点的现状,所以正如表现层框架一样,目前没有一个框架被指定为标准框架,当然,表现层框架现在又出来了一个JSF,它代表的页面组件概念是一个新的发展方向,但是复杂的实现让人有些忘而却步。
  在所有这些J2EE技术中,虽然SUN公司发挥了很大的作用,不过总体来说:网络上有这样一个评价:SUN的理论天下无敌;SUN的产品用起来撞墙;对于初学者,特别是那些试图通过或已经通过SUN认证的初学者,赶快摆脱SUN的阴影,立即开溜,使用开源领域的产品来实现自己的应用系统。
  最后,你的J2EE应用系统如果采取上面提到的表现层、服务层和持久层的框架实现,基本你也可以在无需深刻掌握设计模式的情况下开发出一个高质量的应用系统了。
  还要注意的是: 开发出一个高质量的J2EE系统还需要正确的业务需求理解,那么域建模提供了一种比较切实可行的正确理解业务需求的方法,相关详细知识可从UML角度结合理解。
  当然,如果你想设计自己的行业框架,那么第一步从设计模式开始吧,因为设计模式提供你一个实现JavaBeans或类之间解耦参考实现方法,当你学会了系统基本单元JavaBean或类之间解耦时,那么系统模块之间的解耦你就可能掌握,进而你就可以实现行业框架的提炼了,这又是另外一个发展方向了。
  以上理念可以总结为一句话:Java学习开发三件宝: Domain Model(域建模)、Patterns(模式)和Framework(框架)。集三宝理念于一身,小中型J2EE项目快速开发工具:Jdon Framework
----------------------------------------------------------------------------------------------------
JoannaYe ask:
你好 Banq先生 关注你的文章很长一段时间了, 对你在Java领域的技术水平,以及在很多问题上的看法, 也非常佩服. 国内目前达到你的水平的人真是很少(当然高人也许都隐居起来了). 但是, 有几个问题想与你讨论:
首先,软件是一个绝对的应用技术,任何技术离开了具体的应用, 坦率地说是毫无价值的.我看,Jdon也有在这方面的尝试,如网站,网上商店生成系统等.但这与真正的企业应用还有非常大的距离. 我不了解,你在这一领域里为什么没有涉足,是因为你认为很困难,基本上是以我们国内目前的技术水平无法到达呢, 还是因为你不屑于这方面的深入, 认为你所追求的是纯粹超然的技术概念呢.
我的其他问题有赖于了解你关于这个问题的回答,让我们继续关注和讨论.
banq answer:
 
>但这与真正的企业应用还有非常大的距离. 我不了解,你在这一领域里为什么没有涉足,是因为你认为很困难,基本上是以我们国内目前的技术
多谢探讨,这个问题很复杂,大概有下列几点:
1. 现在软件技术不再象以前的技术,以前的技术可以说只有做个这个行业大型软件系统的经验的人才可以说对这些软件技术有掌握,而现在的技术则不必了,J2EE 讲究架构,J2EE它是一套应用软件的规范,也就是说,J2EE是很多做过大型软件的人进行汇总后的经验精华,一个大型系统需要哪些技术部分、什么时候适合什么技术,在J2EE标准中基本都有涉及,例如EJB技术、JMS等。
这样,如果你能完全掌握和驾驭这些J2EE架构技术,你有时确实不必一定要做个大型软件经验才型,这称为站在巨人的肩膀上。
但是反过来,如果你没有丰富的软件系统实战经验,你去理解EJB/JMS等就很困难,所以这两个技术对初学者比较难的原因之一。
2. UML结合J2EE这样OO一套实施过程从方法论以及模式角度固化了软件数据库系统的分析设计开发,这也是因为有MDA(将这些过程用软件自动生成代码)诞生的原因。虽然这些简化了我们开发系统的过程,但是这只是解决了应用系统的一部分问题,工作流等尚未成熟,使用这样方式开发系统,依据我的经验,最后会将烦琐和细致的工作压在Jsp页面上,目前开发一个系统,结合标签库和用户界面需求这个工作反而花费我更多时间,希望JSF在这方面能有效率提升,等这些技术细节都能解决,基本J2EE非常成熟了。
3.目前我通过咨询角色和一些软件公司一起承接一些企业应用项目,例如去年承接一个大型外资人事系统,他们要求管理GE 等几家外资企业的人事福利(这些企业外包人事给他们),如果专为一家公司开发人事很简单,但是要求这个人事适合多家,那么重用性要求很高,设计抽象面很高,他们在新加坡有类似系统,但技术很老,我听过新加坡的系统,他们也有一些经验总结,大部分和我的J2EE设计相吻合,我和新加坡的人交流过想法,他们很惊奇,不太相信,加上费用问题,只进行了初步架构设计就搁浅了。
4.不要小看网站系统,以前网站系统都是用PHP Perl做,功能很弱,无法和企业系统相比,但是随着Inernet普及,更多人要求联网,例如如果一家公司的ERP通过互联网实现,那么老总出差就很方便,但是现在为一家公司开发一个基于internet的ERP很贵,比传统的贵,这不合理,这也是SOA提出的目的之一,以后ERP实现网上租用,就象你申请一个Blog或论坛或Email,你可以为你的企业申请一个ERP系统,这样只要企业付租费就可以了,这可理想目前已经接近,前段时间美国一家提供这种服务的企业来上海做宣传,他们的业绩增长速度极其快 500%.
通过网站提供ERP等企业服务对于软件设计的重用性要求很高,就一套邮箱系统可以服务很多用户一样,你必须设计出一套重要性、灵活性很高的ERP系统适合不同的用户,可见网站软件的水平是极其高的。前面我做的网站自动生成系统到现在我都认为完成不够好,现在很多网站都提供这种服务,这象Blog,但是Blog等只限制你网站模板,而不是自由定制页面,所以 Blog这些都是小孩玩家家,根本无发走向商业,著名的那个方兴东鼓吹Blog,其实没有技术革新,靠你媒体吹呼就是革命了。
 
JoannaYe ask:
谢谢 Banq 先生在6月23日非常认真的回复(抱歉由于忙,没能马上回复). 总结起来, 如果我的理解不错的话, 你的结论是 1)你认为网站系统并不可小觑(同意,一个高访问量,同时能够实现网上交易的网站的确如此).2)EJB/JMS技术对于初学者来说是不容易,但是对你来说,你是可以Handdle的. 3)你也有承接企业系统的实际经验,象你说的那个HR系统. 但不知您以咨询身份参加的这个HR系统到底都解决的是实际管理中的什么样的问题?在性能方面都达到了什么样的水平? 具体来说,采用了哪些技术(诸如您帖中提到的一些技术)应对了实际中具体的什么样的问题. 此外以你在这个HR系统的经验来看, 是一个多少人的Team,采取什么样的开发方式和开发进度(人员和时间的分配比例)开发的.你认为在这样的一个项目的开发过程中最关键的是什么?最影响 Prductivity的又是什么?
对这样一些问题看上去似乎很空泛,但是实际上能够真正反映出我一开始提出的 issue,"软件是一个绝对的应用技术,任何技术离开了具体的应用, 坦率地说是毫无价值的".举个例子来说,书本上,名家们会告诉你, Value List Handler 这个设计模式是解决这样的问题:"You have a remote client that wants to iterate over a large results list." 一般来说,如果是一个大量地查找某一些"topic/dimension"下的数据,这样的问题,我们也毫无疑问地确定要用到这个模式.但是,如果对一条具体的数据,如某一个销售员,要和他的客户讨论(在线谈判)他们之间的一个具体合同,这时候会不会也需要用到这个模式.如果要用这个模式,到底是用 Stateful Session Bean 还是用 Stateless Session Bean 实现呢,他们各自在实现方法上对性能的影响是什么, 当你决定采用了某种实现方法,你到底是怎样Tradeoff的呢; 最后对这个设计模式来说,在最终的设计方案中如何把它抽象到对一个通用的,普遍的业务问题,而不是仅仅对"某一个销售员,要和他的客户讨论他们之间的一个具体合同"这样的一个特例问题,作出一个通用的解决方案,适应任意规模,任意业务的企业,真正达到软件工程的目标:高度的Reusing 和 Scalablity. 实际的企业应用系统就是充满着类似这样的问题,很有挑战.但有些技术人员就仅仅满足于自己可以用某项技术做出一些小的Demo了,就不愿意,或根本不屑于深入下去面对一个实际的应用问题.
因此, 我相信您应该能够非常理解,我为什么感兴趣了解您对我上面提出问题答案.
您的很多看法都很不错, 我非常同意, 希望我们能在今后进一步深入地探讨. 谢谢!
banq answer:
>你认为在这样的一个项目的开发过程中最关键的是什么?最影响 Prductivity的又是什么?
当这样的项目使用框架组件组合后,由于系统重要重用的功能已经封装在框架软件中,所以,只要能够组装出应用系统,一般第一次测试就会立即通过,我已经不止一次体会这种快感,我现在基本告别以前那种花费大量时间在Java调试上时代,我相信很多初学者还在这个泥潭里挣扎,这就成为影响一个产品的主要原因,现在使用jdon框架开发,几乎消灭这个因素。
那么,现在最影响 Prductivity的是什么?就是技术外的因素:项目管理。
关于你提的性能方面设计达到什么水平等,这些我已经整合进入Jdon框架,使用Jdon框架开发,几乎无需考虑性能设计,一开始就具有优越的性能,这些都是有测试数据,Java产品的好处就是一切可以自己动手,不必听从第三方评价,因为那些都有失公正,服务器配置上Jprofile/Optimizeit,客户端配置Jmeter,启动几个线程一跑,Jdon框架和应用程序的性能真相就出来了,所以,在Java领域,开源和商业产品是在同一起跑线,面对不同的用户:前者是更有头脑,自己动手;后者是对自己缺乏自信的人;服务是两者的重点。
>在最终的设计方案中如何把它抽象到对一个通用的,普遍的业务问题,而不>是仅仅对"某一个销售员,要和他的客户讨论他们之间的一个具体合同"这>样的一个特例问题
其实你说的行业框架提炼的问题,这和业务相关,Jdon框架等都是基础框架,没有这些组件框架的优雅解决方式,就没有行业框架的好的开始,我想你不希望在行业框架提炼之后,发现无法加入一些纵向功能,就象数据库设计好之后,几年以后却成为你发展的障碍。
行业框架需要资深的行业背景,这也不是一般人做的,但是工作流/Portal等都是行业框架的提炼,这些也是我们以后发展的方向。
就我个人来说,我愿意解决重要问题,然后我告诉更多人解决方向,如果他们相信,大家一起努力来解决所有问题,而不是靠我一个人解决所有问题。
JoannaYe ask:
谢谢Banq先生的回复, 你的很多观点都很好,我非常同意.象你所说最影响Prductivity的是技术外的因素:项目管理. 但我不知你能不能有一些具体的看法.因为任何行业,最终的问题, 竞争力的问题都是如何通过管理来提高Prductivity. 不知你对软件这一行业有没有特别的见解.
开源项目的确有它的优势,特别是作这些开源项目的人,往往是一些技术的精英.但是, 我还是以为应该以成熟的Commercial产品作为自己开发的基础,即所谓"巨人的肩膀". 这是因为, 成功的Commercial产品往往更注重最终用户, 这是这些产品能够给它的公司带来巨大的商业利润的源泉.纯技术的专家往往会忽视这一点.
要成就一件事(一个大型企业管理应用的项目), 是需要很多人踏踏实实,坚持不懈(这也非常重要)的努力.这和去上上课,或者在场外指导一下,有很大的区别.
我希望通过你这个论坛, 结识一些志同道合的朋友, 能够作成这样一件事.再次谢谢你的回复, 我因为很多时候很忙,有一些Deadline非常紧的事情,有时没能马上给您回复, 请你见谅.
banq answer:
非常感谢JoannaYe 讨论,从言论中感觉你是一个职业的项目经理。非常专业。
项目经理和设计师良好沟通和理解交流,是一个项目成功的关键。
关于开源和Commercial区别,我个人觉得它们之间真的没有严格的区别,只不过是两种思路的表现:开源通过免费产品卖服务;Commercial是既想卖产品又卖服务,不能因为它的产品卖钱,就是技术好,一般是市场品牌好。
就拿EJB实现来说,在所有J2EE服务器中只有开源JBoss 4.0使用AOP实现,坚持AOP的一些纯设计派认为EJB过时了,那么Weblogic /Websphere等这些以支持EJB自诩的服务器产品反而不如开源产品呢。这些人认为:EJB
但是正如你说:为什么客户还是购买Websphere等服务器,因为它们注重最终用户。
我认为一直试图在这两者之间寻找平衡是挑战的事情。
-------------------------------------------------------------------------------------------------------------------
 
shuiwx ask:
 
banq老师好,最近大致抽象总结出了一个比较浅显的规律,既是您平均一两个月能够发一篇比较的适合初学者的帖子,但每一篇都可以对偶的有关知识的梳理和导向能够起到很重要的作用,不敢说终生受用但也似乎会持久难忘了,在此还是要道一声感谢。
既然题目是初学者...高质量的J2EE系统,那么就题目本身这个用例来说,参与者该是“novice”了,领域模型应该是"高质量的"+"J2EE系统 ",那么能否请您再深一步的举个样例来说明何为"high quality j2ee system"呢?估计您不会选petshop,但有可能会将jive和jdon算进来,但偶真正想看到的是一个就您个人来讲曾经有过 consultant经验的项目作为例子来简要阐述下高质量+j2ee系统的概貌,或者象您前面某篇论oo和数据库的矛盾的文章一样,能否前瞻性的给出一个在您心目中最理想的高质量j2ee系统的轮廓呢?比如jsf(new version>1.1)+ejb3.0+j2ee设计模式?偶觉得struts+spring+hibernate并不敢称为高质量的或是 j2ee系统,所以总觉得从现在开始既该有意识的用一下jsf+ejb3来设计了,但由于不知道有没有人在这方面开始吃螃蟹了,所以只好去随大流的关心些什么ajax,xp之类的流行名词了。但从内心来讲,无论是javascript还是组件式编程,无论是spring+hibernate还是ejb3, 无论是xp还是fdd,无非是想尽量按照客户的要求迅速提交一个界面新颖,结构稳定的一个能够跨平台的良好的系统吧。假如能预知何为一个好的系统的话,似乎事情会变的简单些,也就不必为那些喋喋不休的争论着技术名词的人们所困扰了。
但由于目前偶的能力所限和所处的时期的特殊性,似乎想马上就拿jsf+ejb3来首选做企业级开发还有点不现实,那么作为一个apprentice来说,能够做的似乎只有学习模式了,偶不知道关于模式该学到多深才合适,只相信尽量选择从建模的时候就配合着设计模式来考虑可能会有助于系统的稳定和重用,谈到这里有引申出关于题目的另外一个话题,就是 “初学者”,偶觉得如果想作为计算机编程人员的话,面对着不停的新技术名词和版本更迭,似乎偶总要做一名初学着来的说,于是最近有意识的在看一些数据结构方面的课程,希望能够从一些理论基础中来寻找那些所谓的新技术背后所蕴涵的知识,但还是那句话,由于能力有限,所得甚浅,所以希望您如果能站在一个咨询家的角度来看,能否指点一下,就您认为的如果想设计一个好的软件系统来说,或许不仅限于j2ee,该多看看哪些computer science中的理论知识呢?偶不知道这个问题提的对不对,但总觉得设计模式对于系统的意义,是类似于数据结构和算法之相对于程序的意义的,所以假如您在类似的方面能有些心得的话,希望能够得到一点指点。
(偶的废话似乎不少,希望banq老师能忍受)
 
banq answer:
很抱歉现在才回复你的问题:
>如果想设计一个好的软件系统来说,或许不仅限于j2ee,该多看看哪些>computer science中的理论知识
设计一个好的软件系统我文章里其实写了,掌握分层解耦宗旨,学习使用一些现成的框架就可以了,如果你不原意囫囵吞枣,那么研究一下这些框架设计原理和模式,这些会花费你很长探索,数据结构、编译原理这些已经成为底层机制,就象汇编是底层一样,现在的大学计算机教学完全是错误的,学习的都是正确的废话。所以没有必要在那些大学课程上浪费时间。
增强项目经验,研读源码,自己动手编写项目是提升水平的唯一道路。
以上只是我个人意见。
posted @ 2006-12-31 10:11 保尔任 阅读(428) | 评论 (0)编辑 收藏
 

作者:郎云鹏(dev2dev ID: hippiewolf)

摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

目录:
一、术语session
二、HTTP协议与状态保持
三、理解cookie机制
四、理解session机制
五、理解javax.servlet.http.HttpSession
六、HttpSession常见问题
七、跨应用程序的session共享
八、总结
参考文档

一、术语session
在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。

session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 session。有时候我们可以看到这样的话“在一个浏览器会话期间,...”,这里的会话一词用的就是其本义,是指从一个浏览器窗口打开到关闭这个期间 ①。最混乱的是“用户(客户端)在一次会话期间”这样一句话,它可能指用户的一系列动作(一般情况下是同某个具体目的相关的一系列动作,比如从登录到选购商品到结账登出这样一个网上购物的过程,有时候也被称为一个transaction),然而有时候也可能仅仅是指一次连接,也有可能是指含义①,其中的差别只能靠上下文来推断②。

然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义,“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者“一个POP3 session”③。

而到了web服务器蓬勃发展的时代,session在web开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案 ④。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的 javax.servlet.http.HttpSession简称为session⑥。

鉴于这种混乱已不可改变,本文中session一词的运用也会根据上下文有不同的含义,请大家注意分辨。
在本文中,使用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表达含义⑤,使用具体的“HttpSession”来表达含义⑥

二、HTTP协议与状态保持
HTTP协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。

然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使 HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。

让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:
1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。
3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

三、理解cookie机制
cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。

正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如Javascript或者VBscript也可以生成cookie。

而cookie 的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家店的会员卡。

cookie的内容主要包括:名字,值,过期时间,路径和域。
其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。
路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。
路径与域合在一起就构成了cookie的作用范围。
如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的 cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。

存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于 IE,在一个打开的窗口上按Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存 cookie;对于Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。

下面就是一个goolge设置cookie的响应头的例子
HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html


这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分


浏览器在再次访问goolge的资源时自动向外发送cookie


使用Firefox可以很容易的观察现有的cookie的值
使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。


IE也可以设置在接受cookie前询问


这是一个询问接受cookie的对话框。

四、理解session机制
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于 SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。

由于 cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx; jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。
为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
<form name="testform" action="/xxx">
<input type="text">
</form>
在被传递给客户端之前将被改写成
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>
这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。
实际上这种技术可以简单的用对action应用URL重写来代替。

在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的 HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。

恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。

五、理解javax.servlet.http.HttpSession
HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。

首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域,cookie的生存时间等。

一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用,Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。

复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。

cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。

cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。

关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

六、HttpSession常见问题
(在本小节中session的含义为⑤和⑥的混合)


1、session在何时被创建
一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用 HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <%@page session="false"%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。

由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

2、session何时被删除
综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)

3、如何做到在浏览器关闭时删除session
严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

4、有个HttpSessionListener是怎么回事
你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

5、存放在session中的对象必须是可序列化的吗
不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在 Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

6、如何才能正确的应付客户端禁止cookie的可能性
对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。

8、如何防止用户打开两个浏览器窗口操作导致的session混乱
这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。

9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。

10、为什么session不见了
排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。
出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。

七、跨应用程序的session共享

常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。

然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。

首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从Tomcat设置的cookie路径来看,它对不同的应用程序设置的 cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。

根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。

笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。

iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。
<session-info>
<path>/NASApp</path>
</session-info>

需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。


在Tomcat 中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段。

我们再看一下Weblogic Server是如何处理session的。

从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下

对于这样一种结构,在session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext 中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,

应用程序A
context.setAttribute("appA", session);

应用程序B
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");

值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。

那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为/appA访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。

八、总结
session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。

关于作者:
郎云鹏(dev2dev ID: hippiewolf),软件工程师,从事J2EE开发
电子邮件:langyunpeng@yahoo.com.cn
地址:大连软件园路31号科技大厦A座大连博涵咨询服务有限公司

参考文档:
[1] Preliminary Specification http://wp.netscape.com/newsref/std/cookie_spec.html
[2] RFC2109 http://www.rfc-editor.org/rfc/rfc2109.txt
[3] RFC2965 http://www.rfc-editor.org/rfc/rfc2965.txt
[4] The Unofficial Cookie FAQ http://www.cookiecentral.com/faq/
[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
[6] http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
[7] RFC2616 http://www.rfc-editor.org/rfc/rfc2616.txt

代码下载sampleApp.zip

posted @ 2006-12-31 10:10 保尔任 阅读(304) | 评论 (0)编辑 收藏

早在Java 1.2推出之时,Java平台中就引入了一个新的支持:java.lang.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会为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有必要对该变量

posted @ 2006-12-27 21:37 保尔任 阅读(331) | 评论 (0)编辑 收藏
 

第一,文件的的编码方式其实就包括两方面:存和取,存文件必须以一种编码存;读文件也必须以一种编码读。如果存取按照相同的编码方式,则不会有问题,关键就是很多时候存取的方式不一致,产生乱码。,如不特别设置取系统默认的编码,中文windows为GBK编码。

从.java->.class过程是,先编写.java文件并按莫种编码方式保存,然后用javac方法编译此文件,注意如.java没按系统默认编码保存则要带encoding参数指明实际编码,否则出错,生成的.class文件存为系统默认编码。

从.jsp->.java->.class,先存为某种编码的.jsp文件,然后tomcat根据pageEncoding读取并转化为servlet存为系统默认编码,然后同上面.java->.class过程。

第二,IDE的encoding为对系统下文件打开的解码方式或保存的编码方式。特例:如果.jsp文件有<%@ page language="java" pageEncoding="UTF-8"%>,则eclipse会自动存为UTF-8方式,不管eclipse的encoding是什么,这也是 eclipse的聪明之处。

第三,
pageEncoding="UTF-8"表示此文件的编码方式,必须与此文件存储方式一致(所以eclipse会首选根据它来存文件),tomcat根据这个来读此.jsp文件并编译为servlet(至于编译成的.java和.class文件应该为tomcat服务器默认编码)。
contentType="text/html;charset=UTF-8"表示当服务器给浏览器传页面文件时编码方式为UTF-8,形式为HTML。例如:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ page contentType="text/html;charset=GBK"%>
<html>
 <head>
  <title>test</title>
 </head>
 <body>
  我是个好人
 </body>
</html>

表示本jsp文件存为UTF-8字符集,当浏览器打开此页面后,查看原码就会发现源码为GBK字符集。

第四,
request.setCharacterEncoding("UTF-8")是把提交内容的字符集设为UTF-8
response.setCharacterEncoding("UTF-8")可以把页面中的<%@ page contentType="text/html;charset=iso8859-1"%>换为charset=UTF-8,是给告诉浏览器我这个文件的编码方式。

第五,表单提交:无论何种表单提交都可以在后台的java文件中通过String des = new String(s.getBytes("iso8859-1"),"UTF-8");来转换成你想要的UTF-8编码方式。但如果每处都加词句太麻烦,故分post和get两种方式区分提交(tomcat5以后分开处理,之前处理方式一样,即都可以用 request.setCharacterEncoding("UTF-8")方法处理,不过tomcat5以后get提交方法用此语句无效)。
1,post提交的数据:
程序加上org.springframework.web.filter.CharacterEncodingFilter过滤器.
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>

因为规范要求浏览器提交数据都要用utf8编码,所以这里设置编码方式为UTF8.

特别注意:
a,这个过滤器只是简单的调用:request.setCharacterEncoding(this.encoding);
在这个语句之前不能调用任何的request.getParameter()方法,否则会设置tomcat的缺省字符集为"ISO-8859-1",并且使 setCharacterEncoding的调用失效.所以在这个过滤器之前的过滤器中不能有对getParameter这类方法的调用,比较安全的做法就是把这个过滤器尽量靠前放.
b,在server.xml中不能加上<Valve className="org.apache.catalina.valves.RequestDumperValve"/>
这个value也设置tomcat的缺省字符集为"ISO-8859-1",使setCharacterEncoding的调用失效.可能其他的value也有这个问题,我没有测试过.
如果要观察http请求参数,可以考虑用过滤器或者其他工具,例如ethereal(http://www.ethereal.com/)

2,get提交的数据:
两种情况:
a,如果从地址栏直接输入汉字,则一般编码为"GBK",需要用
new String(request.getParameter("something").getBytes("ISO-8859-1"),"GBK")
取出
b,如果是页面超连接连接中带的汉字,则编码根据页面编码的不同而不同,如果页面的
content="text/html; charset=utf-8",则在tomcat/conf/server.xml中的配置文件中:
<!-- Define a non-SSL Coyote HTTP/1.1 Connector on port 8080 -->
<Connector port="8080"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000" useBodyEncodingForURI="true"
disableUploadTimeout="true" />

加上:useBodyEncodingForURI="true"即可正常使用getParameter取出正确内容.
如果content="text/html; charset=GBK",需用
new String(request.getParameter("something").getBytes("ISO-8859-1"),"GBK")
取出,其他情况类似.

总结:
1,所有页面使用utf8编码,
2,服务器加上过滤器,
3,server.xml中不要使用
<Valve className="org.apache.catalina.valves.RequestDumperValve"/>
4,server.xml文件加上useBodyEncodingForURI="true"
这样应该可以搞定大多数前台的中文问题.至于地址栏输入中文,不支持也罢,一般的程序很少要求
从这里输入.

第六,连接数据库
 

1、mysql配置文件:
修改mysql在windows\my.ini里default-character-set=utf-8

2、mysql里数据库和表也都设为utf8_unicode_ci

3、数据库连结:jdbc:mysql://localhost/mydb?useUnicode=true&characterEncoding=utf-8
注意,关键就在于此:此句中间是'&'不是'&amp;'这是因为数据库连结时,在.jsp和.java文件中应该用&号,而XML文件中需要用&amp

posted @ 2006-12-26 10:52 保尔任 阅读(333) | 评论 (0)编辑 收藏
  

Java基础方面:

1、作用域public,private,protected,以及不写时的区别
答:区别如下:
作用域           当前类       同一package  子孙类       其他package
public            √              √                  √             √
protected        √              √                  √             ×
friendly          √              √                   ×            ×
private           √              ×                   ×            ×
不写时默认为friendly

2、ArrayList和Vector的区别,HashMap和Hashtable的区别
答:
就ArrayList与Vector主要从二方面来说.
一.同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的
二.数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
就HashMap与HashTable主要从三方面来说。
一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现
二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的
三.值:只有HashMap可以让你将空值作为一个表的条目的key或value

3、char型变量中能不能存贮一个中文汉字?为什么?
答:
是能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的

4、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?
答:
多线程有两种实现方法,分别是继承Thread类与实现Runnable接口
同步的实现方面有两种,分别是synchronized,wait与notify

5、继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么?
答:
父类:
package test;
public class  FatherClass
{
    public FatherClass()
 {
  System.out.println("FatherClass Create");
 }
}
子类:
package test;
import test.FatherClass;
public class  ChildClass extends FatherClass
{
 public ChildClass()
 {
  System.out.println("ChildClass Create");
 }
 public static void main(String[] args)
 {
  FatherClass fc = new FatherClass();
  ChildClass cc = new ChildClass();
 }
}
输出结果:
C:\>java test.ChildClass
FatherClass Create
FatherClass Create
ChildClass Create

6、内部类的实现方式?
答:
示例代码如下:
package test;
public class  OuterClass
{
 private class InterClass
 {
  public InterClass()
  {
   System.out.println("InterClass Create");
  }
 }
 public OuterClass()
 {
  InterClass ic = new InterClass();
  System.out.println("OuterClass Create");
 }
 public static void main(String[] args)
 {
  OuterClass oc = new OuterClass();
 }
}
输出结果:
C:\>java test/OuterClass
InterClass Create
OuterClass Create
再一个例题:
public class OuterClass {
  private double d1 = 1.0;
    //insert code here
}
You need to insert an inner class declaration at line 3. Which two inner class declarations are

valid?(Choose two.)
A. class InnerOne{
     public static double methoda() {return d1;}
   }
B. public class InnerOne{
     static double methoda() {return d1;}
   }
C. private class InnerOne{
     double methoda() {return d1;}
   }
D. static class InnerOne{
     protected double methoda() {return d1;}
   }
E. abstract class InnerOne{
     public abstract double methoda();
   }
说明如下:
一.静态内部类可以有静态成员,而非静态内部类则不能有静态成员。 故 A、B 错
二.静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;return d1 出错。

故 D 错
三.非静态内部类的非静态成员可以访问外部类的非静态变量。 故 C 正确
四.答案为C、E

7、垃圾回收机制,如何优化程序?
希望大家补上,谢谢

8、float型float f=3.4是否正确?
答:
不正确。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4

9、介绍JAVA中的Collection FrameWork(包括如何写自己的数据结构)?
答:
Collection FrameWork如下:
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)
Map提供key到value的映射

10、Java中异常处理机制,事件机制?

11、JAVA中的多形与继承?
希望大家补上,谢谢

12、抽象类与接口?
答:
抽象类与接口都用于抽象,但是抽象类(JAVA中)可以有自己的部分实现,而接口则完全是一个标识(同时有多重继承的功能)。

13、Java 的通信编程,编程题(或问答),用JAVA SOCKET编程,读服务器几个字符,再写入本地显示?
答:
Server端程序:
package test;
import java.net.*;
import java.io.*;

public class Server
{
 private ServerSocket ss;
 private Socket socket;
 private BufferedReader in;
 private PrintWriter out;
 public Server()
 {
  try
  {
   ss=new ServerSocket(10000);
   while(true)
   {
    socket = ss.accept();
    String RemoteIP = socket.getInetAddress().getHostAddress();
    String RemotePort = ":"+socket.getLocalPort();
    System.out.println("A client come in!IP:"+RemoteIP+RemotePort);
    in = new BufferedReader(new

InputStreamReader(socket.getInputStream()));
    String line = in.readLine();
    System.out.println("Cleint send is :" + line);
    out = new PrintWriter(socket.getOutputStream(),true);
    out.println("Your Message Received!");
    out.close();
    in.close();
    socket.close();
   }
  }catch (IOException e)
  {
   out.println("wrong");
  }
 }
 public static void main(String[] args)
 {
  new Server();
 }
};
Client端程序:
package test;
import java.io.*;
import java.net.*;

public class Client
{
 Socket socket;
 BufferedReader in;
 PrintWriter out;
 public Client()
 {
  try
  {
   System.out.println("Try to Connect to 127.0.0.1:10000");
   socket = new Socket("127.0.0.1",10000);
   System.out.println("The Server Connected!");
   System.out.println("Please enter some Character:");
   BufferedReader line = new BufferedReader(new

InputStreamReader(System.in));
   out = new PrintWriter(socket.getOutputStream(),true);
   out.println(line.readLine());
   in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   System.out.println(in.readLine());
   out.close();
   in.close();
   socket.close();
  }catch(IOException e)
  {
   out.println("Wrong");
  }
 }
 public static void main(String[] args)
 {
  new Client();
 }
};

14、用JAVA实现一种排序,JAVA类实现序列化的方法(二种)? 如在COLLECTION框架中,实现比较要实现什么样的接口?
答:
用插入法进行排序代码如下
package test;
import java.util.*;
class  InsertSort
{
 ArrayList al;
 public InsertSort(int num,int mod)
 {
  al = new ArrayList(num);
  Random rand = new Random();
  System.out.println("The ArrayList Sort Before:");
  for (int i=0;i<num ;i++ )
  {
   al.add(new Integer(Math.abs(rand.nextInt()) % mod + 1));
   System.out.println("al["+i+"]="+al.get(i));
  }
 }
 public void SortIt()
 {
  Integer tempInt;
  int MaxSize=1;
  for(int i=1;i<al.size();i++)
  {
       tempInt = (Integer)al.remove(i);
    if(tempInt.intValue()>=((Integer)al.get(MaxSize-1)).intValue())
    {
     al.add(MaxSize,tempInt);
     MaxSize++;
     System.out.println(al.toString());
    } else {
     for (int j=0;j<MaxSize ;j++ )
     {
      if

(((Integer)al.get(j)).intValue()>=tempInt.intValue())
      {
       al.add(j,tempInt);
       MaxSize++;
       System.out.println(al.toString());
       break;
      }
     }
    }
  }
  System.out.println("The ArrayList Sort After:");
  for(int i=0;i<al.size();i++)
  {
   System.out.println("al["+i+"]="+al.get(i));
  }
 }
 public static void main(String[] args)
 {
  InsertSort is = new InsertSort(10,100);
  is.SortIt();
 }
}
JAVA类实现序例化的方法是实现java.io.Serializable接口
Collection框架中实现比较要实现Comparable 接口和 Comparator 接口

15、编程:编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。
答:
代码如下:
package test;

class  SplitString
{
 String SplitStr;
 int SplitByte;
 public SplitString(String str,int bytes)
 {
  SplitStr=str;
  SplitByte=bytes;
  System.out.println("The String is:'"+SplitStr+"';SplitBytes="+SplitByte);
 }
 public void SplitIt()
 {
  int loopCount;
  

loopCount=(SplitStr.length()%SplitByte==0)?(SplitStr.length()/SplitByte):(SplitStr.length()/Split

Byte+1);
  System.out.println("Will Split into "+loopCount);
  for (int i=1;i<=loopCount ;i++ )
  {
   if (i==loopCount){
    

System.out.println(SplitStr.substring((i-1)*SplitByte,SplitStr.length()));
   } else {
    

System.out.println(SplitStr.substring((i-1)*SplitByte,(i*SplitByte)));
   }
  }
 }
 public static void main(String[] args)
 {
  SplitString ss = new SplitString("test中dd文dsaf中男大3443n中国43中国人

0ewldfls=103",4);
  ss.SplitIt();
 }
}

16、JAVA多线程编程。 用JAVA写一个多线程程序,如写四个线程,二个加1,二个对一个变量减一,输出。
希望大家补上,谢谢

17、STRING与STRINGBUFFER的区别。
答:
STRING的长度是不可变的,STRINGBUFFER的长度是可变的。如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法

Jsp方面

1、jsp有哪些内置对象?作用分别是什么?
答:
JSP共有以下9种基本内置组件(可与ASP的6种内部组件相对应):
 request 用户端请求,此请求会包含来自GET/POST请求的参数
   response 网页传回用户端的回应
   pageContext 网页的属性是在这里管理
   session 与请求有关的会话期
   application servlet 正在执行的内容
   out 用来传送回应的输出
   config servlet的构架部件
   page JSP网页本身
   exception 针对错误网页,未捕捉的例外

2、jsp有哪些动作?作用分别是什么?
答:
JSP共有以下6种基本动作
   jsp:include:在页面被请求的时候引入一个文件。
   jsp:useBean:寻找或者实例化一个JavaBean。
   jsp:setProperty:设置JavaBean的属性。
   jsp:getProperty:输出某个JavaBean的属性。
   jsp:forward:把请求转到一个新的页面。
   jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记

3、JSP中动态INCLUDE与静态INCLUDE的区别?
答:
动态INCLUDE用jsp:include动作实现
   <jsp:include page="included.jsp" flush="true" />它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数
   静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面
   <%@ include file="included.htm" %>

4、两种跳转方式分别是什么?有什么区别?
答:
有两种,分别为:
  <jsp:include page="included.jsp" flush="true">
  <jsp:forward page= "nextpage.jsp"/>
  前者页面不会转向include所指的页面,只是显示该页的结果,主页面还是原来的页面。执行完后还会回来,相当于函数调用。并且可以带参数.后者完全转向新页面,不会再回来。相当于go to 语句。

Servlet方面

1、说一说Servlet的生命周期?
答:
servlet有良好的生存期的定义,包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。

2、Servlet版本间(忘了问的是哪两个版本了)的不同?
希望大家补上,谢谢

3、JAVA SERVLET API中forward() 与redirect()的区别?
答:
前者仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;后者则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。所以,前者更加高效,在前者可以满足需要时,尽量使用forward()方法,并且,这样也有助于隐藏实际的链接。在有些情况下,比如,需要跳转到一个其它服务器上的资源,则必须使用sendRedirect()方法。

4、Servlet的基本架构
public class ServletName extends HttpServlet {
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws
      ServletException, IOException  {
      }
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws
      ServletException, IOException  {
      }
}

Jdbc、Jdo方面

1、可能会让你写一段Jdbc连Oracle的程序,并实现数据查询.
答:
程序如下:
package hello.ant;
import java.sql.*;
public class  jdbc
{
 String dbUrl="jdbc:oracle:thin:@127.0.0.1:1521:orcl";
 String theUser="admin";
 String thePw="manager";
 Connection c=null;
 Statement conn;
 ResultSet rs=null;
 public jdbc()
 {
  try{
    Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
          c = DriverManager.getConnection(dbUrl,theUser,thePw);
    conn=c.createStatement();
  }catch(Exception e){
   e.printStackTrace();
  }
 }
 public boolean executeUpdate(String sql)
 {
   try
   {
     conn.executeUpdate(sql);
     return true;
   }
   catch (SQLException e)
   {
     e.printStackTrace();
     return false;
   }
 }
 public ResultSet executeQuery(String sql)
 {
   rs=null;
   try
   {
     rs=conn.executeQuery(sql);
   }
   catch (SQLException e)
   {
     e.printStackTrace();
   }
   return rs;
 }
 public void close()
 {
   try
   {
     conn.close();
     c.close();
   }
   catch (Exception e)
   {
     e.printStackTrace();
   }
 }
 public static void main(String[] args)
 {
  ResultSet rs;
  jdbc conn = new jdbc();
  rs=conn.executeQuery("select * from test");
  try{
  while (rs.next())
  {
   System.out.println(rs.getString("id"));
   System.out.println(rs.getString("name"));
  }
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
}

2、Class.forName的作用?为什么要用?
答:
调用该访问返回一个以字符串指定类名的类的对象。

3、Jdo是什么?
答:
JDO 是Java对象持久化的新的规范,为java data object的简称,也是一个用于存取某种数据仓库中的对象的标准化API。JDO提供了透明的对象存储,因此对开发人员来说,存储数据对象完全不需要额外的代码(如JDBC API的使用)。这些繁琐的例行工作已经转移到JDO产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,JDO很灵活,因为它可以在任何数据底层上运行。JDBC只是面向关系数据库(RDBMS)JDO更通用,提供到任何数据底层的存储功能,比如关系数据库、文件、XML以及对象数据库(ODBMS)等等,使得应用可移植性更强。

4、在ORACLE大数据量下的分页解决方法。一般用截取ID方法,还有是三层嵌套方法。
答:
一种分页方法
<%
  int i=1;
  int numPages=14;
  String pages = request.getParameter("page") ;
  int currentPage = 1;
  currentPage=(pages==null)?(1):{Integer.parseInt(pages)}
  sql = "select count(*) from tables";
  ResultSet rs = DBLink.executeQuery(sql) ;
  while(rs.next()) i = rs.getInt(1) ;
  int intPageCount=1;
  intPageCount=(i%numPages==0)?(i/numPages):(i/numPages+1);
  int nextPage ;
  int upPage;
  nextPage = currentPage+1;
  if (nextPage>=intPageCount) nextPage=intPageCount;
  upPage = currentPage-1;
  if (upPage<=1) upPage=1;
  rs.close();
  sql="select * from tables";
  rs=DBLink.executeQuery(sql);
  i=0;
  while((i<numPages*(currentPage-1))&&rs.next()){i++;}
%>
//输出内容
//输出翻页连接
合计:<%=currentPage%>/<%=intPageCount%><a href="List.jsp?page=1">第一页</a><a

href="List.jsp?page=<%=upPage%>">上一页</a>
<%
  for(int j=1;j<=intPageCount;j++){
  if(currentPage!=j){
%>
  <a href="list.jsp?page=<%=j%>">[<%=j%>]</a>
<%
  }else{
  out.println(j);
  }
  }
%>
<a href="List.jsp?page=<%=nextPage%>">下一页</a><a href="List.jsp?page=<%=intPageCount%>">最后页

</a>


Xml方面

1、xml有哪些解析技术?区别是什么?
答:
有DOM,SAX,STAX等
DOM: 处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存, 适合对 XML的随机访问SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问
STAX:Streaming API for XML (StAX)

2、你在项目中用到了xml技术的哪些方面?如何实现的?
答:
用到了数据存贮,信息配置两方面。在做数据交换平台时,将不能数据源的数据组装成XML文件,然后将XML文件压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再同XML文件中还原相关信息进行处理。在做软件配置时,利用XML可以很方便的进行,软件的各种配置参数都存贮在XML文件中。

3、用jdom解析xml文件时如何解决中文问题?如何解析?
答:
看如下代码,用编码方式加以解决
package test;
import java.io.*;
public class DOMTest
{
 private String inFile = "c:\\people.xml";
 private String outFile = "c:\\people.xml"; 
 public static void main(String args[])
 {
     new DOMTest();
    }
 public DOMTest()
 {
  try
     { 
      javax.xml.parsers.DocumentBuilder builder =
       

javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder();
      org.w3c.dom.Document doc = builder.newDocument();
      org.w3c.dom.Element root = doc.createElement("老师");
      org.w3c.dom.Element wang = doc.createElement("王");
   org.w3c.dom.Element liu = doc.createElement("刘");
      wang.appendChild(doc.createTextNode("我是王老师"));
      root.appendChild(wang);
      doc.appendChild(root);
      javax.xml.transform.Transformer transformer =
       javax.xml.transform.TransformerFactory.newInstance().newTransformer();
      transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "gb2312");
      transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");  

   
      transformer.transform(new javax.xml.transform.dom.DOMSource(doc),
            new

javax.xml.transform.stream.StreamResult(outFile));
     }
     catch (Exception e)
     {
      System.out.println (e.getMessage());
     }
    }
}

4、编程用JAVA解析XML的方式.
答:
用SAX方式解析XML,XML文件如下:
<?xml version="1.0" encoding="gb2312"?>
<person>
  <name>王小明</name>
  <college>信息学院</college>  
  <telephone>6258113</telephone>
  <notes>男,1955年生,博士,95年调入海南大学</notes>
 </person>
 事件回调类SAXHandler.java
 import java.io.*;
import java.util.Hashtable;
import org.xml.sax.*;
public class SAXHandler extends HandlerBase
  {
  private Hashtable table = new Hashtable();
  private String currentElement = null;
  private String currentValue = null;
  public void setTable(Hashtable table)
    {
    this.table = table;
    }
  public Hashtable getTable()
    {
    return table;
    }
  public void startElement(String tag, AttributeList attrs)
  throws SAXException
    {
    currentElement = tag;
    }
  public void characters(char[] ch, int start, int length)
  throws SAXException
    {
    currentValue = new String(ch, start, length);
    }
  public void endElement(String name) throws SAXException
    {
    if (currentElement.equals(name))
      table.put(currentElement, currentValue);
    }
  }
JSP内容显示源码,SaxXml.jsp:
<HTML>
<HEAD>
<TITLE>剖析XML文件people.xml</TITLE>
</HEAD>
<BODY>
<%@ page errorPage="ErrPage.jsp"
contentType="text/html;charset=GB2312" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Hashtable" %>
<%@ page import="org.w3c.dom.*" %>
<%@ page import="org.xml.sax.*" %>
<%@ page import="javax.xml.parsers.SAXParserFactory" %>
<%@ page import="javax.xml.parsers.SAXParser" %>
<%@ page import="SAXHandler" %>
<%
File file = new File("c:\\people.xml");
FileReader reader = new FileReader(file);
Parser parser;
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
SAXHandler handler = new SAXHandler();
sp.parse(new InputSource(reader), handler);
Hashtable hashTable = handler.getTable();
out.println("<TABLE BORDER=2><CAPTION>教师信息表</CAPTION>");
out.println("<TR><TD>姓名</TD>" + "<TD>" +
  (String)hashTable.get(new String("name")) + "</TD></TR>");
out.println("<TR><TD>学院</TD>" + "<TD>" +
  (String)hashTable.get(new String("college"))+"</TD></TR>");
out.println("<TR><TD>电话</TD>" + "<TD>" +
  (String)hashTable.get(new String("telephone")) + "</TD></TR>");
out.println("<TR><TD>备注</TD>" + "<TD>" +
  (String)hashTable.get(new String("notes")) + "</TD></TR>");
out.println("</TABLE>");
%>
</BODY>
</HTML>

EJB方面

1、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别?
答:
规范内容包括Bean提供者,应用程序装配者,EJB容器,EJB配置工具,EJB服务提供者,系统管理员。这里面,EJB容器是EJB之所以能够运行的核心。 EJB容器管理着EJB的创建,撤消,激活,去活,与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC, JMS.....

2、EJB与JAVA BEAN的区别?
答:
Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理, EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。

3、EJB的基本架构
答:
一个EJB包括三个部分:
  Remote Interface 接口的代码
  package Beans;
  import javax.ejb.EJBObject;
  import java.rmi.RemoteException;
  public interface Add extends EJBObject
  {
   //some method declare
  }
  Home Interface 接口的代码
  package Beans;
  import java.rmi.RemoteException;
  import jaax.ejb.CreateException;
  import javax.ejb.EJBHome;
  public interface AddHome extends EJBHome
  {
    //some method declare
  }
  EJB类的代码
  package Beans;
  import java.rmi.RemoteException;
  import javax.ejb.SessionBean;
  import javx.ejb.SessionContext;
  public class AddBean Implements SessionBean
  {
    //some method declare
  } 

J2EE,MVC方面

1、MVC的各个部分都有那些技术来实现?如何实现?
答:
MVC 是Model-View- Controller的简写。"Model" 代表的是应用的业务逻辑(通过JavaBean,EJB组件实现), "View" 是应用的表示面(由JSP页面产生),"Controller" 是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。

2、应用服务器与WEB SERVER的区别?
希望大家补上,谢谢


3、J2EE是什么?
答:
Je22 是Sun公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企业级应用模型 (enterpriese application model).在这样的一个应用系统中,可按照功能划分为不同的组件,这些组件又可在不同计算机上,并且处于相应的层次(tier)中。所属层次包括客户层(clietn tier)组件,web层和组件,Business层和组件,企业信息系统(EIS)层。

4、WEB SERVICE名词解释。JSWDL开发包的介绍。JAXP、JAXM的解释。SOAP、UDDI,WSDL解释。
答:
Web Service描述语言WSDL
SOAP即简单对象访问协议(Simple Object Access Protocol),它是用于交换XML编码信息的轻量级协议。
UDDI 的目的是为电子商务建立标准;UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web Service注册,以使别的企业能够发现的访问协议的实现标准。


5、BS与CS的联系与区别。
希望大家补上,谢谢

6、STRUTS的应用(如STRUTS架构)
答:
Struts 是采用Java Servlet/JavaServer Pages技术,开发Web应用程序的开放源码的framework。采用Struts能开发出基于MVC(Model-View- Controller)设计模式的应用构架。 Struts有如下的主要功能:
一.包含一个controller servlet,能将用户的请求发送到相应的Action对象。
二.JSP自由tag库,并且在controller servlet中提供关联支持,帮助开发员创建交互式表单应用。
三.提供了一系列实用对象:XML处理、通过Java reflection APIs自动处理JavaBeans属性、国际化的提示和消息。

设计模式方面

1、开发中都用到了那些设计模式?用在什么场合?
答:
每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。


2、UML方面
答:
标准建模语言UML。用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图,合作图),实现图,

JavaScript方面

1、如何校验数字型?
var re=/^\d{1,8}$|\.\d{1,2}$/;
var str=document.form1.all(i).value;
var r=str.match(re);
if (r==null)
{
   sign=-4;
   break;
}
else{
   document.form1.all(i).value=parseFloat(str);
}


CORBA方面

1、CORBA是什么?用途是什么?
答:
CORBA 标准是公共对象请求代理结构(Common Object Request Broker Architecture),由对象管理组织 (Object Management Group,缩写为 OMG)标准化。它的组成是接口定义语言(IDL), 语言绑定(binding:也译为联编)和允许应用程序间互操作的协议。其目的为:
用不同的程序设计语言书写
在不同的进程中运行
为不同的操作系统开发


LINUX方面

1、LINUX下线程,GDI类的解释。
答:
LINUX实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。
GDI类为图像设备编程接口类库。

posted @ 2006-12-26 10:51 保尔任 阅读(241) | 评论 (0)编辑 收藏
 第一,谈谈final, finally, finalize的区别。

    第二,Anonymous Inner Class (匿名内部类)  是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?

    第三,Static Nested Class 和 Inner Class的不同,说得越多越好(面试题有的很笼统)。

    第四,&和&&的区别。

    第五,HashMap和Hashtable的区别。

    第六,Collection 和 Collections的区别。

    第七,什么时候用assert.

    第八,GC是什么? 为什么要有GC?

    第九,String s = new String("xyz");创建了几个String Object?

    第十,Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

    第十一,short s1 = 1; s1 =  s1 + 1;有什么错? short s1 = 1;  s1 += 1;有什么错?

    第十二,sleep() 和 wait() 有什么区别?

    第十三,Java有没有goto?

    第十四,数组有没有length()这个方法? String有没有length()这个方法?

    第十五,Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?

    第十六,Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?

    第十七,给我一个你最常见到的runtime exception.

    第十八,error和exception有什么区别?

    第十九,List, Set, Map是否继承自Collection接口?

    第二十,abstract class和interface有什么区别?

    第二十一,abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

    第二十二,接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?

    第二十三,启动一个线程是用run()还是start()?

    第二十四,构造器Constructor是否可被override?

    第二十五,是否可以继承String类?

    第二十六,当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

    第二十七,try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?

    第二十八,编程题: 用最有效率的方法算出2乘以8等於几?

    第二十九,两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

    第三十,当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

    第三十一,swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?

    第三十二,编程题: 写一个Singleton出来。

以下是答案

    第一,谈谈final, finally, finalize的区别。

    final?修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载finally?再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。

    finalize?方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。

    第二,Anonymous Inner Class (匿名内部类)  是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?

    匿名的内部类是没有名字的内部类。不能extends(继承) 其它类,但一个内部类可以作为一个接口,由另一个内部类实现。

    第三,Static Nested Class 和 Inner Class的不同,说得越多越好(面试题有的很笼统)。

    Nested Class  (一般是C++的说法),Inner Class (一般是JAVA的说法)。Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用上。具体可见http: //www.frontfree.net/articles/services/view.asp?id=704&page= 1

    注: 静态内部类(Inner Class)意味着1创建一个static内部类的对象,不需要一个外部类对象,2不能从一个static内部类的一个对象访问一个外部类对象

    第四,&和&&的区别。

    &是位运算符。&&是布尔逻辑运算符。

    第五,HashMap和Hashtable的区别。

    都属于Map接口的类,实现了将惟一键映射到特定的值上。

    HashMap 类没有分类或者排序。它允许一个 null 键和多个 null 值。

    Hashtable 类似于 HashMap,但是不允许  null 键和 null 值。它也比 HashMap 慢,因为它是同步的。

    第六,Collection 和 Collections的区别。

    Collections是个java.util下的类,它包含有各种有关集合操作的静态方法。

    Collection是个java.util下的接口,它是各种集合结构的父接口。

第七,什么时候用assert。 

  断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true。如果表达式计算为 false,那么系统会报告一个 AssertionError。它用于调试目的: 

assert(a > 0); // throws an AssertionError if a <= 0 

  断言可以有两种形式: 

  assert Expression1 ; 
  assert Expression1 : Expression2 ; 

  Expression1 应该总是产生一个布尔值。 
  Expression2 可以是得出一个值的任意表达式。这个值用于生成显示更多调试信息的 String 消息。 
断言在默认情况下是禁用的。要在编译时启用断言,需要使用 source 1.4 标记: 

  javac -source 1.4 Test.java 

  要在运行时启用断言,可使用 -enableassertions 或者 -ea 标记。 
  要在运行时选择禁用断言,可使用 -da 或者 -disableassertions 标记。 
  要系统类中启用断言,可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。 

  可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。 


  第八,GC是什么? 为什么要有GC? (基础)。 

  GC是垃圾收集器。Java 程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一: 

  System.gc() 
  Runtime.getRuntime().gc() 

  第九,String s = new String("xyz");创建了几个String Object? 

  两个对象,一个是“xyx”,一个是指向“xyx”的引用对象s。 

  第十,Math.round(11.5)等於多少? Math.round(-11.5)等於多少? 

  Math.round(11.5)返回(long)12,Math.round(-11.5)返回(long)-11; 

  第十一,short s1 = 1; s1 = s1 + 1; 有什么错? short s1 = 1; s1 += 1;有什么错?  

  short s1 = 1; s1 = s1 + 1;有错, s1是short型,s1+1是int型,不能显式转化为short型。可修改为s1 =(short)(s1 + 1)  。short s1 = 1; s1 += 1正确。 

  第十二,sleep() 和 wait() 有什么区别? 搞线程的最爱 

  sleep()方法是使线程停止一段时间的方法。在sleep 时间间隔期满后,线程不一定立即恢复执行。这是因为在那个时刻,其它线程可能正在运行而且没有被调度为放弃执行,除非(a)“醒来”的线程具有更高的优先级,(b)正在运行的线程因为其它原因而阻塞。 

  wait()是线程交互时,如果线程对一个同步对象x 发出一个wait()调用,该线程会暂停执行,被调对象进入等待状态,直到被唤醒或等待时间到。 

  第十三,Java有没有goto? 

  Goto?java中的保留字,现在没有在java中使用。 

  第十四,数组有没有length()这个方法? String有没有length()这个方法? 

  数组没有length()这个方法,有length的属性。 
  String有有length()这个方法。 

  第十五,Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型? 

  方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写  (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。 

  第十六,Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别? 

  Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。 

  equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。 

  第十七,给我一个你最常见到的runtime exception。 

  ArithmeticException, ArrayStoreException,  BufferOverflowException, BufferUnderflowException,  CannotRedoException,   
CannotUndoException,  ClassCastException, CMMException,   ConcurrentModificationException,  
DOMException, EmptyStackException, IllegalArgumentException,  IllegalMonitorStateException,  
IllegalPathStateException,  IllegalStateException,  
ImagingOpException,  
IndexOutOfBoundsException,  MissingResourceException,  NegativeArraySizeException,  NoSuchElementException,  
NullPointerException,  ProfileDataException, ProviderException, 
 RasterFormatException,  SecurityException, SystemException,
 UndeclaredThrowableException,  
UnmodifiableSetException,  UnsupportedOperationException  

  第十八,error和exception有什么区别? 

  error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。

  exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。 


  第十九,List, Set, Map是否继承自Collection接口? 

  List,Set是 

  Map不是 

  第二十,abstract class和interface有什么区别? 

  声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。 

  接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。 

  第二十一,abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized? 

  都不能 

  第二十二,接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? 

  接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。 

  第二十三,启动一个线程是用run()还是start()? 

  启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。 



  第二十四,构造器Constructor是否可被override? 

  构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。 

  第二十五,是否可以继承String类? 

  String类是final类故不可以继承。 

  第二十六,当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 

  不能,一个对象的一个synchronized方法只能由一个线程访问。 

  第二十七,try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后? 

  会执行,在return前执行。 

  第二十八,编程题: 用最有效率的方法算出2乘以8等於几? 

  有C背景的程序员特别喜欢问这种问题。 

  2 << 3 

  第二十九,两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对? 

  不对,有相同的hash code。 

  第三十,当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 

  是值传递。Java 编程语言只由值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。 


  第三十一,swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上? 

  switch(expr1)中,expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long, string 都不能作用于swtich。 

  第三十二,编程题: 写一个Singleton出来。

  Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 

  一般Singleton模式通常有几种种形式: 

  第一种形式: 定义一个类,它的构造函数为private的,它有一个static的private的该类变量,在类初始化时实例话,通过一个public的getInstance方法获取对它的引用,继而调用其中的方法。 

public class Singleton { 
  private Singleton(){} 
  //在自己内部定义自己一个实例,是不是很奇怪? 
  //注意这是private 只供内部调用 
  private static Singleton instance = new Singleton(); 
  //这里提供了一个供外部访问本class的静态方法,可以直接访问   
  public static Singleton getInstance() { 
    return instance;    
   } 


  第二种形式: 

public class Singleton { 
  private static Singleton instance = null; 
  public static synchronized Singleton getInstance() { 
  //这个方法比上面有所改进,不用每次都进行生成对象,只是第一次      
  //使用时生成实例,提高了效率! 
  if (instance==null) 
    instance=new Singleton(); 
return instance;   } 


  其他形式: 

  定义一个类,它的构造函数为private的,所有方法为static的。 

  一般认为第一种形式要更加安全些 

  第三十三 Hashtable和HashMap 

  Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现 

  HashMap允许将null作为一个entry的key或者value,而Hashtable不允许 

  还有就是,HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 

  最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap就必须为之提供外同步。 

  Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。
posted @ 2006-12-26 10:51 保尔任 阅读(181) | 评论 (0)编辑 收藏
     摘要: EJB方面94、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别?答:规范内容包括Bean提供者,应用程序装配者,EJB容器,EJB配置工具,EJB服务提供者,系统管理员。这里面,EJB容器是EJB之所以能够运行的核心。EJB容器管理着EJB的创建,撤消,激活,去活,与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC,JMS.....9...  阅读全文
posted @ 2006-12-26 10:50 保尔任 阅读(418) | 评论 (0)编辑 收藏
     摘要: Java基础方面:1、作用域public,private,protected,以及不写时的区别答:区别如下:作用域           当前类       同一package  子孙类       其他packagepublic            √              √                  √             √protected        √       ...  阅读全文
posted @ 2006-12-26 10:50 保尔任 阅读(253) | 评论 (0)编辑 收藏
2.4版本的servlet规范在部属描述符中新增加了一个<dispatcher>元素,这个元素有四个可能的值:即REQUEST,FORWARD,INCLUDE和ERROR,可以在一个<filter-mapping>元素中加入任意数目的<dispatcher>,使得filter将会作用于直接从客户端过来的request,通过forward过来的request,通过include过来的request和通过<error-page>过来的request。如果没有指定任何<   dispatcher   >元素,默认值是REQUEST。可以通过下面几个例子来辅助理解。  
  例1:  
  <filter-mapping>  
  <filter-name>Logging   Filter</filter-name>  
  <url-pattern>/products/*</url-pattern>  
  </filter-mapping>  
  这种情况下,过滤器将会作用于直接从客户端发过来的以/products/…开始的请求。因为这里没有制定任何的<   dispatcher   >元素,默认值是REQUEST。  
   
  例2:  
  <filter-mapping>  
                  <filter-name>Logging   Filter</filter-name>  
                  <servlet-name>ProductServlet</servlet-name>  
                  <dispatcher>INCLUDE</dispatcher>  
  </filter-mapping>  
  这种情况下,如果请求是通过request   dispatcher的include方法传递过来的对ProductServlet的请求,则要经过这个过滤器的过滤。其它的诸如从客户端直接过来的对ProductServlet的请求等都不需要经过这个过滤器。  
  指定filter的匹配方式有两种方法:直接指定url-pattern和指定servlet,后者相当于把指定的servlet对应的url-pattern作为filter的匹配模式  
  filter的路径匹配和servlet是一样的,都遵循servlet规范中《SRV.11.2   Specification   of   Mappings》一节的说明  
   
  例3:  
  <filter-mapping>  
                  <filter-name>Logging   Filter</filter-name>  
                  <url-pattern>/products/*</url-pattern>  
                  <dispatcher>FORWARD</dispatcher>  
                  <dispatcher>REQUEST</dispatcher>  
  </filter-mapping>  
  在这种情况下,如果请求是以/products/…开头的,并且是通过request   dispatcher的forward方法传递过来或者直接从客户端传递过来的,则必须经过这个过滤器。  
posted @ 2006-12-22 08:46 保尔任 阅读(818) | 评论 (0)编辑 收藏
     摘要: UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同...  阅读全文
posted @ 2006-12-12 11:33 保尔任 阅读(957) | 评论 (2)编辑 收藏

 第一章 一般技术
1.java只有唯一一种参数传递方式:by value(值传递)。对于primitive types(基本型别)很容易理解,对于object references(对象引用),传递的是object reference的拷贝。
2.polymorphism(多态)优于instanceof:instanceof很容易被误用,很多场合都应该以多态代替,无论何时看到instanceof,请判断是否可以改进以消除它。
3.避免创建重复对象。比如一个类A的某个方法新建了一个类B,且此类B不会改变,则每次建立该类A的一个对象就会新建B的对象,此时应把
B设为private static final。
4.清除过期的对象引用。
5.避免使用终结函数。因为终结函数可能得不到执行或很久后得到执行,所以要避免使用。显示的中止方法通常与try-finally结构结合使用,防止出现异常时终结函数得不到执行。
eg: Foo foo = new Foo(...);
    try{
        //do what must be done with foo   
    }finally{
        foo.terminate();
    }
6.通过私有构造函数来强化不可实例化的能力。比如一些工具类不希望被实例化,然而在缺少显示构造函数时编译器会自动提供一个默认构造函数,为防止以上情况要构造一个显示的私有的构造函数。
eg:public class UtilityClass{
     private UtilityClass(){
     }
   }
7.通过私有构造函数强化singleton属性。singleton是指这样的类,它只能实例化一次。singleton通常被用来代表那些本质上具有唯一性的系统组件,比如视频显示或者文件系统。
  eg:public class Elvis{
       public static final Elvis INSTANCE = new Elvis();
       private Elvis(){
       }
     }
8.考虑用静态工厂方法代替构造函数,但如果没有其他强烈的因素,最好还是简单的使用构造函数,毕竟它是语言规范。静态工厂方法实际上是一个简单的静态方法,他返回的是类的一个实例。
  有点:a.与构造函数不同,静态工厂方法具有名字。
        b.与构造函数不同,它们每次被调用的时候不要求非得创建一个对象。
        c.与构造函数不同,与构造函数不同,它们可以返回一个原类型的子类型对象。
第二章 所有对象都通用的方法(equals(),hashCode(),toString(),clone(),Comparable接口)
一.按规则使用equals():
1.使用equals的规则:
  a.如果一个class的两个对象占据不同的内存空间也可被视为逻辑相等的话,那么得为这个class提供一个equals()
  b.检查是否等于this
  c.比较关键域以判断两个对象是否相等
  d.如果有java.lang.Object以外的任何base class实现了equals(),那么就应该调用super.equals()
  e.如果只允许同一个class所产生的对象被视为相等,则通常使用getClass()
    eg1:一般情况
    public boolean equals(Object obj){
        if(this == obj){
          return true;
        }
        if(obj != nul && getClass() == obj.getClass()){
          Test test = (Test)obj;
          if(***){//相等条件
              return true;
          }
        }
        return false;
      }
    eg2:调用super.equals()情况
    public boolean equals(Object obj){
      if(super.equals(obj)){//已经包含了this == obj; obj !=null && getClass() == obj.getClass()的判断
        Test test = (Test)obj;
          if(***){//相等条件
              return true;
          }
        }
        return false;
      }

  f.只有在不得不对derived class对象与base classes对象进行比较的场合中,才使用instanceof,并且你应该明白这样做带来的可能问题和复杂性,并且derived class和base classes都用instanceof实现equals()时,这种比较不会展现“对称相等性”。
    Base b;Derived d;//分别表示父类、子类
    1)父类实现equals,子类继承父类的equals,b.equals(d) == d.equals(d);
    2)父类子类分别实现了equals,b.equals(d) != d.equals(b);
    3)父类未实现equals,子类实现了equals,b.equals(d) != d.equals(b);
2.对于既不是float也不是double类型的primitive types,使用==操作符;对于对象引用域,可以递归的调用equals方法;对于float域,先使用Float.floatToIntBits转换成int类型值,然后使用==操作符比较int类型的值;对于double域,先使用Double.doubleToLongBits转换成int类型的值,然后使用==操作符比较long类型的值.(这是由于存在Float.NaN、-0.0f以及类似的double类型的常量)
二.hashCode():
1。改写equals时总是要改写hashCode方法,如果不这样作,会导致该类无法与所有基于散列值(hash)的集合类在一起正常工作,这样的集合类包括HashMap、HashSet、HashTable
2。hashCode方法的简单方法:
  1。把某个非零数值(比如17),保存在int result变量里。
  2。对于对象中每一个关键域f(指equals方法中考虑的每一个域),完成以下步骤:
  a)为该域计算int类型的散列码c:
    i.该域为boolean型,c = f ? 0 : 1
    ii.byte, char, short, int型, c = (int)f
    iii.long型, c = (int)(f ^ (f >>> 32))
    iv.float型, c = Float.floatToIntBits(f)
    v.double型, Double.doubleToLongBits(f)得到long型,然后按iii计算散列值
    vi.如果是对象引用,c = (this.*** == null) ? 0 : this.***.hashCode();
    vii.如果该域是个数组,则把其中每一个元素当作单独的域来处理
   b)result = 37 * result + c;//把每个c都组合到result中
   3。返回result
   eg1:
 public int hashCode() {
     int result = 17;
     //对于关键域是id的情况
     int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
     result = (result * 37) + idValue;
     //如果还有第二个关键域name
     //int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
     //result = (result * 37) + nameValue;
     this.hashValue = result;
 return this.hashValue;
 }
    eg2:
 如果一个类是非可变的,并且计算散列码代价较大,则应把散列码存到对象内部:
 private int hashValue = 17;//先定义hashValue,不需要get/set方法
 ........................
 public int hashCode() {//对于关键域是id的情况
     if (this.hashValue == 17) {
  int result = 17;
  int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
  result = (result * 37) + idValue;
  //如果还有第二个关键域name
  //int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
  //result = (result * 37) + nameValue;
  this.hashValue = result;
     }
     return this.hashValue;
 }
三。toString():会使这个类用起来更加方便。
四。谨慎的改写clone()。实现拷贝的方法有两个:一是实现cloneable接口(effective java 39页,没仔细看),二是提供拷贝构造函数
  public Yum(Yum yum);
  或是上面的微小变形:提供一个静态工厂来代替构造函数:
  public static Yum newInstance(Yum yum);
五、用到搜索、排序、计算极值的情况时,考虑实现Comparable接口。
public int compareTo(Object o)//方法不需要手工检查参数的类型,如参数类型不符合会抛出ClassCastException;如参数为null,该方法抛出NullPointerException。
第三章 类和接口
1。使类和成员(变量、方法、内部类、内部接口)的可访问能力最小化。
2。private和friendly成员都是一个类实现中的一部分,并不会影响到导出API。然而,如果这些域所在的类实现了Serializable接口,那么这些成员可能会被泄漏到导出API中。
3。如果一个方法改写了超类中的一个方法,那么子类中该方法的访问级别不能低于父类中该方法的访问级别。特别是:类实现了接口,那么接口中的方法在这个类中必须声明为公有的,因为接口中方法默认为public abstract。
六、异常处理
1.决不可忽略异常,即catch后什么也不做。
2.决不可掩盖异常
try{
  e1;//异常1
  e2;//异常2
}catch(Exception e){
  e.printStackTrace()
}//只能捕获异常2
办法:要仔细分析,用栈来保存异常
3.覆写异常处理时:
父类不抛出异常时,自类不能抛出异常。
父类抛出异常时,自类三种情况:a)不抛出异常b)抛出父类异常c)抛出父类异常的派生异常。
4.只要有finally块就一定会进入,即使try-catch块有return/break/continue语句。
5.养成将try/catch块放在循环外的习惯,在不启动JIT时节省时间。

posted @ 2006-12-12 11:32 保尔任 阅读(262) | 评论 (0)编辑 收藏
     摘要: Java 反射机制   摘要 ...  阅读全文
posted @ 2006-12-12 11:32 保尔任 阅读(298) | 评论 (0)编辑 收藏
 在已发布的Java1.4中在核心代码库中增加了许多新的API(如Loging,正则表达式,NIO)等,在最新发布的JDK1.5和即将发布的JDK1.6中也新增了许多API,其中比较有重大意义的就是Generics(范型)。

一.什么是Generics?

Generics可以称之为参数类型(parameterized types),由编译器来验证从客户端将一种类型传送给某一对象的机制。如Java.util.ArrayList,编译器可以用Generics来保证类型安全。
在我们深入了解Generics之前,我们先来看一看当前的java 集合框架(Collection)。在j2SE1.4中所有集合的Root Interface是Collection

Collections example without genericity: Example 1


1 protected void collectionsExample() {
2  ArrayList list = new ArrayList();
3  list.add(new String("test string"));
4  list.add(new Integer(9)); // purposely placed here to create a runtime ClassCastException
5  inspectCollection(list);
6 }
7
8
9 protected void inspectCollection(Collection aCollection) {
10  Iterator i = aCollection.iterator();
11  while (i.hasNext()) {
12   String element = (String) i.next();
13  }
14 }


以上的样例程序包含的两个方法,collectionExample方法建立了一个简单的集合类型ArrayList,并在ArrayList中增加了一个 String和一个Integer对象.而在inspecCollection方法中,我们迭代这个ArrayList用String进行Cast。我们看第二个方法,就出现了一个问题,Collection在内部用的是Object,而我们要取出Collection中的对象时,需要进行Cast,那么开发者必需用实际的类型进行Cast,像这种向下造型,编译器无法进行检查,如此一来我们就要冒在代码在运行抛出ClassCastException的危险。我们看inspecCollection方法,编译时没有问题,但在运行时就会抛出ClassCastException异常。所以我们一定要远离这个重大的运行时错误


二.使用Generics
从上一章节中的CassCastException这种异常,我们期望在代码编译时就能够捕捉到,下面我们使用范型修改上一章的样例程序。
//Example 2
1 protected void collectionsExample() {
2  ArrayList<String> list = new ArrayList<String>();
3  list.add(new String("test string"));
4  // list.add(new Integer(9)); this no longer compiles
5  inspectCollection(list);
6 }
7
8
9 protected void inspectCollection(Collection<String> aCollection) {
10  Iterator<String> i = aCollection.iterator();
11  while(i.hasNext()) {
12   String element = i.next();
13  }
14 }


从上面第2行我们在创建ArrayList时使用了新语法,在JDK1.5中所有的Collection都加入了Generics的声明。例:
//Example 3
1 public class ArrayList<E> extends AbstractList<E> {
2  // details omitted...
3  public void add(E element) {
4   // details omitted
5  }
6  public Iterator<E> iterator() {
7   // details omitted
8  }
9 }


这个E是一个类型变量,并没有对它进行具体类型的定义,它只是在定义ArrayList时的类型占位符,在Example 2中的我们在定义ArrayList的实例时用String绑定在E上,当我们用add(E element)方法向ArrayList中增加对象时, 那么就像下面的写法一样: public void add(String element);因为在ArrayList所有方法都会用String来替代E,无论是方法的参数还是返回值。这时我们在看Example 2中的第四行,编译就会反映出编译错误。
所以在java中增加Generics主要的目的是为了增加类型安全。

通过上面的简单的例子我们看到使用Generics的好处有:
1.在类型没有变化时,Collection是类型安全的。
2.内在的类型转换优于在外部的人工造型。
3.使Java 接口更加强壮,因为它增加了类型。
4.类型的匹配错误在编译阶段就可以捕捉到,而不是在代码运行时。

受约束类型变量
虽然许多Class被设计成Generics,但类型变量可以是受限的
public class C1<T extends Number> { }
public class C2<T extends Person & Comparable> { }
第一个T变量必须继承Number,第二个T必须继承Person和实现Comparable

三.Generics 方法

像Generics类一样,方法和构造函数也可以有类型参数。方法的参数和返回值都可以有类型参数,进行Generics。
//Example 4
1 public <T extends Comparable> T max(T t1, T t2) {
2  if (t1.compareTo(t2) > 0)
3   return t1;
4  else return t2;
5 }


这里,max方法的参数类型为单一的T类型,而T类型继承了Comparable,max的参数和返回值都有相同的超类。下面的Example 5显示了max方法的几个约束。
//Example 5 
1 Integer iresult = max(new Integer(100), new Integer(200));
2 String sresult = max("AA", "BB");
3 Number nresult = max(new Integer(100), "AAA"); // does not compile


在Example 5第1行参数都为Integer,所以返回值也是Integer,注意返回值没有进行造型。
在Example 5第2行参数都为String,所以返回值也是String,注意返回值没有进行造型。以上都调用了同一个方法。
在Example 5第3行产生以下编译错误:
Example.java:10: incompatible types
found  : java.lang.Object&java.io.Serializable&java.lang.Comparable<?>
required: java.lang.Number
    Number nresult = max(new Integer(100), "AAA");


这个错误发生是因为编译器无法确定返回值类型,因为String和Integer都有相同的超类Object,注意就算我们修正了第三行,这行代码在运行仍然会报错,因为比较了不同的对象。

四.向下兼容
任何一个新的特色在新的JDK版本中出来后,我们首先关心的是如何于以前编写的代码兼容。也就是说我们编写的Example 1程序不需要任何的改变就可以运行,但是编译器会给出一个"ROW TYPE"的警告。在JDK1.4中编写的代码如何在JVM1.5中完全兼容运行,我们要人工进行一个:Type erasure处理过程

五.通配符

//Example 6
List<String> stringList = new ArrayList<String>(); //1
List<Object> objectList = stringList ;//2
objectList .add(new Object()); // 3
String s = stringList .get(0);//4
乍一看,Example 6 是正确的。但stringList本意是存放String类型的ArrayList,而objectList中可以存入任何对象,当在第3行进行处理时, stringList也就无法保证是String类型的ArrayList,此时编译器不允许这样的事出现,所以第3行将无法编译。

//Example 7
void printCollection(Collection<Object> c) 
{ for (Object e : c) {
System.out.println(e);
}}


Example 7的本意是打印所有Collection的对象,但是正如Example 6所说的,编译会报错,此时就可以用通配符“?”来修改Example 7

//Example 8
void printCollection(Collection<?> c) 
{ for (Object e : c) {
System.out.println(e);
}}


Example 8中所有Collection类型就可以方便的打印了

有界通配符 <T extends Number>(上界) <T super Number>(下界)

六.创建自己的范型
以下代码来自http://www.java2s.com/ExampleCode/Language-Basics
1.一个参数的Generics
//Example 9(没有使用范型)
class NonGen {  
  Object ob; // ob is now of type Object
  // Pass the constructor a reference to  
  // an object of type Object
  NonGen(Object o) {  
    ob = o;  
  }  
  // Return type Object.
  Object getob() {  
    return ob;  
  }  
  // Show type of ob.  
  void showType() {  
    System.out.println("Type of ob is " +  
                       ob.getClass().getName());  
  }  
}  
// Demonstrate the non-generic class.  
public class NonGenDemo {  
  public static void main(String args[]) {  
    NonGen iOb;  
    // Create NonGen Object and store
    // an Integer in it. Autoboxing still occurs.
    iOb = new NonGen(88);  
    // Show the type of data used by iOb.
    iOb.showType();
    // Get the value of iOb.
    // This time, a cast is necessary.
    int v = (Integer) iOb.getob();  
    System.out.println("value: " + v);  
    System.out.println();  
    // Create another NonGen object and  
    // store a String in it.
    NonGen strOb = new NonGen("Non-Generics Test");  
    // Show the type of data used by strOb.
    strOb.showType();
    // Get the value of strOb.
    // Again, notice that a cast is necessary.  
    String str = (String) strOb.getob();  
    System.out.println("value: " + str);  
    // This compiles, but is conceptually wrong!
    iOb = strOb;
    v = (Integer) iOb.getob(); // runtime error!
  }  
}
  

//Example 10(使用范型)
class Example1<T>{
private T t;
Example1(T o){
  this.t=o;
  }
T getOb(){
  return t;
}
void ShowObject(){
  System.out.println("对象的类型是:"+t.getClass().getName());
}
}
public class GenericsExample1 {

/**
  * @param args
  */
public static void main(String[] args) {
  // TODO Auto-generated method stub
  Example1<Integer> examplei=new Example1<Integer>(100);
  examplei.ShowObject();
  System.out.println("对象是:"+examplei.getOb());
  Example1<String> examples=new Example1<String>("Bill");
  examples.ShowObject();
  System.out.println("对象是:"+examples.getOb());
}

}


我们来看Example 9没有使用范型,所以我们需要进行造型,而Example 10我们不需要任何的造型

2.二个参数的Generics

//Example 11
class TwoGen<T, V> { 
   T ob1;
   V ob2;
   // Pass the constructor a reference to  
   // an object of type T.
   TwoGen(T o1, V o2) {
     ob1 = o1;
     ob2 = o2;
   }
   // Show types of T and V.
   void showTypes() {
     System.out.println("Type of T is " +
                        ob1.getClass().getName());
     System.out.println("Type of V is " +
                        ob2.getClass().getName());
   }
   T getob1() {
     return ob1;
   }
   V getob2() {
     return ob2;
   }
}

public class GenericsExampleByTwoParam {

/**
  * @param args
  */
public static void main(String[] args) {
  // TODO Auto-generated method stub
  TwoGen<Integer, String> tgObj =
       new TwoGen<Integer, String>(88, "Generics");
     // Show the types.
     tgObj.showTypes();
     // Obtain and show values.
     int v = tgObj.getob1();
     System.out.println("value: " + v);
     String str = tgObj.getob2();
     System.out.println("value: " + str);
   }

}


3.Generics的Hierarchy

//Example 12
class Stats<T extends Number> {  
   T[] nums; // array of Number or subclass
   // Pass the constructor a reference to  
   // an array of type Number or subclass.
   Stats(T[] o) {  
     nums = o;  
   }  
   // Return type double in all cases.
   double average() {  
     double sum = 0.0;
     for(int i=0; i < nums.length; i++)  
       sum += nums[i].doubleValue();
     return sum / nums.length;
   }  
}  
public class GenericsExampleByHierarchy {


/**
  * @param args
  */
public static void main(String[] args) {
  // TODO Auto-generated method stub

   Integer inums[] = { 1, 2, 3, 4, 5 };
     Stats<Integer> iob = new Stats<Integer>(inums);  
     double v = iob.average();
     System.out.println("iob average is " + v);
     Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
     Stats<Double> dob = new Stats<Double>(dnums);  
     double w = dob.average();
     System.out.println("dob average is " + w);
     // This won't compile because String is not a
     // subclass of Number.
//     String strs[] = { "1", "2", "3", "4", "5" };
//     Stats<String> strob = new Stats<String>(strs);  
//     double x = strob.average();
//     System.out.println("strob average is " + v);
   }  
}
  

4.使用通配符
//Example 14
class StatsWildCard<T extends Number> {
T[] nums; // array of Number or subclass
// Pass the constructor a reference to
// an array of type Number or subclass.
StatsWildCard(T[] o) {
  nums = o;
}
// Return type double in all cases.
double average() {
  double sum = 0.0;
  for (int i = 0; i < nums.length; i++)
   sum += nums[i].doubleValue();
  return sum / nums.length;
}
// Determine if two averages are the same.
// Notice the use of the wildcard.
boolean sameAvg(StatsWildCard<?> ob) {
  if (average() == ob.average())
   return true;
  return false;
}
}

public class GenericsExampleByWildcard {

/**
  * @param args
  */
public static void main(String[] args) {
  // TODO Auto-generated method stub
  Integer inums[] = { 1, 2, 3, 4, 5 };
  StatsWildCard<Integer> iob = new StatsWildCard<Integer>(inums);
  double v = iob.average();
  System.out.println("iob average is " + v);
  Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
  StatsWildCard<Double> dob = new StatsWildCard<Double>(dnums);
  double w = dob.average();
  System.out.println("dob average is " + w);
  Float fnums[] = { 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
  StatsWildCard<Float> fob = new StatsWildCard<Float>(fnums);
  double x = fob.average();
  System.out.println("fob average is " + x);
  // See which arrays have same average.
  System.out.print("Averages of iob and dob ");
  if (iob.sameAvg(dob))
   System.out.println("are the same.");
  else
   System.out.println("differ.");
  System.out.print("Averages of iob and fob ");
  if (iob.sameAvg(fob))
   System.out.println("are the same.");
  else
   System.out.println("differ.");

}

}


5.使用边界通配符
//Example 15
class TwoD { 
  int x, y;
  TwoD(int a, int b) {
    x = a;
    y = b;
  }
}
// Three-dimensional coordinates.
class ThreeD extends TwoD {
  int z;
  ThreeD(int a, int b, int c) {
    super(a, b);
    z = c;
  }
}
// Four-dimensional coordinates.
class FourD extends ThreeD {
  int t;
  FourD(int a, int b, int c, int d) {
    super(a, b, c);
    t = d;  
  }
}
// This class holds an array of coordinate objects.
class Coords<T extends TwoD> {
  T[] coords;
  Coords(T[] o) { coords = o; }
}
// Demonstrate a bounded wildcard.
public class BoundedWildcard {
  static void showXY(Coords<?> c) {
    System.out.println("X Y Coordinates:");
    for(int i=0; i < c.coords.length; i++)
      System.out.println(c.coords[i].x + " " +
                         c.coords[i].y);
    System.out.println();
  }
  static void showXYZ(Coords<? extends ThreeD> c) {
    System.out.println("X Y Z Coordinates:");
    for(int i=0; i < c.coords.length; i++)
      System.out.println(c.coords[i].x + " " +
                         c.coords[i].y + " " +
                         c.coords[i].z);
    System.out.println();
  }
  static void showAll(Coords<? extends FourD> c) {
    System.out.println("X Y Z T Coordinates:");
    for(int i=0; i < c.coords.length; i++)
      System.out.println(c.coords[i].x + " " +
                         c.coords[i].y + " " +
                         c.coords[i].z + " " +
                         c.coords[i].t);
    System.out.println();
  }
  public static void main(String args[]) {
    TwoD td[] = {
      new TwoD(0, 0),
      new TwoD(7, 9),
      new TwoD(18, 4),
      new TwoD(-1, -23)
    };
    Coords<TwoD> tdlocs = new Coords<TwoD>(td);    
    System.out.println("Contents of tdlocs.");
    showXY(tdlocs); // OK, is a TwoD
//  showXYZ(tdlocs); // Error, not a ThreeD
//  showAll(tdlocs); // Erorr, not a FourD
    // Now, create some FourD objects.
    FourD fd[] = {
      new FourD(1, 2, 3, 4),
      new FourD(6, 8, 14, 8),
      new FourD(22, 9, 4, 9),
      new FourD(3, -2, -23, 17)
    };
    Coords<FourD> fdlocs = new Coords<FourD>(fd);    
    System.out.println("Contents of fdlocs.");
    // These are all OK.
    showXY(fdlocs);  
    showXYZ(fdlocs);
    showAll(fdlocs);
  }
}



6.ArrayList的Generics
//Example 16
public class ArrayListGenericDemo {
  public static void main(String[] args) {
    ArrayList<String> data = new ArrayList<String>();
    data.add("hello");
    data.add("goodbye");

    // data.add(new Date()); This won't compile!

    Iterator<String> it = data.iterator();
    while (it.hasNext()) {
      String s = it.next();
      System.out.println(s);
    }
  }
}


7.HashMap的Generics
//Example 17
public class HashDemoGeneric {
  public static void main(String[] args) {
    HashMap<Integer,String> map = new HashMap<Integer,String>();

    map.put(1, "Ian");
    map.put(42, "Scott");
    map.put(123, "Somebody else");

    String name = map.get(42);
    System.out.println(name);
  }
}


8.接口的Generics
//Example 18
interface MinMax<T extends Comparable<T>> { 
  T min();
  T max();
}
// Now, implement MinMax
class MyClass<T extends Comparable<T>> implements MinMax<T> {
  T[] vals;
  MyClass(T[] o) { vals = o; }
  // Return the minimum value in vals.
  public T min() {
    T v = vals[0];
    for(int i=1; i < vals.length; i++)
      if(vals[i].compareTo(v) < 0) v = vals[i];
    return v;
  }
  // Return the maximum value in vals.
  public T max() {
    T v = vals[0];
    for(int i=1; i < vals.length; i++)
      if(vals[i].compareTo(v) > 0) v = vals[i];
    return v;
  }
}
public class GenIFDemo {
  public static void main(String args[]) {
    Integer inums[] = {3, 6, 2, 8, 6 };
    Character chs[] = {'b', 'r', 'p', 'w' };
    MyClass<Integer> iob = new MyClass<Integer>(inums);
    MyClass<Character> cob = new MyClass<Character>(chs);
    System.out.println("Max value in inums: " + iob.max());
    System.out.println("Min value in inums: " + iob.min());
    System.out.println("Max value in chs: " + cob.max());
    System.out.println("Min value in chs: " + cob.min());
  }
}


9.Exception的Generics
//Example 20
interface Executor<E extends Exception> {
    void execute() throws E;
}

public class GenericExceptionTest {
    public static void main(String args[]) {
        try {
            Executor<IOException> e =
                new Executor<IOException>() {
                public void execute() throws IOException
                {
                    // code here that may throw an
                    // IOException or a subtype of
                    // IOException
                }
            };

            e.execute();
        } catch(IOException ioe) {
            System.out.println("IOException: " + ioe);
            ioe.printStackTrace();
        }
    }
}  


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=661415

posted @ 2006-12-12 11:31 保尔任 阅读(241) | 评论 (0)编辑 收藏

Java虚拟机的深入研究

作者:刘学超

1  Java技术与Java虚拟机

说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。它们的关系如下图所示:

图1  Java四个方面的关系

运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件)。最后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。从上图也可以看出Java平台由Java虚拟机和Java应用程序接口搭建,Java语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平台上。这个平台的结构如下图所示:

在Java平台的结构中, 可以看出,Java虚拟机(JVM) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。它的下方是移植接口,移植接口由两部分组成:适配器和Java操作系统, 其中依赖于平台的部分称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在JVM 的上方是Java的基本类库和扩展类库以及它们的API, 利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java 的平台无关性。

那么到底什么是Java虚拟机(JVM)呢?通常我们谈论JVM时,我们的意思可能是:

  1. 对JVM规范的的比较抽象的说明;
  2. 对JVM的具体实现;
  3. 在程序运行期间所生成的一个JVM实例。

对JVM规范的的抽象说明是一些概念的集合,它们已经在书《The Java Virtual Machine Specification》(《Java虚拟机规范》)中被详细地描述了;对JVM的具体实现要么是软件,要么是软件和硬件的组合,它已经被许多生产厂商所实现,并存在于多种平台之上;运行Java程序的任务由JVM的运行期实例单个承担。在本文中我们所讨论的Java虚拟机(JVM)主要针对第三种情况而言。它可以被看成一个想象中的机器,在实际的计算机上通过软件模拟来实现,有自己想象中的硬件,如处理器、堆栈、寄存器等,还有自己相应的指令系统。

JVM在它的生存周期中有一个明确的任务,那就是运行Java程序,因此当Java程序启动的时候,就产生JVM的一个实例;当程序运行结束的时候,该实例也跟着消失了。下面我们从JVM的体系结构和它的运行过程这两个方面来对它进行比较深入的研究。

2  Java虚拟机的体系结构

刚才已经提到,JVM可以由不同的厂商来实现。由于厂商的不同必然导致JVM在实现上的一些不同,然而JVM还是可以实现跨平台的特性,这就要归功于设计JVM时的体系结构了。

我们知道,一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、数据类型和指令这些部分,它们描述了JVM的一个抽象的内部体系结构,其目的不光规定实现JVM时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部行为。每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为:

图3  JVM的体系结构

JVM的每个实例都有一个它自己的方法域和一个堆,运行于JVM内的所有的线程都共享这些区域;当虚拟机装载类文件的时候,它解析其中的二进制数据所包含的类信息,并把它们放到方法域中;当程序运行的时候,JVM把程序初始化的所有对象置于堆上;而每个线程创建的时候,都会拥有自己的程序计数器和Java栈,其中程序计数器中的值指向下一条即将被执行的指令,线程的Java栈则存储为该线程调用Java方法的状态;本地方法调用的状态被存储在本地方法栈,该方法栈依赖于具体的实现。

下面分别对这几个部分进行说明。

执行引擎处于JVM的核心位置,在Java虚拟机规范中,它的行为是由指令集所决定的。尽管对于每条指令,规范很详细地说明了当JVM执行字节码遇到指令时,它的实现应该做什么,但对于怎么做却言之甚少。Java虚拟机支持大约248个字节码。每个字节码执行一种基本的CPU运算,例如,把一个整数加到寄存器,子程序转移等。Java指令集相当于Java程序的汇编语言。

Java指令集中的指令包含一个单字节的操作符,用于指定要执行的操作,还有0个或多个操作数,提供操作所需的参数或数据。许多指令没有操作数,仅由一个单字节的操作符构成。

虚拟机的内层循环的执行过程如下: 
do{ 
取一个操作符字节; 
根据操作符的值执行一个动作; 
}while(程序未结束)

由于指令系统的简单性,使得虚拟机执行的过程十分简单,从而有利于提高执行的效率。指令中操作数的数量和大小是由操作符决定的。如果操作数比一个字节大,那么它存储的顺序是高位字节优先。例如,一个16位的参数存放时占用两个字节,其值为:

第一个字节*256+第二个字节字节码。

指令流一般只是字节对齐的。指令tableswitch和lookup是例外,在这两条指令内部要求强制的4字节边界对齐。

对于本地方法接口,实现JVM并不要求一定要有它的支持,甚至可以完全没有。Sun公司实现Java本地接口(JNI)是出于可移植性的考虑,当然我们也可以设计出其它的本地接口来代替Sun公司的JNI。但是这些设计与实现是比较复杂的事情,需要确保垃圾回收器不会将那些正在被本地方法调用的对象释放掉。

Java的堆是一个运行时数据区,类的实例(对象)从中分配空间,它的管理是由垃圾回收来负责的:不给程序员显式释放对象的能力。Java不规定具体使用的垃圾回收算法,可以根据系统的需求使用各种各样的算法。

Java方法区与传统语言中的编译后代码或是Unix进程中的正文段类似。它保存方法代码(编译后的java代码)和符号表。在当前的Java实现中,方法代码不包括在垃圾回收堆中,但计划在将来的版本中实现。每个类文件包含了一个Java类或一个Java界面的编译后的代码。可以说类文件是Java语言的执行代码文件。为了保证类文件的平台无关性,Java虚拟机规范中对类文件的格式也作了详细的说明。其具体细节请参考Sun公司的Java虚拟机规范。

Java虚拟机的寄存器用于保存机器的运行状态,与微处理器中的某些专用寄存器类似。Java虚拟机的寄存器有四种:

  1. pc: Java程序计数器;
  2. optop: 指向操作数栈顶端的指针;
  3. frame: 指向当前执行方法的执行环境的指针;。
  4. vars: 指向当前执行方法的局部变量区第一个变量的指针。

在上述体系结构图中,我们所说的是第一种,即程序计数器,每个线程一旦被创建就拥有了自己的程序计数器。当线程执行Java方法的时候,它包含该线程正在被执行的指令的地址。但是若线程执行的是一个本地的方法,那么程序计数器的值就不会被定义。

Java虚拟机的栈有三个区域:局部变量区、运行环境区、操作数区。

局部变量区

每个Java方法使用一个固定大小的局部变量集。它们按照与vars寄存器的字偏移量来寻址。局部变量都是32位的。长整数和双精度浮点数占据了两个局部变量的空间,却按照第一个局部变量的索引来寻址。(例如,一个具有索引n的局部变量,如果是一个双精度浮点数,那么它实际占据了索引n和n+1所代表的存储空间)虚拟机规范并不要求在局部变量中的64位的值是64位对齐的。虚拟机提供了把局部变量中的值装载到操作数栈的指令,也提供了把操作数栈中的值写入局部变量的指令。

运行环境区

在运行环境中包含的信息用于动态链接,正常的方法返回以及异常捕捉。

动态链接

运行环境包括对指向当前类和当前方法的解释器符号表的指针,用于支持方法代码的动态链接。方法的class文件代码在引用要调用的方法和要访问的变量时使用符号。动态链接把符号形式的方法调用翻译成实际方法调用,装载必要的类以解释还没有定义的符号,并把变量访问翻译成与这些变量运行时的存储结构相应的偏移地址。动态链接方法和变量使得方法中使用的其它类的变化不会影响到本程序的代码。

正常的方法返回

如果当前方法正常地结束了,在执行了一条具有正确类型的返回指令时,调用的方法会得到一个返回值。执行环境在正常返回的情况下用于恢复调用者的寄存器,并把调用者的程序计数器增加一个恰当的数值,以跳过已执行过的方法调用指令,然后在调用者的执行环境中继续执行下去。

异常捕捉

异常情况在Java中被称作Error(错误)或Exception(异常),是Throwable类的子类,在程序中的原因是:①动态链接错,如无法找到所需的class文件。②运行时错,如对一个空指针的引用。程序使用了throw语句。

当异常发生时,Java虚拟机采取如下措施:

  • 检查与当前方法相联系的catch子句表。每个catch子句包含其有效指令范围,能够处理的异常类型,以及处理异常的代码块地址。
  • 与异常相匹配的catch子句应该符合下面的条件:造成异常的指令在其指令范围之内,发生的异常类型是其能处理的异常类型的子类型。如果找到了匹配的catch子句,那么系统转移到指定的异常处理块处执行;如果没有找到异常处理块,重复寻找匹配的catch子句的过程,直到当前方法的所有嵌套的catch子句都被检查过。
  • 由于虚拟机从第一个匹配的catch子句处继续执行,所以catch子句表中的顺序是很重要的。因为Java代码是结构化的,因此总可以把某个方法的所有的异常处理器都按序排列到一个表中,对任意可能的程序计数器的值,都可以用线性的顺序找到合适的异常处理块,以处理在该程序计数器值下发生的异常情况。
  • 如果找不到匹配的catch子句,那么当前方法得到一个"未截获异常"的结果并返回到当前方法的调用者,好像异常刚刚在其调用者中发生一样。如果在调用者中仍然没有找到相应的异常处理块,那么这种错误将被传播下去。如果错误被传播到最顶层,那么系统将调用一个缺省的异常处理块。

操作数栈区

机器指令只从操作数栈中取操作数,对它们进行操作,并把结果返回到栈中。选择栈结构的原因是:在只有少量寄存器或非通用寄存器的机器(如Intel486)上,也能够高效地模拟虚拟机的行为。操作数栈是32位的。它用于给方法传递参数,并从方法接收结果,也用于支持操作的参数,并保存操作的结果。例如,iadd指令将两个整数相加。相加的两个整数应该是操作数栈顶的两个字。这两个字是由先前的指令压进堆栈的。这两个整数将从堆栈弹出、相加,并把结果压回到操作数栈中。

每个原始数据类型都有专门的指令对它们进行必须的操作。每个操作数在栈中需要一个存储位置,除了long和double型,它们需要两个位置。操作数只能被适用于其类型的操作符所操作。例如,压入两个int类型的数,如果把它们当作是一个long类型的数则是非法的。在Sun的虚拟机实现中,这个限制由字节码验证器强制实行。但是,有少数操作(操作符dupe和swap),用于对运行时数据区进行操作时是不考虑类型的。

本地方法栈,当一个线程调用本地方法时,它就不再受到虚拟机关于结构和安全限制方面的约束,它既可以访问虚拟机的运行期数据区,也可以使用本地处理器以及任何类型的栈。例如,本地栈是一个C语言的栈,那么当C程序调用C函数时,函数的参数以某种顺序被压入栈,结果则返回给调用函数。在实现Java虚拟机时,本地方法接口使用的是C语言的模型栈,那么它的本地方法栈的调度与使用则完全与C语言的栈相同。

3  Java虚拟机的运行过程

上面对虚拟机的各个部分进行了比较详细的说明,下面通过一个具体的例子来分析它的运行过程。

虚拟机通过调用某个指定类的方法main启动,传递给main一个字符串数组参数,使指定的类被装载,同时链接该类所使用的其它的类型,并且初始化它们。例如对于程序:

class HelloApp 
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!"); 
		for (int i = 0; i < args.length; i++ )
		{
			System.out.println(args[i]);
		}
	}
}

编译后在命令行模式下键入: java HelloApp run virtual machine

将通过调用HelloApp的方法main来启动java虚拟机,传递给main一个包含三个字符串"run"、"virtual"、"machine"的数组。现在我们略述虚拟机在执行HelloApp时可能采取的步骤。

开始试图执行类HelloApp的main方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用ClassLoader试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在main方法被调用之前,必须对类HelloApp与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。整个过程如下:

图4:虚拟机的运行过程

4  结束语

本文通过对JVM的体系结构的深入研究以及一个Java程序执行时虚拟机的运行过程的详细分析,意在剖析清楚Java虚拟机的机理。

posted @ 2006-12-12 11:30 保尔任 阅读(233) | 评论 (0)编辑 收藏

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

理解抽象类

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。


从语法定义层面看abstract class和interface

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。

使用abstract class的方式定义Demo抽象类的方式如下:

abstract class Demo {
abstract void method1();
abstract void method2();

使用interface的方式定义Demo抽象类的方式如下:

interface Demo {
void method1();
void method2();

}

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。


从设计理念层面看abstract class和interface

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用abstract class方式定义Door:

abstract class Door {
abstract void open();
abstract void close();
}


使用interface方式定义Door:


interface Door {
void open();
void close();
}


其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。

解决方案一:

简单的在Door的定义中增加一个alarm方法,如下:

abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}


或者

interface Door {
void open();
void close();
void alarm();
}


那么具有报警功能的AlarmDoor的定义方式如下:

class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}


或者

class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二:

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:

abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}


这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

 

结论

abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法。

posted @ 2006-12-12 11:29 保尔任 阅读(238) | 评论 (0)编辑 收藏
版權申明,獲得授權轉載必須保留以下申明和鏈接:
作者的blog:(http://blog.matrix.org.cn/page/Kaizen)

在论坛上面常常看到初学者对线程的无可奈何,所以总结出了下面一篇文章,希望对一些正在学习使用java线程的初学者有所帮助。

首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多任务,分时操作系统。正是由于这种操作系统的出现才有了多线程这个概念。我们使用的windows,linux就属于此列。什么是分时操作系统呢,通俗一点与就是可以同一时间执行多个程序的操作系统,在自己的电脑上面,你是不是一边听歌,一边聊天还一边看网页呢?但实际上,并不上cpu在同时执行这些程序,cpu只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在cpu的高速计算能力,给人的感觉就像是多个程序在同时执行一样。
一般可以在同一时间内执行多个程序的操作系统都有进程的概念.一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,每一个进程的内部数据和状态都是完全独立的.因此可以想像创建并执行一个进程的系统开像是比较大的,所以线程出现了。在java中,程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务.多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行.(你可以将前面一句话的程序换成进程,进程是程序的一次执行过程,是系统运行程序的基本单位)

线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈.所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-weight process).一个进程中可以包含多个线程.

多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期.用线程的状态(state)表明线程处在生命周期的哪个阶段.线程有创建,可运行,运行中,阻塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每个程序至少自动拥有一个线程,称为主线程.当程序加载到内存时,启动主线程.

[线程的运行机制以及调度模型]
java中多线程就是一个类或一个程序执行或管理多个线程执行任务的能力,每个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制着它的所有线程,可以决定哪个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。
下面是线程的机制图:


线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务.线程有创建,可运行,运行中,阻塞,死亡五中状态.一个具有生命的线程,总是处于这五种状态之一:
1.创建状态
使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread)
2.可运行状态
使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)
3.运行中状态
Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running).此时,系统真正执行线程的run()方法.
4.阻塞状态
一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)
5.死亡状态
线程结束后是死亡状态(Dead)

同一时刻如果有多个线程处于可运行状态,则他们需要排队等待CPU资源.此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度.可运行状态的线程按优先级排队,线程调度依据优先级基础上的"先到先服务"原则.
线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度.当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态.

线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行.先占式调度分为:独占式和分时方式.
独占方式下,当前执行线程将一直执行下去,直 到执行完毕或由于某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占
分时方式下,当前运行线程获得一个时间片,时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度.系统选中其他可运行状态的线程执行
分时方式的系统使每个线程工作若干步,实现多线程同时运行

另外请注意下面的线程调度规则(如果有不理解,不急,往下看):
①如果两个或是两个以上的线程都修改一个对象,那么把执行修改的方法定义为被同步的(Synchronized),如果对象更新影响到只读方法,那么只度方法也应该定义为同步的
②如果一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部等待,它可以调用一个被同步的方法,并让这个方法调用wait()
③每当一个方法改变某个对象的状态的时候,它应该调用notifyAll()方法,这给等待队列的线程提供机会来看一看执行环境是否已发生改变
④记住wait(),notify(),notifyAll()方法属于Object类,而不是Thread类,仔细检查看是否每次执行wait()方法都有相应的notify()或notifyAll()方法,且它们作用与相同的对象 在java中每个类都有一个主线程,要执行一个程序,那么这个类当中一定要有main方法,这个man方法也就是java class中的主线程。你可以自己创建线程,有两种方法,一是继承Thread类,或是实现Runnable接口。一般情况下,最好避免继承,因为java中是单根继承,如果你选用继承,那么你的类就失去了弹性,当然也不能全然否定继承Thread,该方法编写简单,可以直接操作线程,适用于单重继承情况。至于选用那一种,具体情况具体分析。


eg.继承Thread
public class MyThread_1 extends Thread
{
public void run()
{
//some code
}
}


eg.实现Runnable接口
public class MyThread_2 implements Runnable
{
public void run()
{
//some code
}
}



当使用继承创建线程,这样启动线程:
new MyThread_1().start()


当使用实现接口创建线程,这样启动线程:
new Thread(new MyThread_2()).start()


注意,其实是创建一个线程实例,并以实现了Runnable接口的类为参数传入这个实例,当执行这个线程的时候,MyThread_2中run里面的代码将被执行。
下面是完成的例子:

public class MyThread implements Runnable
{

public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
new Thread(new MyThread()).start();
}
}



执行后将打印出:
My Name is Thread-0

你也可以创建多个线程,像下面这样
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();



那么会打印出:
My Name is Thread-0
My Name is Thread-1
My Name is Thread-2


看了上面的结果,你可能会认为线程的执行顺序是依次执行的,但是那只是一般情况,千万不要用以为是线程的执行机制;影响线程执行顺序的因素有几点:首先看看前面提到的优先级别


public class MyThread implements Runnable
{

public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先级
t1.start();
t2.start();
t3.start();
}
}


再看看结果:
My Name is Thread-1
My Name is Thread-0
My Name is Thread-2



线程的优先级分为10级,分别用1到10的整数代表,默认情况是5。上面的t2.setPriority(Thread.MAX_PRIORITY)等价与t2.setPriority(10)
然后是线程程序本身的设计,比如使用sleep,yield,join,wait等方法(详情请看JDKDocument)

public class MyThread implements Runnable
{
public void run()
{
try
{
int sleepTime=(int)(Math.random()*100);//产生随机数字,
Thread.currentThread().sleep(sleepTime);//让其休眠一定时间,时间又上面sleepTime决定
//public static void sleep(long millis)throw InterruptedException (API)
System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime);
}catch(InterruptedException ie)//由于线程在休眠可能被中断,所以调用sleep方法的时候需要捕捉异常
{
ie.printStackTrace();
}
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t1.start();
t2.start();
t3.start();
}
}


执行后观察其输出:

Thread-0 睡了 11
Thread-2 睡了 48
Thread-1 睡了 69




上面的执行结果是随机的,再执行很可能出现不同的结果。由于上面我在run中添加了休眠语句,当线程休眠的时候就会让出cpu,cpu将会选择执行处于runnable状态中的其他线程,当然也可能出现这种情况,休眠的Thread立即进入了runnable状态,cpu再次执行它。
[线程组概念]
线程是可以被组织的,java中存在线程组的概念,每个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组可以同时对其中的多个线程进行操作,如启动一个线程组的所有线程等.Java的线程组由java.lang包中的Thread——Group类实现.
ThreadGroup类用来管理一组线程,包括:线程的数目,线程间的关系,线程正在执行的操作,以及线程将要启动或终止时间等.线程组还可以包含线程组.在Java的应用程序中,最高层的线程组是名位main的线程组,在main中还可以加入线程或线程组,在mian的子线程组中也可以加入线程和线程组,形成线程组和线程之间的树状继承关系。像上面创建的线程都是属于main这个线程组的。
借用上面的例子,main里面可以这样写:

public static void main(String[] args)
{
/***************************************
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent, String name)
***********************************/
ThreadGroup group1=new ThreadGroup("group1");
ThreadGroup group2=new ThreadGroup(group1,"group2");
Thread t1=new Thread(group2,new MyThread());
Thread t2=new Thread(group2,new MyThread());
Thread t3=new Thread(group2,new MyThread());
t1.start();
t2.start();
t3.start();
}



线程组的嵌套,t1,t2,t3被加入group2,group2加入group1。
另外一个比较多就是关于线程同步方面的,试想这样一种情况,你有一笔存款在银行,你在一家银行为你的账户存款,而你的妻子在另一家银行从这个账户提款,现在你有1000块在你的账户里面。你存入了1000,但是由于另一方也在对这笔存款进行操作,人家开始执行的时候只看到账户里面原来的1000元,当你的妻子提款1000元后,你妻子所在的银行就认为你的账户里面没有钱了,而你所在的银行却认为你还有2000元。
看看下面的例子:

class BlankSaving //储蓄账户
{
private static int money=10000;
public void add(int i)
{
money=money+i;
System.out.println("Husband 向银行存入了 [¥"+i+"]");
}
public void get(int i)
{
money=money-i;
System.out.println("Wife 向银行取走了 [¥"+i+"]");
if(money<0)
System.out.println("余额不足!");
}
public int showMoney()
{
return money;
}
}


class Operater implements Runnable
{
String name;
BlankSaving bs;
public Operater(BlankSaving b,String s)
{
name=s;
bs=b;



}
public static void oper(String name,BlankSaving bs)
{



if(name.equals("husband"))
{
try
{
for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.add(1000);
}
}catch(InterruptedException e){}
}else
{
try
{



for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.get(1000);
}
}catch(InterruptedException e){}
}
}
public void run()
{
oper(name,bs);
}
}
public class BankTest
{
public static void main(String[] args)throws InterruptedException
{
BlankSaving bs=new BlankSaving();
Operater o1=new Operater(bs,"husband");
Operater o2=new Operater(bs,"wife");
Thread t1=new Thread(o1);
Thread t2=new Thread(o2);
t1.start();
t2.start();
Thread.currentThread().sleep(500);
}



}



下面是其中一次的执行结果:



---------first--------------
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]


看到了吗,这可不是正确的需求,在husband还没有结束操作的时候,wife就插了进来,这样很可能导致意外的结果。解决办法很简单,就是将对数据进行操作方法声明为synchronized,当方法被该关键字声明后,也就意味着,如果这个数据被加锁,只有一个对象得到这个数据的锁的时候该对象才能对这个数据进行操作。也就是当你存款的时候,这笔账户在其他地方是不能进行操作的,只有你存款完毕,银行管理人员将账户解锁,其他人才能对这个账户进行操作。
修改public static void oper(String name,BlankSaving bs)为public static void oper(String name,BlankSaving bs),再看看结果:

Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]




当丈夫完成操作后,妻子才开始执行操作,这样的话,对共享对象的操作就不会有问题了。
[wait and notify]
你可以利用这两个方法很好的控制线程的执行流程,当线程调用wait方法后,线程将被挂起,直到被另一线程唤醒(notify)或则是如果wait方法指定有时间得话,在没有被唤醒的情况下,指定时间时间过后也将自动被唤醒。但是要注意一定,被唤醒并不是指马上执行,而是从组塞状态变为可运行状态,其是否运行还要看cpu的调度。
事例代码:

class MyThread_1 extends Thread
{
Object lock;
public MyThread_1(Object o)
{
lock=o;
}
public void run()
{
try
{
synchronized(lock)
{
System.out.println("Enter Thread_1 and wait");
lock.wait();
System.out.println("be notified");
}
}catch(InterruptedException e){}
}
}
class MyThread_2 extends Thread
{
Object lock;
public MyThread_2(Object o)
{
lock=o;
}
public void run()
{
synchronized(lock)
{
System.out.println("Enter Thread_2 and notify");
lock.notify();
}
}
}
public class MyThread
{
public static void main(String[] args)
{
int[] in=new int[0];//notice
MyThread_1 t1=new MyThread_1(in);
MyThread_2 t2=new MyThread_2(in);
t1.start();
t2.start();
}
}




执行结果如下:
Enter Thread_1 and wait
Enter Thread_2 and notify
Thread_1 be notified


可能你注意到了在使用wait and notify方法得时候我使用了synchronized块来包装这两个方法,这是由于调用这两个方法的时候线程必须获得锁,也就是上面代码中的lock[],如果你不用synchronized包装这两个方法的得话,又或则锁不一是同一把,比如在MyThread_2中synchronized(lock)改为synchronized(this),那么执行这个程序的时候将会抛出java.lang.IllegalMonitorStateException执行期异常。另外wait and notify方法是Object中的,并不在Thread这个类中。最后你可能注意到了这点:int[] in=new int[0];为什么不是创建new Object而是一个0长度的数组,那是因为在java中创建一个0长度的数组来充当锁更加高效。

Thread作为java中一重要组成部分,当然还有很多地方需要更深刻的认识,上面只是对Thread的一些常识和易错问题做了一个简要的总结,若要真正的掌握java的线程,还需要自己多做总结
posted @ 2006-12-12 11:28 保尔任 阅读(184) | 评论 (0)编辑 收藏
从基础的开始

  最小的单元是位(bit),接着是字节(Byte),一个字节=8位,英语表示是1 byte=8 bits 。机器语言的单位Byte。接着是KB,1 KB=1024 Byte;  接着是MB,1 MB=1024 KB;  接着是GB,1 GB=1024 MB ;接着是TB, 1TB=1024 GB。
  接着是进制:二进制0和1,8进制0-7, 十进制不用说,10进制0-9后面是A,B,C,D,E,F 他们关系如下:
Binary     Octal  Decimal Hex
0          0      0       0
1          1      1       1
10         2      2       2
11         3      3       3
100        4      4       4
101        5      5       5
110        6      6       6
111        7      7       7
1000       10     8       8
1001       11     9       9
1010       12     10      A
1011       13     11      B
1100       14     12      C
1101       15     13      D
1110       16     14      E
1111       17     15      F

接着是上层建筑字符:

  字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。字符集是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、 GB 18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。

ASCII 字符集
  ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是基于罗马字母表的一套电脑编码系统,它主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO 646。

包含内容:

控制字符:回车键、退格、换行键等。

可显示字符:英文大小写字符、阿拉伯数字和西文符号

ASCII扩展字符集扩展:表格符号、计算符号、希腊字母和特殊的拉丁符号。

  第0~32号及第127号(共34个)是控制字符或通讯专用字符,如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BEL(振铃)等;通讯专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;

  第33~126号(共94个)是字符,其中第48~57号为0~9十个阿拉伯数字;65~90号为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。 

  注意:在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。

DEC   HEX  CHAR  CODE  C 程序(转义) 
0     00   NUL (’\0’) 
1     01   SOH   
2     02   STX   
3     03   ETX   
4     04   EOT   
5     05   ENQ   
6     06   ACK   
7     07   BEL (’\a’) 
8     08   BS (’\b’) 
9     09   HT (’\t’) 
10    0A   LF (’\n’) 
11    0B   VT (’\v’) 
12    0C   FF (’\f’) 
13    0D   CR (’\r’) 
14    0E   SO   
15    0F   SI   
16    10   DLE   
17    11   DC1   
18    12   DC2   
19    13   DC1   
20    14   DC4   
21    15   NAK   
22    16   SYN   
23    17   ETB   
24    18   CAN   
25    19   EM   
26    1A   SUB   
27    1B   ESC   
28    1C   FS   
29    1D   GS   
30    1E   RS   
31    1F   US   
32    20 (space,空格)     
33    21    !     
34    22    "     
35    23    #     
36    24    $     
37    25    %     
38    26    &     
39    27    ’     
40    28    (     
41    29    )     
42    2A    *     
43    2B    +     
44    2C    ,     
45    2D    -     
46    2E    .     
47    2F    /     
48    30    0     
49    31    1     
50    32    2     
51    33    3     
52    34    4     
53    35    5     
54    36    6     
55    37    7     
56    38    8     
57    39    9     
58    3A    :     
59    3B    ;     
60    3C    <     
61    3D    =     
62    3E    >     
63    3F    ?     
64    40    @     
65    41    A     
66    42    B     
67    43    C     
68    44    D     
69    45    E     
70    46    F     
71    47    G     
72    48    H     
73    49    I     
74    4A    J     
75    4B    K     
76    4C    L     
77    4D    M     
78    4E    N     
79    4F    O     
80    50    P     
81    51    Q     
82    52    R     
83    53    S     
84    54    T     
85    55    U     
86    56    V     
87    57    W     
88    58    X     
89    59    Y     
90    5A    Z     
91    5B    [     
92    5C    \   (’\\’) 
93    5D    ]     
94    5E    ^     
95    5F    _     
96    60    `     
97    61    a     
98    62    b     
99    63    c     
100   64    d     
101   65    e     
102   66    f     
103   67    g     
104   68    h     
105   69    i     
106   6A    j     
107   6B    k     
108   6C    l     
109   6D    m     
110   6E    n     
111   6F    o     
112   70    p     
113   71    q     
114   72    r     
115   73    s     
116   74    t     
117   75    u     
118   76    v     
119   77    w     
120   78    x     
121   79    y     
122   7A    z     
123   7B    {     
124   7C    |     
125   7D    }     
126   7E    ~     
127   7F       DEL


GB2312 字符集

  GB2312又称为GB2312-80字符集,全称为《信息交换用汉字编码字符集·基本集》,由原中国国家标准总局发布,1981年5月1日实施,是中国国家标准的简体中文字符集。它所收录的汉字已经覆盖99.75%的使用频率,基本满足了汉字的计算机处理需要。在中国大陆和新加坡获广泛使用。
 
  GB2312收录简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中包括6763个汉字,其中一级汉字3755个,二级汉字3008个;包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。

  GB2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。

它是用双字节表示的,两个字节中前面的字节为第一字节,后面的字节为第二字节。习惯上称第一字节为“高字节” ,而称第二字节为“低字节”。“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。

  以GB2312字符集的第一个汉字“啊”字为例,它的区号16,位号01,则区位码是1601,在大多数计算机程序中,高字节和低字节分别加0xA0得到程序的汉字处理编码0xB0A1。计算公式是:0xB0=0xA0+16, 0xA1=0xA0+1。

GBK字符集
  GBK字符集是GB2312的扩展(K),GBK1.0收录了21886个符号,它分为汉字区和图形符号区,汉字区包括21003个字符。GBK字符集主要扩展了繁体中文字的支持。


BIG5 字符集

  BIG5又称大五码或五大码,1984年由台湾财团法人信息工业策进会和五间软件公司宏碁 (Acer)、神通 (MiTAC)、佳佳、零壹 (Zero One)、大众 (FIC)创立,故称大五码。Big5码的产生,是因为当时台湾不同厂商各自推出不同的编码,如倚天码、IBM PS55、王安码等,彼此不能兼容;另一方面,台湾政府当时尚未推出官方的汉字编码,而中国大陆的GB2312编码亦未有收录繁体中文字。

  Big5字符集共收录13,053个中文字,该字符集在中国台湾使用。耐人寻味的是该字符集重复地收录了两个相同的字:“兀”(0xA461及0xC94A)、“嗀”(0xDCD1及0xDDFC)。

  Big5码使用了双字节储存方法,以两个字节来编码一个字。第一个字节称为“高位字节”,第二个字节称为“低位字节”。高位字节的编码范围0xA1-0xF9,低位字节的编码范围0x40-0x7E及0xA1-0xFE。

  尽管Big5码内包含一万多个字符,但是没有考虑社会上流通的人名、地名用字、方言用字、化学及生物科等用字,没有包含日文平假名及片假字母。

例如台湾视“着”为“著”的异体字,故没有收录“着”字。康熙字典中的一些部首用字(如“亠”、“疒”、“辵”、“癶”等)、常见的人名用字(如“堃”、“煊”、“栢”、“喆”等) 也没有收录到Big5之中。


GB18030 字符集

GB18030的全称是GB18030-2000《信息交换用汉字编码字符集基本集的扩充》,是我国政府于2000年3月17日发布的新的汉字编码国家标准,2001年8月31日后在中国市场上发布的软件必须符合本标准。GB 18030字符集标准的出台经过广泛参与和论证,来自国内外知名信息技术行业的公司,信息产业部和原国家质量技术监督局联合实施。

GB 18030字符集标准解决汉字、日文假名、朝鲜语和中国少数民族文字组成的大字符集计算机编码问题。该标准的字符总编码空间超过150万个编码位,收录了27484个汉字,覆盖中文、日文、朝鲜语和中国少数民族文字。满足中国大陆、香港、台湾、日本和韩国等东亚地区信息交换多文种、大字量、多用途、统一编码格式的要求。并且与Unicode 3.0版本兼容,填补Unicode扩展字符字汇“统一汉字扩展A”的内容。并且与以前的国家字符编码标准(GB2312,GB13000.1)兼容。

编码方法:
GB 18030标准采用单字节、双字节和四字节三种方式对字符编码。单字节部分使用0×00至0×7F码(对应于ASCII码的相应码)。双字节部分,首字节码从0×81至0×FE,尾字节码位分别是0×40至0×7E和0×80至0×FE。四字节部分采用GB/T 11383未采用的0×30到0×39作为对双字节编码扩充的后缀,这样扩充的四字节编码,其范围为0×81308130到0×FE39FE39。其中第一、三个字节编码码位均为0×81至0×FE,第二、四个字节编码码位均为0×30至0×39。

按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。

接着是国际通用的unicode字符集

Unicode字符集(简称为UCS)

1.名称的由来

Unicode字符集编码是(Universal Multiple-Octet Coded Character Set) 通用多八位编码字符集的简称,支持世界上超过650种语言的国际字符集。Unicode允许在同一服务器上混合使用不同语言组的不同语言。它是由一个名为 Unicode 学术学会(Unicode Consortium)的机构制订的字符编码系统,支持现今世界各种不同语言的书面文本的交换、处理及显示。该编码于1990年开始研发,1994年正式公布,最新版本是2005年3月31日的Unicode 4.1.0。Unicode是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

2.编码方法

Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,例如字母“A”的编码为 004116 。所以“A”的编码书写为“U+0041”。

3.UTF-8 编码
UTF-8是Unicode的其中一个使用方式。 UTF是 Unicode Translation Format,即把Unicode转做某种格式的意思。

UTF-8便于不同的计算机之间使用网络传输不同语言和编码的文字,使得双字节的Unicode能够在现存的处理单字节的系统上正确传输。

UTF-8使用可变长度字节来储存 Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。

4.UTF-16 和 UTF-32 编码
UTF-32、UTF-16 和 UTF-8 是 Unicode 标准的编码字符集的字符编码方案,UTF-16 使用一个或两个未分配的 16 位代码单元的序列对 Unicode 代码点进行编码;UTF-32 即将每一个 Unicode 代码点表示为相同值的 32 位整数

通过一个问题了解unicode编码
 
问题:使用Windows记事本的“另存为”,可以在ANSI、GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows怎样识别编码方式的呢?
我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?

答案:

ANSI字符集定义:ASCII字符集,以及由此派生并兼容的字符集,如:GB2312,正式的名称为MBCS(Multi-Byte Chactacter System,多字节字符系统),通常也称为ANSI字符集。

UNICODE 与 UTF8、UTF16

  由于每种语言都制定了自己的字符集,导致最后存在的各种字符集实在太多,在国际交流中要经常转换字符集非常不便。因此,产生了Unicode字符集,它固定使用16 bits(两个字节)来表示一个字符,共可以表示65536个字符
  标准的 Unicode 称为UTF-16(UTF:UCS Transformation Format )。后来为了双字节的Unicode能够在现存的处理单字节的系统上正确传输,出现了UTF-8,使用类似MBCS的方式对Unicode进行编码。(Unicode字符集有多种编码形式)
例如"连通"两个字的Unicode标准编码UTF-16 (big endian)为:DE 8F 1A 90 
而其UTF-8编码为:E8 BF 9E E9 80 9A

当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码:
检测文件头标识,提示用户选择,根据一定的规则猜测
最标准的途径是检测文本最开头的几个字节,开头字节 Charset/encoding,如下表:
EF BB BF      UTF-8
FE FF           UTF-16/UCS-2, little endian
FF FE           UTF-16/UCS-2, big endian
FF FE 00 00  UTF-32/UCS-4, little endian.
00 00 FE FF  UTF-32/UCS-4, big-endian. 


1、big endian和little endian
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

2、字符编码、内码,顺带介绍汉字编码
  字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。
有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。
这里还有一些细节:
GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
在DBCS中,GB内码的存储格式始终是big endian,即高位在前。
GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。

3、Unicode、UCS和UTF(UCS Transformation Format)
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。

  UCS规定了怎么用多个字节表示各种文字。而怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的!常见的UTF规范包括UTF-8、UTF-7、UTF-16。

4、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。


  写到这里对编码有了大致的了解了,就可以理解网上一些文章的话了,比如有一篇很流行的文章《URL编码与SQL注射》里面有一段是这么说的:

其实url编码就是一个字符ascii码的十六进制。不过稍微有些变动,需要在前面加上“%”。比如“\”,它的ascii码是92,92的十六进制是5c,所以“\”的url编码就是%5c。那么汉字的url编码呢?很简单,看例子:“胡”的ascii码是-17670,十六进制是BAFA,url编码是“%BA%FA”。呵呵,知道怎么转换的了吧。


   这得从ASCII说起,扩展的ASCII字符集采用8bit255个字符显然不够用,于是各个国家纷纷制定了自己的文字编码规范,其中中文的文字编码规范叫做“GB2312-80”(就是GB2312),它是和ASCII兼容的一种编码规范,其实就是用扩展ASCII没有真正标准化这一点,把一个中文字符用两个扩展ASCII字符来表示。文中说的的中文ASCII码实际上就是简体中文的编码2312GB!它把ASCII又扩充了一个字节,由于高位的第一位是0,所以会出现负数的形式,url编码就是将汉字的这个GB2312编码转化成UTF-8的编码并且每8位即一个字节前面加上%符号表示。

那为何UTF-8是进行网络的规范传输编码呢?

在Unicode里,所有的字符被一视同仁。汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,现在的汉字是“一个字符”了,于是,拆字、统计字数这些问题也就自然而然的解决了。但是,这个世界不是理想的,不可能在一夜之间所有的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。

我们知道,ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了

另一个更加严重的问题是,C语言使用'\0'作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉

于是,比Unicode更伟大的东东诞生了,之所以说它更伟大是因为它让Unicode不再存在于纸上,而是真实的存在于我们大家的电脑中。那就是:UTF

UTF= UCS Transformation Format UCS转换格式,它是将Unicode编码规则和计算机的实际编码对应起来的一个规则。现在流行的UTF有2种:UTF-8和UTF-16

其中UTF-16和上面提到的Unicode本身的编码规范是一致的,这里不多说了。而UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容,这样做的好处是压缩了字符在西欧一些国家的内存消耗,减少了不必要的资源浪费,这在实际应用中是非常有必要的。

UTF-8有点类似于Haffman编码,它将Unicode编码为:
00000000-0000007F的字符,用单个字节来表示;

00000080-000007FF的字符用两个字节表示  (中文的编码范围)

00000800-0000FFFF的字符用3字节表示

因为目前为止Unicode-16规范没有指定FFFF以上的字符,所以UTF-8最多是使用3个字节来表示一个字符。但理论上来说,UTF-8最多需要用6字节表示一个字符。

在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库可以继续使用。而中文的编码范围是在0080-07FF之间,因此是2个字节表示(但这两个字节和GB编码的两个字节是不同的)。


看看编码之多:ANSI,AscII,GB2312,GBK,BIG5,GB18030,Unicode,UCS(就是unicode)Utf-8,utf-16,utf-32 整整10种编码~,算是够复杂了
可是这还仅仅是个开始,应用方面变化无穷,不过现在看到这些东西起码再不会头大了!呼呼~


哦,漏了一个加密的base64编码。

什么是Base64?

按照RFC2045的定义,Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable.)

为什么要使用Base64?

在设计这个编码的时候,我想设计人员最主要考虑了3个问题:
1.是否加密?
2.加密算法复杂程度和效率
3.如何处理传输?

    加密是肯定的,但是加密的目的不是让用户发送非常安全的Email。这种加密方式主要就是“防君子不防小人”。即达到一眼望去完全看不出内容即可。
基于这个目的加密算法的复杂程度和效率也就不能太大和太低。和上一个理由类似,MIME协议等用于发送Email的协议解决的是如何收发Email,而并不是如何安全的收发Email。因此算法的复杂程度要小,效率要高,否则因为发送Email而大量占用资源,路就有点走歪了。

    但是,如果是基于以上两点,那么我们使用最简单的恺撒法即可,为什么Base64看起来要比恺撒法复杂呢?这是因为在Email的传送过程中,由于历史原因,Email只被允许传送ASCII字符,即一个8位字节的低7位。因此,如果您发送了一封带有非ASCII字符(即字节的最高位是1)的Email通过有“历史问题”的网关时就可能会出现问题。网关可能会把最高位置为0!很明显,问题就这样产生了!因此,为了能够正常的传送Email,这个问题就必须考虑!所以,单单靠改变字母的位置的恺撒之类的方案也就不行了。关于这一点可以参考RFC2046。
基于以上的一些主要原因产生了Base64编码。

鉴于算法比较让人头大,想看的人自然会有看到的办法拉,俺是头大得很,就不放上来了。

posted @ 2006-12-12 11:27 保尔任 阅读(358) | 评论 (0)编辑 收藏

一、正则表达式基础知识:(此文讲的是符合perl的正则表达式匹配方法,与jdk1.4上的不一样,但讲的很清晰,可作为基础知识讲解看)

如果你曾经用过Perl或任何其他内建正则表达式支持的语言,你一定知道用正则表达式处理文本和匹配模式是多么简单。如果你不熟悉这个术语,那么“正则表达式”(Regular Expression)就是一个字符构成的串,它定义了一个用来搜索匹配字符串的模式。
许多语言,包括Perl、PHP、Python、JavaScript和JScript,都支持用正则表达式处理文本,一些文本编辑器用正则表达式实现高级“搜索-替换”功能。那么Java又怎样呢?本文写作时,一个包含了用正则表达式进行文本处理的Java规范需求(Specification Request)已经得到认可,你可以期待在JDK的下一版本中看到它。
然而,如果现在就需要使用正则表达式,又该怎么办呢?你可以从Apache.org下载源代码开放的Jakarta-ORO库。本文接下来的内容先简要地介绍正则表达式的入门知识,然后以Jakarta-ORO API为例介绍如何使用正则表达式。
一、正则表达式基础知识
我们先从简单的开始。假设你要搜索一个包含字符“cat”的字符串,搜索用的正则表达式就是“cat”。如果搜索对大小写不敏感,单词“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是说:
1.1 句点符号
假设你在玩英文拼字游戏,想要找出三个字母的单词,而且这些单词必须以“t”字母开头,以“n”字母结束。另外,假设有一本英文字典,你可以用正则表达式搜索它的全部内容。要构造出这个正则表达式,你可以使用一个通配符——句点符号“.”。这样,完整的表达式就是“t.n”,它匹配“tan”、“ten”、“tin”和“ton”,还匹配“t#n”、“tpn”甚至“t n”,还有其他许多无意义的组合。这是因为句点符号匹配所有字符,包括空格、Tab字符甚至换行符:
1.2 方括号符号
为了解决句点符号匹配范围过于广泛这一问题,你可以在方括号(“[]”)里面指定看来有意义的字符。此时,只有方括号里面指定的字符才参与匹配。也就是说,正则表达式“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配,因为在方括号之内你只能匹配单个字符:
1.3 “或”符号
如果除了上面匹配的所有单词之外,你还想要匹配“toon”,那么,你可以使用“|”操作符。“|”操作符的基本意义就是“或”运算。要匹配“toon”,使用“t(a|e|i|o|oo)n”正则表达式。这里不能使用方扩号,因为方括号只允许匹配单个字符;这里必须使用圆括号“()”。圆括号还可以用来分组,具体请参见后面介绍。
1.4 表示匹配次数的符号
表一显示了表示匹配次数的符号,这些符号用来确定紧靠该符号左边的符号出现的次数:

假设我们要在文本文件中搜索美国的社会安全号码。这个号码的格式是999-99-9999。用来匹配它的正则表达式如图一所示。在正则表达式中,连字符(“-”)有着特殊的意义,它表示一个范围,比如从0到9。因此,匹配社会安全号码中的连字符号时,它的前面要加上一个转义字符“\”。

图一:匹配所有123-12-1234形式的社会安全号码

假设进行搜索的时候,你希望连字符号可以出现,也可以不出现——即,999-99-9999和999999999都属于正确的格式。这时,你可以在连字符号后面加上“?”数量限定符号,如图二所示:

图二:匹配所有123-12-1234和123121234形式的社会安全号码

下面我们再来看另外一个例子。美国汽车牌照的一种格式是四个数字加上二个字母。它的正则表达式前面是数字部分“[0-9]{4}”,再加上字母部分“[A-Z]{2}”。图三显示了完整的正则表达式。

图三:匹配典型的美国汽车牌照号码,如8836KV

1.5 “否”符号
“^”符号称为“否”符号。如果用在方括号内,“^”表示不想要匹配的字符。例如,图四的正则表达式匹配所有单词,但以“X”字母开头的单词除外。

图四:匹配所有单词,但“X”开头的除外

1.6 圆括号和空白符号
假设要从格式为“June 26, 1951”的生日日期中提取出月份部分,用来匹配该日期的正则表达式可以如图五所示:

图五:匹配所有Moth DD,YYYY格式的日期

新出现的“\s”符号是空白符号,匹配所有的空白字符,包括Tab字符。如果字符串正确匹配,接下来如何提取出月份部分呢?只需在月份周围加上一个圆括号创建一个组,然后用ORO API(本文后面详细讨论)提取出它的值。修改后的正则表达式如图六所示:

图六:匹配所有Month DD,YYYY格式的日期,定义月份值为第一个组

1.7 其它符号
为简便起见,你可以使用一些为常见正则表达式创建的快捷符号。如表二所示:
表二:常用符号

例如,在前面社会安全号码的例子中,所有出现“[0-9]”的地方我们都可以使用“\d”。修改后的正则表达式如图七所示:

图七:匹配所有123-12-1234格式的社会安全号码

-------------------------
二、正则表达式在java中
应用
(java编程思想第三版P565页有讲解)
简介:

java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包。

它包括两个类: PatternMatcher

Pattern一个Pattern是一个正则表达式经编译后的表现模式。
Matcher一个Matcher对象是一个状态机器,它依据Pattern对象做为匹配模式对字符串展开匹配检查。

首先一个Pattern实例订制了一个所用语法与PERL的类似的正则表达式经编译后的模式,然后一个Matcher实例在这个给定的Pattern实例的模式控制下进行字符串的匹配工作。

以下我们就分别来看看这两个类:



Pattern类:

Pattern的方法如下:

static Patterncompile(String regex)
将给定的正则表达式编译并赋予给Pattern类
static Patterncompile(String regex, int flags)
同上,但增加flag参数的指定,可选的flag参数包括:CASE INSENSITIVE,MULTILINE,DOTALL,UNICODE CASE, CANON EQ
intflags()
返回当前Pattern的匹配flag参数.
Matchermatcher(CharSequence input)
生成一个给定命名的Matcher对象
static booleanmatches(String regex, CharSequence input)
编译给定的正则表达式并且对输入的字串以该正则表达式为模开展匹配,该方法适合于该正则表达式只会使用一次的情况,也就是只进行一次匹配工作,因为这种情况下并不需要生成一个Matcher实例。
Stringpattern()
返回该Patter对象所编译的正则表达式。
String[]split(CharSequence input)
将目标字符串按照Pattern里所包含的正则表达式为模进行分割。
String[]split(CharSequence input, int limit)
作用同上,增加参数limit目的在于要指定分割的段数,如将limi设为2,那么目标字符串将根据正则表达式分为割为两段。

一个正则表达式,也就是一串有特定意义的字符,必须首先要编译成为一个Pattern类的实例,这个Pattern对象将会使用 matcher()方法来生成一个Matcher实例,接着便可以使用该 Matcher实例以编译的正则表达式为基础对目标字符串进行匹配工作,多个Matcher是可以共用一个Pattern对象的。

现在我们先来看一个简单的例子,再通过分析它来了解怎样生成一个Pattern对象并且编译一个正则表达式,最后根据这个正则表达式将目标字符串进行分割:

import java.util.regex.*;
public class Replacement{ public static void main(String[] args) throws Exception { // 生成一个Pattern,同时编译一个正则表达式 Pattern p = Pattern.compile("[/]+"); //用Pattern的split()方法把字符串按"/"分割 String[] result = p.split( "Kevin has seen《LEON》seveal times,because it is a good film." +"/ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部" +"好电影。/名词:凯文。"); for (int i=0; i<result.length; i++) System.out.println(result[i]); } }

输出结果为:

Kevin has seen《LEON》seveal times,because it is a good film.
凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。
名词:凯文。

很明显,该程序将字符串按"/"进行了分段,我们以下再使用 split(CharSequence input, int limit)方法来指定分段的段数,程序改动为:
tring[] result = p.split("Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。",2);

这里面的参数"2"表明将目标语句分为两段。

输出结果则为:

Kevin has seen《LEON》seveal times,because it is a good film.
凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。

由上面的例子,我们可以比较出java.util.regex包在构造Pattern对象以及编译指定的正则表达式的实现手法与我们在上一篇中所介绍的Jakarta-ORO 包在完成同样工作时的差别,Jakarta-ORO 包要先构造一个PatternCompiler类对象接着生成一个Pattern对象,再将正则表达式用该PatternCompiler类的compile()方法来将所需的正则表达式编译赋予Pattern类:

PatternCompiler orocom=new Perl5Compiler();

Pattern pattern=orocom.compile("REGULAR EXPRESSIONS");

PatternMatcher matcher=new Perl5Matcher();

但是在java.util.regex包里,我们仅需生成一个Pattern类,直接使用它的compile()方法就可以达到同样的效果: Pattern p = Pattern.compile("[/]+");

因此似乎java.util.regex的构造法比Jakarta-ORO更为简洁并容易理解。





回页首


Matcher类:

Matcher方法如下:

MatcherappendReplacement(StringBuffer sb, String replacement)
将当前匹配子串替换为指定字符串,并且将替换后的子串以及其之前到上次匹配子串之后的字符串段添加到一个StringBuffer对象里。
StringBufferappendTail(StringBuffer sb)
将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里。
intend()
返回当前匹配的子串的最后一个字符在原目标字符串中的索引位置 。
intend(int group)
返回与匹配模式里指定的组相匹配的子串最后一个字符的位置。
booleanfind()
尝试在目标字符串里查找下一个匹配子串。
booleanfind(int start)
重设Matcher对象,并且尝试在目标字符串里从指定的位置开始查找下一个匹配的子串。
Stringgroup()
返回当前查找而获得的与组匹配的所有子串内容
Stringgroup(int group)
返回当前查找而获得的与指定的组匹配的子串内容
intgroupCount()
返回当前查找所获得的匹配组的数量。
booleanlookingAt()
检测目标字符串是否以匹配的子串起始。
booleanmatches()
尝试对整个目标字符展开匹配检测,也就是只有整个目标字符串完全匹配时才返回真值。
Patternpattern()
返回该Matcher对象的现有匹配模式,也就是对应的Pattern 对象。
StringreplaceAll(String replacement)
将目标字符串里与既有模式相匹配的子串全部替换为指定的字符串。
StringreplaceFirst(String replacement)
将目标字符串里第一个与既有模式相匹配的子串替换为指定的字符串。
Matcherreset()
重设该Matcher对象。
Matcherreset(CharSequence input)
重设该Matcher对象并且指定一个新的目标字符串。
intstart()
返回当前查找所获子串的开始字符在原目标字符串中的位置。
intstart(int group)
返回当前查找所获得的和指定组匹配的子串的第一个字符在原目标字符串中的位置。

(光看方法的解释是不是很不好理解?不要急,待会结合例子就比较容易明白了)

一个Matcher实例是被用来对目标字符串进行基于既有模式(也就是一个给定的Pattern所编译的正则表达式)进行匹配查找的,所有往Matcher的输入都是通过CharSequence接口提供的,这样做的目的在于可以支持对从多元化的数据源所提供的数据进行匹配工作。

我们分别来看看各方法的使用:

★matches()/lookingAt ()/find():
一个Matcher对象是由一个Pattern对象调用其matcher()方法而生成的,一旦该Matcher对象生成,它就可以进行三种不同的匹配查找操作:

  1. matches()方法尝试对整个目标字符展开匹配检测,也就是只有整个目标字符串完全匹配时才返回真值。
  2. lookingAt ()方法将检测目标字符串是否以匹配的子串起始。
  3. find()方法尝试在目标字符串里查找下一个匹配子串。

以上三个方法都将返回一个布尔值来表明成功与否。

★replaceAll ()/appendReplacement()/appendTail():
Matcher类同时提供了四个将匹配子串替换成指定字符串的方法:

  1. replaceAll()
  2. replaceFirst()
  3. appendReplacement()
  4. appendTail()

replaceAll()与replaceFirst()的用法都比较简单,请看上面方法的解释。我们主要重点了解一下appendReplacement()和appendTail()方法。

appendReplacement(StringBuffer sb, String replacement) 将当前匹配子串替换为指定字符串,并且将替换后的子串以及其之前到上次匹配子串之后的字符串段添加到一个StringBuffer对象里,而appendTail(StringBuffer sb) 方法则将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里。

例如,有字符串fatcatfatcatfat,假设既有正则表达式模式为"cat",第一次匹配后调用appendReplacement(sb,"dog"),那么这时StringBuffer sb的内容为fatdog,也就是fatcat中的cat被替换为dog并且与匹配子串前的内容加到sb里,而第二次匹配后调用appendReplacement(sb,"dog"),那么sb的内容就变为fatdogfatdog,如果最后再调用一次appendTail(sb),那么sb最终的内容将是fatdogfatdogfat。

还是有点模糊?那么我们来看个简单的程序:

//该例将把句子里的"Kelvin"改为"Kevin"
import java.util.regex.*;
public class MatcherTest{
    public static void main(String[] args) 
                         throws Exception {
        //生成Pattern对象并且编译一个简单的正则表达式"Kelvin"
        Pattern p = Pattern.compile("Kevin");
        //用Pattern类的matcher()方法生成一个Matcher对象
        Matcher m = p.matcher("Kelvin Li and Kelvin Chan are both working 
in Kelvin Chen's KelvinSoftShop company"); StringBuffer sb = new StringBuffer(); int i=0; //使用find()方法查找第一个匹配的对象 boolean result = m.find(); //使用循环将句子里所有的kelvin找出并替换再将内容加到sb里 while(result) { i++; m.appendReplacement(sb, "Kevin"); System.out.println("第"+i+"次匹配后sb的内容是:"+sb); //继续查找下一个匹配对象 result = m.find(); } //最后调用appendTail()方法将最后一次匹配后的剩余字符串加到sb里; m.appendTail(sb); System.out.println("调用m.appendTail(sb)后sb的最终内容是:"+ sb.toString()); } }

最终输出结果为:
第1次匹配后sb的内容是:Kevin
第2次匹配后sb的内容是:Kevin Li and Kevin
第3次匹配后sb的内容是:Kevin Li and Kevin Chan are both working in Kevin
第4次匹配后sb的内容是:Kevin Li and Kevin Chan are both working in Kevin Chen's Kevin
调用m.appendTail(sb)后sb的最终内容是:Kevin Li and Kevin Chan are both working in Kevin Chen's KevinSoftShop company.

看了上面这个例程是否对appendReplacement(),appendTail()两个方法的使用更清楚呢,如果还是不太肯定最好自己动手写几行代码测试一下。

★group()/group(int group)/groupCount():
该系列方法与我们在上篇介绍的Jakarta-ORO中的MatchResult .group()方法类似(有关Jakarta-ORO请参考上篇的内容),都是要返回与组匹配的子串内容,下面代码将很好解释其用法:

import java.util.regex.*;

public class GroupTest{
    public static void main(String[] args) 
                         throws Exception {
        Pattern p = Pattern.compile("(ca)(t)");        
        Matcher m = p.matcher("one cat,two cats in the yard");
        StringBuffer sb = new StringBuffer();
        boolean result = m.find();
        System.out.println("该次查找获得匹配组的数量为:"+m.groupCount());
        for(int i=1;i<=m.groupCount();i++){
         System.out.println("第"+i+"组的子串内容为: "+m.group(i));
        }
    }
}

输出为:
该次查找获得匹配组的数量为:2
第1组的子串内容为:ca
第2组的子串内容为:t

Matcher对象的其他方法因比较好理解且由于篇幅有限,请读者自己编程验证。





回页首


一个检验Email地址的小程序:

最后我们来看一个检验Email地址的例程,该程序是用来检验一个输入的EMAIL地址里所包含的字符是否合法,虽然这不是一个完整的EMAIL地址检验程序,它不能检验所有可能出现的情况,但在必要时您可以在其基础上增加所需功能。

import java.util.regex.*;
public class Email {
   public static void main(String[] args) throws Exception {
      String input = args[0];
      //检测输入的EMAIL地址是否以 非法符号"."或"@"作为起始字符      
      Pattern p = Pattern.compile("^\\.|^\\@");
      Matcher m = p.matcher(input);
      if (m.find()){
        System.err.println("EMAIL地址不能以'.'或'@'作为起始字符");
      }
      //检测是否以"www."为起始
      p = Pattern.compile("^www\\.");
      m = p.matcher(input);
      if (m.find()) {
        System.out.println("EMAIL地址不能以'www.'起始");
      }
      //检测是否包含非法字符
      p = Pattern.compile("[^A-Za-z0-9\\.\\@_\\-~#]+");
      m = p.matcher(input);
      StringBuffer sb = new StringBuffer();
      boolean result = m.find();
      boolean deletedIllegalChars = false;
      while(result) {
         //如果找到了非法字符那么就设下标记
         deletedIllegalChars = true;
         //如果里面包含非法字符如冒号双引号等,那么就把他们消去,加到SB里面
         m.appendReplacement(sb, "");
         result = m.find();
      }
      m.appendTail(sb);
      input = sb.toString();
      if (deletedIllegalChars) {
          System.out.println("输入的EMAIL地址里包含有冒号、逗号等非法字符,请修改");
          System.out.println("您现在的输入为: "+args[0]);
          System.out.println("修改后合法的地址应类似: "+input);
     }
   }
}

例如,我们在命令行输入:java Email www.kevin@163.net

那么输出结果将会是:EMAIL地址不能以'www.'起始

如果输入的EMAIL为@kevin@163.net

则输出为:EMAIL地址不能以'.'或'@'作为起始字符

当输入为:cgjmail#$%@163.net

那么输出就是:

输入的EMAIL地址里包含有冒号、逗号等非法字符,请修改
您现在的输入为: cgjmail#$%@163.net
修改后合法的地址应类似: cgjmail@163.net
posted @ 2006-12-12 11:26 保尔任 阅读(307) | 评论 (0)编辑 收藏
当年,国际巨星成龙的「龙种」曝光,众人指责他对不起娇妻林凤娇,逼得他出面召开记者会,向世人自白他犯了「全世界所有男人都会犯的错误」。从来没犯过这种错误的我,也因此常常认为自己不是个男人。
虽然没犯过「全世界所有男人都会犯的错误」,但是我倒是曾经犯了「全世界所有程序员都会犯的错误」。不管使用何种语言,全世界所有程序员都一定犯过这种错误,那就是:太依赖编译器,却不知道编译器做了哪些事。
一般来说,越高阶的程序语言,会提供越多语法上的便利,以方便程序撰写,这就俗称为syntactic sugar,我称其为「语法上的甜头」。虽说是甜头,但是如果你未能了解该语法的实质内涵,很可能会未尝甜头,却吃尽苦头。
不久前,我收到一个电子邮件,读者列出下面的Java程序,向我求救。看过这个程序之后,我确定这又是一个「全世界所有程序员都会犯的错误」。
// 程序1
class Singleton {
private static Singleton obj = new Singleton();
public static int counter1;
public static int counter2 = 0;
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getInstance() {
return obj;
}
}
// 程序2
public class MyMain {
public static void main(String[] args) {
Singleton obj = Singleton.getInstance();
System.out.println("obj.counter1=="+obj.counter1);
System.out.println("obj.counter2=="+obj.counter2);
}
}
执行结果是:
obj.counter1==1
obj.counter2==0
你有没有被此结果吓一跳?乍看程序代码,你很可能会认为counter1和counter2的值一定会相等,但执行结果显然不是如此。其实,程序1被编译后的程序应该等同于下面的程序3:
// 程序3
class Singleton {
private static Singleton obj;
public static int counter1;
public static int counter2;
static { // 这就是class constructor
// 在进入此class constructor之前,class已经被JVM
// 配置好内存,所有的static field都会被先设定为0,
// 所以此时counter1和counter2都已经是0,且singleton为null
obj = new Singleton(); // 问题皆由此行程序产生
// counter1不会在此被设定为0
counter2 = 0; // counter2再被设定一次0(其实是多此一举)
}
private Singleton() { // 这是instance constructor
counter1++;
counter2++;
}
public static Singleton getInstance() {
return obj;
}
}
这是因为:当class具有static field,且直接在宣告处透过「=...」的方式设定其值时,编译器会自动将这些叙述依序搬到class constructor内。同样地,当class具有instance field,且直接在宣告处透过「=...」的方式设定其值时,编译器会自动将这些叙述依序搬到instance constructor内。
此程序在class constructor内,还未将static field初始化时(这时候,counter1和counter2都是0),就呼叫instance constructor,而instance constructor竟然还会去更动static field的值,使得counter1和counter2都变成1。然后instance constructor执行完,回到class constructor,再把counter2的值设为0(但是
counter1维持不变)。最后的结果:counter1等于1,counter2等于0。
欲改正程序1,方法有三:
-方法一:将singleton field的宣告调到counter1与counter2 field之后。
这是最好的作法。
-方法二:将counter2=0的宣告中,「=0」的部分删除。这种作法只有在希望
-方法三:将初始化的动作搬到class constructors内,自行撰写,而不依赖
编译器产生。这是最保险的作法。
如何避免犯下「全世界所有程序员都会犯的错误」,我给各位Java程序员
的建议是:
-熟读Java Language Specification
-在有疑问时,使用J2SDK所提供的javap来反组译Java Bytecode,直接观察
编译后的结果。
下面是我用javap来反组译程序1的示范:
C:\>javap -c -classpath . Singleton
Compiled from MyMain.java
class Singleton extends java.lang.Object {
public static int counter1;
public static int counter2;
public static Singleton getInstance();
static {};
}
Method Singleton()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 getstatic #2 <Field int counter1>
7 iconst_1
8 iadd
9 putstatic #2 <Field int counter1>
12 getstatic #3 <Field int counter2>
15 iconst_1
16 iadd
17 putstatic #3 <Field int counter2>
20 return
Method Singleton getInstance()
0 getstatic #4 <Field Singleton obj>
3 areturn
Method static {}
0 new #5 <Class Singleton>
3 dup
4 invokespecial #6 <Method Singleton()>
7 putstatic #4 <Field Singleton obj>
10 iconst_0
11 putstatic #3 <Field int counter2>
14 return
其实Java的syntactic sugar并不算多,C#的syntactic sugar才真的是无所不在,
也因此C#的初学者更容易犯了「全世界所有程序员都会犯的错误」。许多C#的书都会一边介绍C#语法,一边介绍编译之后MSIL(.NET的中间语言,类似Java的Bytecode)的结果,然而Java的书却鲜少这么做。
虽说是「全世界所有程序员都会犯的错误」,但是这不代表你犯了此错误之后,仍可以同爱借钱的曹启泰一般地「抬头挺胸、理直气壮」。只要有心,其实这一类的错误仍是可以避免的。
posted @ 2006-12-12 11:25 保尔任 阅读(211) | 评论 (0)编辑 收藏

转自:http://china.manufacturer.com/article/study_for_character_encoding_java.htm

问题研究

--字符集编码

1. 概述

本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。

在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示"。

2. 编码基本知识

最早的编码是iso8859-1,和ascii编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。

2.1. iso8859-1

属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母'a'的编码为0x61=97。

很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。

2.2. GB2312/GBK

这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。

2.3. unicode

这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前面增加了一个0字节,比如字母'a'为"00 61"。

需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。

2.4. UTF

考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。

注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网页中包含了很多的英文字符。

3. java对字符的处理

在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。

3.1. getBytes(charset)

这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。

3.2. new String(charset)

这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。参考上述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。

因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。

3.3. setCharacterEncoding()

该函数用来设置http请求或者相应的编码。

对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()。java doc上说明:This method must be called prior to reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。

对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。

3.4. 处理过程

下面分析两个有代表性的例子,说明java对编码有关问题的处理方法。

3.4.1. 表单输入

User input  *(gbk:d6d0 cec4)  browser  *(gbk:d6d0 cec4)  web server  iso8859-1(00d6 00d 000ce 00c4)  class,需要在class中进行处理:getbytes("iso8859-1")为d6 d0 ce c4,new String("gbk")为d6d0 cec4,内存中以unicode编码则为4e2d 6587

l 用户输入的编码方式和页面指定的编码有关,也和用户的操作系统有关,所以是不确定的,上例以gbk为例。

l 从browser到web server,可以在表单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。上述仍旧以gbk编码为例。

l Web server接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进行处理。但如果预先设置了编码(通过request. setCharacterEncoding ()),则能够直接获取到正确的结果。

l 在页面中指定编码是个好习惯,否则可能失去控制,无法指定正确的编码。

3.4.2. 文件编译

假设文件是gbk编码保存的,而编译有两种编码选择:gbk或者iso8859-1,前者是中文windows的默认编码,后者是linux的默认编码,当然也可以在编译时指定编码。

Jsp  *(gbk:d6d0 cec4)  java file  *(gbk:d6d0 cec4)  compiler read  uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  compiler write  utf(gbk: e4b8ad e69687; iso8859-1: *)  compiled file  unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  class。所以用gbk编码保存,而用iso8859-1编译的结果是不正确的。

class  unicode(4e2d 6587)  system.out / jsp.out  gbk(d6d0 cec4)  os console / browser。

l 文件可以以多种编码方式保存,中文windows下,默认为ansi/gbk。

l 编译器读取文件时,需要得到文件的编码,如果未指定,则使用系统默认编码。一般class文件,是以系统默认编码保存的,所以编译不会出问题,但对于jsp文件,如果在中文windows下编辑保存,而部署在英文linux下运行/编译,则会出现问题。所以需要在jsp文件中用pageEncoding指定编码。

l Java编译的时候会转换成统一的unicode编码处理,最后保存的时候再转换为utf编码。

l 当系统输出字符的时候,会按指定编码输出,对于中文windows下,System.out将使用gbk编码,而对于response(浏览器),则使用jsp文件头指定的contentType,或者可以直接为response指定编码。同时,会告诉browser网页的编码。如果未指定,则会使用iso8859-1编码。对于中文,应该为browser指定输出字符串的编码。

l browser显示网页的时候,首先使用response中指定的编码(jsp文件头指定的contentType最终也反映在response上),如果未指定,则会使用网页中meta项指定中的contentType。

3.5. 几处设置

对于web应用程序,和编码有关的设置或者函数如下。

3.5.1. jsp编译

指定文件的存储编码,很明显,该设置应该置于文件的开头。例如:<%@page pageEncoding="GBK"%>。另外,对于一般class文件,可以在编译的时候指定编码。

3.5.2. jsp输出

指定文件输出到browser是使用的编码,该设置也应该置于文件的开头。例如:<%@ page contentType="text/html; charset= GBK" %>。该设置和response.setCharacterEncoding("GBK")等效。

3.5.3. meta设置

指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用jsp的设置,而且也无法执行response.setCharacterEncoding()。例如:<META http-equiv="Content-Type" content="text/html; charset=GBK" />

如果同时采用了jsp输出和meta设置两种编码指定方式,则jsp指定的优先。因为jsp指定的直接体现在response中。

需要注意的是,apache有一个设置可以给无编码指定的网页指定编码,该指定等同于jsp的编码指定方式,所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。

3.5.4. form设置

当浏览器提交表单的时候,可以指定相应的编码。例如:<form accept-charset= "gb2312">。一般不必不使用该设置,浏览器会直接使用网页的编码。

4. 系统软件

下面讨论几个相关的系统软件。

4.1. mysql数据库

很明显,要支持多语言,应该将数据库的编码设置成utf或者unicode,而utf更适合与存储。但是,如果中文数据中包含的英文字母很少,其实unicode更为适合。

数据库的编码可以通过mysql的配置文件设置,例如default-character-set=utf8。还可以在数据库链接URL中设置,例如: useUnicode=true&characterEncoding=UTF-8。注意这两者应该保持一致,在新的sql版本里,在数据库链接URL里可以不进行设置,但也不能是错误的设置。

4.2. apache

appache和编码有关的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,该功能会将所有静态页面的编码设置为UTF-8,最好关闭该功能。

另外,apache还有单独的模块来处理网页响应头,其中也可能对编码进行设置。

4.3. linux默认编码

这里所说的linux默认编码,是指运行时的环境变量。两个重要的环境变量是LC_ALL和LANG,默认编码会影响到java URLEncode的行为,下面有描述。

建议都设置为"zh_CN.UTF-8"。

4.4. 其它

为了支持中文文件名,linux在加载磁盘时应该指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。

另外,如前所述,使用GET方法提交的信息不支持request.setCharacterEncoding(),但可以通过tomcat的配置文件指定字符集,在tomcat的server.xml文件中,形如:<Connector ... URIEncoding="GBK"/>。这种方法将统一设置所有请求,而不能针对具体页面进行设置,也不一定和browser使用的编码相同,所以有时候并不是所期望的。

5. URL地址

URL地址中含有中文字符是很麻烦的,前面描述过使用GET方法提交表单的情况,使用GET方法时,参数就是包含在URL中。

5.1. URL编码

对于URL中的一些特殊字符,浏览器会自动进行编码。这些字符除了"/?&"等外,还包括unicode字符,比如汉子。这时的编码比较特殊。

IE有一个选项"总是使用UTF-8发送URL",当该选项有效时,IE将会对特殊字符进行UTF-8编码,同时进行URL编码。如果改选项无效,则使用默认编码"GBK",并且不进行URL编码。但是,对于URL后面的参数,则总是不进行编码,相当于UTF-8选项无效。比如"中文.html?a=中文",当UTF-8选项有效时,将发送链接"%e4%b8%ad%e6%96%87.html?a=\x4e\x2d\x65\x87";而UTF-8选项无效时,将发送链接"\x4e\x2d\x65\x87.html?a=\x4e\x2d\x65\x87"。注意后者前面的"中文"两个字只有4个字节,而前者却有18个字节,这主要时URL编码的原因。

当web server(tomcat)接收到该链接时,将会进行URL解码,即去掉"%",同时按照ISO8859-1编码(上面已经描述,可以使用URLEncoding来设置成其它编码)识别。上述例子的结果分别是"\ue4\ub8\uad\ue6\u96\u87.html?a=\u4e\u2d\u65\u87"和"\u4e\u2d\u65\u87.html?a=\u4e\u2d\u65\u87",注意前者前面的"中文"两个字恢复成了6个字符。这里用"\u",表示是unicode。

所以,由于客户端设置的不同,相同的链接,在服务器上得到了不同结果。这个问题不少人都遇到,却没有很好的解决办法。所以有的网站会建议用户尝试关闭UTF-8选项。不过,下面会描述一个更好的处理办法。

5.2. rewrite

熟悉的人都知道,apache有一个功能强大的rewrite模块,这里不描述其功能。需要说明的是该模块会自动将URL解码(去除%),即完成上述web server(tomcat)的部分功能。有相关文档介绍说可以使用[NE]参数来关闭该功能,但我试验并未成功,可能是因为版本(我使用的是apache 2.0.54)问题。另外,当参数中含有"?& "等符号的时候,该功能将导致系统得不到正常结果。

rewrite本身似乎完全是采用字节处理的方式,而不考虑字符串的编码,所以不会带来编码问题。

5.3. URLEncode.encode()

这是Java本身提供对的URL编码函数,完成的工作和上述UTF-8选项有效时浏览器所做的工作相似。值得说明的是,java已经不赞成不指定编码来使用该方法(deprecated)。应该在使用的时候增加编码指定。

当不指定编码的时候,该方法使用系统默认编码,这会导致软件运行结果得不确定。比如对于"中文",当系统默认编码为"gb2312"时,结果是"%4e%2d%65%87",而默认编码为"UTF-8",结果却是"%e4%b8%ad%e6%96%87",后续程序将难以处理。另外,这儿说的系统默认编码是由运行tomcat时的环境变量LC_ALL和LANG等决定的,曾经出现过tomcat重启后就出现乱码的问题,最后才郁闷的发现是因为修改修改了这两个环境变量。

建议统一指定为"UTF-8"编码,可能需要修改相应的程序。

5.4. 一个解决方案

上面说起过,因为浏览器设置的不同,对于同一个链接,web server收到的是不同内容,而软件系统有无法知道这中间的区别,所以这一协议目前还存在缺陷。

针对具体问题,不应该侥幸认为所有客户的IE设置都是UTF-8有效的,也不应该粗暴的建议用户修改IE设置,要知道,用户不可能去记住每一个web server的设置。所以,接下来的解决办法就只能是让自己的程序多一点智能:根据内容来分析编码是否UTF-8。

比较幸运的是UTF-8编码相当有规律,所以可以通过分析传输过来的链接内容,来判断是否是正确的UTF-8字符,如果是,则以UTF-8处理之,如果不是,则使用客户默认编码(比如"GBK"),下面是一个判断是否UTF-8的例子,如果你了解相应规律,就容易理解。

public static boolean isValidUtf8(byte[] b,int aMaxCount){

       int lLen=b.length,lCharCount=0;

       for(int i=0;i<lLen && lCharCount<aMaxCount;++lCharCount){

              byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;)

              if(lByte>=0) continue;//>=0 is normal ascii

              if(lByte<(byte)0xc0 || lByte>(byte)0xfd) return false;

              int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4

                     :lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1;

              if(i+lCount>lLen) return false;

              for(int j=0;j<lCount;++j,++i) if(b[i]>=(byte)0xc0) return false;

       }

       return true;

}

相应地,一个使用上述方法的例子如下:

public static String getUrlParam(String aStr,String aDefaultCharset)

throws UnsupportedEncodingException{

       if(aStr==null) return null;

       byte[] lBytes=aStr.getBytes("ISO-8859-1");

       return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset);

}

不过,该方法也存在缺陷,如下两方面:

l 没有包括对用户默认编码的识别,这可以根据请求信息的语言来判断,但不一定正确,因为我们有时候也会输入一些韩文,或者其他文字。

l 可能会错误判断UTF-8字符,一个例子是"学习"两个字,其GBK编码是" \xd1\xa7\xcf\xb0",如果使用上述isValidUtf8方法判断,将返回true。可以考虑使用更严格的判断方法,不过估计效果不大。

有一个例子可以证明google也遇到了上述问题,而且也采用了和上述相似的处理方法,比如,如果在地址栏中输入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=学习",google将无法正确识别,而其他汉字一般能够正常识别。

最后,应该补充说明一下,如果不使用rewrite规则,或者通过表单提交数据,其实并不一定会遇到上述问题,因为这时可以在提交数据时指定希望的编码。另外,中文文件名确实会带来问题,应该谨慎使用。

6. 其它

下面描述一些和编码有关的其他问题。

6.1. SecureCRT

除了浏览器和控制台与编码有关外,一些客户端也很有关系。比如在使用SecureCRT连接linux时,应该让SecureCRT的显示编码(不同的session,可以有不同的编码设置)和linux的编码环境变量保持一致。否则看到的一些帮助信息,就可能是乱码。

另外,mysql有自己的编码设置,也应该保持和SecureCRT的显示编码一致。否则通过SecureCRT执行sql语句的时候,可能无法处理中文字符,查询结果也会出现乱码。

对于Utf-8文件,很多编辑器(比如记事本)会在文件开头增加三个不可见的标志字节,如果作为mysql的输入文件,则必须要去掉这三个字符。(用linux的vi保存可以去掉这三个字符)。一个有趣的现象是,在中文windows下,创建一个新txt文件,用记事本打开,输入"连通"两个字,保存,再打开,你会发现两个字没了,只留下一个小黑点。

6.2. 过滤器

如果需要统一设置编码,则通过filter进行设置是个不错的选择。在filter class中,可以统一为需要的请求或者回应设置编码。参加上述setCharacterEncoding()。这个类apache已经给出了可以直接使用的例子SetCharacterEncodingFilter。

6.3. POST和GET

很明显,以POST提交信息时,URL有更好的可读性,而且可以方便的使用setCharacterEncoding()来处理字符集问题。但GET方法形成的URL能够更容易表达网页的实际内容,也能够用于收藏。

从统一的角度考虑问题,建议采用GET方法,这要求在程序中获得参数是进行特殊处理,而无法使用setCharacterEncoding()的便利,如果不考虑rewrite,就不存在IE的UTF-8问题,可以考虑通过设置URIEncoding来方便获取URL中的参数。

6.4. 简繁体编码转换

GBK同时包含简体和繁体编码,也就是说同一个字,由于编码不同,在GBK编码下属于两个字。有时候,为了正确取得完整的结果,应该将繁体和简体进行统一。可以考虑将UTF、GBK中的所有繁体字,转换为相应的简体字,BIG5编码的数据,也应该转化成相应的简体字。当然,仍旧以UTF编码存储。

例如,对于"语言 語言",用UTF表示为"\xE8\xAF\xAD\xE8\xA8\x80 \xE8\xAA\x9E\xE8\xA8\x80",进行简繁体编码转换后应该是两个相同的 "\xE8\xAF\xAD\xE8\xA8\x80>"。

 

Eceel东西在线 刘科垠

2006-3-8

转自:http://china.eceel.com/article/study_for_character_encoding_java.htm

posted @ 2006-12-12 11:23 保尔任 阅读(210) | 评论 (0)编辑 收藏

这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:

  问题一:

  使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?

  我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?

  问题二:

  最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。

  查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。

0、big endian和little endian

  big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。

  “endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

  我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

1、字符编码、内码,顺带介绍汉字编码

  字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。

  GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。

  GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。

  从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。

  有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。

  这里还有一些细节:

  GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。

  在DBCS中,GB内码的存储格式始终是big endian,即高位在前。

  GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。

2、Unicode、UCS和UTF

  前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。

  Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。

  根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。

  在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。

  目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。

  UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。

  IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。

3、UCS-2、UCS-4、BMP

  UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:

  UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。

  UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。

  group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。

  将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。

4、UTF编码

  UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:

UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx


  例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

  读者可以用记事本测试一下我们的编码是否正确。

  UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。

5、UTF的字节序和BOM

  UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?

  Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:

  在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。

  这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。

  UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

  Windows就是使用BOM来标记文本文件的编码方式的。

6、进一步的参考资料

  本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。

  我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:

"Understanding Unicode A general introduction to the Unicode Standard" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)
"Character set encoding basics Understanding character set encodings and legacy encodings" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter03)

  我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用Windows API和不使用Windows API的版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com)。

  我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。

posted @ 2006-12-12 11:22 保尔任 阅读(292) | 评论 (0)编辑 收藏

链接都已失效,可以在网上搜索。

高效解析XML

详细解析XML与J2EE组合

初学者入门 JAVA 的 XML 编程实例解析

Java中四种XML解析技术之不完全测试

XML认证教程,第 7 部分

详细解析XML与J2EE组合技术的精髓

Java高手解析XML配置文件的读取操作

使用dom4j解析xml    http://www-128.ibm.com/developerworks/cn/xml/x-dom4j.html

初学者入门 JAVA 的 XML 编程实例解析 http://tech.ccidnet.com/art/1077/20050307/219781_1.html

posted @ 2006-12-12 11:21 保尔任 阅读(805) | 评论 (0)编辑 收藏
Calendar与Date、long的转换:
  Calendar ca = Calendar.getInstance();
  Date d = ca.getTime();
  long l = ca.getTimeInMillis();
  ca.setTime(d);
  ca.setTimeInMillis(l);

Date和long间的转换:
  Date d = new Date();
  long l = d.getTime();
  d.setTime(l);
  d = new Date(l);
————————————————————————————————
  当前年月实际的总天数:   
  Calendar   cal   =   new   GregorianCalendar();  
   
  int   year_days     =   cal.getActualMaximum(Calendar.DAY_OF_YEAR   );  
  int   month_days   =   cal.getActualMaximum(Calendar.DAY_OF_MONTH);   
   
  可能出现的最大天数:   
    
  int   month_days   =   cal.getMaximum(Calendar.DAY_OF_MONTH);  
  //   这种方式不随当前日期的影响,如果取   2   月份,总是的到   29  
   
   
  在给   Calendar   指定月份时要注意:     
    
  Java   中的月份,0   -   表示1月份,   .....     11   -   表示12月份,不要搞错了哟   保险的方式是,使用常量:Calendar.JANUARY   ...  

————————————————————————————————


学习日期, 日期格式, 日期的解析和日期的计算

Java 语言的 Calendar,GregorianCalendar (日历),Date(日期), 和DateFormat(日期格式)组成了Java标准的一个基本但是非常重要的部分. 日期是商业逻辑计算一个关键的部分. 所有的开发者都应该能够计算未来的日期, 定制日期的显示格式, 并将文本数据解析成日期对象。学习日期, 日期格式, 日期的解析和日期的计算。

我们将讨论下面的类:

1、 具体类(和抽象类相对)java.util.Date

2、 抽象类java.text.DateFormat 和它的一个具体子类,java.text.SimpleDateFormat

3、 抽象类java.util.Calendar 和它的一个具体子类,java.util.GregorianCalendar

具体类可以被实例化, 但是抽象类却不能. 你首先必须实现抽象类的一个具体子类.

1.   java.util.Date及其格式化
Date 类从Java 开发包(JDK) 1.0 就开始进化, 当时它只包含了几个取得或者设置一个日期数据的各个部分的方法, 比如说月, 日, 和年. 这些方法现在遭到了批评并且已经被转移到了Calendar类里去了, 我们将在本文中进一步讨论它. 这种改进旨在更好的处理日期数据的国际化格式. 就象在JDK 1.1中一样, Date 类实际上只是一个包裹类, 它包含的是一个长整型数据, 表示的是从GMT(格林尼治标准时间)1970年, 1 月 1日00:00:00这一刻之前或者是之后经历的毫秒数.

1.1. 创建java.util.Date
Java统计从1970年1月1日起的毫秒的数量表示日期。也就是说,例如,1970年1月2日,是在1月1日后的86,400,000毫秒。同样的,1969年12 月31日是在1970年1月1日前86,400,000毫秒。Java的Date类使用long类型纪录这些毫秒值.因为long是有符号整数,所以日期可以在1970年1月1日之前,也可以在这之后。Long类型表示的最大正值和最大负值可以轻松的表示290,000,000年的时间,这适合大多数人的时间要求。

让我们看一个使用系统的当前日期和时间创建一个日期对象并返回一个长整数的简单例子. 这个时间通常被称为Java 虚拟机(JVM)主机环境的系统时间.
import java.util.Date;

public class DateExample1 {

public static void main(String[] args) {

// Get the system date/time

Date date = new Date();

// 打印出具体的年,月,日,小时,分钟,秒钟以及时区

System.out.println(date.getTime());

}  

}

在星期六, 2001年9月29日, 下午大约是6:50的样子, 上面的例子在系统输出设备上显示的结果是 1001803809710. 在这个例子中,值得注意的是我们使用了Date 构造函数创建一个日期对象, 这个构造函数没有接受任何参数. 而这个构造函数在内部使用了 System.currentTimeMillis() 方法来从系统获取日期.

//1年前日期

  java.util.Date myDate=new java.util.Date();

  long myTime=(myDate.getTime()/1000)-60*60*24*365;

  myDate.setTime(myTime*1000);

  String mDate=formatter.format(myDate);

//明天日期

  myDate=new java.util.Date();

  myTime=(myDate.getTime()/1000)+60*60*24;

  myDate.setTime(myTime*1000);

  mDate=formatter.format(myDate);

//两个时间之间的天数

  SimpleDateFormat myFormatter = new SimpleDateFormat("yyyy-MM-dd");

  java.util.Date date= myFormatter.parse("2003-05-1");

  java.util.Date mydate= myFormatter.parse("1899-12-30");

  long day=(date.getTime()-mydate.getTime())/(24*60*60*1000);

//加半小时

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

java.util.Date date1 = format.parse("2002-02-28 23:16:00");

long Time=(date1.getTime()/1000)+60*30;

date1.setTime(Time*1000);

String mydate1=formatter.format(date1);

//年月周求日期

SimpleDateFormat formatter2 = new SimpleDateFormat("yyyy-MM F E");

java.util.Date date2= formatter2.parse("2003-05 5 星期五");

SimpleDateFormat formatter3 = new SimpleDateFormat("yyyy-MM-dd");

String mydate2=formatter3.format(date2);

//求是星期几

mydate= myFormatter.parse("2001-1-1");

SimpleDateFormat formatter4 = new SimpleDateFormat("E");

String mydate3=formatter4.format(mydate);

 

 


1.2. Date格式化
能以一种用户明白的格式来显示这个日期呢? 在这里类java.text.SimpleDateFormat 和它的抽象基类 java.text.DateFormat。那么, 现在我们已经知道了如何获取从1970年1月1日开始经历的毫秒数了. 我们如何才format 就派得上用场了.

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

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


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

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


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

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

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

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


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

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

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


那么当我们要把这个 long 值已更为友好的表现形式显示处理的时候,我们可以用它来构造 Date 对象:

Date date = new Date(dateInMilliSeconds);

System.out.println(date);


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

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

假如我们希望定制日期数据的格式, 比方星期六-9月-29日-2001年. 下面的例子展示了如何完成这个工作:

import java.text.SimpleDateFormat;

import java.util.Date;

public class DateExample2 {

public static void main(String[] args) {

SimpleDateFormat bartDateFormat = new SimpleDateFormat("EEEE-MMMM-dd-yyyy"); Date date = new Date();

System.out.println(bartDateFormat.format(date));

}

}


只要通过向SimpleDateFormat 的构造函数传递格式字符串"EEE-MMMM-dd-yyyy", 我们就能够指明自己想要的格式. 你应该可以看见, 格式字符串中的ASCII 字符告诉格式化函数下面显示日期数据的哪一个部分. EEEE是星期, MMMM是月, dd是日, yyyy是年. 字符的个数决定了日期是如何格式化的.传递"EE-MM-dd-yy"会显示 Sat-09-29-01. 请察看Sun 公司的Web 站点获取日期格式化选项的完整的指示.

1.3. 文本数据解析成日期对象
假设我们有一个文本字符串包含了一个格式化了的日期对象, 而我们希望解析这个字符串并从文本日期数据创建一个日期对象. 我们将再次以格式化字符串"MM-dd-yyyy" 调用 SimpleDateFormat类, 但是这一次, 我们使用格式化解析而不是生成一个文本日期数据. 我们的例子, 显示在下面, 将解析文本字符串 "9-29-2001"并创建一个值为001736000000 的日期对象.

通过parse()方法,DateFormat能够以一个字符串创立一个Date对象。这个方法能抛出ParseException异常,所以你必须使用适当的异常处理技术。

例子程序:

import java.text.SimpleDateFormat;

import java.util.Date;

public class DateExample3 {

public static void main(String[] args) {

// Create a date formatter that can parse dates of

// the form MM-dd-yyyy.

SimpleDateFormat bartDateFormat = new SimpleDateFormat("MM-dd-yyyy");

// Create a string containing a text date to be parsed.

String dateStringToParse = "9-29-2001";

try {

// Parse the text version of the date.

// We have to perform the parse method in a

// try-catch construct in case dateStringToParse

// does not contain a date in the format we are expecting.

Date date = bartDateFormat.parse(dateStringToParse);

// Now send the parsed date as a long value

// to the system output.

System.out.println(date.getTime());

}catch (Exception ex) {

System.out.println(ex.getMessage());

}

}

}


1.4. 使用标准的日期格式化过程
既然我们已经可以生成和解析定制的日期格式了, 让我们来看一看如何使用内建的格式化过程. 方法 DateFormat.getDateTimeInstance() 让我们得以用几种不同的方法获得标准的日期格式化过程. 在下面的例子中, 我们获取了四个内建的日期格式化过程. 它们包括一个短的, 中等的, 长的, 和完整的日期格式.

import java.text.DateFormat;

import java.util.Date;

public class DateExample4 {

public static void main(String[] args) {

Date date = new Date();

DateFormat shortDateFormat = DateFormat.getDateTimeInstance(

DateFormat.SHORT, DateFormat.SHORT);

DateFormat mediumDateFormat = DateFormat.getDateTimeInstance(

DateFormat.MEDIUM, DateFormat.MEDIUM);

DateFormat longDateFormat = DateFormat.getDateTimeInstance(

DateFormat.LONG, DateFormat.LONG);

DateFormat fullDateFormat = DateFormat.getDateTimeInstance(

DateFormat.FULL, DateFormat.FULL);

System.out.println(shortDateFormat.format(date)); System.out.println(mediumDateFormat.format(date)); System.out.println(longDateFormat.format(date)); System.out.println(fullDateFormat.format(date));

}

}

注意我们在对 getDateTimeInstance的每次调用中都传递了两个值. 第一个参数是日期风格, 而第二个参数是时间风格. 它们都是基本数据类型int(整型). 考虑到可读性, 我们使用了DateFormat 类提供的常量: SHORT, MEDIUM, LONG, 和 FULL. 要知道获取时间和日期格式化过程的更多的方法和选项, 请看Sun 公司Web 站点上的解释.

运行我们的例子程序的时候, 它将向标准输出设备输出下面的内容:
9/29/01 8:44 PM
Sep 29, 2001 8:44:45 PM
September 29, 2001 8:44:45 PM EDT
Saturday, September 29, 2001 8:44:45 PM EDT

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

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

比如有:

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

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

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

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

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

 


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

Calendar 与 Date 的转换非常简单:

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

 

 

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

1. Calendar 的 set() 方法

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

field 的定义在 Calendar 中

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

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

calendar.set(Calendar.MONTH, 7);

我们通常需要在程序逻辑中set(Calendar.MILLISECOND, 0),否则可能会出现下面的情况:
//ObjectOutputStream和ObjectOutputStream是对象存储的类
//Calendar.MILLISECOND如不设为0则会是个与当前系统时间有关的数

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

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


我们将 Calendar 保存到文件中

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

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


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

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

 

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

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


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

2. Calendar 对象的容错性,Lenient 设置

我们知道特定的月份有不同的日期,当一个用户给出错误的日期时,Calendar 如何处理的呢?

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

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


它的执行结果是:

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


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

3. 不稳定的 Calendar

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

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

  public class UnstableCalendar implements Serializable
  {

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

 


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

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

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

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

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


运行的结果是: Tue Aug 01 00:00:00 PDT 2000,这个问题主要会影响到在 EJB 编程中,参数对象中包含 Calendar 时。经过 Serialize/Deserialize 后,直接操作 Calendar 会产生不稳定的情况。

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

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

add() 有两条规则:

当被修改的字段超出它可以的范围时,那么比它大的字段会自动修正。如:

Calendar cal1 = Calendar.getInstance();

cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31

cal1.add(Calendar.MONTH, 1); //2000-9-31 => 2000-10-1,对吗?System.out.println(cal1.getTime()); //结果是 2000-9-30


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

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

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

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


我们现在已经能够格式化并创建一个日期对象了, 但是我们如何才能设置和获取日期数据的特定部分呢, 比如说小时, 日, 或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类. 就如我们前面提到的那样, Calendar 类中的方法替代了Date 类中被人唾骂的方法.

假设你想要设置, 获取, 和操纵一个日期对象的各个部分, 比方一个月的一天或者是一个星期的一天. 为了演示这个过程, 我们将使用具体的子类 java.util.GregorianCalendar. 考虑下面的例子, 它计算得到下面的第十个星期五是13号.

import java.util.GregorianCalendar;

import java.util.Date;

import java.text.DateFormat;

public class DateExample5 {

public static void main(String[] args) {

DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL);

// Create our Gregorian Calendar.

GregorianCalendar cal = new GregorianCalendar();

// Set the date and time of our calendar

// to the system&s date and time

cal.setTime(new Date());

System.out.println("System Date: " + dateFormat.format(cal.getTime())); // Set the day of week to FRIDAY

cal.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.FRIDAY); System.out.println("After Setting Day of Week to Friday: " + dateFormat.format(cal.getTime()));

int friday13Counter = 0;

while (friday13Counter <= 10) {

// Go to the next Friday by adding 7 days. cal.add(GregorianCalendar.DAY_OF_MONTH, 7);

// If the day of month is 13 we have

// another Friday the 13th.

if (cal.get(GregorianCalendar.DAY_OF_MONTH) == 13) {

friday13Counter++; System.out.println(dateFormat.format(cal.getTime()));

}

}

}

}

在这个例子中我们作了有趣的函数调用:

cal.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.FRIDAY);

和:cal.add(GregorianCalendar.DAY_OF_MONTH, 7);

set 方法能够让我们通过简单的设置星期中的哪一天这个域来将我们的时间调整为星期五. 注意到这里我们使用了常量 DAY_OF_WEEK 和 FRIDAY 来增强代码的可读性. add 方法让我们能够在日期上加上数值. 润年的所有复杂的计算都由这个方法自动处理.

我们这个例子的输出结果是:

System Date: Saturday, September 29, 2001

当我们将它设置成星期五以后就成了: Friday, September 28, 2001

Friday, September 13, 2002

Friday, December 13, 2002

Friday, June 13, 2003

Friday, February 13, 2004

Friday, August 13, 2004

Friday, May 13, 2005

Friday, January 13, 2006

Friday, October 13, 2006

Friday, April 13, 2007

Friday, July 13, 2007

Friday, June 13, 2008

Calendar类的基础即有变量域的观念。每个类元素都是域,并且这些域在Calendar类中表现为静态变量。这些变量域,可以通过get/set类方法来获得或者设置域值。

// 获得默认的Calendar实例,给它设置时间
Calendarcal = Calendar.getInstance();
intyear = cal.get(Calendar.YEAR);
cal.set(Calendar.MONTH,Calendar.NOVEMBER);
Calendar类的add和roll方法提供在日期之间转换的能力。每个方法都由一个参数变量和一个参数值来修改,通过这个可为正数或负数的参数值来修改它。仅仅不同的是,add方法可以向高阶的变量域溢出。例如,如果从九月三号向后倒退三天,将得到:

Calendar cal = Calendar.getInstance();

cal.add(Calendar.DATE,-3);

// 值为: 星期六八月 31 23:43:19 EDT 2002

然而使用roll方法向后回滚三天得出:

Calendar cal = Calendar.getInstance();

cal.roll(Calendar.DATE,-3);

// 值为: 星期一九月 30 23:43:47 EDT 2002
这就是为什么通常主要使用add方法的原因。

还有一个隐藏在最通用的Calendar的子类中的功能性方法--isLeapYear(判断是否为闰年)方法。

Calendar cal = Calendar.getInstance();

booleanleapYear = ( (GregorianCalendar)cal ).isLeapYear(2002);

// 这个值是false

尽管它是一个实例方法,isLeapYear方法的行为表现像静态方法,需要提供年份的参数传值给日历。

其实求几天几月几年前/后的方法,应该用Calendar类比较好的(比Date)。

Calendar cal = Calendar.getInstance();

cal.setTime(date);

cal.add(Calendar.MONTH,1);

cal.add(Calendar.YEAR,2000);

date = cal.getTime();

通过接管日期修改的功能,java.util.Calendar类看上去更像是Data类的复杂版本。但是它还提供额外的功能,更不用说它的国际化支持,使得它值得拥有学习的难度曲线。

3.     使用GregorianCalendar类
创建一个代表任意日期的一个途径使用GregorianCalendar类的构造函数,它包含在java.util包中:

GregorianCalendar(int year, int month, int date)
注意月份的表示,一月是0,二月是1,以此类推,是12月是11。因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类 Calendar使用常量来表示月份:JANUARY, FEBRUARY,等等。所以,创建Wilbur 和 Orville制造第一架动力飞机的日期(December 17, 1903),你可以使用:

GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);

出于清楚的考虑,你应该使用前面的形式。但是,你也应该学习怎样阅读下面的短格式。下面的例子同样表示December 17,1903(记住,在短格式中,11表示December)

    GregorianCalendar firstFlight = new GregorianCalendar(1903, 11, 17);   在上一节中,你学习了转换Date对象到字符串。这里,你可以做同样的事情;但是首先,你需要将GregorianCalendar对象转换到Date。要做到这一点,你可以使用getTime()方法,从它得父类 Calendar继承而来。GetTime()方法返回GregorianCalendar相应的Date对象。你能够创建 GregorianCalendar对象,转换到Date对象,得到和输出相应的字符串这样一个过程。下面是例子:

import java.util.*;

import java.text.*;

public class Flight {

  public static void main(String[] args) {

GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);  

Date d = firstFlight.getTime();

DateFormat df = DateFormat.getDateInstance();

String s = df.format(d);

System.out.println("First flight was " + s);

}

有时候创建一个代表当前时刻的GregorianCalendar类的实例是很有用的。你可以简单的使用没有参数的GregorianCalendar构造函数,象这样:

GregorianCalendar thisday = new GregorianCalendar();
一个输出今天日期的例子程序,使用GregorianCalendar对象:

import java.util.*;
import java.text.*;
class Today {
  public static void main(String[] args) {
GregorianCalendar thisday = new GregorianCalendar();
Date d = thisday.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
    System.out.println("Today is " + s);
  }
}
注意到,Date()构造函数和GregorianCalendar()构造函数很类似:都创建一个对象,条件简单,代表今天。
GregorianCalendar 类提供处理日期的方法。一个有用的方法是add().使用add()方法,你能够增加象年,月数,天数到日期对象中。要使用add()方法,你必须提供要增加的字段,要增加的数量。一些有用的字段是DATE, MONTH, YEAR, 和 WEEK_OF_YEAR。下面的程序使用add()方法计算未来80天的一个日期。在Jules的<环球80天>是一个重要的数字,使用这个程序可以计算Phileas Fogg从出发的那一天1872 年10月2日后80天的日期:

import java.util.*;
import java.text.*;
public class World {
  public static void main(String[] args) {
GregorianCalendar worldTour = new GregorianCalendar(1872, Calendar.OCTOBER, 2);
    worldTour.add(GregorianCalendar.DATE, 80);
Date d = worldTour.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
System.out.println("80 day trip will end " + s);
  }
}
add ()一个重要的副作用是它改变了原来的日期。有时候,拥有原始日期和修改后的日期很重要。不幸的是,你不能简单的创建一个 GregorianCalendar对象,设置它和原来的相等(equal)。原因是两个变量指向同一个Date()对象地址。如果Date对象改变,两个变量就指向改变后的日期对象。代替这种做法,应该创建一个新对象。下面的程序示范了这种做法:import java.util.*;

import java.text.*;

public class ThreeDates {

  public static void main(String[] args) {

GregorianCalendar gc1 = new GregorianCalendar(2000, Calendar.JANUARY, 1);

GregorianCalendar gc2 = gc1;

GregorianCalendar gc3 = new GregorianCalendar(2000, Calendar.JANUARY, 1);
    //Three dates all equal to January 1, 2000

gc1.add(Calendar.YEAR, 1);

//gc1 and gc2 are changed    

DateFormat df = DateFormat.getDateInstance();

Date d1 = gc1.getTime();

Date d2 = gc2.getTime();

Date d3 = gc3.getTime();

String s1 = df.format(d1);

String s2 = df.format(d2);

String s3 = df.format(d3);

System.out.println("gc1 is " + s1);

System.out.println("gc2 is " + s2);

System.out.println("gc3 is " + s3);

  }

}

      程序运行后,gc1和gc2被变成2001年(因为两个对象指向同一个Date,而Date已经被改变了)。对象gc3指向一个单独的Date,它没有被改变。
package com.minght.sys.util;

/**
* <p>Title: 开源,开放</p>
* <p>Description: opeansource</p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: ?海棠</p>
* @author HaiTang Ming
* @version 1.0
*/

import java.util.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.*;


public class timeUtil {

/**
  * 将Date类型日期转化成String类型"任意"格式
  * java.sql.Date,java.sql.Timestamp类型是java.util.Date类型的子类
  * @param date Date
  * @param format String
  *           "2003-01-01"格式
  *           "yyyy年M月d日"
  *           "yyyy-MM-dd HH:mm:ss"格式
  * @return String
  */
public static String dateToString(java.util.Date date,String format) {

    if (date==null || format==null) {
      return null;
    }

    SimpleDateFormat sdf = new SimpleDateFormat(format);
    String str = sdf.format(date);
    return str;
}

/**
  * 将String类型日期转化成java.utl.Date类型"2003-01-01"格式
  * @param str String 要格式化的字符串
  * @param format String
  * @return Date
  */
public static java.util.Date stringToUtilDate(String str,String format) {

    if (str==null||format==null) {
      return null;
    }

    SimpleDateFormat sdf = new SimpleDateFormat(format);

    java.util.Date date = null;
    try
    {
      date = sdf.parse(str);
    }
    catch(Exception e)
    {
    }
    return date;
}


/**
  * 将String类型日期转化成java.sql.Date类型"2003-01-01"格式
  * @param str String
  * @param format String
  * @return Date
  */
public static java.sql.Date stringToSqlDate(String str,String format) {

    if (str==null||format==null) {
      return null;
    }

    SimpleDateFormat sdf = new SimpleDateFormat(format);

    java.util.Date date = null;
    try
    {
      date = sdf.parse(str);
    }
    catch(Exception e)
    {
      return null;
    }
    return new java.sql.Date(date.getTime());
}

/**
  * 将String类型日期转化成java.sql.Date类型"2003-01-01"格式
  * @param str String
  * @param format String
  * @return Timestamp
  */
public static java.sql.Timestamp stringToTimestamp(String str,String format) {

    if (str==null||format==null) {
      return null;
    }

    SimpleDateFormat sdf = new SimpleDateFormat(format);

    java.util.Date date = null;
    try
    {
      date = sdf.parse(str);
    }
    catch(Exception e)
    {
      return null;
    }
    return new java.sql.Timestamp(date.getTime());
}


/**
  * 将java.util.Date日期转化成java.sql.Date类型
  * @param Date
  * @return 格式化后的java.sql.Date
  */
public static java.sql.Date toSqlDate(Date date) {

    if (date==null) {
      return null;
    }

  return new java.sql.Date(date.getTime());
}
/**
  * 将字符串转化为时间格式 string to string
  * @param str String
  * @param format String
  * @return String
  */
public static String toDateString(String str,String oldformat,String newformat){

    return dateToString(stringToUtilDate(str,oldformat),newformat);

}

/**
  * 将日历转化为日期
  * @param calendar Calendar
  * @return Date
  */
public static java.util.Date converToDate(java.util.Calendar calendar){
  return Calendar.getInstance().getTime();
}

/**
  * 将日期转化为日历
  * @param date Date
  * @return Calendar
  */
public static java.util.Calendar converToCalendar(java.util.Date date){
  Calendar calendar = Calendar.getInstance();
  calendar.setTime(date);
  return calendar;
}

/**
  * 求得从某天开始,过了几年几月几日几时几分几秒后,日期是多少
  * 几年几月几日几时几分几秒可以为负数
  * @param date Date
  * @param year int
  * @param month int
  * @param day int
  * @param hour int
  * @param min int
  * @param sec int
  * @return Date
  */
public static java.util.Date modifyDate(java.util.Date date,int year ,int month,int day,int hour,int min,int sec){
  Calendar cal = Calendar.getInstance();
  cal.setTime(date);
  cal.add(Calendar.YEAR,year);
  cal.add(Calendar.MONTH,month);
  cal.add(Calendar.DATE,day);
  cal.add(Calendar.HOUR,hour);
  cal.add(Calendar.MINUTE,min);
  cal.add(Calendar.SECOND,sec);

  return cal.getTime();

}


/**
  * 取得当前日期时间
  * 1:year
  * 2:month
  * 3:day
  */
public static int getCurTime(int i) {
  if (i == 1) {
    return java.util.Calendar.getInstance().get(Calendar.YEAR);
  }
  else if (i == 2) {
    return java.util.Calendar.getInstance().get(Calendar.MONTH) + 1;
  }
  else if (i == 3) {
    return java.util.Calendar.getInstance().get(Calendar.DATE);
  }
  return 0;

}

public static void main(String[] args){
  System.out.println(dateToString(modifyDate(Calendar.getInstance().getTime(),-1,-1,-1,-1,-1,-1),"yyyy-MM-dd HH:mm:ss"));

}

}

加一:为了保证跨年的周属于同一周,java API规定跨年的周都是新的一年的第一周,例如:
 public static void main(String[] args){
  Calendar c = Calendar.getInstance();
  c.set(2005, 11, 31);
  System.out.println(DateUtil.formatDate(c.getTime()));//2006.12.31
  System.out.println(c.get(Calendar.WEEK_OF_MONTH));//06年12月的最后一周
  System.out.println(c.get(Calendar.WEEK_OF_YEAR));//07年的第一周
 }

posted @ 2006-12-12 11:20 保尔任 阅读(492) | 评论 (0)编辑 收藏

第一篇、http://www.blueidea.com/bbs/newsdetail.asp?id=996916(里面有很多例子)

第二篇、彻底明白Java的IO系统(文摘)---JAVA之精髓IO流
一. Input和Output
1. stream代表的是任何有能力产出数据的数据源,或是任何有能力接收数据的接收源。在Java的IO中,所有的stream(包括Input和Out stream)都包括两种类型:
1.1 以字节为导向的stream
以字节为导向的stream,表示以字节为单位从stream中读取或往stream中写入信息。以字节为导向的stream包括下面几种类型:
1) input stream:
1) ByteArrayInputStream:把内存中的一个缓冲区作为InputStream使用
2) StringBufferInputStream:把一个String对象作为InputStream
3) FileInputStream:把一个文件作为InputStream,实现对文件的读取操作
4) PipedInputStream:实现了pipe的概念,主要在线程中使用
5) SequenceInputStream:把多个InputStream合并为一个InputStream
2) Out stream
1) ByteArrayOutputStream:把信息存入内存中的一个缓冲区中
2) FileOutputStream:把信息存入文件中
3) PipedOutputStream:实现了pipe的概念,主要在线程中使用
4) SequenceOutputStream:把多个OutStream合并为一个OutStream
1.2 以Unicode字符为导向的stream
以Unicode字符为导向的stream,表示以Unicode字符为单位从stream中读取或往stream中写入信息。以Unicode字符为导向的stream包括下面几种类型:
1) Input Stream
1) CharArrayReader:与ByteArrayInputStream对应
2) StringReader:与StringBufferInputStream对应
3) FileReader:与FileInputStream对应
4) PipedReader:与PipedInputStream对应
2) Out Stream
1) CharArrayWriter:与ByteArrayOutputStream对应
2) StringWriter:无与之对应的以字节为导向的stream
3) FileWriter:与FileOutputStream对应
4) PipedWriter:与PipedOutputStream对应
以字符为导向的stream基本上对有与之相对应的以字节为导向的stream。两个对应类实现的功能相同,字是在操作时的导向不同。如CharArrayReader:和ByteArrayInputStream的作用都是把内存中的一个缓冲区作为InputStream使用,所不同的是前者每次从内存中读取一个字节的信息,而后者每次从内存中读取一个字符。
1.3 两种不现导向的stream之间的转换
InputStreamReader和OutputStreamReader:把一个以字节为导向的stream转换成一个以字符为导向的stream。
2. stream添加属性
2.1 “为stream添加属性”的作用
运用上面介绍的Java中操作IO的API,我们就可完成我们想完成的任何操作了。但通过FilterInputStream和FilterOutStream的子类,我们可以为stream添加属性。下面以一个例子来说明这种功能的作用。
如果我们要往一个文件中写入数据,我们可以这样操作:
FileOutStream fs = new FileOutStream(“test.txt”);
然后就可以通过产生的fs对象调用write()函数来往test.txt文件中写入数据了。但是,如果我们想实现“先把要写入文件的数据先缓存到内存中,再把缓存中的数据写入文件中”的功能时,上面的API就没有一个能满足我们的需求了。但是通过FilterInputStream和FilterOutStream的子类,为FileOutStream添加我们所需要的功能。
2.2 FilterInputStream的各种类型
2.2.1 用于封装以字节为导向的InputStream
1) DataInputStream:从stream中读取基本类型(int、char等)数据。
2) BufferedInputStream:使用缓冲区
3) LineNumberInputStream:会记录input stream内的行数,然后可以调用getLineNumber()和setLineNumber(int)
4) PushbackInputStream:很少用到,一般用于编译器开发
2.2.2 用于封装以字符为导向的InputStream
1) 没有与DataInputStream对应的类。除非在要使用readLine()时改用BufferedReader,否则使用DataInputStream
2) BufferedReader:与BufferedInputStream对应
3) LineNumberReader:与LineNumberInputStream对应
4) PushBackReader:与PushbackInputStream对应
2.3 FilterOutStream的各种类型
2.2.3 用于封装以字节为导向的OutputStream
1) DataIOutStream:往stream中输出基本类型(int、char等)数据。
2) BufferedOutStream:使用缓冲区
3) PrintStream:产生格式化输出
2.2.4 用于封装以字符为导向的OutputStream
1) BufferedWrite:与对应
2) PrintWrite:与对应
3. RandomAccessFile
1) 可通过RandomAccessFile对象完成对文件的读写操作
2) 在产生一个对象时,可指明要打开的文件的性质:r,只读;w,只写;rw可读写
3) 可以直接跳到文件中指定的位置
4. I/O应用的一个例子
import java.io.*;
public class TestIO{
public static void main(String[] args)
throws IOException{
//1.以行为单位从一个文件读取数据
BufferedReader in =
new BufferedReader(
new FileReader("F:\\nepalon\\TestIO.java"));
String s, s2 = new String();
while((s = in.readLine()) != null)
s2 += s + "\n";
in.close();

//1b. 接收键盘的输入
BufferedReader stdin =
new BufferedReader(
new InputStreamReader(System.in));
System.out.println("Enter a line:");
System.out.println(stdin.readLine());

//2. 从一个String对象中读取数据
StringReader in2 = new StringReader(s2);
int c;
while((c = in2.read()) != -1)
System.out.println((char)c);
in2.close();

//3. 从内存取出格式化输入
try{
DataInputStream in3 =
new DataInputStream(
new ByteArrayInputStream(s2.getBytes()));
while(true)
System.out.println((char)in3.readByte());
}
catch(EOFException e){
System.out.println("End of stream");
}

//4. 输出到文件
try{
BufferedReader in4 =
new BufferedReader(
new StringReader(s2));
PrintWriter out1 =
new PrintWriter(
new BufferedWriter(
new FileWriter("F:\\nepalon\\ TestIO.out")));
int lineCount = 1;
while((s = in4.readLine()) != null)
out1.println(lineCount++ + ":" + s);
out1.close();
in4.close();
}
catch(EOFException ex){
System.out.println("End of stream");
}

//5. 数据的存储和恢复
try{
DataOutputStream out2 =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("F:\\nepalon\\ Data.txt")));
out2.writeDouble(3.1415926);
out2.writeChars("\nThas was pi:writeChars\n");
out2.writeBytes("Thas was pi:writeByte\n");
out2.close();
DataInputStream in5 =
new DataInputStream(
new BufferedInputStream(
new FileInputStream("F:\\nepalon\\ Data.txt")));
BufferedReader in5br =
new BufferedReader(
new InputStreamReader(in5));
System.out.println(in5.readDouble());
System.out.println(in5br.readLine());
System.out.println(in5br.readLine());
}
catch(EOFException e){
System.out.println("End of stream");
}

//6. 通过RandomAccessFile操作文件
RandomAccessFile rf =
new RandomAccessFile("F:\\nepalon\\ rtest.dat", "rw");
for(int i=0; i<10; i++)
rf.writeDouble(i*1.414);
rf.close();

rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "r");
for(int i=0; i<10; i++)
System.out.println("Value " + i + ":" + rf.readDouble());
rf.close();

rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "rw");
rf.seek(5*8);
rf.writeDouble(47.0001);
rf.close();

rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "r");
for(int i=0; i<10; i++)
System.out.println("Value " + i + ":" + rf.readDouble());
rf.close();
}
}
关于代码的解释(以区为单位):
1区中,当读取文件时,先把文件内容读到缓存中,当调用in.readLine()时,再从缓存中以字符的方式读取数据(以下简称“缓存字节读取方式”)。
1b区中,由于想以缓存字节读取方式从标准IO(键盘)中读取数据,所以要先把标准IO(System.in)转换成字符导向的stream,再进行BufferedReader封装。
2区中,要以字符的形式从一个String对象中读取数据,所以要产生一个StringReader类型的stream。
4区中,对String对象s2读取数据时,先把对象中的数据存入缓存中,再从缓冲中进行读取;对TestIO.out文件进行操作时,先把格式化后的信息输出到缓存中,再把缓存中的信息输出到文件中。
5区中,对Data.txt文件进行输出时,是先把基本类型的数据输出屋缓存中,再把缓存中的数据输出到文件中;对文件进行读取操作时,先把文件中的数据读取到缓存中,再从缓存中以基本类型的形式进行读取。注意in5.readDouble()这一行。因为写入第一个writeDouble(),所以为了正确显示。也要以基本类型的形式进行读取。
6区是通过RandomAccessFile类对文件进行操作。

第三篇、花1K内存实现高效I/O的RandomAccessFile类(http://www-128.ibm.com/developerworks/cn/java/l-javaio/index.html),解决RandomAccessFile类效率低下的问题,特别是“与JDK1.4新类MappedByteBuffer+RandomAccessFile的对比”部分讲了怎样用jdk自己的功能实现。

posted @ 2006-12-12 11:19 保尔任 阅读(231) | 评论 (0)编辑 收藏
初始化(initialization)其实包含两部分:
1.类的初始化(initialization class & interface)
2.对象的创建(creation of new class instances)。
因为类的初始化其实是类加载(loading of classes)的最后一步,所以很多书中把它归结为“对象的创建”的第一步。其实只是看问题的角度不同而已。为了更清楚的理解,这里还是分开来。
顺序:
应为类的加载肯定是第一步的,所以类的初始化在前。大体的初始化顺序是:
类初始化 -> 子类构造函数 -> 父类构造函数 -> 实例化成员变量 -> 继续执行子类构造函数的语句
下面结合例子,具体解释一下。
1。类的初始化(Initialization classes and interfaces),其实很简单,具体来说有:
(a)初始化类(initialization of class),是指初始化static field 和执行static初始化块。
例如:
class Super {
        static String s = “initialization static field”; //初始化static field,其中“= “initialization static field” ”又叫做static field initializer
        // static初始化块,又叫做static initializer,或 static initialization block
        static {
        System.out.println(“This is static initializer”);
}
}
btw,有些书上提到static initializer 和 static field initializer 的概念,与之对应的还有 instance initializer 和 instance variable initializer。例子中的注释已经解释了其含义。
(b)初始化接口(initialization of interface),是指初始化定义在该interface中的field。
*注意*
--initialization classes 时,该class的superclass 将首先被初始化,但其实现的interface则不会被初始化。
--initialization classes 时,该class的superclass,以及superlcass的superclass 会首先被递归地初始化,从java.lang.Object一直到该class为止。但initialiazation interface的时候,却不需如此,只会初始化该interface本身。
--对于由引用类变量(class field)所引发的初始化,只会初始化真正定义该field的class。
--如果一个static field是编译时常量(compile-time constant)(即定义为static final field),则对它的引用不会引起定义它的类的初始化。
为了帮助理解最后两点,请试试看下面的例子:
public class Initialization {
       
        public static void main(String[] args) {
               
                System.out.println(Sub.x); // Won't cause initialization of Sub, because x is declared by Super, not Sub.
                                         // 不会引起Sub类的初始化,因为x是定义在Super类中的
                System.out.println("-------------------------");
                System.out.println(Sub.y); // Won't cause initialization of Sub, because y is constant.
                                         // 不会引起Sub类的初始化,因为y是常量
                System.out.println("-------------------------");
                System.out.println(Sub.z = 2004); // Will cause initialization of Sub class
  // 将会引起Sub的初始化
  }
}
class Super{
        static int x = 2006;
}
class Sub extends Super {
       
        static final int y = 2005;
       
static int z;
       
static {
                System.out.println("Initialization Sub");
        }
}
2。对象的创建(creation of new class instances),稍微有点烦琐,具体的步骤如下
(a) 所有的成员变量—包括该类,及它的父类中的成员变量--被分配内存空间,并赋予默认值。(Btw,这里是第一次初始化成员变量)
(b) 为所调用的构造函数初始化其参数变量。(如果有参数)
(c) 如果在构造函数中用this 调用了同类中的其他构造函数,则按照步骤(b)~(f)去处理被调用到的构造函数。
(d) 如果在构造函数中用super调用了其父类的构造函数,则按照步骤(b)~(f)去处理被调用到的父类构造函数。
(e) 按照书写顺序,执行instance initializer 和 instance variable initializer来初始化成员变量。(Btw,这里是第二次初始化成员变量)
(f) 按照书写顺序,执行constructor的其余部分。
*注意*
成员变量其实都被初始化2次,第一次是赋予默认值,第二次才是你想要设定的值。
最后看一个例子:
public class InitializationOrder {
        public static void main(String[] args) {
                Subclass sb = new Subclass();
        }
}
class Super{
       
        static {
                System.out.println(1);
        }
       
        Super(int i){
                System.out.println(i);
        }
}
class Subclass extends Super implements Interface{
       
        static {
                System.out.println(2);
        }       
       
        Super su = new Super(4);
       
        Subclass() {
                super(3);
                new Super(5);
        }
}
interface Interface{
        static Super su = new Super(0);
}
稍微解释一下:
首先,Java虚拟机要执行InitializationOrder类中的static 方法main(),这引起了类的初始化。开始初始化InitializationOrder类。具体的步骤略去不说。
接着,InitializationOrder类初始化完毕后,开始执行main()方法。语句Subclass sb = new Subclass()将创建一个Subclass对象。加载类Subclass后对其进行类初始化,但因为Subclass有一个父类Super,所以先初始化Super类,初始化块static {System.out.println(1);}被执行,打印输出1;
第三,Super初始化完毕后,开始初始化Subclass类。static {System.out.println(2);}被执行,打印输出2;
第四,至此,类的加载工作全部完成。开始进入创建Subclass的对象过程。先为Subclass类和其父类Super类分配内存空间,这时Super su 被附值为null;
第五,执行构造函数Subclass()时,super(3)被执行。如前面(d)所说,Super类的构造函数Super(int i){….}被调用,并按照步骤(b)~(f)来处理。因此,递归调用Super类的父类Object类的构造函数,并按照步骤(b)~(f)来初始化Object类,不过没有任何输入结果。最后打印输出3;
第六,如前面(e)所说,初始化成员变量su,其结果是打印输出4;
第七,如前面(f)所说,执行new Super(5),并打印输出5;
最后,Subclass虽然实现了接口Interface,但是初始化它的时候并不会引起接口的初始化,所以接口Interface中的static Super su = new Super(0)自始至终都没有被执行到。
max做的小改动:
public class Test {
    public static void main(String[] args) {
            Subclass sb = new Subclass();
    }
}
class SS{
 public SS(int i){
  System.out.println(i);
 }
}
class Super{
   
    static {
            System.out.println(1);
    }
    SS ss = new SS(100);
   
    Super(int i){
            System.out.println(i);
    }
}
class Subclass extends Super implements Interface{
   
    static {
            System.out.println(2);
    }       
   
    Super su = new Super(4);
   
    Subclass() {
            super(3);
            new Super(5);
    }
}
interface Interface{
    static Super su = new Super(0);
}
--------------------
结果为:
1
2
100
3
100
4
100
5
posted @ 2006-12-12 11:13 保尔任 阅读(353) | 评论 (0)编辑 收藏
  serialVersionUID 用来表明类的不同版本间的兼容性.如果你修改了此类, 要修改此值. 否则以前用老版本的类序列化的类恢复时会出错.
  可以利用JDK的bin目录下的serialver.exe工具产生这个serialVersionUID
  对于Test.class,执行命令: serialver Test
  为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入private static final long serialVersionUID这个属性,具体数值自己定义。这样,即使某个类在与之对应的对象已经序列化出去后做了修改,该对象依然可以被正确反序列化。否则,如果不显示定义该属性,这个属性值将由JVM根据类的相关信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本不兼容而失败。
  不显示定义这个属性值的另一个坏处是,不利于程序在不同的JVM之间的移植。因为不同的编译器实现的该属性值的计算策略可能不同,从而造成虽然类没有改变,但是因为JVM不同,依然会有因类版本不兼容而无法正确反序列化的现象出现。
  因为我做的系统不太会经常需要序列化类,所以为了去掉这些警告,做如下设置:
Window-Preferences-Java,如图所示,将serializable class without serialVersionUID的设置由warning改为Ignore。然后Eclipse会重新编译程序,那些警告信息也就会消失了。
  小结:如果我们开发大量需要序列化的类的时候,我们最好还是还原为原来的设置。这样可以保证系统的性能和健壮。
-------------------------------------------------------------------
java对象序列化问题研究(转自http://www.54bk.com/user1/2064/archives/2005/1864.html
    序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。
一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
  从上面的叙述中,我们知道了对象序列化是java编程中的必备武器,那么让我们从基础开始,好好学习一下它的机制和用法。
    java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
序列化机制:
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:

处理对象流:
(序列化过程和反序列化过程)
  java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
    我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。
writeObject()方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
下面,让我们从例子中来了解ObjectOutputStream这个类吧。
// 序列化 today's date 到一个文件中.
    FileOutputStream f = new FileOutputStream("tmp");
    ObjectOutputStream s = new ObjectOutputStream(f);
    s.writeObject("Today");
    s.writeObject(new Date());
    s.flush();
   现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:
//从文件中反序列化 string 对象和 date 对象
    FileInputStream in = new FileInputStream("tmp");
    ObjectInputStream s = new ObjectInputStream(in);
    String today = (String)s.readObject();
    Date date = (Date)s.readObject();

定制序列化过程:

序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。
例子:一个非常简单的序列化类。
public class simpleSerializableClass implements Serializable{
    String sToday="Today:";
    transient Date dtToday=new Date();
}
序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:
//重写writeObject()方法以便处理transient的成员。
public void writeObject(ObjectOutputStream outputStream) throws IOException{
    outputStream.defaultWriteObject();//使定制的writeObject()方法可以
                        利用自动序列化中内置的逻辑。
    outputStream.writeObject(oSocket.getInetAddress());
    outputStream.writeInt(oSocket.getPort());
}
//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{
    inputStream.defaultReadObject();//defaultReadObject()补充自动序列化
    InetAddress oAddress=(InetAddress)inputStream.readObject();
    int iPort =inputStream.readInt();
    oSocket = new Socket(oAddress,iPort);
    iID=getID();
    dtToday =new Date();
}

完全定制序列化过程:
如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序
列化的高级教程,以后再述。

 
posted @ 2006-12-12 11:12 保尔任 阅读(352) | 评论 (0)编辑 收藏

Java FAQ
 
目录:
Q1.1 什么是Java、Java2、JDK?JDK后面的1.3、1.4版本号又是怎么回事?
Q1.2 什么是JRE/J2RE?
Q1.3 学习Java用什么工具比较好?
Q1.4  学习Java有哪些好的参考书?
Q1.5  Java和C++哪个更好?
Q1.6  什么是J2SE/J2EE/J2ME?
Q2.1  我写了第一个Java程序,应该如何编译/运行?
Q2.2  我照你说的做了,但是出现什么“'javac' 不是内部或外部命令,也不是可运行的
程序或批处理文件。”。
Q2.3  环境变量怎么设置?
Q2.4  我在javac xxx.java的时候显示什么“unreported exception java.io.IOExcepti
on;”。
Q2.5  javac xxx.java顺利通过了,但是java xxx的时候显示什么“NoClassDefFoundErr
or”。
Q2.6  我在java xxx的时候显示“Exception in thread "main" java.lang.NoSuchMetho
dError: main”。
Q2.7  在java xxx的时候显示“Exception in thread "main" java.lang.NullPointerEx
ception”。
Q2.8 package是什么意思?怎么用?
Q2.9 我没有声明任何package会怎么样?
Q2.10 在一个类中怎么使用其他类?
Q2.11 我用了package的时候显示"NoClassDefFoundError",但是我把所有package去掉的
时候能正常运行。
Q2.12 我想把java编译成exe文件,该怎么做?
Q2.13 我在编译的时候遇到什么"deprecated API",是什么意思?
Q3.1 我怎么给java程序加启动参数,就像dir /p/w那样?
Q3.2 我怎么从键盘输入一个int/double/字符串?
Q3.3 我怎么输出一个int/double/字符串?
Q3.4 我发现有些书上直接用System.in输入,比你要简单得多。
Q3.5 我怎么从文件输入一个int/double/字符串?
Q3.6 我想读写文件的指定位置,该怎么办?
Q3.7 怎么判断要读的文件已经到了尽头?
Q4.1  java里面怎么定义宏?
Q4.2  java里面没法用const。
Q4.3  java里面也不能用goto。
Q4.4  java里面能不能重载操作符?
Q4.5  我new了一个对象,但是没法delete掉它。
Q4.6  我想知道为什么main方法必须被声明为public static?为什么在main方法中不能调
用非static成员?
Q4.7  throw和throws有什么不同?
Q4.8  什么是异常?
Q4.9  final和finally有什么不同?
Q5.1  extends和implements有什么不同?
Q5.2  java怎么实现多继承?
Q5.3 abstract是什么?
Q5.4 public,protected,private有什么不同?
Q5.5 Override和Overload有什么不同?
Q5.6 我继承了一个方法,但现在我想调用在父类中定义的方法。
Q5.7 我想在子类的构造方法中调用父类的构造方法,该怎么办?
Q5.8 我在同一个类中定义了好几个构造方法并且想在一个构造方法中调用另一个。
Q5.9 我没有定义构造方法会怎么样?
Q5.10 我调用无参数的构造方法失败了。
Q5.11 我该怎么定义类似于C++中的析构方法(destructor)?
Q5.12 我想将一个父类对象转换成一个子类对象该怎么做?
Q5.13 其实我不确定a是不是B的实例,能不能分情况处理?
Q5.14 我在方法里修改了一个对象的值,但是退出方法后我发现这个对象的值没变!
Q6.1 java能动态分配数组吗?
Q6.2 我怎么知道数组的长度?
Q6.3 我还想让数组的长度能自动改变,能够增加/删除元素。
Q     什么是链表?为什么要有ArrayList和LinkedList两种List?
Q6.5 我想用队列/栈。
Q6.6 我希望不要有重复的元素。
Q6.7 我想遍历集合/Map。
Q6.8 我还要能够排序。
Q6.9 但是我想给数组排序。
Q6.10 我想按不同方式排序。
Q6.11 Map有什么用?
Q6.12 set方法没问题,但是get方法返回的是Object。
Q6.13 ArrayList和Vector有什么不同?HashMap和Hashtable有什么不同?
Q6.14 我要获得一个随机数。
Q6.15 我比较两个String总是false,但是它们明明都是"abc" !
Q6.16 我想修改一个String但是在String类中没找到编辑方法。
Q6.17 我想处理日期/时间。

一、准备篇

Q1.1 什么是Java、Java2、JDK?JDK后面的1.3、1.4版本号又是怎么回事?
答:Java是一种通用的,并发的,强类型的,面向对象的编程语言(摘自Java规范第二版
)。
JDK是Sun公司分发的免费Java开发工具包,正式名称为J2SDK(Java2 Software Develop K
it)。
包括基本的java工具包和标准类库。
到目前(2003年7月)为止,Java有3个主要版本,即1.0,1.1,2.0;
JDK有1.0,1.1,1.2,1.3,1.4五个版本。
从JDK1.2起,Sun公司觉得Java改变足够大而将java语言版本号提升为2.0。
不同的JDK主要在于提供的类库不同。作为学习你可以下载最新的JDK1.4.2。
真正开发时则应考虑向前兼容,比如1.3。下载请去http://java.sun.com
JDK1.5预计将在2004年推出,届时其中将包含若干崭新的特性。

Q1.2 什么是JRE/J2RE?
答:J2RE是Java2 Runtime Environment,即Java运行环境,有时简称JRE。
如果你只需要运行Java程序或Applet,下载并安装它即可。
如果你要自行开发Java软件,请下载JDK。在JDK中附带有JRE。
注意由于Microsoft对Java的支持不完全,请不要使用IE自带的虚拟机来运行Applet,务必
安装一个JRE或JDK。

Q1.3 学习Java用什么工具比较好?
答:作者建议首先使用JDK+文本编辑器,这有助你理解下列几个基础概念:path,classp
ath,package
并熟悉基本命令:javac和java。并且下载和你的JDK版本一致的API帮助。
如果你不确定类或函数的用法,请先查阅API而不是发贴求助。
当你熟悉Java之后,你可以考虑开始使用一个IDE。
作者推荐eclipse,下载网址http://www.eclipse.org。因为eclispe是免费的,插件化的

eclispe的主要缺点是缺乏一个可视化的桌面程序开发工具,
幸运的是IBM在2003年11月已经将部分代码捐给eclipse组织,可以预计这个缺点很快就会
得到弥补。
无论如何,请不要使用Microsoft的VJ++!众所周知Microsoft从来就没有认真支持过Java

最后但并非最不重要,要有一本好的参考书,并且英文要过关。

Q1.4  学习Java有哪些好的参考书?
答:作者首先推荐Thinking in Java,中文名《Java编程思想》,有中文版。
目前的最新版本是第三版。
http://64.78.49.204可以免费下载英文版。
该书第一章介绍了很多面向对象的编程思想,作为新手应当认真阅读。
除此以外,O'relly出版社和Wrox出版社的书也不错。作者本人不喜欢大陆作者的书。
也许你觉得英文太难,但是网上大多数资料都是英文的。另外,你需要经常查阅API,而那
也是英文的。

Q1.5  Java和C++哪个更好?
答:这个问题是一个很不恰当的问题。你应该问:Java和C++哪个更适用于我的项目?
Java的优点和缺点一样明显。
跨平台是Java的主要优点,但代价是运行速度的下降。
VC和Windows平台有良好的集成和足够快的速度,但是也只能局限在Windows平台上。
和C++相比,Java学起来更快,开发人员不会碰到很多容易出错的特性。
但是VB程序员甚至只需要拼装模块就可以了。

Q1.6  什么是J2SE/J2EE/J2ME?
答:J2SE就是一般的Java。
J2ME是针对嵌入式设备的,比如支持Java的手机,它有自己的JRE和SDK。
J2EE是一组用于企业级程序开发的规范和类库,它使用J2SE的JRE。

二、命令篇

Q2.1  我写了第一个Java程序,应该如何编译/运行?
答:首先请将程序保存为xxx.java文件,注意你可能需要修改文件后缀名。
然后在dos窗口下使用javac xxx.java命令,你会发现该目录下多了一个xxx.class文件,

再使用java xxx命令,你的java程序就开始运行了。

Q2.2  我照你说的做了,但是出现什么“'javac' 不是内部或外部命令,也不是可运行的
程序或批处理文件。”。
答:你遇到了path问题。操作系统在一定的范围(path)内搜索javac.exe,但是没能找到。

请编辑你的操作系统环境变量,新增一个JAVA_HOME变量,设为你JDK的安装目录,
再编辑Path变量,加上一项 %JAVA_HOME%\bin。
然后保存并新开一个dos窗口,你就可以使用javac和java命令了。

Q2.3  环境变量怎么设置?
答:请向身边会设的人咨询。

Q2.4  我在javac xxx.java的时候显示什么“unreported exception java.io.IOExcepti
on;”。
答:参见Q4.8以了解java中的异常机制。

Q2.5  javac xxx.java顺利通过了,但是java xxx的时候显示什么“NoClassDefFoundErr
or”。
答:1. 你遇到了classpath问题。java命令在一定的范围(classpath)内搜索你直接或间接
使用的class文件,但是未能找到。
首先请确认你没有错敲成java xxx.class,
其次,检查你的CLASSPATH环境变量,其实你并不需要设置该变量,
但如果你设置了该变量又没有包含.(代表当前目录)的项,
你就会遇到这个问题。请在你的CLASSPATH环境变量中加入一项. 或干脆删掉这个变量。

2. 如果你使用了并非JDK自带的标准包,比如javax.servlet.*包,也会遇到这个问题,请
将相应的jar文件加入classpath。
3. 如果你在java源文件中定义了package,请参见Q2.11。


Q2.6  我在java xxx的时候显示“Exception in thread "main" java.lang.NoSuchMetho
dError: main”。
答:首先,在你的程序中每个java文件有且只能有一个public类,
这个类的类名必须和文件名的大小写完全一样。
其次,在你要运行的类中有且只能有一个public static void main(String[] args)方法

这个方法就是你的主程序。


Q2.7  在java xxx的时候显示“Exception in thread "main" java.lang.NullPointerEx
ception”。
答:在程序中你试图在值为null的对象变量上调用方法,请检查你的程序确保你的对象被恰当的初始化。
参见Q4.8以了解java中的异常机制。


Q2.8 package是什么意思?怎么用?
答:为了唯一标识每个类并分组,java使用了package的概念。
每个类都有一个全名,例如String的全名是java.lang.String,其中java.lang是包名,S
tring是短名。按照java命名惯例,包名是全部小写的,而类名的第一个字母是大写的。
这样,如果你自行定义了同样名字的类String,你可以把它放在mypackage中,
通过使用全名mypackage.String和java.lang.String来区分这两个类。
同时,将逻辑上相关的类放在同一个包中,可以使程序结构更为清楚。
为了定义包,你要做的就是在java文件开头加一行“package mypackage;”。
注意包没有嵌套或包含关系,mypackage包和mypackage.mysubpackage包对JRE来说是并列的两个包(虽然开发者可
能暗示包含关系)。

Q2.9 我没有声明任何package会怎么样?
答:你的类被认为放在默认包中。这时全名和短名是一致的。

Q2.10 在一个类中怎么使用其他类?
答:如果你使用java.lang包或者默认包中的类,不用做任何事。
如果你的类位于mypackage包中,并且要调用同一包中的其他类,也不用做任何事。
如果你使用其他包中的类,在package声明之后,类声明之前使用import otherpackage1.Class
1; 或 import otherpackage2.*; 
这里.*表示引入这个包中的所有类。然后在程序中你可以使用其他类的短名。
如果短名间有重名冲突,必须使用全名来区分。
注意在使用其他包中的类时,你只能使用public的类和接口,参见Q5.4。

Q2.11 我用了package的时候显示"NoClassDefFoundError",但是我把所有package去掉的
时候能正常运行。
答:将你的java文件按包名组织存放。
比如你的工作目录是/work,你的类是package1.Class1,那么将它存放为/work/package1
/Class1.java。
如果没有声明包,那么直接放在/work下。
在/work下执行javac package1/class1.java,再执行java package1.class1,你会发现一
切正常。
另外,如果你的类的个数已经多到了你需要使用包来组织的话,你可以考虑开始使用IDE。

Q2.12 我想把java编译成exe文件,该怎么做?
答:JDK只能将java源文件编译为class文件。
class文件是一种跨平台的字节码,必须依赖平台相关的JRE来运行。Java以此来实现跨平
台性。
有些开发工具可以将java文件编译为exe文件。作者反对这种做法,因为这样就取消了跨平
台性。
如果你确信你的软件只在Windows平台上运行,你可以考虑使用C++/C#来编程。

Q2.13 我在编译的时候遇到什么"deprecated API",是什么意思?
答:所谓deprecated是指已经过时,但是为了向前兼容起见仍然保留的方法。
这些方法可能会在以后取消支持。你应当改用较新的方法。
在API里面会说明你应当用什么方法来代替之。

三、I/O篇

Q3.1 我怎么给java程序加启动参数,就像dir /p/w那样?
答:还记得public static void main(String[] args)吗?这里的args就是你的启动参数

在运行时你输入java package1.class1 arg1 arg2,args中就会有两个String,第一个是
arg1,第二个是arg2。

Q3.2 我怎么从键盘输入一个int/double/字符串?
答:java的I/O操作比C++要复杂一点。如果要从键盘输入,样例代码如下:
BufferedReader cin = new BufferedReader( new InputStreamReader( System.in ) );

String s = cin.readLine();
这样你就获得了一个字符串,如果你需要数字的话再使用:
int n = Integer.parseInt( s ); 或者 double d = Double.parseDouble( s );
来将字符串"534"转换成int或double。

Q3.3 我怎么输出一个int/double/字符串?
答:使用System.out.println(n)或者System.out.println("Hello")等等。

Q3.4 我发现有些书上直接用System.in输入,比你要简单得多。
答:java使用unicode,是双字节。而System.in是单字节的stream。
如果你要输入双字节文字比如中文,请使用作者的做法。

Q3.5 我怎么从文件输入/输出一个int/double/字符串?
答:类似于从键盘输入,只不过换成
BufferedReader fin = new BufferedReader( new FileReader(" myFileName " ) );
PrintWriter fout = new PrintWriter( new FileWriter(" myFileName " ) );
另外如果你还没下载API,请开始下载并阅读java.io包中的内容。

Q3.6 我想读写文件的指定位置,该怎么办?
答:java.io.RandomAccessFile可以满足你的需要。

Q3.7 怎么判断要读的文件已经到了尽头?
答:在Reader的read方法中明确说明返回-1表示流的结尾。

四、 关键字篇

Q4.1  java里面怎么定义宏?
答:java不支持宏,因为宏代换不能保证类型安全。
如果你需要定义常量,可以将它定义为某个类的static final成员。参见Q4.2和Q4.6。


Q4.2  java里面没法用const。
答:你可以用final关键字。例如 final int m = 9。被声明为final的变量不能被再次赋
值。唯一的例外是所谓blank final,如下例所示:
public class MyClass1 {
    private final int a = 3;
    private final int b; // blank final

    public MyClass1() {
        a = 5; // 不合法,final变量不能被再次赋值。
        b = 4; // 合法,这是b第一次被赋值。
        b = 6; // 不合法,b不能被再次赋值。
    }
}
final也可以用于声明方法或类,被声明为final的方法或类不能被继承。
注意const是java的保留字以备扩充。

Q4.3  java里面也不能用goto。
答:甚至在面向过程的语言中你也可以完全不用goto。请检查你的程序流程是否合理。

如果你需要从多层循环中迅速跳出,java增强了(和C++相比)break和continue的功能,
支持label。
例如:
outer :
while( ... )
{
inner :
for( ... )
{
           ...   break inner; ...
           ... continue outer; ...
}
}
和const一样,goto也是java的保留字以备扩充。

Q4.4  java里面能不能重载操作符?
答:不能。String的+号是唯一一个内置的重载操作符。你可以通过定义接口和方法来实现
类似功能。

Q4.5  我new了一个对象,但是没法delete掉它。
答:java有自动内存回收机制,即所谓Garbarge Collection。你不需要删除对象。你再也
不用担心指针错误,内存溢出了。

Q4.6  我想知道为什么main方法必须被声明为public static?为什么在main方法中不能调
用非static成员?
答:声明为public是为了这个方法可以被外部调用,详情见Q5.4。
static是为了将某个成员变量/方法关联到类(class)而非实例(instance)。
你不需要创建一个对象就可以直接使用这个类的static成员,因而在static成员中不能调
用非static成员,因为后者是关联到对象实例(instance)的。
在A类中调用B类的static成员可以使用B.staticMember的写法。
注意一个类的static成员变量是唯一的,被所有该类对象所共享的,在多线程程序设计中尤其要谨慎小心。
类的static成员是在类第一次被JRE装载的时候初始化的。
你可以使用如下方法来使用非static成员:
public class A
{
    private void someMethod() //非static成员
    {}
    public static void main(String args)
    {
         A a = new A();  //创建一个对象实例
         a.someMethod();  //现在你可以使用非static方法了
    }
}


Q4.7  throw和throws有什么不同?
答:throws用于方法声明中,声明一个方法会抛出哪些异常。而throw是在方法体中实际执行抛出异常的
动作。
如果你在方法中throw一个异常,却没有在方法声明中声明之,编译器会报错。
注意Error和RuntimeException的子类是例外,无需特别声明。

Q4.8  什么是异常?
答:异常最早在Ada语言中引入,用于在程序中动态处理错误并恢复。
你可以在方法中拦截底层异常并处理之,也可以抛给更高层的模块去处理。
你也可以抛出自己的异常指示发生了某些不正常情况。常见的拦截处理代码如下:
try
{
......//以下是可能发生异常的代码
        ...... //异常被你或低层API抛出,执行流程中断并转向拦截代码。
        ......
}
catch(Exception1 e) //如果Exception1是Exception2的子类并要做特别处理,应排在前

{
  //发生Exception1时被该段拦截
}
catch(Exception2 e)
{
  //发生Exception2时被该段拦截
}
finally //这是可选的
{
   //无论异常是否发生,均执行此段代码
   //即使在catch段中又向外抛出了新的exception,finally段也会得到执行。
}

Q4.9  final和finally有什么不同?
答:final请见Q4.2。finally用于异常机制,参见Q4.8。

五、 面向对象篇

Q5.1  extends和implements有什么不同?
答:对于class而言,extends用于(单)继承一个类(class),而implements用于实现一个接口(interf
ace)。
interface的引入是为了部分地提供多继承的功能。
在interface中只需声明方法头,而将方法体留给实现的class来做。
这些实现的class的实例完全可以当作interface的实例来对待。
在interface之间也可以声明为extends(多继承)的关系。
注意一个interface可以extends多个其他interface。

Q5.2  java怎么实现多继承?
答:java不支持显式的多继承。
因为在显式多继承的语言例如c++中,会出现子类被迫声明祖先虚基类构造函数的问题,

而这是违反面向对象的封装性原则的。
java提供了interface和implements关键字来部分地实现多继承。参见Q5.1。

Q5.3 abstract是什么?
答:被声明为abstract的方法无需给出方法体,留给子类来实现。
而如果一个类中有abstract方法,那么这个类也必须声明为abstract。
被声明为abstract的类无法实例化,尽管它可以定义构造方法供子类使用。

Q5.4 public,protected,private有什么不同?
答:这些关键字用于声明类和成员的可见性。
public成员可以被任何类访问,
protected成员限于自己和子类访问,
private成员限于自己访问。
Java还提供了第四种的默认可见性,一般称为package private,当没有任何public,protected,private修饰符时,成员
是同一包内可见。
类可以用public或默认来修饰。

Q5.5 Override和Overload有什么不同?
答:Override是指父类和子类之间方法的继承关系,这些方法有着相同的名称和参数类型

Overload是指同一个类中不同方法(可以在子类也可以在父类中定义)间的关系,
这些方法有着相同的名称和不同的参数类型。


Q5.6 我继承了一个方法,但现在我想调用在父类中定义的方法。
答:用super.xxx()可以在子类中调用父类方法。

Q5.7 我想在子类的构造方法中调用父类的构造方法,该怎么办?
答:在子类构造方法的第一行调用super(...)即可。

Q5.8 我在同一个类中定义了好几个构造方法并且想在一个构造方法中调用另一个。
答:在构造方法第一行调用this(...)。

Q5.9 我没有定义构造方法会怎么样?
答:自动获得一个无参数的构造方法。

Q5.10 我调用无参数的构造方法失败了。
答:如果你至少定义了一个构造方法,就不再有自动提供的无参数的构造方法了。
你需要另外显式定义一个无参数的构造方法。
另外一种可能是你的构造方法或者类不是public的,参见Q5.4了解java中的可见性。

Q5.11 我该怎么定义类似于C++中的析构方法(destructor)?
答:提供一个void finalize()方法。在Garbarge Collector回收该对象时会调用该方法。

注意实际上你很难判断一个对象会在什么时候被回收。作者从未感到需要用到该方法。


Q5.12 我想将一个父类对象转换成一个子类对象该怎么做?
答:强制类型转换。如
public void meth(A a)
{
B b = (B)a;
}
如果a实际上并不是B的实例,会抛出ClassCastException。所以请确保a确实是B的实例。


Q5.13 其实我不确定a是不是B的实例,能不能分情况处理?
答:可以使用instanceof操作符。例如
if( a instanceof B )
{
B b = (B)a;
}
else
{
...
}

Q5.14 我在方法里修改了一个对象的值,但是退出方法后我发现这个对象的值没变!
答:很可能你把传入参数重赋了一个新对象,例如下列代码就会造成这种错误:
public void fun1(A a) //a是局部参数,指向了一个外在对象。
{
a = new A(); //a指向了一个新对象,和外在对象脱钩了。如果你要让a作为传出变量,
不要写这一句。
        a.setAttr(attr);//修改了新对象的值,外在对象没有被修改。
}
基本类型也会出现这种情况。例如:
public void fun2(int a)
{
a = 10;//只作用于本方法,外面的变量不会变化。
}

六、java.util篇

Q6.1 java能动态分配数组吗?
答:可以。例如int n = 3; Language[] myLanguages = new Language[n];

Q6.2 我怎么知道数组的长度?
答:用length属性。如上例中的  myLanguages.length 就为 3。

Q6.3 我还想让数组的长度能自动改变,能够增加/删除元素。
答:用顺序表--java.util.List接口。
你可以选择用ArrayList或是LinkedList,前者是数组实现,后者是链表实现。
例如:  List list = new ArrayList(); 或是 List list = new LinkedList();  。

Q    什么是链表?为什么要有ArrayList和LinkedList两种List?
答:请补习数据结构。

Q6.5 我想用队列/栈。
答:用java.util.LinkedList。

Q6.6 我希望不要有重复的元素。
答:用集合--java.util.Set接口。例如:Set set = new HashSet()。

Q6.7 我想遍历集合/Map。
答:用java.util.Iterator。参见API。

Q6.8 我还要能够排序。
答:用java.util.TreeSet。例如:Set set = new TreeSet()。放进去的元素会自动排序

你需要为元素实现Comparable接口,还可能需要提供equals()方法,compareTo()方法,h
ashCode()方法。

Q6.9 但是我想给数组排序。
答:java.util.Arrays类包含了sort等实用方法。

Q6.10 我想按不同方式排序。
答:为每种方式定义一个实现了接口Comparator的排序类并和Arrays或TreeSet综合运用。


Q6.11 Map有什么用?
答:存储key-value的关键字-值对,你可以通过关键字来快速存取相应的值。

Q6.12 set方法没问题,但是get方法返回的是Object。
答:强制类型转换成你需要的类型。参见Q5.12。

Q6.13 ArrayList和Vector有什么不同?HashMap和Hashtable有什么不同?
答:ArrayList和HashMap是多线程不安全的,在多个线程中访问同一个ArrayList对象可能
会引起冲突并导致错误。
而Vector和Hashtable是多线程安全的,即使在多个线程中同时访问同一个Vector对象也不
会引起差错。
看起来我们更应该使用Vector和Hashtable,但是实际上Vector和Hashtable的性能太差,
所以如果你不在多线程中使用的话,还是应该用ArrayList和HashMap。

Q6.14 我要获得一个随机数。
答:使用java.util.Random类。

Q6.15 我比较两个String总是false,但是它们明明都是"abc" !
答:比较String一定要使用equals或equalsIgnoreCase方法,不要使用 == !
==比较的是两个引用(变量)是否指向了同一个对象,而不是比较其内容。

Q6.16 我想修改一个String但是在String类中没找到编辑方法。
答:使用StringBuffer类。
String str = "......."; //待处理的字符串
StringBuffer buffer = new StringBuffer(str); //使用该字符串初始化一个StringBuf
fer
buffer.append("..."); //调用StringBuffer的相关API来编辑字符串
String str2 = buffer.toString(); //获得编辑后的字符串。
另外,如果你需要将多个字符串连接起来,请尽量避免使用+号直接连接,而是使用Strin
gBuffer.append()方法。

Q6.17 我想处理日期/时间。
答:使用java.util.Date类。你可以使用java.text.SimpleDateFormat类来在String和Da
te间互相转换。
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //规
定日期格式
Date date = formatter.parse("2003-07-26 18:30:35"); //将符合格式的String转换为
Date
String s = formatter.format(date); //将Date转换为符合格式的String
关于定义日期格式的详细信息请参见API。
 

J2EE FAQ
 
目录:

一、准备篇
Q1.1   什么是J2EE?它和普通的Java有什么不同?
Q1.2   J2EE好学吗?
Q1.3   J2EE有什么用?
Q1.4   学J2EE有前途吗?
Q1.5   据说J2EE的性能不如.NET好,是真的吗?
Q1.6   听你说了这么多,我想学着玩玩。
Q1.7   学习J2EE该怎么开始?
Q1.8   我下了一个J2EE服务器但是不会配置。
Q1.9   我发现你没有提到Tomcat。

二、 Servlet/JSP篇
Q2.1   什么是Servlet?
Q2.2   我怎么获得Http请求里的参数?
Q2.3   我怎么返回结果?
Q2.4   sendRedirect()和forward()有什么不同?
Q2.5   我写了一个Servlet程序,怎么运行它?
Q2.6   EAR和WAR有什么不同?
Q2.7   EAR格式是怎样的?
Q2.8   WAR格式是怎样的?
Q2.9   我的普通HTML文件/JSP文件应当放在哪里?
Q2.10  我访问不到servlet,甚至连HTML文件都访问不到!
Q2.11  我能访问HTML但是访问不到servlet。
Q2.12  什么是JSP?它和Servlet有什么区别?
Q2.13  我的JSP显示的汉字是乱码。
Q2.14  为什么使用gb18030而不是gb2312?
Q2.15  在JSP里面怎么引用Java Bean。
Q2.16  我想在servlet间传递数据。
Q2.17  怎么调用cookie?
Q2.18  怎么在JSP里面实现文件下载?
Q2.19  怎么实现文件上传?
Q2.20  我想让页面自动刷新,比如聊天室。
Q2.21  我想让用户登录以后才能访问页面。
Q2.22  我想要能注册用户。
Q2.23  怎么在JSP中访问数据库?
Q2.24  什么是JSTL?

一、准备篇

Q1.1  什么是J2EE?它和普通的Java有什么不同?
答:J2EE全称为Java2 Platform, Enterprise Edition。
“J2EE平台本质上是一个分布式的服务器应用程序设计环境——一个Java环境,它提供了

·宿主应用的一个运行基础框架环境。
·一套用来创建应用的Java扩展API。”

Q1.2  J2EE好学吗?
答:J2EE是很多技术的集合体,并且还在成长中。
你会遇到很多专有名词:比如(X)HTML,Servlet/JSP,JDBC,JMS,JNDI,EJB,XML,Web
Service……。
尤其是XML和Web Service正在快速成长。幸运的是,你不需要等到学会所有技术后再开始
编程。
大体上J2EE可以分成3个主要应用方式:Servlet/JSP,EJB,Web Service 和一些支撑技术
例如JDBC和JNDI。
你可以一个一个的学。

Q1.3 J2EE有什么用?
答:用来建设大型的分布式企业级应用程序。或者用更时髦的名词说就是“电子商务”应
用程序。
这些企业可能大到拥有中心数据库服务器,Web服务器集群和遍布全国的办公终端,也可能
小到只不过想做一个网站。但是你肯定听过“杀鸡焉用牛刀”的古训。

Q1.4 学J2EE有前途吗?
答:在这一市场目前只有一种技术可以和J2EE竞争,那就是Microsoft的.NET。
相对来说.NET要“新”一些而J2EE要“老”一些。这也意味着.NET更易用一点而J2EE更成
熟一点。
但是.NET只能用于Windows平台(Microsoft声称要开发C#在Linux上的虚拟机但是尚未兑现
该诺言)。
在过去几年,.NET的市场份额并不理想。不过Microsoft还有Longhorn这一杀手锏,鹿死谁
手还很难说。

Q1.5 据说J2EE的性能不如.NET好,是真的吗?
答:在Sun公司提供的样例程序Pet Store上,Microsoft声称不如相同的.NET程序好。
而Sun公司反驳说这一程序不能真正体现J2EE的性能,并且指责Microsoft在数据库上做了
优化。
作者没有学习过.NET因而不能妄下断言。
无论如何,大型分布式程序中的性能瓶颈通常首先来自于错误的设计。

Q1.6 听你说了这么多,我想学着玩玩。
答:除非你想靠它当饭吃或者作为技术储备,否则请不要浪费你的时间。
Flash要好玩得多。计算机游戏就更加好玩了。

Q1.7 学习J2EE该怎么开始?
答:首先,下载一个免费的J2EE服务器。其次,去java.sun.com下载J2EE的API。第三,找
一本好的参考书。最后,找一个顺手的IDE。
J2EE服务器。你可以用Sun的J2EE SDK(免费),或者Weblogic(性能最好,但是太大,而
且作者不推荐盗版行为),
或者JBoss(免费,就是文档太少),或者JRun(开发版免费,作者用这个)。
参考书作者感觉Wrox的《J2EE服务器端高级编程》不错,但是太老(作者手头的是2001年
中文版)。
(似乎很多人不喜欢这本书......所以你得自己判断它是否适合你。)
你还需要去下载一些最新的技术资料(当然肯定是英文的)。
IDE如果你的机器配置够好(内存至少512M以上,256M或以下请勿考虑),可以用IBM的WS
AD,不然就继续用Eclipse或者其他。
你也可以经常去水木清华的Java版逛逛,但是在发贴前先看看精华区里有没有你要的答案

Q1.8 我下了一个J2EE服务器但是不会配置。
答:请认真阅读随机指导文档,不同的服务器的配置都不一样,作者爱莫能助。

Q1.9 我发现你没有提到Tomcat。
答:Tomcat只是一个Web服务器,更准确地说主要只是一个Web Container。
如果你想要学习EJB的话,Tomcat无法满足你的需要。

二、 Servlet/JSP篇

Q2.1 什么是Servlet?
答:一个Servlet是一个Java类。它处理Http(s)请求并作出响应,包括返回一个HTML页面
或转交给其他URL处理。
Servlet必须运行在一个Web Container例如Tomcat中。
Servlet必须是javax.servlet.http.HttpServlet的子类,
你可以继承doGet()或者doPost()方法,两者分别对应于Http(s)中的Get请求和Post请求。


Q2.2 我怎么获得Http请求里的参数?
答:HttpRequest的getParameter()方法。例如:String paramValue = request.getPara
meter("paramName");

Q2.3 我怎么返回结果?
答:你可以利用相关API打开一个输出流,并向流中直接写入一个HTML页面。
但是作者完全不赞成这样做。一方面这样做会很罗嗦。
另一方面从Model-View-Controller模式(在《J2EE核心模式》中被归为Front Controlle
r模式)的观点来看,
你应当提供一些HTML或者JSP作为视图(view),而Servlet则根据请求参数决定转到哪一
个视图。
你可以利用response.sendRedirect(...)方法或request.getDispatcher(...).forward()
方法来实现。

Q2.4 sendRedirect()和forward()有什么不同?
答:sendRedirect()是向浏览器发送一个redirect通知,浏览器向新的URL发送一个新的请
求。
而forward是在服务器端直接将请求转到新的URL,对于浏览器是透明的。
换而言之,sendRedirect()应当将共享数据放在session中,forward应当将共享数据放在
request中(当然你也可以放在session中,但放在request中可以有效减小session中的数
据量,从而改善性能)。
前者浏览器的地址栏显示的是新的URL,后者浏览器的地址栏显示的是Servlet的URL。
因而当刷新目标URL时,两者会造成一些差别。

Q2.5 我写了一个Servlet程序,怎么运行它?
答:开发J2EE程序有一个部署(deploy)的概念,实际上是开发——部署——运行的三部
曲。
大多数服务器支持Hot deploy。你只需要在相应的Application目录(具体路径依赖于服务
器)下面
建立一个符合WAR或EAR格式(参见Q2.7,Q2.8)的目录,启动服务器,就可以通过浏览器
访问了。
特别的,你的Servlet的class文件应当放在/WEB-INF/classes目录中。
注意J2EE SDK不支持Hot deploy,你需要通过它的deploy tool来部署。
Tomcat只支持WAR格式。

Q2.6 EAR和WAR有什么不同?
答:EAR是一个完整的J2EE应用程序,包括Web部分和EJB部分。
WAR只是其中的Web部分。

Q2.7 EAR格式是怎样的?
答:一个EAR可以包含任意多个WAR或EJB JAR,并且包含一个META-INF的目录。
在/META-INF中包含了一个application.xml,其中描述了这个EAR包含哪些模块,以及安全
性配置。
细节请看参考书。

Q2.8 WAR格式是怎样的?
答:一个WAR包含一个WEB-INF的目录,这个目录下包含classes目录,lib目录和web.xml。

/WEB-INF/classes存放按package组织的class文件,/WEB-INF/lib目录存放jar文件,
web.xml描述了很多东西,请读参考书。

Q2.9 我的普通HTML文件/JSP文件应当放在哪里?
答:放在除了/WEB-INF以外的其他地方。

感谢antegg网友对于安全性的提醒:
如果你想直接用http://url/***.jsp的方式来访问,就要像上面说得那样放。
但是这样的做法是不安全的,安全的做法是把所有的JSP页面放在/WEB-INF目录下面,并且

通过WEB-CONTAINER来访问。

作者意见:
我更喜欢用filter来做安全性检查。
在MVC模式中,JSP只是一个视图而已,一般无需特别担忧安全性。和普通的html放在一起
也利于维护。

Q2.10 我访问不到servlet,甚至连HTML文件都访问不到!
答:
第一你没启动服务器。
第二你敲错了端口。
第三你没有正确配置context-path。
第四你的服务器不支持auto reload或者你关闭了这一选项,你得重启服务器或重新部署W
AR。
第五确认你没有把HTML放在/WEB-INF目录下,那是访问不到的。

Q2.11 我能访问HTML但是访问不到servlet。
答:请检查你的web.xml文件。确保你正确定义了<servlet>和<servlet-mapping>元素。

前者标识了一个servlet,后者将一个相对于context-path的URL映射到一个servlet。
在Tomcat中你可以通过/context-path/servlet/package/servletname的形式访问servlet

但是这只是Tomcat的便捷访问方式,并不是正式规范。
细节请看参考书。

Q2.12  什么是JSP?它和Servlet有什么区别?
答:你可以将JSP当做一个可扩充的HTML来对待。
虽然在本质上JSP文件会被服务器自动翻译为相应的Servlet来执行。
可以说Servlet是面向Java程序员而JSP是面向HTML程序员的,除此之外两者功能完全等价

Q2.13  我的JSP显示的汉字是乱码。
答:在你的JSP开头加上一行 <%@ page contentType="text/html; charset=gb18030"%>

如果你已经声明了page我想你知道该怎么修改。

Q2.14  为什么使用gb18030而不是gb2312?
答:gb18030是继gb2312之后的下一代汉字编码标准,最终将过渡到Unicode。

Q2.15  在JSP里面怎么引用Java Bean。
答:首先,确认你要引用的类在/WEB-INF/classes下或在/WEB-INF/lib的某个jar内。
其次,在JSP里加一行 <jsp:useBean id="..." scope="..." class="..."/>
具体解释请看参考书。

Q2.16  我想在servlet间传递数据。
答:利用session。在Servlet/JSP中,你可以在4个地方保存数据。
1) page,本页面。
2) session,用来存放客户相关的信息,比如购物车,对应接口为javax.servlet.http.H
ttpSession。
session机制实际上是cookie和URL Rewriting的抽象,服务器会自动使用cookie或URL Re
writing来实现。
3) request,可以在forward()时传递信息,对应接口为javax.servlet.http.HttpReques
t。
4) application,或称context,存放全局信息,对应接口为javax.servlet.ServletCont
ext。

Q2.17  怎么调用cookie?
答:作者建议使用session,你总是会遇到某些禁用cookie的用户。这时session会自动使
用URL重写来实现。

Q2.18  怎么在JSP里面实现文件下载?
答:实际上这是一个HTML的问题。答案是一个超链接<a>。

Q2.19  怎么实现文件上传?
答:客户端是HTML问题,在form中设置method为post,enctype为multi-part/form-data,
加一个<input type="file">。
而在接收的servlet中只是一个I/O问题,你可以使用jakarta的file-upload库。

Q2.20  我想让页面自动刷新,比如聊天室。
答:这是一个HTML问题,在<head>部分中加一条<meta http-equiv="refresh" content="
5" url="...">。
这是所谓的Client-pull,客户端刷新技术。
相对的还有Server-push,服务器端刷新技术,但是这一技术由于要占用服务器端资源而会
在大量访问时出现瓶颈现象,参见http://216.239.33.104/search?q=cache:autUfoakirY
J:www.kfunigraz.ac.at/edvndwww/books/books/javaenterprise/servlet/ch06_03.htm+
server-push+servlet&hl=zh-CN&ie=UTF-8

Q2.21  我想让用户登录以后才能访问页面。
答:使用声明式安全措施。
你只需要在web.xml中定义安全角色(Role),并定义受保护的URL集合只能由特定Role访
问。
大多数服务器支持基于数据库的用户映射,你只要在相应数据库中建立两张表并配置服务
器就可以了。
注意J2EE SDK不支持基于数据库的用户映射。
细节请看参考书和服务器文档。
不过在商业环境中,J2EE所提供的声明式安全措施仍然偏弱。一般商业程序会使用数据库
存储user-role-privilege模型来达到安全性要求,细节请询问你的构架设计师。

Q2.22  我想要能注册用户。
答:参看Q2.21。在接受注册请求的Servlet中执行写入数据库操作即可。

Q2.23  怎么在JSP中访问数据库?
答:标准做法是使用DAO模式,定义一个Java bean来访问数据库并在JSP中使用。
然而,当你的数据库模式很简单时,你可以使用JSTL中的<sql:query>标签来快速访问。

在一般的J2EE项目中,JSP处于表示层(展现层),需要先后通过业务层和集成层才会访问
到数据库,所以这个问题确实只会在很小的程序中才会遇到。

Q2.24  什么是JSTL?
答:JSTL是Jsp Standard Tag Library的缩写。这是一组通用标签并将成为JSP 2.0的一部
分。
其中包含赋值<c:set>,分支<c:if>,循环<c:forEach>,查询数据库<sql:query>,更新数
据库<sql:update>
等。目前你需要像添加自定义标签库一样来添加JSTL,但是可以预计JSP 2.0会将JSTL作为
组成部分。
标签库可以在http://jakarta.apache.org下载。注意JSTL需要在支持JSP 1.2或更高版本
的容器下运行。
帮助文件可以阅读sun的JSTL正式规范。

posted @ 2006-12-12 11:12 保尔任 阅读(200) | 评论 (0)编辑 收藏

   (1) 若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母,单词之间用“_”连接。eg:private static final int MAX_LENGTH = 1000;Java包(Package)全都是小写字母,即便中间的单词亦是如此。

  (2) 为了常规用途而创建一个类时,请采取“经典形式”,并包含对下述元素的定义: (规范要求,如果两个对象进行equals比较时如果返回true,那么它们的hashcode要求返回相等的值。但hashcode一样时两个对象不==)
equals()
hashCode()
toString()
clone()(mplement Cloneable)
mplement Serializable

  (3) 对于自己创建的每一个类,都考虑置入一个main(),其中包含了用于测试那个类的代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动,可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。main()方法在类定义的最底部。

    (4)

    (5) 设计一个类时,请设身处地为客户程序员考虑一下(类的使用方法应该是非常明确的)。然后,再设身处地为管理代码的人考虑一下(预计有可能进行哪些形式的修改,想想用什么方法可把它们变得更简单)。

  (6) 使类尽可能短小精悍,而且只解决一个特定的问题。下面是对类设计的一些建议:

  ■一个复杂的开关语句:考虑采用“多形”机制

  ■数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现

  ■许多成员变量在特征上有很大的差别:考虑使用几个类 。

  (7) 让一切东西都尽可能地“私有”——private。可使库的某一部分“公共化”(一个方法、类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变其他任何东西。在多线程环境中,隐私是特别重要的一个因素——只有private字段才能在非同步使用的情况下受到保护。

  (8) 谨惕“巨大对象综合症”。对一些习惯于顺序编程思维、且初涉OOP领域的新手,往往喜欢先写一个顺序执行的程序,再把它嵌入一个或两个巨大的对象里。根据编程原理,对象表达的应该是应用程序的概念,而非应用程序本身。

  (9) 若不得已进行一些不太雅观的编程,至少应该把那些代码置于一个类的内部。

  (10) 任何时候只要发现类与类之间结合得非常紧密,就需要考虑是否采用内部类,从而改善编码及维护工作。

  (11) 尽可能细致地加上注释,并用javadoc注释文档语法生成自己的程序文档。

  (12) 避免使用“魔术数字”,这些数字很难与代码很好地配合。如以后需要修改它,无疑会成为一场噩梦,因为根本不知道“100”到底是指“数组大小”还是“其他全然不同的东西”。所以,我们应创建一个常数,并为其使用具有说服力的描述性名称,并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。

  (13) 涉及构建器和异常的时候,通常希望重新丢弃在构建器中捕获的任何异常——如果它造成了那个对象的创建失败。这样一来,调用者就不会以为那个对象已正确地创建,从而盲目地继续。

  (14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码置于一个良好定义的方法里,采用类似于cleanup()这样的名字,明确表明自己的用途。除此以外,可在类内放置一个boolean(布尔)标记,指出对象是否已被清除。在类的finalize()方法里,请确定对象已被清除,并已丢弃了从RuntimeException继承的一个类(如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定finalize()能够在自己的系统中工作(可能需要调用System.runFinalizersonExit(true),从而确保这一行为)。

  (15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用下述方法:初始化对象;若成功,则立即进入一个含有finally从句的try块,开始清除工作。

  (16) 若在初始化过程中需要覆盖(取消)finalize(),请记住调用super.finalize()(若Object属于我们的直接超类,则无此必要)。在对finalize()进行覆盖的过程中,对super.finalize()的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需要基础类组件的时候它们依然有效。

  (17) 创建大小固定的对象集合时,请将它们传输至一个数组(若准备从一个方法里返回这个集合,更应如此操作)。这样一来,我们就可享受到数组在编译期进行类型检查的好处。此外,为使用它们,数组的接收者也许并不需要将对象“造型”到数组里。

  (18) 尽量使用interfaces,不要使用abstract类。若已知某样东西准备成为一个基础类,那么第一个选择应是将其变成一个interface(接口)。只有在不得不使用方法定义或者成员变量的时候,才需要将其变成一个abstract(抽象)类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实施细节。

  (19) 在构建器内部,只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法,因为那些方法可能被其他人覆盖或取消,从而在构建过程中产生不可预知的结果(参见第7章的详细说明)。

  (20) 对象不应只是简单地容纳一些数据;它们的行为也应得到良好的定义。

  (21) 在现成类的基础上创建新类时,请首先选择“新建”或“创作”。只有自己的设计要求必须继承时,才应考虑这方面的问题。若在本来允许新建的场合使用了继承,则整个设计会变得没有必要地复杂。

  (22) 用继承及方法覆盖来表示行为间的差异,而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色,这是绝对应该避免的:应直接使用一个“颜色”字段。

  (23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到了类路径问题,请试试在类路径的每一个起点,搜索一下同名的.class文件。

  (24) 在Java 1.1 AWT中使用事件“适配器”时,特别容易碰到一个陷阱。若覆盖了某个适配器方法,同时拼写方法没有特别讲究,最后的结果就是新添加一个方法,而不是覆盖现成方法。然而,由于这样做是完全合法的,所以不会从编译器或运行期系统获得任何出错提示——只不过代码的工作就变得不正常了。

  (25) 用合理的设计方案消除“伪功能”。也就是说,假若只需要创建类的一个对象,就不要提前限制自己使用应用程序,并加上一条“只生成其中一个”注释。请考虑将其封装成一个“独生子”的形式。若在主程序里有大量散乱的代码,用于创建自己的对象,请考虑采纳一种创造性的方案,将些代码封装起来。

  (26) 警惕“分析瘫痪”。请记住,无论如何都要提前了解整个项目的状况,再去考察其中的细节。由于把握了全局,可快速认识自己未知的一些因素,防止在考察细节的时候陷入“死逻辑”中。

  (27) 警惕“过早优化”。首先让它运行起来,再考虑变得更快——但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。除非用专门的工具分析瓶颈,否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于理解,而且难于维护。

  (28) 请记住,阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解的程序,但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己,还是对后来的人,它们都是相当重要的。如对此仍有怀疑,那么请试想自己试图从联机Java文档里找出有用信息时碰到的挫折,这样或许能将你说服。

  (29) 如认为自己已进行了良好的分析、设计或者实施,那么请稍微更换一下思维角度。试试邀请一些外来人士——并不一定是专家,但可以是来自本公司其他部门的人。请他们用完全新鲜的眼光考察你的工作,看看是否能找出你一度熟视无睹的问题。采取这种方式,往往能在最适合修改的阶段找出一些关键性的问题,避免产品发行后再解决问题而造成的金钱及精力方面的损失。

  (30) 良好的设计能带来最大的回报。简言之,对于一个特定的问题,通常会花较长的时间才能找到一种最恰当的解决方案。但一旦找到了正确的方法,以后的工作就轻松多了,再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报(甚至无可估量)。而且由于自己倾注了大量心血,最终获得一个出色的设计方案,成功的快感也是令人心动的。坚持抵制草草完工的诱惑——那样做往往得不偿失

    (31) JAVA 对枚举的支持不好,但是下面的代码是一种很有用的模板:
class Colour {
public static final Colour BLACK = new Colour(0, 0, 0);
public static final Colour RED = new Colour(0xFF, 0, 0);
public static final Colour GREEN = new Colour(0, 0xFF, 0);
public static final Colour BLUE = new Colour(0, 0, 0xFF);
public static final Colour WHITE = new Colour(0xFF, 0xFF, 0xFF);
}
这种技术实现了RED, GREEN, BLUE 等可以象其他语言的枚举类型一样使用的常量。 他们可以用 == 操作符来比较。 但是这样使用有一个缺陷:如果一个用户用这样的方法来创建颜色 BLACK new Colour(0,0,0) 那么这就是另外一个对象,==操作符就会产生错误。她的 equal() 方法仍然有效。由于这个原因,这个技术的缺陷最好注明在文档中,或者只在自己的包中使用。

posted @ 2006-12-12 11:11 保尔任 阅读(173) | 评论 (0)编辑 收藏

按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.

2.2 堆和栈的比较
上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:
在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).


2.3 JVM中的堆和栈
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。


2.4 GC的思考
Java为什么慢?JVM的存在当然是一个原因,但有人说,在Java中,除了简单类型(int,char等)的数据结构,其它都是在堆中分配内存(所以说Java的一切都是对象),这也是程序慢的原因之一。
我的想法是(应该说代表TIJ的观点),如果没有Garbage Collector(GC),上面的说法就是成立的.堆不象栈是连续的空间,没有办法指望堆本身的内存分配能够象堆栈一样拥有传送带般的速度,因为,谁会为你整理庞大的堆空间,让你几乎没有延迟的从堆中获取新的空间呢?
这个时候,GC站出来解决问题.我们都知道GC用来清除内存垃圾,为堆腾出空间供程序使用,但GC同时也担负了另外一个重要的任务,就是要让Java中堆的内存分配和其他语言中堆栈的内存分配一样快,因为速度的问题几乎是众口一词的对Java的诟病.要达到这样的目的,就必须使堆的分配也能够做到象传送带一样,不用自己操心去找空闲空间.这样,GC除了负责清除Garbage外,还要负责整理堆中的对象,把它们转移到一个远离Garbage的纯净空间中无间隔的排列起来,就象堆栈中一样紧凑,这样Heap Pointer就可以方便的指向传送带的起始位置,或者说一个未使用的空间,为下一个需要分配内存的对象"指引方向".因此可以这样说,垃圾收集影响了对象的创建速度,听起来很怪,对不对?
那GC怎样在堆中找到所有存活的对象呢?前面说了,在建立一个对象时,在堆中分配实际建立这个对象的内存,而在堆栈中分配一个指向这个堆对象的指针(引用),那么只要在堆栈(也有可能在静态存储区)找到这个引用,就可以跟踪到所有存活的对象.找到之后,GC将它们从一个堆的块中移到另外一个堆的块中,并将它们一个挨一个的排列起来,就象我们上面说的那样,模拟出了一个栈的结构,但又不是先进后出的分配,而是可以任意分配的,在速度可以保证的情况下, Isn't it great?
但是,列宁同志说了,人的优点往往也是人的缺点,人的缺点往往也是人的优点(再晕~~).GC()的运行要占用一个线程,这本身就是一个降低程序运行性能的缺陷,更何况这个线程还要在堆中把内存翻来覆去的折腾.不仅如此,如上面所说,堆中存活的对象被搬移了位置,那么所有对这些对象的引用都要重新赋值.这些开销都会导致性能的降低.
此消彼长,GC()的优点带来的效益是否盖过了它的缺点导致的损失,我也没有太多的体会,Bruce Eckel 是Java的支持者,王婆卖瓜,话不能全信.个人总的感觉是,Java还是很慢,它的发展还需要时间.


上面的体会是我看了TIJ.3rdEdition.Revision4.0中第四章之后得出的,内容和前面的有些不同.我没有看过侯捷的中文版本,但我觉得,在关键问题上,原版的TIJ的确更值得一读.所以和中文版配合起来学习是比较不错的选择.
我只能算一个Java的初学者,没想到起了这么个题目,却受到这么多人的关注,欣喜之余,也决心尽力写好下面的每一篇.不过这一篇完了,我就该准备赴美签证了,如果成功,那就要等到8月27号CS的研究生院开学之后,才有时间会开始研究下一章了,希望可以多从原版中获取一点经验.

posted @ 2006-12-12 11:06 保尔任 阅读(743) | 评论 (0)编辑 收藏

数组和List的转换:

        List<Integer> l = new ArrayList<Integer>();
        l.add(1);
        l.add(2);
        l.add(3);
        Integer[] ints = l.toArray(new Integer[0]);
        List<Integer> l2 = Arrays.asList(ints);
        assert l.equals(l2);
        System.out.println("ok");

数字类型转换成字符串型:
String s = String.valueOf(value); 

数字对象转换成字符串型
Long l = new Long(10);
String s = l.toString();

数字类型/字符串转换成数字对象:
byte b = 169;
String b = "169";
Byte bo = new Byte( b );

short t = 169;
String b = "169";
Short to = new Short( t );

int i = 169;
String b = "169";
Integer io = new Integer( i );

long l = 169;
String b = "169";
Long lo = new Long( l );

float f = 169f;
String b = "169";
Float fo = new Float( f );

double d = 169f;
String b = "169";
Double dObj = new Double( d ); 

字符串型转换成各种数字类型:
String s = "169";
byte b = Byte.parseByte( s );
short t = Short.parseShort( s );
int i = Integer.parseInt( s );
long l = Long.parseLong( s );
float f = Float.parseFloat( s );
double d = Double.parseDouble( s );

数字对象转换成数字类型:(*o为一数字对象)
b = bo.byteValue();
t = to.shortValue();
i = io.intValue();
l = lo.longValue();
f = fo.floatValue();
d = dObj.doubleValue();

posted @ 2006-12-12 10:19 保尔任 阅读(539) | 评论 (0)编辑 收藏

第一章 一般技术
1.java只有唯一一种参数传递方式:by value(值传递)。对于primitive types(基本型别)很容易理解,对于object references(对象引用),传递的是object reference的拷贝。
2.polymorphism(多态)优于instanceof:instanceof很容易被误用,很多场合都应该以多态代替,无论何时看到instanceof,请判断是否可以改进以消除它。
3.避免创建重复对象。比如一个类A的某个方法新建了一个类B,且此类B不会改变,则每次建立该类A的一个对象就会新建B的对象,此时应把
B设为private static final。
4.清除过期的对象引用。
5.避免使用终结函数。因为终结函数可能得不到执行或很久后得到执行,所以要避免使用。显示的中止方法通常与try-finally结构结合使用,防止出现异常时终结函数得不到执行。
eg: Foo foo = new Foo(...);
    try{
        //do what must be done with foo   
    }finally{
        foo.terminate();
    }
6.通过私有构造函数来强化不可实例化的能力。比如一些工具类不希望被实例化,然而在缺少显示构造函数时编译器会自动提供一个默认构造函数,为防止以上情况要构造一个显示的私有的构造函数。
eg:public class UtilityClass{
     private UtilityClass(){
     }
   }
7.通过私有构造函数强化singleton属性。singleton是指这样的类,它只能实例化一次。singleton通常被用来代表那些本质上具有唯一性的系统组件,比如视频显示或者文件系统。
  eg:public class Elvis{
       public static final Elvis INSTANCE = new Elvis();
       private Elvis(){
       }
     }
8.考虑用静态工厂方法代替构造函数,但如果没有其他强烈的因素,最好还是简单的使用构造函数,毕竟它是语言规范。静态工厂方法实际上是一个简单的静态方法,他返回的是类的一个实例。
  有点:a.与构造函数不同,静态工厂方法具有名字。
        b.与构造函数不同,它们每次被调用的时候不要求非得创建一个对象。
        c.与构造函数不同,与构造函数不同,它们可以返回一个原类型的子类型对象。
第二章 所有对象都通用的方法(equals(),hashCode(),toString(),clone(),Comparable接口)
一.按规则使用equals():
1.使用equals的规则:
  a.如果一个class的两个对象占据不同的内存空间也可被视为逻辑相等的话,那么得为这个class提供一个equals()
  b.检查是否等于this
  c.比较关键域以判断两个对象是否相等
  d.如果有java.lang.Object以外的任何base class实现了equals(),那么就应该调用super.equals()
  e.如果只允许同一个class所产生的对象被视为相等,则通常使用getClass()
    eg1:一般情况
    public boolean equals(Object obj){
        if(this == obj){
          return true;
        }
        if(obj != nul && getClass() == obj.getClass()){
          Test test = (Test)obj;
          if(***){//相等条件
              return true;
          }
        }
        return false;
      }
    eg2:调用super.equals()情况
    public boolean equals(Object obj){
      if(super.equals(obj)){//已经包含了this == obj; obj !=null && getClass() == obj.getClass()的判断
        Test test = (Test)obj;
          if(***){//相等条件
              return true;
          }
        }
        return false;
      }

  f.只有在不得不对derived class对象与base classes对象进行比较的场合中,才使用instanceof,并且你应该明白这样做带来的可能问题和复杂性,并且derived class和base classes都用instanceof实现equals()时,这种比较不会展现“对称相等性”。
    Base b;Derived d;//分别表示父类、子类
    1)父类实现equals,子类继承父类的equals,b.equals(d) == d.equals(d);
    2)父类子类分别实现了equals,b.equals(d) != d.equals(b);
    3)父类未实现equals,子类实现了equals,b.equals(d) != d.equals(b);
2.对于既不是float也不是double类型的primitive types,使用==操作符;对于对象引用域,可以递归的调用equals方法;对于float域,先使用Float.floatToIntBits转换成int类型值,然后使用==操作符比较int类型的值;对于double域,先使用Double.doubleToLongBits转换成int类型的值,然后使用==操作符比较long类型的值.(这是由于存在Float.NaN、-0.0f以及类似的double类型的常量)
二.hashCode():
1。改写equals时总是要改写hashCode方法,如果不这样作,会导致该类无法与所有基于散列值(hash)的集合类在一起正常工作,这样的集合类包括HashMap、HashSet、HashTable
2。hashCode方法的简单方法:
  1。把某个非零数值(比如17),保存在int result变量里。
  2。对于对象中每一个关键域f(指equals方法中考虑的每一个域),完成以下步骤:
  a)为该域计算int类型的散列码c:
    i.该域为boolean型,c = f ? 0 : 1
    ii.byte, char, short, int型, c = (int)f
    iii.long型, c = (int)(f ^ (f >>> 32))
    iv.float型, c = Float.floatToIntBits(f)
    v.double型, Double.doubleToLongBits(f)得到long型,然后按iii计算散列值
    vi.如果是对象引用,c = (this.*** == null) ? 0 : this.***.hashCode();
    vii.如果该域是个数组,则把其中每一个元素当作单独的域来处理
   b)result = 37 * result + c;//把每个c都组合到result中
   3。返回result
   eg1:
 public int hashCode() {
     int result = 17;
     //对于关键域是id的情况
     int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
     result = (result * 37) + idValue;
     //如果还有第二个关键域name
     //int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
     //result = (result * 37) + nameValue;
     this.hashValue = result;
 return this.hashValue;
 }
    eg2:
 如果一个类是非可变的,并且计算散列码代价较大,则应把散列码存到对象内部:
 private int hashValue = 17;//先定义hashValue,不需要get/set方法
 ........................
 public int hashCode() {//对于关键域是id的情况
     if (this.hashValue == 17) {
  int result = 17;
  int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
  result = (result * 37) + idValue;
  //如果还有第二个关键域name
  //int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
  //result = (result * 37) + nameValue;
  this.hashValue = result;
     }
     return this.hashValue;
 }
三。toString():会使这个类用起来更加方便。
四。谨慎的改写clone()。实现拷贝的方法有两个:一是实现cloneable接口(effective java 39页,没仔细看),二是提供拷贝构造函数
  public Yum(Yum yum);
  或是上面的微小变形:提供一个静态工厂来代替构造函数:
  public static Yum newInstance(Yum yum);
五、用到搜索、排序、计算极值的情况时,考虑实现Comparable接口。
public int compareTo(Object o)//方法不需要手工检查参数的类型,如参数类型不符合会抛出ClassCastException;如参数为null,该方法抛出NullPointerException。
第三章 类和接口
1。使类和成员(变量、方法、内部类、内部接口)的可访问能力最小化。
2。private和friendly成员都是一个类实现中的一部分,并不会影响到导出API。然而,如果这些域所在的类实现了Serializable接口,那么这些成员可能会被泄漏到导出API中。
3。如果一个方法改写了超类中的一个方法,那么子类中该方法的访问级别不能低于父类中该方法的访问级别。特别是:类实现了接口,那么接口中的方法在这个类中必须声明为公有的,因为接口中方法默认为public abstract。
六、异常处理
1.决不可忽略异常,即catch后什么也不做。
2.决不可掩盖异常
try{
  e1;//异常1
  e2;//异常2
}catch(Exception e){
  e.printStackTrace()
}//只能捕获异常2
办法:要仔细分析,用栈来保存异常
3.覆写异常处理时:
父类不抛出异常时,自类不能抛出异常。
父类抛出异常时,自类三种情况:a)不抛出异常b)抛出父类异常c)抛出父类异常的派生异常。
4.只要有finally块就一定会进入,即使try-catch块有return/break/continue语句。
5.养成将try/catch块放在循环外的习惯,在不启动JIT时节省时间。

posted @ 2006-12-05 16:16 保尔任 阅读(169) | 评论 (0)编辑 收藏

<2006年12月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

常用链接

留言簿(4)

随笔分类

随笔档案

文章分类

文章档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜