2005年11月12日

  Java程序员是美国SUN公司国际认证的程序员,她是目前全球最受重视、最受欢迎的程序员资格认证之一,具备这一认证就可以获得极好的工作机会和丰厚待遇。
   
  Java跨平台等许多特性使之成为当代成长最快的软件产品:它受到了大约150个许可证颁发机构、200所大学和50万开发者的拥戴;1000多个应用程序是用它编写的;有关它的书籍有800余种;Java开发套件的下载次数超过一百万次;Java BEANS? 开发套件的下载次数超过10万次;受到了全球所有主要计算机厂商的支持,而这一切都发生在其发表后的800天内;Java闯入企业计算的心脏正成为大众传媒舆论的推动力量,正在改变企业的计算环境。它通过为因特网商务提供安全和稳健平台的方式改变商业自身。Java计算是各行各业中倍受信赖的企业解决方案。独立于平台的Java计算环境可以降低企业的总成本,缩短产品上市周期,安全地扩展公司的网络。不少国家到中国寻求合作伙伴时,都把能否用Java进行编程作为合作的前提条件。 
  
  当前世界各地持有Java Programmer证书的人员供需差距极大,迫使企业不得不用高薪聘请Java程序员。因此,Java Programmer的含金量比一般的技术人员要高出很大一块。在美国、加拿大、澳大利亚、新加坡等发达国家和中等发达国家,持有Java Programmer认证证书的人年薪均在4-10万美金,而在国内持有Java Programmer认证的程序员也有极好的工作机会和很高的薪水。

2005年11月02日

public class iso
{
public static String ISOtoGB(String iso)
{
String gb;
try
{
if(iso.equals("") || iso == null)
{
return "";
}
else
{
iso = iso.trim();
gb = new String(iso.getBytes("ISO-8859-1"),"GBK");
return gb;
}
}
catch(Exception e)
{
System.err.print("编码转换错误:"+e.getMessage());
return "";
}
}
}
另一种方法是在写数据库连接驱动时进行转换:
url="jdbc:mysql://localhost/marry?useUnicode=true&characterEncoding=GBK";

JAVA与MYSQL的默认字符集均是ISO-8859-1所以理论上POST取值时不需要转换。当然这也跟中间件有关系,使用Weblogic/Tomcat/Resin时POST取值插入数据库时不需要转换,但当从数据库中取值时需要转换(因为JSP页的字符集是GBK或GB2312),但使用JRUN时就反过来了。总之楼主多试试,不是什么大问题。

SELECT something FROM table
      WHERE TO_DAYS(NOW()) – TO_DAYS(date_col) <= 30;
来选择当前日期前30天的记录!

备份数据库两个主要方法是用mysqldump程序或直接拷贝数据库文件(如用cp、cpio或tar等)。每种方法都有其优缺点:


  • mysqldump与MySQL服务器协同操作。直接拷贝方法在服务器外部进行,并且你必须采取措施保证没有客户正在修改你将拷贝的表。如果你想用文件系统备份来备份数据库,也会发生同样的问题:如果数据库表在文件系统备份过程中被修改,进入备份的表文件主语不一致的状态,而对以后的恢复表将失去意义。文件系统备份与直接拷贝文件的区别是对后者你完全控制了备份过程,这样你能采取措施确保服务器让表不受干扰。
  • mysqldump比直接拷贝要慢些。
  • mysqldump生成能够移植到其它机器的文本文件,甚至那些有不同硬件结构的机器上。直接拷贝文件不能移植到其它机器上,除非你正在拷贝的表使用MyISAM存储格式。ISAM表只能在相似的硬件结构的机器上拷贝。在MySQL 3.23中引入的MyISAM表存储格式解决了该问题,因为该格式是机器无关的,所以直接拷贝文件可以移植到具有不同硬件结构的机器上。只要满足两个条件:另一台机器必须也运行MySQL 3.23或以后版本,而且文件必须以MyISAM格式表示,而不是ISAM格式。


不管你使用哪种备份方法,如果你需要恢复数据库,有几个原则应该遵守,以确保最好的结果:


  • 定期实施备份。建立一个计划并严格遵守。
  • 让服务器执行更新日志。当你在崩溃后需要恢复数据时,更新日志将帮助你。在你用备份文件恢复数据到备份时的状态后,你可以通过运行更新日志中的查询再次运用备份后面的修改,这将数据库中的表恢复到崩溃发生时的状态。

    以文件系统备份的术语讲,数据库备份文件代表完全倾倒(full dump),而更新日志代表渐进倾倒(incremental dump)。


  • 使用一种统一的和易理解的备份文件命名机制。象backup1、buckup2等不是特别有意义。当实施你的恢复时,你将浪费时间找出文件里是什么东西。你可能发觉用数据库名和日期构成备份文件名会很有用。例如:

    %mysqldump samp_db >/usr/archives/mysql/samp_db.1999-10-02


    %mysqldump menagerie >/usr/archives/mysql/menagerie.1999-10-02


    你可能想在生成备份后压缩它们。备份一般都很大!你也需要让你的备份文件有过期期限以避免它们填满你的磁盘,就象你让你的日志文件过期那样。


  • 用文件系统备份备份你的备份文件。如果遇上了一个彻底崩溃,不仅清除了你的数据目录,也清除了包含你的数据库备份的磁盘驱动器,你将真正遇上了麻烦。也要备份你的更新日志。
  • 将你的备份文件放在不同于用于你的数据库的文件系统上。这将降低由于生成备份而填满包含数据目录的文件系统的可能性。


用于创建备份的技术同样对拷贝数据库到另一台机器有用。最常见地,一个数据库被转移到了运行在另一台主机上的服务器,但是你也可以将数据转移到同一台主机上的另一个服务器。



1 使用mysqldump备份和拷贝数据库


当你使用mysqldumo程序产生数据库备份文件时,缺省地,文件内容包含创建正在倾倒的表的CREATE语句和包含表中行数据的INSERT语句。换句话说,mysqldump产生的输出可在以后用作mysql的输入来重建数据库。


你可以将整个数据库倾倒进一个单独的文本文件中,如下:


%mysqldump samp_db >/usr/archives/mysql/samp_db.1999-10-02


输出文件的开头看起来象这样:

# MySQL Dump 6.0
#
# Host: localhost    Database: samp_db
#---------------------------------------
# Server version 3.23.2-alpha-log
#
# Table structure for table 'absence'
#
CREATE TABLE absence(
  student_id int(10) unsigned DEFAULT '0' NOT NULL,
  date date DEFAULT '0000-00-00' NOT NULL,
  PRIMARY KEY (student_id,date)
);
#
# Dumping data for table 'absence'
#
INSERT INTO absence VALUES (3,'1999-09-03');
INSERT INTO absence VALUES (5,'1999-09-03');
INSERT INTO absence VALUES (10,'1999-09-08');
...... 


文件剩下的部分有更多的INSERT和CREATE TABLE语句组成。


如果你想压缩备份,使用类似如下的命令:


%mysqldump samp_db | gzip >/usr/archives/mysql/samp_db.1999-10-02.gz


如果你要一个庞大的数据库,输出文件也将很庞大,可能难于管理。如果你愿意,你可以在mysqldump命令行的数据库名后列出单独的表名来倾到它们的内容,这将倾倒文件分成较小、更易于管理的文件。下例显示如何将samp_db数据库的一些表倾到进分开的文件中:


%mysqldump samp_db student score event absence >grapbook.sql
%mysqldump samp_db member president >hist-league.sql


如果你生成准备用于定期刷新另一个数据库内容的备份文件,你可能想用–add-drop-table选项。这告诉服务器将DROP TABLE IF EXISTS语句写入备份文件,然后,当你取出备份文件并把它装载进第二个数据库时,如果表已经存在,你不会得到一个错误。


如果你倒出一个数据库以便能把数据库转移到另一个服务器,你甚至不必创建备份文件。要保证数据库存在于另一台主机,然后用管道倾倒数据库,这样mysql能直接读取mysqldump的输出。例如:你想从主机pit-viper.snake.net拷贝数据库samp_db到boa.snake.net,可以这样很容易做到:


%mysqladmin -h boa.snake.net create samp_db
%mysqldump samp_db | mysql -h boa.snake.net samp_db


以后,如果你想再次刷新boa.snake.net上的数据库,跳过mysqladmin命令,但要对mysqldump加上–add-drop-table以避免的得到表已存在的错误:


%mysqldump –add-drop-table samp_db | mysql -h boa.snake.net samp_db


mysqldump其它有用的选项包括:


  • –flush-logs和–lock-tables组合将对你的数据库检查点有帮助。–lock-tables锁定你正在倾倒的所有表,而–flush-logs关闭并重新打开更新日志文件,新的更新日志将只包括从备份点起的修改数据库的查询。这将设置你的更新日志检查点位备份时间。(然而如果你有需要执行个更新的客户,锁定所有表对备份期间的客户访问不是件好事。)

    如果你使用–flush-logs设置检查点到备份时,有可能最好是倾倒整个数据库。如果你倾倒单独的文件,较难将更新日志检查点与备份文件同步。在恢复期间,你通常按数据库为基础提取更新日志内容,对单个表没有提取更新的选择,所以你必须自己提取它们。


  • 缺省地,mysqldump在写入前将一个表的整个内容读进内存。这通常确实不必要,并且实际上如果你有一个大表,几乎是失败的。你可用–quick选项告诉mysqldump只要它检索出一行就写出每一行。为了进一步优化倾倒过程,使用–opt而不是–quick。–opt选项打开其它选项,加速数据的倾倒和把它们读回。

    用–opt实施备份可能是最常用的方法,因为备份速度上的优势。然而,要警告你,–opt选项确实有代价,–opt优化的是你的备份过程,不是其他客户对数据库的访问。–opt选项通过一次锁定所有表阻止任何人更新你正在倾倒的任何表。你可在一般数据库访问上很容易看到其效果。当你的数据库一般非常频繁地使用,只是一天一次地调节备份。


  • 一个具有–opt的相反效果的选项是–dedayed。该选项使得mysqldump写出INSERT DELAYED语句而不是INSERT语句。如果你将数据文件装入另一个数据库并且你想是这个操作对可能出现在该数据库中的查询的影响最小,–delayed对此很有帮助。
  • –compress选项在你拷贝数据库到另一台机器上时很有帮助,因为它减少网络传输字节的数量。下面有一个例子,注意到–compress对与远端主机上的服务器通信的程序才给出,而不是对与本地主机连接的程序:
    %mysqldump --opt samp_db | mysql --compress -h boa.snake.net samp_db


    mysqldump有很多选项,详见《MySQL参考手册》。



2 使用直接拷贝数据库的备份和拷贝方法


另一种不涉及mysqldump备份数据库和表的方式是直接拷贝数据库表文件。典型地,这用诸如cp、tar或cpio实用程序。本文的例子使用cp。


当你使用一种直接备份方法时,你必须保证表不在被使用。如果服务器在你则正在拷贝一个表时改变它,拷贝就失去意义。


保证你的拷贝完整性的最好方法是关闭服务器,拷贝文件,然后重启服务器。如果你不想关闭服务器,要在执行表检查的同时锁定服务器。如果服务器在运行,相同的制约也适用于拷贝文件,而且你应该使用相同的锁定协议让服务器“安静下来”。


假设服务器关闭或你已经锁定了你想拷贝的表,下列显示如何将整个samp_db数据库备份到一个备份目录(DATADIR表示服务器的数据目录):

%cd DATADIR
%cp -r samp_db /usr/archive/mysql


单个表可以如下备份:

%cd DATADIR/samp_db
%cp member.* /usr/archive/mysql/samp_db
%cp score.* /usr/archive/mysql/samp_db
    ....
   


当你完成了备份时,你可以重启服务器(如果关闭了它)或释放加在表上的锁定(如果你让服务器运行)。


要用直接拷贝文件把一个数据库从一台机器拷贝到另一台机器上,只是将文件拷贝到另一台服务器主机的适当数据目录下即可。要确保文件是MyIASM格式或两台机器有相同的硬件结构,否则你的数据库在另一台主机上有奇怪的内容。你也应该保证在另一台机器上的服务器在你正在安装数据库表时不访问它们。



3 复制数据库(Replicating Database)


复制(Replication)类似于拷贝数据库到另一台服务器上,但它的确切含义是实时地保证两个数据库的完全同步。这个功能将在3.23版中出现,而且还不很成熟,因此本文不作详细介绍。



4 用备份恢复数据


数据库损坏的发生有很多原因,程度也不同。如果你走运,你可能仅损坏一两个表(如掉电),如果你倒霉,你可能必须替换整个数据目录(如磁盘损坏)。在某些情况下也需要恢复,比如用户错误地删除了数据库或表。不管这些倒霉事件的原因,你将需要实施某种恢复。


如果表损坏但没丢失,尝试用myisamchk或isamchk修复它们,如果这样的损坏可有修复程序修复,你可能根本不需要使用备份文件。关于表修复的过程,见《数据库维护与修复》。


恢复过程涉及两种信息源:你的备份文件和个更新日志。备份文件将表恢复到实施备份时的状态,然而一般表在备份与发生问题之间的时间内已经被修改,更新日志包含了用于进行这些修改的查询。你可以使用日志文件作为mysql的输入来重复查询。这已正是为什么要启用更新日志的原因。


恢复过程视你必须恢复的信息多少而不同。实际上,恢复整个数据库比单个表跟容易,因为对于数据库运用更新日志比单个表容易。



4.1 恢复整个数据库


首先,如果你想恢复的数据库是包含授权表的mysql数据库,你需要用–skip-grant-table选项运行服务器。否则,它会抱怨不能找到授权表。在你已经恢复表后,执行mysqladmin flush-privileges告诉服务器装载授权标并使用它们。


  • 将数据库目录内容拷贝到其它某个地方,如果你在以后需要它们。
  • 用最新的备份文件重装数据库。如果你用mysqldump产生的文件,将它作为mysql的输入。如果你用直接从数据库拷贝来的文件,将它们直接拷回数据库目录,然而,此时你需要在拷贝文件之前关闭数据库,然后重启它。
  • 使用更新日志重复做备份以后的修改数据库表的查询。对于任何可适用的更新日志,将它们作为mysql的输入。指定–one-database选项使得mysql只执行你有兴趣恢复的数据库的查询。如果你知道你需要运用所有更新日志文件,你可以在包含日志的目录下使用这条命令:
     % ls -t -r -1 update.[0-9]* | xargs cat | mysql --one-database db_name


ls命令生成更新日志文件的一个单列列表,根据服务器产生它们的次序排序(主意:如果你修改任何一个文件,你将改变排序次序,这导致更新日志一错误的次序被运用。)


很可能你会是运用某几个更新日志。例如,自从你备份以来产生的更新日志被命名为update.392、update.393等等,你可以这样重新运行:


%mysql –one-database db_name < update.392
%mysql –one-database db_name < update.393
…..


如果你正在实施恢复且使用更新日志恢复由于一个错误建议的DROP DATABASE、DROP TABLE或DELETE语句造成丢失的信息,在运用更新日志之前,要保证从其中删除这些语句。



4.2 恢复单个表


恢复单个表较为复杂。如果你用一个由mysqldump生成的备份文件,并且它不包含你感兴趣的表的数据,你需要从相关行中提取它们并将它们用作mysql的输入。这是容易的部分。难的部分是从只运用于该表的更新日志中拉出片断。你会发觉mysql_find_rows实用程序对此很有帮助,它从更新日志中提取多行查询。


另一个可能性是使用另一台服务器恢复整个数据库,然后拷贝你想要的表文件到原数据库中。这可能真的很容易!当你将文件拷回数据库目录时,要确保原数据库的服务器关闭。MySQL数据库备份

1.数据库连接:      mysql -h主机地址 -u用户名 -p用户密码  eg:mysql -h110.110.110.110 -uroot -pabcd123

2.密码:    mysqladmin -u用户名 -p旧密码 password 新密码.如果开始时root没有密码,-p旧密码一项就可以省略了。

以上无需在mysql命令行下,直接在bin目录下就可以操作

3.增加新用户: grant select on 数据库.* to 用户名@登录主机 identified by "密码";  eg:增加一个远程test1帐号密码为abc,拥有所有权限: grant select,insert,update,delete on *.* to test1@"%" Identified by "abc";  增加一个test2密码为abc,让他只可以在localhost上登录,并且只对数据库mydb进行操作:

grant  select,insert,update,delete on mydb.* to test2@localhost identified by "abc";如果设置没有密码则为:grant select,insert,update,delete on mydb.* to test2@localhost identified by "";

4.从外部文本中导入数据库语句: mysql -uroot -p密码 < c:\school.sql   从文本school.sql中导入sql语句.            1.文本数据插入规则:字段数据之间用tab键隔开,null值用\n来代替.   2、数据传入命令 load data local infile "文件名" into table 表名;注意:你最好将文件复制到\mysql\bin目录下,并且要先用use命令打表所在的库 。

5.备份数据库::(命令在DOS的\mysql\bin目录下执行)mysqldump –opt school>school.bbb

在建设网站的过程中,经常要处理一些数据的导入及导出.在Mysql数据库中,有两种方法来处理数据的导出(一般).
1. 使用select * from table_name into outfile “file_name”;
2. 使用mysqldump实用程序
下面我们来举例说明:
假设我们的数据库中有一个库为samp_db,一个表为samp_table.现在要把samp_table的数据导出.则我们可以利用以下方法来实现:
在Mysql提示符下打入select * from samp_table into outfile “file_name”;
在系统命令提示符下打入mysqldump –u root samp_db samp_table >samp.sql
(当然mysqldump有很多选项.如 -d 表示只导出表结构; -t 表示只导入表数据)
如何来处理数据的导入:一般我们在系统命令提示符下打入mysqlimport –u root samp_db samp_table.txt(注意:这个TXT文件名必须以表的名字命名).对于mysqldump导出的数据我们还可以用mysql –u root samp_db < file_name来导入.在Mysql提示符下我们用Load data infile “file_name” into table samp_table.
另外除了以上方法,对于移动后的数据库系统与原系统一致的前提下,我们可以通过文件的拷贝来实现数据的导入与导出.首先我们用mysqladmin –u root variables(在系统命令提示符下)或者用show variables;(在Mysql提示符下)来找到datadir.如在我的环境下,此目录在c:mysqldata.然后对其中的一些文件进行拷贝.一切ok!
有了以上知识后,我们进入正题:如何把ACCESS的数据导入到Mysql中.
首先我们把数据从ACCESS中导出为文本文件.在导出过程中注意选择好字段分隔符和文本标识符,且查看一下文本文件,确定一个记录是否在同一行上,如不是,则手工把回车键除掉.假如我们导出的文本文件为c:samp_table.txt.其内容如下:
1,张新化,男
2,江先进,女
接下来我们用mysqlimport –u root –fields-terminated-by=”,” samp_db samp_table.txt
或者用load data infile “c:\samp_table.txt” into table samp_table fields terminated by “,”;
去试一下(注意转义字符),是不是一切ok!如果还不行,请仔细看一下具体命令的帮助.下面列出一些选项:
–fields-enclosed-by=char 指明列值应包括在指定的字符中.通常用引号.缺省时,假定列值不包括在任何字符中.
–fields-escaped-by=char 表示用于转义特殊字符的转义符.缺省时表示无转义符
–fields-terminated-by=char 指定分隔列的字符.缺省时假定列值由制表符分隔.
–lines-terminated-by=str 指定结束输出。

在网上看到很多问题是关于如何学习jsp的,正好网上看到一篇关于学习jsp的文章,就摘了一部分翻译过来,希望能对大家学习jsp有点指

导。

一个普通的错误是把JSP当作简化的 Java。它不是,(事实上, JSP 是简化的 servlets 。)程序员通常试着没有学习要求的支持技巧而

直接学习 JSP 。JSP 是一个衔接技术,并且成功地连接你需要理解的另外的技术。如果你已经知道 Java , HTML 和 Javascript,这意味着

JSP 将确实是简单的。

需要成为一个成功的 JSP 程序员可以参考这个时间表。请注意下列:

*忽略你已经熟悉的步骤。
*训练的时间只是代表学习好足够的基础时间,这样才能转移到下一步。

1、建立并且理解你的Web Server。
因为Apache 是免费的并且在大多数平台上工作,为训练目的推荐 Apache。
安装时间:2 天。


2、 保证你理解 HTML / XHTML 。
你将需要了解html基础, 特别是 HTML 布局中的table的使用。XHTML 不久将代替 HTML ,学习 XHTML 的基础是一个好主意。许多程序员

通过 HTML IDE 学习 HTML ( 集成开发环境 ) 。因为大多数 HTML IDE产生混乱的HTMl语法,所以花时间学习手工写作html是很有必要的。因

为你将会使用 JSP 和 HTML 混合编程,精通HTML语法是重要的。所以,你必须能流利地写 HTML 。
训练时间:2 ~ 4 个星期。


3、开始学习 Java 。
开始学习 Java 1.3 理解 Java 基础是很重要的。不用担心学习Swing或 Java 的图形方面,因为在JSP 中你不会使用这些特征。集中精力

在 Java 工作的细节,学习 Java 的逻辑,也在 Java Bean上花时间。学习Applet是好的, 但是就象Swing, JSP 的大多数应用将不使用小程

序。
训练时间:3 ~ 6 个星期。


3、学习 JavaScript
学习怎么将 JavaScript在HTML中验证输入的Form元素。也学习 JavaScript怎么能在一 HTML 页以内修改Form的元素。最后要求你能从一

HTML 页内的事件中触发 JavaScript Function。
训练时间:一~ 2 个星期。


4、学习并且理解你的Web Server的更好的细节。
熟悉Web Server的特征,这是很重要的。
训练时间:2 天。


5、建立你的 JSP Server
我推荐以Tomcat开始。它可以很好地运行JSP程序。当你不能在生产使用Tomcat时,学习尽可能多的知识以便于更好的运行程序。另外, 许

多 JSP 程序员使用Tomcat。因此当你遇到一个问题时,你将容易发现帮助。
安装时间:一~ 2 天。


6、开始学习 JSP 。
基本的 JSP 学习通过的步骤 1到步骤6可以完成, 然后使用 JSP 对象和脚本写 JSP 程序来联系。学习 JSP 的另外一个方面可以学习怎么创

建一个分布式的应用程序。
训练时间:4 ~ 6 个星期。


7、学习更多的 JSP server。
没有关于更多的 JSP Server当然也可以运行jsp程序。然而, 许多 JSP server都由自己特殊的特征,可以让你更好的理解你的JSP 工程。

学习更多的Jsp server如何处理jsp程序是有必要的。同样也可以优化你的 JSP 应用程序,并且使之运行得更快而不出任何问题。
训练时间:2 ~ 7 天。


8、 学习 JDBC 。
JSP 大多数应用将使用数据库,JDBC 被用于数据库连接。经常忽略的一个事实就是,每个 JDBC Driver 所支持的东西是相当不同的。了

解并熟悉在jsp工程上被使用的 JDBC driver的细节是很重要的。
(有时这部分的学习被包含在前面 Java 或JSP的学习中了 。)
训练时间:1~ 2 个星期。

到现在,你已经成为了熟练的 JSP 程序员。仍然有很多需要学习,你可以考虑扩展你的知识比如 DHTML , XML ,java证书, JSP Tag

Libraries 或 Servlets , 看你想要造什么类型的网站而决定了。

这些训练是JSP 的核心。你不必都学习上面所有的, 取决于你在工程中分配到什么任务和你已经有什么知识。但是这是我成功地训练程序员

的时间表。关键的单元是时间。平均的说, 5 个月时间确实能够训练一个人 ( 从开始到完成 ) 成为一个对jsp熟悉程序员。5 个月时间似乎很

长,但要成为一个资深的WEB程序员所学的东西远远不止这一些。

也许你认为这样学习一种语言花费的时间太长了,因为学 ASP 会更快、时间会更短。 但是学习 ASP 不需要学习java的。

下面是部分比较好的jsp学习书籍,可以直接在网上找到:

Servlets and JavaServer Pages (JSP) 1.0: A Tutorial (Marty Hall. 1999)
http://www.apl.jhu.edu/~hall/java/Servlet-Tutorial/

JSP: The Short Course (Ray Carnes 8.26.2000)
http://www.jspinsider.com/tutorials/jsp/Ray/JSPB_Intro.html

JavaServer Pages Fundamentals (Govind Seshadri 9.13.2000)
http://developer.java.sun.com/developer/onlineTraining/JSPIntro/

The Java Tutorial (Sun)
http://java.sun.com/docs/books/tutorial/

JSP Tag Extensions (Wrox 2000)
http://www.jspinsider.com/tutorials/tagextensions/wrox/4656_Content.html

JSP Product Page (Sun)
http://java.sun.com/products/jsp/  

Java Servlet作为首选的服务器端数据处理技术,正在迅速取代CGI脚本。Servlet超越CGI的优势之一在于,不仅多个请求可以共享公用资源,而且还可以在不同用户请求之间保留持续数据。本文介绍一种充分发挥该特色的实用技术,即数据库连接池。

一、实现连接池的意义

动态Web站点往往用数据库存储的信息生成Web页面,每一个页面请求导致一次数据库访问。连接数据库不仅要开销一定的通讯和内存资源,还必须完成用户验证、安全上下文配置这类任务,因而往往成为最为耗时的操作。当然,实际的连接时间开销千变万化,但1到2秒延迟并非不常见。如果某个基于数据库的Web应用只需建立一次初始连
接,不同页面请求能够共享同一连接,就能获得显著的性能改善。
Servlet是一个Java类。Servlet引擎(它可能是Web服务软件的一部分,也可能是一个独立的附加模块)在系统启动或Servlet第一次被请求时将该类装入Java虚拟机并创建它的一个实例。不同用户请求由同一Servlet实例的多个独立线程处理。那些要
求在不同请求之间持续有效的数据既可以用Servlet的实例变量来保存,也可以保存在独立的辅助对象中。
用JDBC访问数据库首先要创建与数据库之间的连接,获得一个连接对象(Connection),由连接对象提供执行SQL语句的方法。
本文介绍的数据库连接池包括一个管理类DBConnectionManager,负责提供与多个连接池对象(DBConnectionPool类)之间的接口。每一个连接池对象管理一组JDBC连接对象,每一个连接对象可以被任意数量的Servlet共享。
类DBConnectionPool提供以下功能:

1) 从连接池获取(或创建)可用连接。
2) 把连接返回给连接池。
3) 在系统关闭时释放所有资源,关闭所有连接。

此外, DBConnectionPool类还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题)
,并能够限制连接池中的连接总数不超过某个预定值。
管理类DBConnectionManager用于管理多个连接池对象,它提供以下功能:

1) 装载和注册JDBC驱动程序。
2) 根据在属性文件中定义的属性创建连接池对象。
3) 实现连接池名字与其实例之间的映射。
4) 跟踪客户程序对连接池的引用,保证在最后一个客户程序结束时安全地关闭所有连接池。

本文余下部分将详细说明这两个类,最后给出一个示例演示Servlet使用连接池的一般过程。

二、具体实现

DBConnectionManager.java程序清单如下:

001 import java.io.*;
002 import java.sql.*;
003 import java.util.*;
004 import java.util.Date;
005
006 /**
007 * 管理类DBConnectionManager支持对一个或多个由属性文件定义的数据库连接
008 * 池的访问.客户程序可以调用getInstance()方法访问本类的唯一实例.
009 */
010 public class DBConnectionManager {
011 static private DBConnectionManager instance; // 唯一实例
012 static private int clients;
013
014 private Vector drivers = new Vector();
015 private PrintWriter log;
016 private Hashtable pools = new Hashtable();
017
018 /**
019 * 返回唯一实例.如果是第一次调用此方法,则创建实例
020 *
021 * @return DBConnectionManager 唯一实例
022 */
023 static synchronized public DBConnectionManager getInstance() {
024 if (instance == null) {
025 instance = new DBConnectionManager();
026 }
027 clients++;
028 return instance;
029 }
030
031 /**
032 * 建构函数私有以防止其它对象创建本类实例
033 */
034 private DBConnectionManager() {
035 init();
036 }
037
038 /**
039 * 将连接对象返回给由名字指定的连接池
040 *
041 * @param name 在属性文件中定义的连接池名字
042 * @param con 连接对象
043 */
044 public void freeConnection(String name, Connection con) {
045 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
046 if (pool != null) {
047 pool.freeConnection(con);
048 }
049 }
050
051 /**
052 * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数
053 * 限制,则创建并返回新连接
054 *
055 * @param name 在属性文件中定义的连接池名字
056 * @return Connection 可用连接或null
057 */
058 public Connection getConnection(String name) {
059 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
060 if (pool != null) {
061 return pool.getConnection();
062 }
063 return null;
064 }
065
066 /**
067 * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,
068 * 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.
069 *
070 * @param name 连接池名字
071 * @param time 以毫秒计的等待时间
072 * @return Connection 可用连接或null
073 */
074 public Connection getConnection(String name, long time) {
075 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
076 if (pool != null) {
077 return pool.getConnection(time);
078 }
079 return null;
080 }
081
082 /**
083 * 关闭所有连接,撤销驱动程序的注册
084 */
085 public synchronized void release() {
086 // 等待直到最后一个客户程序调用
087 if (–clients != 0) {
088 return;
089 }
090
091 Enumeration allPools = pools.elements();
092 while (allPools.hasMoreElements()) {
093 DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
094 pool.release();
095 }
096 Enumeration allDrivers = drivers.elements();
097 while (allDrivers.hasMoreElements()) {
098 Driver driver = (Driver) allDrivers.nextElement();
099 try {
100 DriverManager.deregisterDriver(driver);
101 log("撤销JDBC驱动程序 " + driver.getClass().getName()+"的注册");
102 }
103 catch (SQLException e) {
104 log(e, "无法撤销下列JDBC驱动程序的注册: " + driver.getClass().getName());
105 }
106 }
107 }
108
109 /**
110 * 根据指定属性创建连接池实例.
111 *
112 * @param props 连接池属性
113 */
114 private void createPools(Properties props) {
115 Enumeration propNames = props.propertyNames();
116 while (propNames.hasMoreElements()) {
117 String name = (String) propNames.nextElement();
118 if (name.endsWith(".url")) {
119 String poolName = name.substring(0, name.lastIndexOf("."));
120 String url = props.getProperty(poolName + ".url");
121 if (url == null) {
122 log("没有为连接池" + poolName + "指定URL");
123 continue;
124 }
125 String user = props.getProperty(poolName + ".user");
126 String password = props.getProperty(poolName + ".password");
127 String maxconn = props.getProperty(poolName + ".maxconn", "0");
128 int max;
129 try {
130 max = Integer.valueOf(maxconn).intValue();
131 }
132 catch (NumberFormatException e) {
133 log("错误的最大连接数限制: " + maxconn + " .连接池: " + poolName);
134 max = 0;
135 }
136 DBConnectionPool pool =
137 new DBConnectionPool(poolName, url, user, password, max);
138 pools.put(poolName, pool);
139 log("成功创建连接池" + poolName);
140 }
141 }
142 }
143
144 /**
145 * 读取属性完成初始化
146 */
147 private void init() {
148 InputStream is = getClass().getResourceAsStream("/db.properties");
149 Properties dbProps = new Properties();
150 try {
151 dbProps.load(is);
152 }
153 catch (Exception e) {
154 System.err.println("不能读取属性文件. " +
155 "请确保db.properties在CLASSPATH指定的路径中");
156 return;
157 }
158 String logFile = dbProps.getProperty("logfile", "DBConnectionManager.log");
159 try {
160 log = new PrintWriter(new FileWriter(logFile, true), true);
161 }
162 catch (IOException e) {
163 System.err.println("无法打开日志文件: " + logFile);
164 log = new PrintWriter(System.err);
165 }
166 loadDrivers(dbProps);
167 createPools(dbProps);
168 }
169
170 /**
171 * 装载和注册所有JDBC驱动程序
172 *
173 * @param props 属性
174 */
175 private void loadDrivers(Properties props) {
176 String driverClasses = props.getProperty("drivers");
177 StringTokenizer st = new StringTokenizer(driverClasses);
178 while (st.hasMoreElements()) {
179 String driverClassName = st.nextToken().trim();
180 try {
181 Driver driver = (Driver)
182 Class.forName(driverClassName).newInstance();
183 DriverManager.registerDriver(driver);
184 drivers.addElement(driver);
185 log("成功注册JDBC驱动程序" + driverClassName);
186 }
187 catch (Exception e) {
188 log("无法注册JDBC驱动程序: " +
189 driverClassName + ", 错误: " + e);
190 }
191 }
192 }
193
194 /**
195 * 将文本信息写入日志文件
196 */
197 private void log(String msg) {
198 log.println(new Date() + ": " + msg);
199 }
200
201 /**
202 * 将文本信息与异常写入日志文件
203 */
204 private void log(Throwable e, String msg) {
205 log.println(new Date() + ": " + msg);
206 e.printStackTrace(log);
207 }
208
209 /**
210 * 此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最
211 * 大连接数为止.在返回连接给客户程序之前,它能够验证连接的有效性.
212 */
213 class DBConnectionPool {
214 private int checkedOut;
215 private Vector freeConnections = new Vector();
216 private int maxConn;
217 private String name;
218 private String password;
219 private String URL;
220 private String user;
221
222 /**
223 * 创建新的连接池
224 *
225 * @param name 连接池名字
226 * @param URL 数据库的JDBC URL
227 * @param user 数据库帐号,或 null
228 * @param password 密码,或 null
229 * @param maxConn 此连接池允许建立的最大连接数
230 */
231 public DBConnectionPool(String name, String URL, String user, String password,
232 int maxConn) {
233 this.name = name;
234 this.URL = URL;
235 this.user = user;
236 this.password = password;
237 this.maxConn = maxConn;
238 }
239
240 /**
241 * 将不再使用的连接返回给连接池
242 *
243 * @param con 客户程序释放的连接
244 */
245 public synchronized void freeConnection(Connection con) {
246 // 将指定连接加入到向量末尾
247 freeConnections.addElement(con);
248 checkedOut–;
249 notifyAll();
250 }
251
252 /**
253 * 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接
254 * 数限制,则创建新连接.如原来登记为可用的连接不再有效,则从向量删除之,
255 * 然后递归调用自己以尝试新的可用连接.
256 */
257 public synchronized Connection getConnection() {
258 Connection con = null;
259 if (freeConnections.size() > 0) {
260 // 获取向量中第一个可用连接
261 con = (Connection) freeConnections.firstElement();
262 freeConnections.removeElementAt(0);
263 try {
264 if (con.isClosed()) {
265 log("从连接池" + name+"删除一个无效连接");
266 // 递归调用自己,尝试再次获取可用连接
267 con = getConnection();
268 }
269 }
270 catch (SQLException e) {
271 log("从连接池" + name+"删除一个无效连接");
272 // 递归调用自己,尝试再次获取可用连接
273 con = getConnection();
274 }
275 }
276 else if (maxConn == 0 || checkedOut < maxConn) {
277 con = newConnection();
278 }
279 if (con != null) {
280 checkedOut++;
281 }
282 return con;
283 }
284
285 /**
286 * 从连接池获取可用连接.可以指定客户程序能够等待的最长时间
287 * 参见前一个getConnection()方法.
288 *
289 * @param timeout 以毫秒计的等待时间限制
290 */
291 public synchronized Connection getConnection(long timeout) {
292 long startTime = new Date().getTime();
293 Connection con;
294 while ((con = getConnection()) == null) {
295 try {
296 wait(timeout);
297 }
298 catch (InterruptedException e) {}
299 if ((new Date().getTime() – startTime) >= timeout) {
300 // wait()返回的原因是超时
301 return null;
302 }
303 }
304 return con;
305 }
306
307 /**
308 * 关闭所有连接
309 */
310 public synchronized void release() {
311 Enumeration allConnections = freeConnections.elements();
312 while (allConnections.hasMoreElements()) {
313 Connection con = (Connection) allConnections.nextElement();
314 try {
315 con.close();
316 log("关闭连接池" + name+"中的一个连接");
317 }
318 catch (SQLException e) {
319 log(e, "无法关闭连接池" + name+"中的连接");
320 }
321 }
322 freeConnections.removeAllElements();
323 }
324
325 /**
326 * 创建新的连接
327 */
328 private Connection newConnection() {
329 Connection con = null;
330 try {
331 if (user == null) {
332 con = DriverManager.getConnection(URL);
333 }
334 else {
335 con = DriverManager.getConnection(URL, user, password);
336 }
337 log("连接池" + name+"创建一个新的连接");
338 }
339 catch (SQLException e) {
340 log(e, "无法创建下列URL的连接: " + URL);
341 return null;
342 }
343 return con;
344 }
345 }
346 }

三、类DBConnectionPool说明

该类在209至345行实现,它表示指向某个数据库的连接池。数据库由JDBC URL标识。一个JDBC URL由三部分组成:协议标识(总是jdbc),驱动程序标识(如 odbc、idb、oracle等),数据库标识(其格式依赖于驱动程序)。例如,jdbc:odbc:de
mo,即是一个指向demo数据库的JDBC URL,而且访问该数据库要使用JDBC-ODBC驱动程序。每个连接池都有一个供客户程序使用的名字以及可选的用户帐号、密码、最大连接数限制。如果Web应用程序所支持的某些数据库操作可以被所有用户执行,而其它一些操作应由特别许可的用户执行,则可以为两类操作分别定义连接池,两个连接池使用相同的JDBC URL,但使用不同的帐号和密码。
类DBConnectionPool的建构函数需要上述所有数据作为其参数。如222至238行所示,这些数据被保存为它的实例变量:
如252至283行、285至305行所示, 客户程序可以使用DBConnectionPool类提供的两个方法获取可用连接。两者的共同之处在于:如连接池中存在可用连接,则直接返回,否则创建新的连接并返回。如果没有可用连接且已有连接总数等于最大限制
数,第一个方法将直接返回null,而第二个方法将等待直到有可用连接为止。
所有的可用连接对象均登记在名为freeConnections的向量(Vector)中。如果向量中有多于一个的连接,getConnection()总是选取第一个。同时,由于新的可用连接总是从尾部加入向量,从而使得数据库连接由于长时间闲置而被关闭的风险减低到最小程度。
第一个getConnection()在返回可用连接给客户程序之前,调用了isClosed()方法验证连接仍旧有效。如果该连接被关闭或触发异常,getConnection()递归地调用自己以尝试获取另外的可用连接。如果在向量freeConnections中不存在任何可用连
接,getConnection()方法检查是否已经指定最大连接数限制。如已经指定,则检查当前连接数是否已经到达极限。此处maxConn为0表示没有限制。如果没有指定最大连接数限制或当前连接数小于该值,该方法尝试创建新的连接。如创建成功,则增加已使用连接的计数并返回,否则返回空值。
如325至345行所示,创建新连接由newConnection()方法实现。创建过程与是否已经指定数据库帐号、密码有关。
JDBC的DriverManager类提供多个getConnection()方法,这些方法要用到JDBC URL与其它一些参数,如用户帐号和密码等。
DriverManager将使用指定的JDBC URL确定适合于目标数据库的驱动程序及建立连接。
在285至305行实现的第二个getConnection()方法需要一个以毫秒为单位的时间参数,该参数表示客户程序能够等待的最长时间。建立连接的具体操作仍旧由第一个getConnection()方法实现。
该方法执行时先将startTime初始化为当前时间。在while循环中尝试获得一个连接。如果失败,则以给定的时间值为参数调用wait()。wait()的返回可能是由于其它线程调用notify()或notifyAll(),也可能是由于预定时间已到。为找出wait()返回的真正原因,程序用当前时间减开始时间(startTime),如差值大于预定时间则返回空值,否则再次调用getConnection()。
把空闲的连接登记到连接池由240至250行的freeConnection()方法实现,它的参数为返回给连接池的连接对象。该对象被加入到freeConnections向量的末尾,然后减少已使用连接计数。调用notifyAll()是为了通知其它正在等待可用连接的线程。
许多Servlet引擎为实现安全关闭提供多种方法。数据库连接池需要知道该事件以保证所有连接能够正常关闭。DBConnectionManager类负协调整个关闭过程,但关闭连接池中所有连接的任务则由DBConnectionPool类负责。在307至323行实现的release()方法供DBConnectionManager调用。该方法遍历freeConnections向量并关闭所有连接,然后从向量中删除这些连接。

四、类DBConnectionManager 说明

该类只能创建一个实例,其它对象能够调用其静态方法(也称为类方法)获得该唯一实例的引用。如031至036行所示,DBConnectionManager类的建构函数是私有的,这是为了避免其它对象创建该类的实例。
DBConnectionManager类的客户程序可以调用getInstance()方法获得对该类唯一实例的引用。如018至029行所示,类的唯一实例在getInstance()方法第一次被调用期间创建,此后其引用就一直保存在静态变量instance中。每次调用getInstance()
都增加一个DBConnectionManager的客户程序计数。即,该计数代表引用DBConnectionManager唯一实例的客户程序总数,它将被用于控制连接池的关闭操作。
该类实例的初始化工作由146至168行之间的私有方法init()完成。其中 getResourceAsStream()方法用于定位并打开外部文件。外部文件的定位方法依赖于类装载器的实现。标准的本地类装载器查找操作总是开始于类文件所在路径,也能够搜索CLASSPATH中声明的路径。db.properties是一个属性文件,它包含定义连接池的键-值对。可供定义的公用属性如下:

drivers 以空格分隔的JDBC驱动程序类列表
logfile 日志文件的绝对路径

其它的属性和特定连接池相关,其属性名字前应加上连接池名字:

<poolname>.url 数据库的 JDBC URL
<poolname>.maxconn 允许建立的最大连接数,0表示没有限制
<poolname>.user 用于该连接池的数据库帐号
<poolname>.password 相应的密码

其中url属性是必需的,而其它属性则是可选的。数据库帐号和密码必须合法。用于Windows平台的db.properties文件示例
如下:

drivers=sun.jdbc.odbc.JdbcOdbcDriver jdbc.idbDriver
logfile=D:\\user\\src\\java\\DBConnectionManager\\log.txt

idb.url=jdbc:idb:c:\\local\\javawebserver1.1\\db\\db.prp
idb.maxconn=2

access.url=jdbc:odbc:demo
access.user=demo
access.password=demopw

注意在Windows路径中的反斜杠必须输入2个,这是由于属性文件中的反斜杠同时也是一个转义字符。
init()方法在创建属性对象并读取db.properties文件之后,就开始检查logfile属性。如果属性文件中没有指定日志文件,则默认为当前目录下的DBConnectionManager.log文件。如日志文件无法使用,则向System.err输出日志记录。
装载和注册所有在drivers属性中指定的JDBC驱动程序由170至192行之间的loadDrivers()方法实现。该方法先用StringTokenizer将drivers属性值分割为对应于驱动程序名称的字符串,然后依次装载这些类并创建其实例,最后在 DriverManager中注册
该实例并把它加入到一个私有的向量drivers。向量drivers将用于关闭服务时从DriverManager取消所有JDBC 驱动程序的注册。
init()方法的最后一个任务是调用私有方法createPools()创建连接池对象。如109至142行所示,createPools()方法先创建所有属性名字的枚举对象(即Enumeration对象,该对象可以想象为一个元素系列,逐次调用其nextElement()方法将顺序返
回各元素),然后在其中搜索名字以“.url”结尾的属性。对于每一个符合条件的属性,先提取其连接池名字部分,进而读取所有属于该连接池的属性,最后创建连接池对象并把它保存在实例变量pools中。散列表(Hashtable类 )pools实现连接
池名字到连接池对象之间的映射,此处以连接池名字为键,连接池对象为值。
为便于客户程序从指定连接池获得可用连接或将连接返回给连接池,类DBConnectionManager提供了方法getConnection()和freeConnection()。所有这些方法都要求在参数中指定连接池名字,具体的连接获取或返回操作则调用对应的连接池对象完成。它们的实现分别在051至064行、066至080行、038至049行。
如082至107行所示,为实现连接池的安全关闭,DBConnectionManager提供了方法release()。在上面我们已经提到,所有DBConnectionManager的客户程序都应该调用静态方法getInstance()以获得该管理器的引用,此调用将增加客户程序计数。
客户程序在关闭时调用release()可以递减该计数。当最后一个客户程序调用release(),递减后的引用计数为0,就可以调用各个连接池的release()方法关闭所有连接了。管理类release()方法最后的任务是撤销所有JDBC驱动程序的注册。

五、Servlet使用连接池示例

Servlet API所定义的Servlet生命周期类如:

1) 创建并初始化Servlet(init()方法)。
2) 响应客户程序的服务请求(service()方法)。
3) Servlet终止运行,释放所有资源(destroy()方法)。

本例演示连接池应用,上述关键步骤中的相关操作为:

1) 在init(),用实例变量connMgr 保存调用DBConnectionManager.getInstance()所返回的引用。
2) 在service(),调用getConnection(),执行数据库操作,用freeConnection()将连接返回给连接池。
3) 在destroy(),调用release()关闭所有连接,释放所有资源。

示例程序清单如下:

import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class TestServlet extends HttpServlet {
private DBConnectionManager connMgr;

public void init(ServletConfig conf) throws ServletException {
super.init(conf);
connMgr = DBConnectionManager.getInstance();
}

public void service(HttpServletRequest req, HttpServletResponse res)
throws IOException {

res.setContentType("text/html");
PrintWriter out = res.getWriter();
Connection con = connMgr.getConnection("idb");
if (con == null) {
out.println("不能获取数据库连接.");
return;
}
ResultSet rs = null;
ResultSetMetaData md = null;
Statement stmt = null;
try {
stmt = con.createStatement();
rs = stmt.executeQuery("SELECT * FROM EMPLOYEE");
md = rs.getMetaData();
out.println("<H1>职工数据</H1>");
while (rs.next()) {
out.println("<BR>");
for (int i = 1; i < md.getColumnCount(); i++) {
out.print(rs.getString(i) + ", ");
}
}
stmt.close();
rs.close();
}
catch (SQLException e) {
e.printStackTrace(out);
}
connMgr.freeConnection("idb", con);
}

public void destroy() {
connMgr.release();
super.destroy();
}
}

Java2十大经典中文图书
只针对Java2平台-而且是在国内可以找到的中文版的,其实书好不好是见仁见智的(高手可以去看Sun的开发文档,这也可以理解)–这只是一家之见而已–欢迎大家点评。
第一名:Java编程思想第二版(Thinking in java second)-包含范围:全部。
没说的–绝对的大师作品–当之无愧的第一–第一版时就享誉整个java界(当时网上好象也有人译了)–国内版是京京工作室翻译的-基本上毁了此书–错误术语太多-推荐高手看E文版,新手就不要先看此书了。第二版更精采–台湾的侯捷译了此书-可以到www.csdn.net看看前几章的中文版(不过是台湾术语)。希望国内会快些引入此书,你也可到http://www.BruceEckel.com–作者的主页免费下载此书推荐http://www.bdelmee.easynet.be/java/index.html–有chm格式的–非常棒!BTW:这位大师最近在写Thinking in Python相信又是一本经典名著:)

第二名:Java2编程详解(special edition java2)-包含范围:全部–这本书会排在core java2的前面可能很多人会不同意–但是就中译本和内容来看非常全面-适合新手成为高手-虽然国内的价位高了些(150)-但基本还是值得的–该有的内容都有了,做参考书也很不错-BTW-这个系列中的oracle8/8i使用手册也是一本很经典的书–同样推荐。

第三名:Java2核心技术卷一,二(core java2 volume1,2)-包含范围-全部
这两本我把它们看成一本不会有人有异议吧-这也是Sun的官方的书-我觉得相对来说-第二卷高级特性要比第一卷基础知识好(第一卷又是京京工作室译的–真影响情绪:()-内容同样很全面–但是卷一虽说是基础知识,同样对新手来说不是很合适–感觉条理性不强-而且内容有些混杂-但第二卷完全可以弥补这些—精辟而细致-很适合有一定基础的Java程序员看。

第四名:Java 2图形设计 卷1:AWT 卷2:Swing-Graphic Java 1.2 Mastering the JFC Volume I:AWT SWING 3rd Edition包含范围–Java图形设计–没什么说的了–尽管图形设计不如J2EE那么火 ,而且Win32下做应用程序也不是java的强项–但是AWT和Swing仍然是Java程序员的必修课–看这两本就够了–看看厚度就知道了–而且这也是Sun官方出的图书。

第五名:J2EE构建企业系统??专家级解决方案 包含范围J2ee清华大学出版社译作者:
[美]Paul J. Perrone,et al.著 张志伟等译–又是一本极厚的书1038页,105元–不过内容十分丰富–适合想对J2EE整体做了解的程序员,至于每项都想精就不太可能了-呵呵-毕竟在Java中思想是主要的。在这类中有本Java服务器高级编程也很不错-机工华章出的-wrox系列中的。

第六名: Java XML编程指南 电子工业出版社译作者: [美]Tom Myers,Alexander Nakhimovsky著包含范围Java+XML-XML在Java中的地位也越来越重要了–而国内能看到的还有一本中国电力出的o eilly的Java和XML–最后我还是选了这本wrox的,当然你要是想好好学学XML本身-还是看看那本XML高级编程吧–wrox系列中的-机工华章出的。

第七名:书名:Jini核心技术英文原书名: Core Jini
作者: W.Keith Edwards 包含范围Jini–Jini也是Java体系中很重要的部分–而且更重要的是这本可能是国内唯一的一本Jini专著-翻译的也不错–在我看来是当之无愧的经典-同样是Sun的官方图书–内容很清晰透彻。

第八名:Enterprise JavaBeans第二版英文原书名: Enterprise JAVABEANS 作者: (美)Richard Monson-Haefel包含范围EJB –O‘reilly出的–ejb的重要性我不用多说了吧–尽管有人说这本译的不好–但是同样它是国内目前唯一的EJB专著-o eilly的书就是只讲一方面或一项技术-很细-但价格也不菲,这本书的架构还可以–值得一看。

第九名:数据结构与算法分析(Java版)译作者: [美]Clifford A.Shaffer著 张铭 刘晓丹译包含范围Java算法–尽管基本上Java图书都是讲Java本身的特性–因为Java的体系太庞大了–而用Java来实现的数据结构与算法–这本书也是唯一一本—所以尽管这本不是那么的好但还是做以推荐。

第十名:软件工程Java语言实现-英文原书名: Software Engineering with Java 作者: Stephen R.Schach 译者: 袁兆山等–这本书没什么多说的–纯理论性的东西–但软件工程的重要也是有目共睹的-而且同样是这个领域中唯一的一本Java书。