2009年5月13日
#
1. 编码问题的由来,相关概念的理解
1.1 字符与编码的发展
从计算机对多国语言的支持角度看,大致可以分为三个阶段:
系统内码 说明 系统
阶段一 ASCII 计算机刚开始只支持英语,其它语言不能够在计算机上存储和显示。 英文 DOS
阶段二 ANSI编码
(本地化) 为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 '中' 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。
不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。 中文 DOS,中文 Windows 95/98,日文 Windows 95/98
阶段三 UNICODE
(国际化) 为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。 Windows NT/2000/XP,Linux,Java
字符串在内存中的存放方法:
在 ASCII 阶段,单字节字符串使用一个字节存放一个字符(SBCS)。比如,"Bob123" 在内存中为:
42 6F 62 31 32 33 00
B o b 1 2 3 \0
在使用 ANSI 编码支持多种语言阶段,每个字符使用一个字节或多个字节来表示(MBCS),因此,这种方式存放的字符也被称作多字节字符。比如,"中文123" 在中文 Windows 95 内存中为7个字节,每个汉字占2个字节,每个英文和数字字符占1个字节:
D6 D0 CE C4 31 32 33 00
中 文 1 2 3 \0
在 UNICODE 被采用之后,计算机存放字符串时,改为存放每个字符在 UNICODE 字符集中的序号。目前计算机一般使用 2 个字节(16 位)来存放一个序号(DBCS),因此,这种方式存放的字符也被称作宽字节字符。比如,字符串 "中文123" 在 Windows 2000 下,内存中实际存放的是 5 个序号:
2D 4E 87 65 31 00 32 00 33 00 00 00 ← 在 x86 CPU 中,低字节在前
中 文 1 2 3 \0
一共占 10 个字节。
回页首
1.2 字符,字节,字符串
理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分:
概念描述 举例
字符 人们使用的记号,抽象意义上的一个符号。 '1', '中', 'a', '$', '¥', ……
字节 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间。 0x01, 0x45, 0xFA, ……
ANSI
字符串 在内存中,如果“字符”是以 ANSI 编码形式存在的,一个字符可能使用一个字节或多个字节来表示,那么我们称这种字符串为 ANSI 字符串或者多字节字符串。 "中文123"
(占7字节)
UNICODE
字符串 在内存中,如果“字符”是以在 UNICODE 中的序号存在的,那么我们称这种字符串为 UNICODE 字符串或者宽字节字符串。 L"中文123"
(占10字节)
由于不同 ANSI 编码所规定的标准是不相同的,因此,对于一个给定的多字节字符串,我们必须知道它采用的是哪一种编码规则,才能够知道它包含了哪些“字符”。而对于 UNICODE 字符串来说,不管在什么环境下,它所代表的“字符”内容总是不变的。
回页首
1.3 字符集与编码
各个国家和地区所制定的不同 ANSI 编码标准中,都只规定了各自语言所需的“字符”。比如:汉字标准(GB2312)中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义:
使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”。
规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。
各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。
“UNICODE 字符集”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很多种,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。
1.4 常用的编码简介
简单介绍一下常用的编码规则,为后边的章节做一个准备。在这里,我们根据编码规则的特点,把所有的编码分成三类:
分类 编码标准 说明
单字节字符编码 ISO-8859-1 最简单的编码规则,每一个字节直接作为一个 UNICODE 字符。比如,[0xD6, 0xD0] 这两个字节,通过 iso-8859-1 转化为字符串时,将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符,即 "ÖÐ"。
反之,将 UNICODE 字符串通过 iso-8859-1 转化为字节串时,只能正常转化 0~255 范围的字符。
ANSI 编码 GB2312,
BIG5,
Shift_JIS,
ISO-8859-2 …… 把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时,根据各自编码的规定,一个 UNICODE 字符可能转化成一个字节或多个字节。
反之,将字节串转化成字符串时,也可能多个字节转化成一个字符。比如,[0xD6, 0xD0] 这两个字节,通过 GB2312 转化为字符串时,将得到 [0x4E2D] 一个字符,即 '中' 字。
“ANSI 编码”的特点:
1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。
2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。
UNICODE 编码 UTF-8,
UTF-16, UnicodeBig …… 与“ANSI 编码”类似的,把字符串通过 UNICODE 编码转化成“字节串”时,一个 UNICODE 字符可能转化成一个字节或多个字节。
与“ANSI 编码”不同的是:
1. 这些“UNICODE 编码”能够处理所有的 UNICODE 字符。
2. “UNICODE 字符”与“转换出来的字节”之间是可以通过计算得到的。
我们实际上没有必要去深究每一种编码具体把某一个字符编码成了哪几个字节,我们只需要知道“编码”的概念就是把“字符”转化成“字节”就可以了。对于“UNICODE 编码”,由于它们是可以通过计算得到的,因此,在特殊的场合,我们可以去了解某一种“UNICODE 编码”是怎样的规则。
2. 字符与编码在程序中的实现
2.1 程序中的字符与字节
在 C++ 和 Java 中,用来代表“字符”和“字节”的数据类型,以及进行编码的方法:
类型或操作 C++ Java
字符 wchar_t char
字节 char byte
ANSI 字符串 char[] byte[]
UNICODE 字符串 wchar_t[] String
字节串→字符串 mbstowcs(), MultiByteToWideChar() string = new String(bytes, "encoding")
字符串→字节串 wcstombs(), WideCharToMultiByte() bytes = string.getBytes("encoding")
以上需要注意几点:
Java 中的 char 代表一个“UNICODE 字符(宽字节字符)”,而 C++ 中的 char 代表一个字节。
MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函数。
2.2 C++ 中相关实现方法
声明一段字符串常量:
// ANSI 字符串,内容长度 7 字节
char sz[20] = "中文123";
// UNICODE 字符串,内容长度 5 个 wchar_t(10 字节)
wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033";
UNICODE 字符串的 I/O 操作,字符与字节的转换操作:
// 运行时设定当前 ANSI 编码,VC 格式
setlocale(LC_ALL, ".936");
// GCC 中格式
setlocale(LC_ALL, "zh_CN.GBK");
// Visual C++ 中使用小写 %s,按照 setlocale 指定编码输出到文件
// GCC 中使用大写 %S
fwprintf(fp, L"%s\n", wsz);
// 把 UNICODE 字符串按照 setlocale 指定的编码转换成字节
wcstombs(sz, wsz, 20);
// 把字节串按照 setlocale 指定的编码转换成 UNICODE 字符串
mbstowcs(wsz, sz, 20);
在 Visual C++ 中,UNICODE 字符串常量有更简单的表示方法。如果源程序的编码与当前默认 ANSI 编码不符,则需要使用 #pragma setlocale,告诉编译器源程序使用的编码:
// 如果源程序的编码与当前默认 ANSI 编码不一致,
// 则需要此行,编译时用来指明当前源程序使用的编码
#pragma setlocale(".936")
// UNICODE 字符串常量,内容长度 10 字节
wchar_t wsz[20] = L"中文123";
以上需要注意 #pragma setlocale 与 setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在编译时起作用,setlocale() 在运行时起作用。
2.3 Java 中相关实现方法
字符串类 String 中的内容是 UNICODE 字符串:
// Java 代码,直接写中文
String string = "中文123";//come from http://www.bt285.cn http://www.5a520.cn
// 得到长度为 5,因为是 5 个字符
System.out.println(string.length());
字符串 I/O 操作,字符与字节转换操作。在 Java 包 java.io.* 中,以“Stream”结尾的类一般是用来操作“字节串”的类,以“Reader”,“Writer”结尾的类一般是用来操作“字符串”的类。
// 字符串与字节串间相互转化
// 按照 GB2312 得到字节(得到多字节字符串)
byte [] bytes = string.getBytes("GB2312");
// 从字节按照 GB2312 得到 UNICODE 字符串
string = new String(bytes, "GB2312");
// 要将 String 按照某种编码写入文本文件,有两种方法:
// 第一种办法:用 Stream 类写入已经按照指定编码转化好的字节串
OutputStream os = new FileOutputStream("1.txt");
os.write(bytes);
os.close();
// 第二种办法:构造指定编码的 Writer 来写入字符串
Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312");
ow.write(string);
ow.close();
/* 最后得到的 1.txt 和 2.txt 都是 7 个字节 */
如果 java 的源程序编码与当前默认 ANSI 编码不符,则在编译的时候,需要指明一下源程序的编码。比如:
E:\>javac -encoding BIG5 Hello.java
以上需要注意区分源程序的编码与 I/O 操作的编码,前者是在编译时起作用,后者是在运行时起作用。
3. 几种误解,以及乱码产生的原因和解决办法
3.1 容易产生的误解
对编码的误解
误解一 在将“字节串”转化成“UNICODE 字符串”时,比如在读取文本文件时,或者通过网络传输文本时,容易将“字节串”简单地作为单字节字符串,采用每“一个字节”就是“一个字符”的方法进行转化。
而实际上,在非英文的环境中,应该将“字节串”作为 ANSI 字符串,采用适当的编码来得到 UNICODE 字符串,有可能“多个字节”才能得到“一个字符”。
通常,一直在英文环境下做开发的程序员们,容易有这种误解。
误解二 在 DOS,Windows 98 等非 UNICODE 环境下,字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串,必须知道是哪种编码才能被正确地使用。这使我们形成了一个惯性思维:“字符串的编码”。
当 UNICODE 被支持后,Java 中的 String 是以字符的“序号”来存储的,不是以“某种编码的字节”来存储的,因此已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时,或者,将一个“字节串”当成一个 ANSI 字符串时,才有编码的概念。
不少的人都有这个误解。
第一种误解,往往是导致乱码产生的原因。第二种误解,往往导致本来容易纠正的乱码问题变得更复杂。
在这里,我们可以看到,其中所讲的“误解一”,即采用每“一个字节”就是“一个字符”的转化方法,实际上也就等同于采用 iso-8859-1 进行转化。因此,我们常常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操作,得到原始的“字节串”。然后再使用正确的 ANSI 编码,比如 string = new String(bytes, "GB2312"),来得到正确的“UNICODE 字符串”。
3.2 非 UNICODE 程序在不同语言环境间移植时的乱码
非 UNICODE 程序中的字符串,都是以某种 ANSI 编码形式存在的。如果程序运行时的语言环境与开发时的语言环境不同,将会导致 ANSI 字符串的显示失败。
比如,在日文环境下开发的非 UNICODE 的日文程序界面,拿到中文环境下运行时,界面上将显示乱码。如果这个日文程序界面改为采用 UNICODE 来记录字符串,那么当在中文环境下运行时,界面上将可以显示正常的日文。
由于客观原因,有时候我们必须在中文操作系统下运行非 UNICODE 的日文软件,这时我们可以采用一些工具,比如,南极星,AppLocale 等,暂时的模拟不同的语言环境。
3.3 网页提交字符串
当页面中的表单提交字符串时,首先把字符串按照当前页面的编码,转化成字节串。然后再将每个字节转化成 "%XX" 的格式提交到 Web 服务器。比如,一个编码为 GB2312 的页面,提交 "中" 这个字符串时,提交给服务器的内容为 "%D6%D0"。
在服务器端,Web 服务器把收到的 "%D6%D0" 转化成 [0xD6, 0xD0] 两个字节,然后再根据 GB2312 编码规则得到 "中" 字。
在 Tomcat 服务器中,request.getParameter() 得到乱码时,常常是因为前面提到的“误解一”造成的。默认情况下,当提交 "%D6%D0" 给 Tomcat 服务器时,request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 UNICODE 字符,而不是返回一个 "中" 字符。因此,我们需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字节串,再用 string = new String(bytes, "GB2312") 重新得到正确的字符串 "中"。
3.4 从数据库读取字符串
通过数据库客户端(比如 ODBC 或 JDBC)从数据库服务器中读取字符串时,客户端需要从服务器获知所使用的 ANSI 编码。当数据库服务器发送字节流给客户端时,客户端负责将字节流按照正确的编码转化成 UNICODE 字符串。
如果从数据库读取字符串时得到乱码,而数据库中存放的数据又是正确的,那么往往还是因为前面提到的“误解一”造成的。解决的办法还是通过 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字节串,再重新使用正确的编码转化成字符串。
3.5 电子邮件中的字符串
当一段 Text 或者 HTML 通过电子邮件传送时,发送的内容首先通过一种指定的字符编码转化成“字节串”,然后再把“字节串”通过一种指定的传输编码(Content-Transfer-Encoding)进行转化得到另一串“字节串”。比如,打开一封电子邮件源代码,可以看到类似的内容:
Content-Type: text/plain;
charset="gb2312"
Content-Transfer-Encoding: base64
sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==
最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 两种。在对二进制文件或者中文文本进行转化时,Base64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行转化时,Quoted-Printable 得到的“字节串”比 Base64 更短。
邮件的标题,用了一种更简短的格式来标注“字符编码”和“传输编码”。比如,标题内容为 "中",则在邮件源代码中表示为:
// 正确的标题格式
Subject: =?GB2312?B?1tA=?=
其中,
第一个“=?”与“?”中间的部分指定了字符编码,在这个例子中指定的是 GB2312。
“?”与“?”中间的“B”代表 Base64。如果是“Q”则代表 Quoted-Printable。
最后“?”与“?=”之间的部分,就是经过 GB2312 转化成字节串,再经过 Base64 转化后的标题内容。
如果“传输编码”改为 Quoted-Printable,同样,如果标题内容为 "中":
// 正确的标题格式
Subject: =?GB2312?Q?=D6=D0?=
如果阅读邮件时出现乱码,一般是因为“字符编码”或“传输编码”指定有误,或者是没有指定。比如,有的发邮件组件在发送邮件时,标题 "中":
// 错误的标题格式
Subject: =?ISO-8859-1?Q?=D6=D0?=
这样的表示,实际上是明确指明了标题为 [0x00D6, 0x00D0],即 "ÖÐ",而不是 "中"。
4. 几种错误理解的纠正
误解:“ISO-8859-1 是国际编码?”
非也。iso-8859-1 只是单字节字符集中最简单的一种,也就是“字节编号”与“UNICODE 字符编号”一致的那种编码规则。当我们要把一个“字节串”转化成“字符串”,而又不知道它是哪一种 ANSI 编码时,先暂时地把“每一个字节”作为“一个字符”进行转化,不会造成信息丢失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢复到原始的字节串。
误解:“Java 中,怎样知道某个字符串的内码?”
Java 中,字符串类 java.lang.String 处理的是 UNICODE 字符串,不是 ANSI 字符串。我们只需要把字符串作为“抽象的符号的串”来看待。因此不存在字符串的内码的问题。
摘要: 对于一个非常熟悉 C++ 异常处理模型的程序员来说,它几乎可以不经任何其它培训和学习,就可以完全接受和能够轻松地使用 Java 语言中的异常处理编程方法。这是因为 Java 语言中的异常处理模型几乎与 C++ 中异常处理模型有 99% 的相似度,无论是从语法规则,还是语义上来说,它们二者都几乎完全一致...
阅读全文
1.死锁
多线程编程在实际的网络程序开发中,在客户端程序实现中使用的比较简单,但是在服务器端程序实现中却不仅是大量使用,而且会出现比客户端更多的问题。
另外一个容易在服务器端出现的多线程问题是——死锁。死锁指两个或两个以上的线程为了使用某个临界资源而无限制的等待下去。还是以前面卫生间的例子来说明死锁,例如两个人都同时到达卫生间,而且两个人都比较礼貌,第一个人和第二个人说:你先吧,第二个人和第一个人说:你先吧。这两个人就这样一直在互相礼让,谁也不进入,这种现象就是死锁。这里的两个人就好比是线程,而卫生间在这里就是临界资源,而由于这两个线程在一直谦让,谁也不使用临界资源。
死锁不仅使程序无法达到预期实现的功能,而且浪费系统的资源,所以在服务器端程序中危害比较大,在实际的服务器端程序开发中,需要注意避免死锁。
而死锁的检测比较麻烦,而且不一定每次都出现,这就需要在测试服务器端程序时,有足够的耐心,仔细观察程序执行时的性能检测,如果发现执行的性能显著降低,则很可能是发生了死锁,然后再具体的查找死锁出现的原因,并解决死锁的问题。
死锁出现的最本质原因还是逻辑处理不够严谨,在考虑时不是很周全,所以一般需要修改程序逻辑才能够很好的解决死锁。
2. 线程优先级
在日常生活中,例如火车售票窗口等经常可以看到“XXX优先”,那么多线程编程中每个线程是否也可以设置优先级呢?
在多线程编程中,支持为每个线程设置优先级。优先级高的线程在排队执行时会获得更多的CPU执行时间,得到更快的响应。在实际程序中,可以根据逻辑的需要,将需要得到及时处理的线程设置成较高的优先级,而把对时间要求不高的线程设置成比较低的优先级。
在Thread类中,总计规定了三个优先级,分别为:
l MAX_PRIORITY——最高优先级
l NORM_PRIORITY——普通优先级,也是默认优先级
l MIN_PRIORITY——最低优先级
在前面创建的线程对象中,由于没有设置线程的优先级,则线程默认的优先级是NORM_PRIORITY,在实际使用时,也可以根据需要使用Thread类中的setPriority方法设置线程的优先级,该方法的声明为:
public final void setPriority(int newPriority)
假设t是一个初始化过的线程对象,需要设置t的优先级为最高,则实现的代码为:
t. setPriority(Thread. MAX_PRIORITY);
这样,在该线程执行时将获得更多的执行机会,也就是优先执行。如果由于安全等原因,不允许设置线程的优先级,则会抛出SecurityException异常。
下面使用一个简单的输出数字的线程演示线程优先级的使用,实现的示例代码如下:
package priority;
/**
* 测试线程优先级
* author by http://www.bt285.cn http://www.5a520.cn
*/
public class TestPriority {
public static void main(String[] args) {
PrintNumberThread p1 = new PrintNumberThread("高优先级");
PrintNumberThread p2 = new PrintNumberThread("普通优先级");
PrintNumberThread p3 = new PrintNumberThread("低优先级");
p1.setPriority(Thread.MAX_PRIORITY);
p2.setPriority(Thread.NORM_PRIORITY);
p3.setPriority(Thread.MIN_PRIORITY);
p1.start();
p2.start();
p3.start();
}
}
package priority;
/**
* 输出数字的线程
*/
public class PrintNumberThread extends Thread {
String name;
public PrintNumberThread(String name){
this.name = name;
}
public void run(){
try{
for(int i = 0;i < 10;i++){
System.out.println(name + ":" + i);
}
}catch(Exception e){}
}
}
程序的一种执行结果为:
高优先级:0
高优先级:1
高优先级:2
普通优先级:0
高优先级:3
普通优先级:1
高优先级:4
普通优先级:2
高优先级:5
高优先级:6
高优先级:7
高优先级:8
高优先级:9
普通优先级:3
普通优先级:4
普通优先级:5
普通优先级:6
普通优先级:7
普通优先级:8
普通优先级:9
低优先级:0
低优先级:1
低优先级:2
低优先级:3
低优先级:4
低优先级:5
低优先级:6
低优先级:7
低优先级:8
低优先级:9
在该示例程序,PrintNumberThread线程实现的功能是输出数字,每次数字输出之间没有设置时间延迟,在测试类TestPriority中创建三个PrintNumberThread类型的线程对象,然后分别设置线程优先级是最高、普通和最低,接着启动线程执行程序。从执行结果可以看出高优先级的线程获得了更多的执行时间,首先执行完成,而低优先级的线程由于优先级较低,所以最后一个执行结束。
其实,对于线程优先级的管理主要由系统的线程调度实现,较高优先级的线程优先执行,所以可以通过设置线程的优先级影响线程的执行。
5 总结
关于多线程的基础知识就介绍这么多,在本章中介绍了线程的概念、线程的实现方式以及使用多线程时会遇到的问题以及解决办法,而需要建立多线程的概念,也就是并发编程的概念还需要进行比较多的练习,理解多线程的概念并熟悉多线程的编程。
而关于多线程编程的高级知识,如线程组等则可以在熟悉了线程的基本概念以后再进行更加深入的学习。
多数 java 程序员都非常清楚使用 jar 文件将组成 java 解决方案的各种资源(即 .class 文件、声音和图像)打包的优点。刚开始使用 jar 文件的人常问的一个问题是:“如何从 jar 文件中提取图像呢?”本文将回答这个问题,并会提供一个类,这个类使从 jar 文件中提取任何资源变得非常简单!
加载 gif 图像
假定我们有一个 jar 文件,其中包含我们的应用程序要使用的一组 .gif 图像。下面就是使用 JarResources 访问 jar 文件中的图像文件的方法:
JarResources JR=new JarResources(" http://www.bt285.cn /GifBundle.jar");
Image logo=Toolkit.getDefaultToolkit().createImage(JR.getResources("logo.gif"));
这段代码说明我们可以创建一个JarResources
对象,并将其初始化为包含我们要使用的资源的 jar 文件 -- images.jar
。随后我们使用JarResources
的getResource()
方法将来自 logo.gif 文件的原始数据提供给 awt Toolkit 的createImage()
方法。
命名说明
JarResource 是一个非常简单的示例,它说明了如何使用 java 所提供的各种功能来处理 jar 和 zip 档案文件。
工作方式
JarReources
类的重要数据域用来跟踪和存储指定 jar 文件的内容:
public final class JarResources {
public boolean debugon=false;
private Hashtable htsizes=new Hashtable();
private Hashtable htjarcontents=new Hashtable();
private String jarfilename;
这样,该类的实例化设置 jar 文件的名称,然后转到init()
方法完成全部实际工作。
public JarResources(String jarfilename) {
this.jarfilename=jarfilename;
init();
}
现在,init()
方法只将指定 jar 文件的整个内容加载到一个 hashtable(通过资源名访问)中。
这是一个相当有用的方法,下面我们对它作进一步的分析。ZipFile
类为我们提供了对 jar/zip 档案头信息的基本访问方法。这类似于文件系统中的目录信息。下面我们列出ZipFile
中的所有条目,并用档案中每个资源的大小添充 htsizes hashtable:
private void init() {
try {
// extracts just sizes only.
ZipFile zf=new ZipFile(jarFileName);
Enumeration e=zf.entries();
while (e.hasMoreElements()) {
ZipEntry ze=(ZipEntry)e.nextElement();
if (debugOn) {
System.out.println(dumpZipEntry(ze));
}
htSizes.put(ze.getName(),new Integer((int)ze.getSize()));
}
zf.close();
接下来,我们使用ZipInputStream
类访问档案。ZipInputStream
类完成了全部魔术,允许我们单独读取档案中的每个资源。我们从档案中读取组成每个资源的精确字节数,并将其存储在 htjarcontents hashtable 中,您可以通过资源名访问这些数据:
// extract resources and put them into the hashtable.
FileInputStream fis=new FileInputStream(jarFileName);
BufferedInputStream bis=new BufferedInputStream(fis);
ZipInputStream zis=new ZipInputStream(bis);
ZipEntry ze=null;
while ((ze=zis.getNextEntry())!=null) {
if (ze.isDirectory()) {
continue;////啊哟!没有处理子目录中的资源啊 http://www.5a520.cn 小说520网
}
if (debugOn) {
System.out.println(
"ze.getName()="+ze.getName()+","+"getSize()="+ze.getSize()
);
}
int size=(int)ze.getSize();
// -1 means unknown size.
if (size==-1) {
size=((Integer)htSizes.get(ze.getName())).intValue();
}
byte[] b=new byte[(int)size];
int rb=0;
int chunk=0;
while (((int)size - rb) > 0) {
chunk=zis.read(b,rb,(int)size - rb);
if (chunk==-1) {
break;
}
rb+=chunk;
}
// add to internal resource hashtable
htJarContents.put(ze.getName(),b);
if (debugOn) {
System.out.println(
ze.getName()+" rb="+rb+
",size="+size+
",csize="+ze.getCompressedSize()
);
}
}
} catch (NullPointerException e) {
System.out.println("done.");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
请注意,用来标识每个资源的名称是档案中资源的限定路径名,例如,不是包中的类名 -- 即 java.util.zip 包中的ZipEntry
类将被命名为 "java/util/zip/ZipEntry",而不是 "java.util.zip.ZipEntry"。
其它方法:
/**
* Dumps a zip entry into a string.
* @param ze a ZipEntry
*/
private String dumpZipEntry(ZipEntry ze) {
StringBuffer sb=new StringBuffer();
if (ze.isDirectory()) {
sb.append("d ");
} else {
sb.append("f ");
}
if (ze.getMethod()==ZipEntry.STORED) {
sb.append("stored ");
} else {
sb.append("defalted ");
}
sb.append(ze.getName());
sb.append("\t");
sb.append(""+ze.getSize());
if (ze.getMethod()==ZipEntry.DEFLATED) {
sb.append("/"+ze.getCompressedSize());
}
return (sb.toString());
}
/**
* Extracts a jar resource as a blob.
* @param name a resource name.
*/
public byte[] getResource(String name) {
return (byte[])htJarContents.get(name);
}
代码的最后一个重要部分是简单的测试驱动程序。该测试驱动程序是一个简单的应用程序,它接收 jar/zip 档案名和资源名。它试图发现档案中的资源文件,然后将成功或失败的消息报告出来:
public static void main(String[] args) throws IOException {
if (args.length!=2) {
System.err.println(
"usage: java JarResources < jar file name> < resource name>"
);
System.exit(1);
}
JarResources jr=new JarResources(args[0]);
byte[] buff=jr.getResource(args[1]);
if (buff==null) {
System.out.println("Could not find "+args[1]+".");
} else {
System.out.println("Found "+args[1]+ " (length="+buff.length+").");
}
}
} // End of JarResources class.
您已了解了这个类。一个易于使用的类,它隐藏了使用打包在 jar 文件中的资源的全部棘手问题。
小结
如果您曾经渴望知道如何从 jar 文件中提取图像,那么您现在已学到了一种方法。有了本技巧提供的这个新类,您就不仅可以用 jar 文件处理图像,而且可以将提取魔术用于 jar 文件中的任何资源。
背景:
1. 需要将数据库查询结果在JSP中以列表方式显示
2. 在一个良好的J2EE模式中数据库查询一般用DAO实现(Data Access Object), JSP仅用于显示数据
问题:
通过JDBC ResultSet可获取查询结果(存在于数据库缓冲区内),但在Statement、Connection关闭后ResultSet即不可用。因此需要一种方式取出所有查询结果并传递至JSP页面。
解决方法一:
使用Value Object。将每条记录均封装成JavaBean对象,如:http://www.bt285.cn 把这些对象装入Collection传送给JSP显示。这种方法的缺点是每一种查询都需要定义一个java class,并且将记录数据封装成java对象时也需要很多额外的代码。
示例代码:
-
- //查询数据代码
- Connection conn = DBUtil.getConnection();
- PreparedStatement pst = null;
- ResultSet rs = null;
- try{
- String sql=“select emp_code, real_name from t_employee where organ_id=?”;
- pst = conn.preparedStatement(sql);
- pst.setString(1, “101”);
- ResultSet rs = pst.executeQuery();
- List list = new ArrayList();
- Employee emp;
- while (rs.next()){
- emp = new Employee();
- emp.setReakName(rs.getString(“real_name”));
- emp.setEmpCode(rs.getString(“emp_code”));
- …
- list.add(emp);
- }
- return list;
- }finally{
- DBUtil.close(rs, pst ,conn);
- }
-
-
- //jsp显示部分代码
- <%
- List empList = (List)request.getAttribute(“empList”);
- if (empList == null) empList = Collections.EMPTY_LIST;
- %>
- …
- <table cellspacing="0" width=”90%”>
- <tr> <td> http://www.5a520.cn 小说520网 代码</td> <td>姓名</td> </tr>
- <%
- Employee emp;
- for (int i=0; i< empList.size(); i++){
- emp = (Employee) empList.get(i);
- %>
- <tr>
- <td><%= emp.getEmpCode()%></td>
- <td><%= emp.getRealName()%></td>
- </tr>
- <%
- }// end for
- %>
- </table>
解决方法二:
遍历ResultSet取出所有数据封装进Collection。
具体做法:
1. 生成一个List对象(List list = new ArrayList() )。
2. 生成一个Map对象(Map map = new HashMap() )。使用Map封装一行数据,key为各字段名,value为对应的值。(map.put(“USER_NAME”), rs.getString(“USER_NAME”))
3. 将第2 步生成的Map对象装入第1步的list对象中(list.add(map) )。
4. 重复2、3步直到ResultSet遍历完毕
在DBUtil. resultSetToList(ResultSet rs)方法中实现了上述过程(所有列名均使用大写),可参考使用。
示例代码:
-
- //查询数据部分代码:
- …
- Connection conn = DBUtil.getConnection();
- PreparedStatement pst = null;
- ResultSet rs = null;
- try{
- String sql=“select emp_code, real_name from t_employee where organ_id=?”;
- pst = conn.preparedStatement(sql);
- pst.setString(1, “101”);
- rs = pst.executeQuery();
- List list = DBUtil. resultSetToList(ResultSet rs);
- return list;
- }finally{
- DBUtil.close(rs, pst ,conn);
- }
-
-
-
- //JSP显示部分代码
- <%
- List empList = (List)request.getAttribute(“empList”);
- if (empList == null) empList = Collections.EMPTY_LIST;
- %>
- …
- <table cellspacing="0" width=”90%”>
- <tr> <td> http://www.feng123.com 蜂蜜 代码</td> <td>姓名</td> </tr>
- <%
- Map colMap;
- for (int i=0; i< empList.size(); i++){
- colMap = (Map) empList.get(i);
- %>
- <tr>
- <td><%=colMap.get(“EMP_CODE”)%></td>
- <td><%=colMap.get(“REAL_NAME”)%></td>
- </tr>
- <%
- }// end for
- %>
- </table>
解决方法三:
使用RowSet。
RowSet是JDBC2.0中提供的接口,Oracle对该接口有相应实现,其中很有用的是oracle.jdbc.rowset.OracleCachedRowSet。 OracleCachedRowSet实现了ResultSet中的所有方法,但与ResultSet不同的是,OracleCachedRowSet中的数据在Connection关闭后仍然有效。
oracle的rowset实现在http://otn.oracle.com/software/content.html的jdbc下载里有,名称是ocrs12.zip
示例代码:
- //查询数据部分代码:
- import javax.sql.RowSet;
- import oracle.jdbc.rowset.OracleCachedRowSet;
- …
- Connection conn = DBUtil.getConnection();
- PreparedStatement pst = null;
- ResultSet rs = null;
- try{……
- String sql=“select emp_code, real_name from t_employee where organ_id=?”;
- pst = conn.preparedStatement(sql);
- pst.setString(1, “101”);
- rs = pst.executeQuery();
- OracleCachedRowSet ors = newOracleCachedRowSet();
- //将ResultSet中的数据封装到RowSet中
- ors.populate(rs);
- return ors;
- }finally{
- DBUtil.close(rs, pst, conn);
- }
-
-
- //JSP显示部分代码
- <%
- javax.sql.RowSet empRS = (javax.sql.RowSet) request.getAttribute(“empRS”);
- %>
- …
- <table cellspacing="0" width=”90%”>
- <tr> <td>代码</td> <td>姓名</td> </tr>
- <%
- if (empRS != null) while (empRS.next() ) {
- %>
- <tr>
- <td><%= empRS.get(“EMP_CODE”)%></td>
- <td><%= empRS.get(“REAL_NAME”)%></td>
- </tr>
- <%
- }// end while
- %>
- </table>
适用场合:
方法一使用于定制的查询操作
方法二适用于多条查询语句或需要对查询结果进行处理的情况。
方法三适合于单条查询语句,适用于快速开发。
概述
本文主要包括以下几个方面:编码基本知识,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编码处理之,结果是不正确的,所以需要进行处理。如
http://www.5a520.cn 小说520网 但如果预先设置了编码(通过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选项无效。比如"
http://www.bt285.cn 中文.html?a=中文",当UTF-8选项有效时,将发送链接"
http://www.bt285.cn %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来设置成其它编码)识别。上述例子的结果分别是"
http://www.feng123.com \ue4\ub8\uad\ue6\u96\u87.html?a=\u4e\u2d\u65\u87"和"
http://www.feng123.com \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>"。
import java.awt.image.BufferedImage;
public class ImageScale {
private int width;
private int height;
private int scaleWidth;
double support = (double) 3.0;
double PI = (double) 3.14159265358978;
double[] contrib;
double[] normContrib;
double[] tmpContrib;
int startContrib, stopContrib;
int nDots;
int nHalfDots;
public BufferedImage imageZoomOut(BufferedImage srcBufferImage, int w, int h) {
width = srcBufferImage.getWidth();
height = srcBufferImage.getHeight();
scaleWidth = w;
if (DetermineResultSize(w, h) == 1) {
return srcBufferImage;
}
CalContrib();
BufferedImage pbOut = HorizontalFiltering(srcBufferImage, w);
BufferedImage pbFinalOut = VerticalFiltering(pbOut, h);
return pbFinalOut;
}
/**
* 决定图像尺寸
*/
private int DetermineResultSize(int w, int h) {
double scaleH, scaleV;
scaleH = (double) w / (double) width;
scaleV = (double) h / (double) height;
// 需要判断一下scaleH,scaleV,不做放大操作
if (scaleH >= 1.0 && scaleV >= 1.0) {
return 1;
}
return 0;
} // end of DetermineResultSize()
private double Lanczos(int i, int inWidth, int outWidth, double Support) {
double x;
x = (double) i * (double) outWidth / (double) inWidth;
return Math.sin(x * PI) / (x * PI) * Math.sin(x * PI / Support)
/ (x * PI / Support);
} // end of Lanczos()
//
// Assumption: same horizontal and vertical scaling factor
//
private void CalContrib() {
nHalfDots = (int) ((double) width * support / (double) scaleWidth);
nDots = nHalfDots * 2 + 1;
try {
contrib = new double[nDots];
normContrib = new double[nDots];
tmpContrib = new double[nDots];
} catch (Exception e) {
System.out.println("init contrib,normContrib,tmpContrib" + e);
}
int center = nHalfDots;
contrib[center] = 1.0;
double weight = 0.0;
int i = 0;
for (i = 1; i <= center; i++) {
contrib[center + i] = Lanczos(i, width, scaleWidth, support);
weight += contrib[center + i];
}
for (i = center - 1; i >= 0; i--) {
contrib[i] = contrib[center * 2 - i];
}
weight = weight * 2 + 1.0;
for (i = 0; i <= center; i++) {
normContrib[i] = contrib[i] / weight;
}
for (i = center + 1; i < nDots; i++) {
normContrib[i] = normContrib[center * 2 - i];
}
} // end of CalContrib()
// 处理边缘
private void CalTempContrib(int start, int stop) {
double weight = 0;
int i = 0;
for (i = start; i <= stop; i++) {
weight += contrib[i];
}
for (i = start; i <= stop; i++) {
tmpContrib[i] = contrib[i] / weight;
}
} // end of CalTempContrib()
private int GetRedValue(int rgbValue) {
int temp = rgbValue & 0x00ff0000;
return temp >> 16;
}
private int GetGreenValue(int rgbValue) {
int temp = rgbValue & 0x0000ff00;
return temp >> 8;
}
private int GetBlueValue(int rgbValue) {
return rgbValue & 0x000000ff;
}
private int ComRGB(int redValue, int greenValue, int blueValue) {
return (redValue << 16) + (greenValue << 8) + blueValue;
}
// 行水平滤波
private int HorizontalFilter(BufferedImage bufImg, int startX, int stopX,
int start, int stop, int y, double[] pContrib) {
double valueRed = 0.0;
double valueGreen = 0.0;
double valueBlue = 0.0;
int valueRGB = 0;
int i, j;
for (i = startX, j = start; i <= stopX; i++, j++) {
valueRGB = bufImg.getRGB(i, y);
valueRed += GetRedValue(valueRGB) * pContrib[j];
valueGreen += GetGreenValue(valueRGB) * pContrib[j];
valueBlue += GetBlueValue(valueRGB) * pContrib[j];
}
valueRGB = ComRGB(Clip((int) valueRed), Clip((int) valueGreen),
Clip((int) valueBlue));
return valueRGB;
} // end of HorizontalFilter()
// 图片水平滤波
private BufferedImage HorizontalFiltering(BufferedImage bufImage, int iOutW) {
int dwInW = bufImage.getWidth();
int dwInH = bufImage.getHeight();
int value = 0;
BufferedImage pbOut = new BufferedImage(iOutW, dwInH,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < iOutW; x++) {
int startX;
int start;
int X = (int) (((double) x) * ((double) dwInW) / ((double) iOutW) + 0.5);
int y = 0;
startX = X - nHalfDots;
if (startX < 0) {
startX = 0;
start = nHalfDots - X;
} else {
start = 0;
}
int stop;
int stopX = X + nHalfDots;
if (stopX > (dwInW - 1)) {
stopX = dwInW - 1;
stop = nHalfDots + (dwInW - 1 - X);
} else {
stop = nHalfDots * 2;
}
if (start > 0 || stop < nDots - 1) {
CalTempContrib(start, stop);
for (y = 0; y < dwInH; y++) {
value = HorizontalFilter(bufImage, startX, stopX, start,
stop, y, tmpContrib);
pbOut.setRGB(x, y, value);
}
} else {
for (y = 0; y < dwInH; y++) {
value = HorizontalFilter(bufImage, startX, stopX, start,
stop, y, normContrib);
pbOut.setRGB(x, y, value);
}
}
}
return pbOut;
} // end of HorizontalFiltering()
private int VerticalFilter(BufferedImage pbInImage, int startY, int stopY,
int start, int stop, int x, double[] pContrib) {
double valueRed = 0.0;
double valueGreen = 0.0;
double valueBlue = 0.0;
int valueRGB = 0;
int i, j;
for (i = startY, j = start; i <= stopY; i++, j++) {
valueRGB = pbInImage.getRGB(x, i);
valueRed += GetRedValue(valueRGB) * pContrib[j];
valueGreen += GetGreenValue(valueRGB) * pContrib[j];
valueBlue += GetBlueValue(valueRGB) * pContrib[j];
// System.out.println(valueRed+"->"+Clip((int)valueRed)+"<-");
//
// System.out.println(valueGreen+"->"+Clip((int)valueGreen)+"<-");
// System.out.println(valueBlue+"->"+Clip((int)valueBlue)+"<-"+"-->");
}
valueRGB = ComRGB(Clip((int) valueRed), Clip((int) valueGreen),
Clip((int) valueBlue));
// System.out.println(valueRGB);
return valueRGB;
} // end of VerticalFilter()
private BufferedImage VerticalFiltering(BufferedImage pbImage, int iOutH) {
int iW = pbImage.getWidth();
int iH = pbImage.getHeight();
int value = 0;
BufferedImage pbOut = new BufferedImage(iW, iOutH,
BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < iOutH; y++) {
int startY;
int start;
int Y = (int) (((double) y) * ((double) iH) / ((double) iOutH) + 0.5);
startY = Y - nHalfDots;
if (startY < 0) {
startY = 0;
start = nHalfDots - Y;
} else {
start = 0;
}
int stop;
int stopY = Y + nHalfDots;
if (stopY > (int) (iH - 1)) {
stopY = iH - 1;
stop = nHalfDots + (iH - 1 - Y);
} else {
stop = nHalfDots * 2;
}
if (start > 0 || stop < nDots - 1) {
CalTempContrib(start, stop);
for (int x = 0; x < iW; x++) {
value = VerticalFilter(pbImage, startY, stopY, start, stop,
x, tmpContrib);
pbOut.setRGB(x, y, value);
}
} else {
for (int x = 0; x < iW; x++) {
value = VerticalFilter(pbImage, startY, stopY, start, stop,
x, normContrib);
pbOut.setRGB(x, y, value);
}
}
}
return pbOut;
} // end of VerticalFiltering()
int Clip(int x) {
if (x < 0)
return 0;
if (x > 255)
return 255;
return x;
}
public static void main(String[] args) {
}
}
}
-----------------------------------------------------------------------
public BufferedImage imageZoomOut(BufferedImage srcBufferImage, int w, int h)
我将这个方法改成了
public BufferedImage imageZoomOut(BufferedImage srcBufferImage, float w, float h)
为了方便同比例缩放,将w, h改为flaot型作为缩放比率,其他地方作相应的改动。
如果是在网页上显示的话,需要建一个Servlet,代码如下:
package servlet;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ImageScale;
public class ImageServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
// 图片放在当前应用的images目录下 如http://www.feng123.com 下的文件目录。
String path = getServletContext().getRealPath("images/2.jpg");
BufferedImage image1 = ImageIO.read(new File(path));
if(request.getParameter("x") == null) {
ImageIO.write(image1, "jpeg", response.getOutputStream());
}else {
float w = Float.parseFloat(request.getParameter("x"));
float h;
if(request.getParameter("y") == null) {
h = w;
}else{
h = Float.parseFloat(request.getParameter("y"));
}
ImageScale is = new ImageScale();
BufferedImage image2 = is.imageZoomOut(image1, w, h);
ImageIO.write(image2, "jpeg", response.getOutputStream());
}
}
}
在web.xml中增加相应的配置:
<servlet>
<servlet-name>ImageServlet</servlet-name>
<servlet-class>servlet.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ImageServlet</servlet-name>
<url-pattern>/imageServlet</url-pattern>
</servlet-mapping>
测试页面 http://www.feng123.com 下的:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
</head>
<body>
图片缩放测试http://www.bt285.cn <br/>
<img src="imageServlet" />
<img src="imageServlet?x=0.9&y=0.9" />
<img src="imageServlet?x=0.8&y=0.8" />
<img src="imageServlet?x=0.7&y=0.7" />
<img src="imageServlet?x=0.6&y=0.6" />
<img src="imageServlet?x=0.5&y=0.5" />
<img src="imageServlet?x=0.4&y=0.4" />
<img src="imageServlet?x=0.3&y=0.3" />
<img src="imageServlet?x=0.2&y=0.2" />
<img src="imageServlet?x=0.1&y=0.1" />
<img src="imageServlet?x=0.08" />
<img src="imageServlet?x=0.5&y=0.7" />
</body>
</html>
首先到SUN下载最新的JMF,然后安装。http://java.sun.com/products/java-media/jmf/index.jsp http://www.bt285.cn
然后,说一下需求
1. 用摄像头拍照
2. 在文本框输入文件名
3. 按下拍照按钮,获取摄像头内的图像
4. 在拍下的照片上有一红框截取固定大小的照片。
5. 保存为本地图像为jpg格式,不得压缩画质
技术关键,相信也是大家最感兴趣的部分也就是如何让一个摄像头工作,并拍下一张照片了。
利用JMF,代码很简单:
//利用这三个类分别获取摄像头驱动,和获取摄像头内的图像流,获取到的图像流是一个Swing的Component组件类
public static Player player = null;
private CaptureDeviceInfo di = null;
private MediaLocator ml = null;
String str1 = "vfw:Logitech USB Video Camera:0";
String str2 = "vfw:Microsoft WDM Image Capture (Win32):0";
di = CaptureDeviceManager.getDevice(str2);
ml = di.getLocator();
try
{
player = Manager.createRealizedPlayer(ml);
player.start();
Component comp;
if ((comp = player.getVisualComponent()) != null)
{
add(comp, BorderLayout.NORTH);
}
}
catch (Exception e)
{
e.printStackTrace();
}
|
接下来就是点击拍照,获取摄像头内的当前图像。
代码也是很简单:
private JButton capture;
private Buffer buf = null;
private BufferToImage btoi = null;
private ImagePanel imgpanel = null;
private Image img = null;
private ImagePanel imgpanel = null;
JComponent c = (JComponent) e.getSource();
if (c == capture)//如果按下的是拍照按钮 http://www.5a520.cn
{
FrameGrabbingControl fgc =(FrameGrabbingControl)player.getControl
("javax.media.control.FrameGrabbingControl");
buf = fgc.grabFrame(); // 获取当前祯并存入Buffer类 http://www.bt285.cn
btoi = new BufferToImage((VideoFormat) buf.getFormat());
img = btoi.createImage(buf); // show the image
imgpanel.setImage(img);
}
|
保存图像的就不多说了,以下为示例代码
BufferedImage bi = (BufferedImage) createImage(imgWidth, imgHeight);
Graphics2D g2 = bi.createGraphics();
g2.drawImage(img, null, null);
FileOutputStream out = null;
try
{
out = new FileOutputStream(s);
}
catch (java.io.FileNotFoundException io)
{
System.out.println("File Not Found");
}
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
param.setQuality(1f, false);//不压缩图像 http://www.bt285.cn
encoder.setJPEGEncodeParam(param);
try
{
encoder.encode(bi);
out.close();
}
catch (java.io.IOException io)
{
System.out.println("IOException");
}
|
已经申请将JWebCam建立为一个开源项目,放到GRO,大家发挥自己的想象力加入自己的代码吧,比如拍摄视频,添加图像处理功能,等等
目标:用户Login一次之后,可以访问同一Server上的不同Webapp, 具体实现上采用Tomcat的Single Sign-On实现. 主要分为下面几个步骤:
- 修改Tomcat conf/server.xml 打开SSO支持
<Host> 节点下增加一个Value节点
<Valve className="org.apache.catalina.authenticator.SingleSignOn"
debug="0" requireReauthentication="false"/>
</Host>
- container认证realm: user、role、server.xml的<Realm...>设置.
tomcat的认证机制有2个要素: user 和 role.
- user 是区别一个个用户的唯一识别了。
- role 就是一些抽象的权限级别,比如“admin”、“manager”、“member”、“guest”等等,都是可以自己定义的.一个user可以拥有多种role.
“可是tomcat怎么去拿到我的user/role信息呢?我的这些数据都在数据库里阿?” 可以在tomcat的server.xml里用 <Realm> tag来读取这些信息,并且tomcat提供了3、4种现成的Realm实现,其中有从文件里读的,有从JDBC读的,有从DataSource读的,也有从LDAP读的。具体Realm的写法,和提供的几种Realm的配置方法,可以参考tomcat自己的文档,在此不作细述。 (把tomcat自带的webapp: tomcat-docs.war 展开,看里面的 config/realm.html) 如果连这些现成的配置都不能满足你的要求的话,那也可以考虑自己写一个Realm的实现类来满足具体要求。下面举一个JDBC的Realm的配置例子看一下:
<Realm className="org.apache.catalina.realm.JDBCRealm" debug="99"
driverName="your.jdbc.driver.here"
connectionURL="your.jdbc.url.here"
connectionName="test"
connectionPassword="test"
userTable="users"
userNameCol="user_name"
userCredCol="user_pass"
userRoleTable="user_roles"
roleNameCol="role_name" />
- webapp使用SSO:
- 告诉tomcat这个webapp要通过container的认证
具体做法: 在web.xml里面加上如下的配置:
<security-constraint>
<web-resource-collection>
<web-resource-name>http://www.bt285.cn BT下载 </web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<!-- role name 指定哪个role可以访问,可以为多个role,如两个网站:http://www.5a520.cn http://www.feng123.com -->
<role-name>intrauser</role-name>
</auth-constraint>
</security-constraint>
在web.xml里面加上如下的配置:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Intra Web Application</realm-name>
</login-config>
<security-role>
<description>The role that is required to access intrasites</description>
<role-name>intrauser</role-name>
</security-role>
这里有2个要点:
举例为了简单用了最基本的一种BASIC。若使用BASIC方式,当你去访问受保护认证的资源时,浏览器会弹出一个小窗口让你输入用户名和密码。(就像我们访问ioffice时,第一次弹出来的那个认证窗口)其他还有几种认证方式如:FORM、DIGEST、CLIENT-CERT。其中FORM是可以自己写login画面的,当然html的form内容有些规定(要符合j2ee和container的要求嘛)。 DIGEST是一种加密的传输,而CLIENT-CERT没有查过,有兴趣可以去查一下。
这个realm-name是这个webapp的认证realm名,注意几个处于同一SSO下的webapp,他们的realm-name要设成一样的值。 如果不设成一样,那么换一个webapp就要重新认证一次,达不到SSO的效果。
原本都习惯在login以后,把一些login用户信息放到session里面的. 现在认证都交给container去做了,我们的webapp怎么拿到login用户信息啊? 确实,现在我们的webapp能做的,只有从request里面拿到login用户的userid了。
String userid = request.gerRemoteUser();
以上是在一个Tomcat Container上的SSO实现.
如果是不同的Container上的webapp要做SSO,这种时候一种可行的方案是,最前面架一个webserver(比如apache),在webserver这层承担SSO的认证任务,后面内部就可用挂多个container了. 具体都用到的时候再调查吧.
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.awt.image.*;
import org.w3c.dom.*;
import com.sun.image.codec.jpeg.*;
import javax.imageio.*;
/**
author by http://www.5a520.cn
*/
public class ImgBean{
public void ImgBean(){}
public void ImgYin(String s,String ImgName){
try{
File _file = new File(ImgName);
Image src = ImageIO.read(_file);
int wideth=src.getWidth(null);
int height=src.getHeight(null);
BufferedImage image=new BufferedImage(wideth,height,BufferedImage.TYPE_INT_RGB);
Graphics g=image.createGraphics();
g.drawImage(src,0,0,wideth,height,null);
String s="我要加的水印 ,来源http://www.bt285.cn BT下载";
g.setColor(Color.RED);
g.setFont(new Font("宋体",Font.PLAIN,20));
Font aa=new Font("宋体",Font.PLAIN,20);
g.drawString(s,wideth-150,height-10);
g.dispose();
FileOutputStream out=new FileOutputStream(ImgName);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(image);
out.close();
}
catch(Exception e){
System.out.println(e);
}
}
}
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成.由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器.Tomcat 很受广大程序员的喜欢,因为它运行时占用的系统资源小,扩展性好,支持负载平衡与邮件服务等开发应用系统常用的功能;而且它还在不断的改进和完善中,任何一个感兴趣的程序员都可以更改它或在其中加入新的功能.
下载:Tomcat 6.0.20
Apache Tomcat Version 6.0.20
Release Notes
=============================
KNOWN ISSUES IN THIS RELEASE:
=============================
* Dependency Changes
* JNI Based Applications
* Bundled APIs
* Web application reloading and static fields in shared libraries
* Tomcat on Linux
* Enabling SSI and CGI Support
* Security manager URLs
* Symlinking static resources
* Enabling invoker servlet
* Viewing the Tomcat Change Log
* When all else fails
http://www.bt285.cn
http://www.feng123.com
===================
Dependency Changes:
===================
Tomcat 6.0 is designed to run on JSE 5.0 and later.
In addition, Tomcat 6.0 uses the Eclipse JDT Java compiler for compiling
JSP pages. This means you no longer need to have the complete
Java Development Kit (JDK) to run Tomcat, but a Java Runtime Environment
(JRE) is sufficient. The Eclipse JDT Java compiler is bundled with the
binary Tomcat distributions. Tomcat can also be configured to use the
compiler from the JDK to compile JSPs, or any other Java compiler supported
by Apache Ant.
=======================
JNI Based Applications:
=======================
Applications that require native libraries must ensure that the libraries have
been loaded prior to use. Typically, this is done with a call like:
static {
System.loadLibrary("path-to-library-file");
}
in some class. However, the application must also ensure that the library is
not loaded more than once. If the above code were placed in a class inside
the web application (i.e. under /WEB-INF/classes or /WEB-INF/lib), and the
application were reloaded, the loadLibrary() call would be attempted a second
time.
To avoid this problem, place classes that load native libraries outside of the
web application, and ensure that the loadLibrary() call is executed only once
during the lifetime of a particular JVM.
=============
Bundled APIs:
=============
A standard installation of Tomcat 6.0 makes all of the following APIs available
for use by web applications (by placing them in "lib"):
* annotations-api.jar (Annotations package)
* catalina.jar (Tomcat Catalina implementation)
* catalina-ant.jar (Tomcat Catalina Ant tasks)
* catalina-ha.jar (High availability package)
* catalina-tribes.jar (Group communication)
* el-api.jar (EL 2.1 API)
* jasper.jar (Jasper 2 Compiler and Runtime)
* jasper-el.jar (Jasper 2 EL implementation)
* jasper-jdt.jar (Eclipse JDT 3.3 Java compiler)
* jsp-api.jar (JSP 2.1 API)
* servlet-api.jar (Servlet 2.5 API)
* tomcat-coyote.jar (Tomcat connectors and utility classes)
* tomcat-dbcp.jar (package renamed database connection pool based on Commons DBCP)
You can make additional APIs available to all of your web applications by
putting unpacked classes into a "classes" directory (not created by default),
or by placing them in JAR files in the "lib" directory.
To override the XML parser implementation or interfaces, use the endorsed
mechanism of the JVM. The default configuration defines JARs located in
"endorsed" as endorsed.
================================================================
Web application reloading and static fields in shared libraries:
================================================================
Some shared libraries (many are part of the JDK) keep references to objects
instantiated by the web application. To avoid class loading related problems
(ClassCastExceptions, messages indicating that the classloader
is stopped, etc.), the shared libraries state should be reinitialized.
Something which might help is to avoid putting classes which would be
referenced by a shared static field in the web application classloader,
and putting them in the shared classloader instead (JARs should be put in the
"lib" folder, and classes should be put in the "classes" folder).
================
Tomcat on Linux:
================
GLIBC 2.2 / Linux 2.4 users should define an environment variable:
export LD_ASSUME_KERNEL=2.2.5
Redhat Linux 9.0 users should use the following setting to avoid
stability problems:
export LD_ASSUME_KERNEL=2.4.1
There are some Linux bugs reported against the NIO sendfile behavior, make sure you
have a JDK that is up to date, or disable sendfile behavior in the Connector.<br/>
6427312: (fc) FileChannel.transferTo() throws IOException "system call interrupted"<br/>
5103988: (fc) FileChannel.transferTo should return -1 for EAGAIN instead throws IOException<br/>
6253145: (fc) FileChannel.transferTo on Linux fails when going beyond 2GB boundary<br/>
6470086: (fc) FileChannel.transferTo(2147483647, 1, channel) cause "Value too large" exception<br/>
=============================
Enabling SSI and CGI Support:
=============================
Because of the security risks associated with CGI and SSI available
to web applications, these features are disabled by default.
To enable and configure CGI support, please see the cgi-howto.html page.
To enable and configue SSI support, please see the ssi-howto.html page.
======================
Security manager URLs:
======================
In order to grant security permissions to JARs located inside the
web application repository, use URLs of of the following format
in your policy file:
file:${catalina.home}/webapps/examples/WEB-INF/lib/driver.jar
============================
Symlinking static resources:
============================
By default, Unix symlinks will not work when used in a web application to link
resources located outside the web application root directory.
This behavior is optional, and the "allowLinking" flag may be used to disable
the check.
=========================
Enabling invoker servlet:
=========================
Starting with Tomcat 4.1.12, the invoker servlet is no longer available by
default in all webapps. Enabling it for all webapps is possible by editing
$CATALINA_HOME/conf/web.xml to uncomment the "/servlet/*" servlet-mapping
definition.
Using the invoker servlet in a production environment is not recommended and
is unsupported. More details are available on the Tomcat FAQ at
http://tomcat.apache.org/faq/misc.html#invoker.
==============================
Viewing the Tomcat Change Log:
==============================
See changelog.html in this directory.
====================
When all else fails:
====================
See the FAQ
http://tomcat.apache.org/faq/
2009-02-02日 Fire Workflow 的Eclispe设计器插件上传到google code ,并有较详细的文档(2_通过设计器和模拟器快速了解Fire Workflow.pdf,3_各种工作流模式的实现.pdf)。
请到http://code.google.com/p/fireflow下载。
镜像:http://www.bt285.cn http://www.5a520.cn
Fire workflow FAQ
1、为什么要写Fire Workflow
本人从事企业MIS系统开发很多年头了,感觉MIS系统很多领域都有比较好的解决方案并已成为事实标准,例如Spring,Hibernate等等;然而工作流还没有令人满意的开源产品。我了解过的工作流产品(主要是开源的,收费产品没有什么研究,仅仅看看其白皮书而已)都存在如下毛病:
- 缺乏严密的理论做支撑,工作流模型大多千篇一律地照搬WfMC的xpdl,
- 因为缺乏理论支撑,所以工作流引擎的算法有点七拼八凑,扩展性也比较差。
- 没有好的设计器,应用比较困难
最近研究并应用了一下JBoss的Jbpm,除了其面向图的引擎算法让我眼前一亮外,其他的也不是令人满意。其引擎的扩展性不好,表结构太复杂,在大数据量系统中,性能令人堪忧。
鉴于此,我动手写了一个Fire Workflow,抛砖引玉。
2、Fire Workflow的定位
我从来不认为工作流可以“自定义”,所以Fire Workflow是面向开发人员的。Fire Workflow和Spring、Hibernate一样,是一个或几个普普通通的jar包,嵌入到系统中,用以解决系统开发中工作流领域的问题。
因为Fire Workflow是面向开发人员的,所以在下面两个方面花了较大功夫。
- 流程设计器。Fire Workflow用于帮助开发人员解决系统中的流程问题,所以好的流程设计器有助于开发人员提高开发效率。Fire Workflow设计器和主流IDE紧密集成(目前有Eclipse插件和NetBeans插件),使得开发人员开发调试流程就像开发调试一个普通java类一样简单。
- 工作流引擎。Fire Workflow引擎设计充分考虑扩展性,因为业务系统的需求五花八门,所以一个写的很死的引擎必然没有生命力。Fire Workflow引擎的各种服务都可以扩展或者替换。
Fire Workflow虽然不认可流程“自定义”需求,但是并不否认用户有调整业已存在的流程的需求,而且这种需求还比较普遍。所以Fire Workflow计划提供Web界面,让最终用户的系统管理员在一定范围内调整流程。
3、Fireflow的特点
理论严密
Fire Workflow以Petri Net作为理论基础,流程的顺序流转、分支、汇聚、跳转等算法都有定义/定理为依据。
设计合理
Fire workflow将工作流引擎的职责分解委派到各种服务中,每中服务都可以被扩展或者替换。
应用简单
Fire workflow的API以及数据库表结构非常简单。
性能优良
Fire workflow着重在流程实例的数据量,数据库IO等方面进行性能优化。
4、Fireflow的构成
Fire Workflow由模型、引擎、设计器(包含模拟器)三部分组成。
- 模型部分规定了流程定义文件的各种元素及其相互关系,例如流程(WorkflowProcess)、活动(Activity)、转移(Transition)、开始节点(StartNode)、结束节点(EndNode)、同步器(Synchronizer)。模型部分的实现在org-fireflow-model.jar中。
- 引擎读取流程定义文件并解释执行。引擎提供一组对象和相关的API供外部系统调用,如流程实例(ProcessInstance)、任务实例(TaskInstance)、工单(WorkItem)、事件等等。引擎部分的实现在org-fireflow-engine.jar中。
- 设计器编辑并输出流程定义文件。Fire Workflow的设计器附带了强大的模拟器,可以在设计时模拟流程的执行,从而检查流程定义的正确性。
此处附带解释一下我的一个观点:我认为,流程定义文件和java文件一样,是应用系统源代码的一部分。因此,流程设计器做成了当前流行的IDE的插件,便于开发人员进行流程开发。而且每个流程单独一个定义文件,就像每个java类在通常情况下单独一个文件一样。
5、Fire Workflow的流程定义语言为什么不使用Xpdl
本人认为Xpdl好看不好用。
相较于Xpdl,Fire workflow 的流程定义语言主要做了如下变动。
- 废除Package的概念:在我看来一个流程一个文件比较方便开发,流程定义文件在某中程度上和java类文件一样,是系统源代码的一部分
- 废除全局和局部的概念:在xpdl中有全局DataField和局部DataField区分,实际上其作用不大。
- 增加同步器节点:Fire Workflow将流程中的节点分成两类 ,即Acitivyt和Sychronizer(Start Node和End Node是synchronizer的特例)。这两类节点分别代表了业务子系统的逻辑操作和工作流子系统的逻辑操作。
- 增加Task元素:一个Activity可以包含多个Task,Task代表实际的业务逻辑。
摘要: package yourpackage;
import java.util.ArrayList;
/** *//**
* 分页信息
* jdk 1.4 ...
阅读全文