Java Tools

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  83 随笔 :: 0 文章 :: 16 评论 :: 0 Trackbacks

#

这个插件不错,可以监视JVM的内存使用情况,并且可以强制GC工作。


Current Version 1.0.0
. Released Feb 1st 2004

Description

Cloudgarden's MemoryManager is a small plugin for IBM's Eclipse Java IDE, which displays the current memory usage of Eclipse (letting you know when Eclipse is close to using up all it's memory allowance), and automatically invokes garbage collection when deemed necessary by a simple but effective algorithm (see below), thus preventing or reducing times of forced inactivity while the Eclipse JVM cleans up it's virtual memory space.

The plugin takes up little screen real estate, and provides a visual and numerical display of the free, total and maximum memory allocations, as well as indicating when it forced a garbage collection (it also has a button to manually force garbage collection). In the screen shot, the green region represents the free memory, the red region the used memory (which is equal to the total memory minus the free memory) and the black region represents space for expansion. The blue lines indicate when a garbage-collection happened. Scrolling of the display can be paused and re-started, and past values can be stepped through as a simple tool for analysing memory usage by applications in the workbench.

The plugin is Open Source, (the source is here) and should work on most platforms (it has been tested on Windows, Linux and Mac).


Download

The plugin is contained in this file. Simply extract and install in your eclipse folder, then start eclipse.


Usage

Show the plugin by choosing "Windows->Views->Other->MemoryManager->Memory" in the eclipse main menu. The plugin will immediately start displaying memory usage and collecting garbage when necessary. Note: If total usage is less than half of the maximum allowed space, the display will be scaled vertically by a factor of two (ie, the height of the display represents only half of the maximum memory), but once the total memory excedes half of the maximum, the height represents the maximum allowed memory usage.


Requirements

Eclipse version 2.1.2 or 3M6. If run under a 1.3 JVM, the maximum memory cannot be calculated (since there is no such method in the 1.3 API), and the display will have no black area.


Garbage collection algorithm


1) At startup, or immediately after garbage-collection, find the free memory.
2) Keep checking free memory every second or two.
3) When the free memory drops below 75% of the free memory after the last garbage collection (or at startup), do another garbage-collection.

That's it - simple, but apparently effective.
posted @ 2007-07-05 16:18 和田雨 阅读(488) | 评论 (0)编辑 收藏

作者: CNET科技资讯网
CNETNews.com.cn
2007-07-04 11:36:28
关键词: 智能手机 手机 苹果 iPhone

CNET科技资讯网7月4日国际报道 苹果iPhone已经发布有几天了,iSuppli市场研究公司为了探究其内部硬件零部件的成本,将iPhone进行了拆解。

1.屏幕

图解:苹果iPhone真机拆解 零部件成本大揭底

iPhone的屏幕为3.5英寸触摸屏,480*320像素。除了内存以及触摸屏元件外,屏幕是iPhone最昂贵的部件。iPhone屏幕的成本达到了24.5美元,按8GB型号的价格计算,这一成本占其零售价格的9.8%。iPhone的触摸屏由爱普生,夏普以及东芝松下显示器技术公司生产。

2.触摸屏元件

图解:苹果iPhone真机拆解 零部件成本大揭底

iSuppli测算,iPhone硬件的总体成本分别是,4GB型号为225.85美元,8GB版本为249.85美元。分析师Jagdish Rebello说,;两种型号的iPhone唯一的区别是闪存的不同。iPhone的触摸屏元件产自Balda以及TPK Solutions,其成本占8GB型号价格的11%。

3.电话卡插槽及闪存位置

图解:苹果iPhone真机拆解 零部件成本大揭底

底部主板。图示1的位置为电话卡插槽位置,仅能支持AT&T SIM的电话卡,而且目前iPhone仅支持AT&T服务。我们曾经尝试插入非AT&T SIM电话卡,iPhone显示不支持这些卡。图示2处,下面就是一块8MB多功能闪存。

4.电路板三明治

图解:苹果iPhone真机拆解 零部件成本大揭底

iPhone非常薄,经测量仅12毫米厚。iPhone内部的电路板设计体现了苹果的风格,电路板象被聪明的叠加在了一起,象三明治一样。为了探究各个部件,你只能分别拆开各层电路板。

5.鸟瞰图

图解:苹果iPhone真机拆解 零部件成本大揭底

图示1处为程序电路板,图示3处为无线接口电路板,图示2处为立体声耳机电路。虽然iPhone的耳机插孔是标准插孔,但当电路板被组合起来以后,普通的耳机很难插入插孔当中,有些紧。如果你想在iPhone上使用你现在的耳机,你要购买一个10美元的适配器。

6.无线接口部件

图解:苹果iPhone真机拆解 零部件成本大揭底

这块电路板上都是无线通讯部件:(A)处为四频GSM(GSM850,GSM900,GSM1800以及GSM1900-MHz)/EDGE收发器;(B)处为功率放大器;(C)处为蓝牙2.0芯片组;(D)处为无线802.11 a/b/g芯片组;(E)处为基带芯片组;(F)处为电源管理芯片组。它的成本分别是2.23美元,2.55美元,1.9美元,6美元,11.5美元以及1.4美元。

7.固定电池

图解:苹果iPhone真机拆解 零部件成本大揭底

用户无法拆下iPhone的锂离子电池。这种电池内置于iPhone当中,与无线接口电路板焊接在一起。苹果表示,这种电池可以被充电300到400次。如果要想更换电池,你需要将手机送到苹果,为此,你需要掏79美元外加6.95美元的运输费。

苹果承诺,一旦电池的电量低于原容量的50%,苹果将更换电池,但iPhone的电量显示为图示的,不是数字式的。iPhone电池的成本为5.2美元。

8.iPhone大脑第一部分

图解:苹果iPhone真机拆解 零部件成本大揭底

iPhone核心电路。闪存成本在iPhone当中所占的比例很大,4GB的三星NAND闪存成本24美元,8GB的成本为48美元。三星的NAND闪存使用了MLC(多层式储存单元)技术,虽然这种技术可以比SLC(单层式储存单元)存储更多的信息,但耗电量也比后者大。

9.iPhone大脑第二部分

图解:苹果iPhone真机拆解 零部件成本大揭底

iPhone核心电路板上还包括运动感应/加速计(图示B,成本为1.5美元),它可以让iPhone自动判断屏幕的方向;C处为24位RGB显示接口,由National制造,成本为1.3美元;D处为Wolfson Microelectronics生产的音频解码器,成本为28.25美元;左上角是iPhone照相机。

iPhone的核心处理器是620-MHz ARM1176JZF处理器,它有1GBDDR SDRAM内存,三星生产。(还有人说是Marvell生产)

10.iPhone的相机

图解:苹果iPhone真机拆解 零部件成本大揭底

iPhone的相机为200万像素,固定镜头模块,带CMOS感应器,成本估计为9.5美元。

posted @ 2007-07-04 12:12 和田雨 阅读(320) | 评论 (0)编辑 收藏

对一个简单的 JDBC 包装器的扩展及应用

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项



级别: 初级

宗锋 (zong_feng@263.net)西北大学计算机系硕士

2001 年 12 月 16 日

本文将对 《一个简单的 JDBC 包装器》中的JDBC包装器进行一些扩展,然后介绍一下其在jsp+javabean开发模式中的应用。

最近看了 《一个简单的 JDBC 包装器》,觉得这篇文章很有应用价值,我便在自己的开发中使用了它,不过这个包装器也存在一些不足,于是我对它进行了一些扩展。首先原文中的Table类缺少删除功能,我便增加了删除功能。代码如下:
public void delRow(Row row) throws SQLException {
                        String ss="";
                        ss = "delete from "+name+" where ";
                        for (int i=0; i<row.length(); ++i) {
                        String k = row.getKey( i );
                        String v = row.get( i );
                        ss += k+"='"+v+"'";
                        if (i != row.length()-1)
                        ss += " and ";
                        }
                        Connection con = database.getConnection();
                        Statement st = con.createStatement();
                        st.executeUpdate( ss );
                        }
                        public void delRow(String conditions)throws SQLException {
                        String ss="";
                        ss = "delete from "+name+" where ";
                        ss +=conditions;
                        Connection con = database.getConnection();
                        Statement st = con.createStatement();
                        st.executeUpdate( ss );
                        }

这两个函数分别用于删除一个Row和满足一定条件的记录。对于具有主关键字的表,我们可以用下面代码中的方法二来进行删除,如果没有主关键字,我们可以用方法一删除。

示例如下:
//方法一
                        Row e = table.getRow( "id=2001" );
                        table.delRow(e);
                        //方法二
                        table.delRow("id=2001");
                        

另外这个包装器没有对查询结果为NULL的情况作处理,我通过修改Table类的execute函数和RowSet类的get函数对这种情况作了处理。具体代码见附件。

下面谈谈利用这个JDBC包装器实现对数据库的封装,假定我们有一个表:student,创建表的Sql语句如下:
create table student(
                        id varchar(10) not null primary key,
                        name varchar(16) not null,
                        sex char(2) not null,
                        password varchar(16) not null,
                        department varchar(32) not null
                        )

我们对这个表进行封装,下面是Student类的主要代码:
public class Student{
                        private Row r;
                        public Student() {
                        r=new Row();
                        }
                        public Student(Row row) {
                        this.r=row;
                        }
                        private Table getTable() {
                        Database db =
                        new Database( "jdbc:mysql://localhost:3306/manger",
                        "zf", "zf" );
                        return db.getTable("student");
                        }
                        public void setName(String name){
                        r.put("name",name);
                        }
                        public void setPassword(String pass){
                        r.put("password",pass);
                        }
                        public void setId(String number){
                        r.put("id",number);
                        }
                        public void setDepart(String depart){
                        r.put("department",depart);
                        }
                        public void setSex(String sex){
                        r.put("sex",sex);
                        }
                        public String getName(){
                        return r.get("name");
                        }
                        public String getPassword(){
                        return r.get("password");
                        }
                        public String getId(){
                        return r.get("id");
                        }
                        public String getDepart(){
                        return r.get("department");
                        }
                        public String getSex(){
                        return r.get("sex");
                        }
                        /**
                        *condition表示限制条件,如果为空,则插入新记录,否则更新记录
                        */
                        public void save(String conditions) throws SQLException{
                        if(conditions==null)
                        {getTable().putRow(r);}
                        else
                        getTable().putRow(r,conditions);
                        }
                        /**
                        *由于id作为主关键字,所以我们使用字符串为参数的delRow()函数
                        */
                        public void delete()throws SQLException{
                        //getTable().delRow(this.r);
                        String conditions="";
                        conditions = "id=" + "'"+getId()+"'";
                        getTable().delRow(conditions);
                        }
                        }

下面这个类是相应的一个查询类的主要代码:
public class StudentFactory{
                        public static Student findStudentById(String id)
                        throws SQLException{
                        Row r=getTable().getRow("id="+id);
                        if(r==null)
                        return null;
                        else
                        return new Student(r);
                        }
                        public static Student[] findAllStudents()
                        throws SQLException{
                        RowSet rs=getTable().getRows("1>0");
                        if (rs==null)
                        return null;
                        else
                        Student[] stu=null;
                        stu=new Student[rs.length()];
                        for(int i=0;i<rs.length(); i++){
                        stu[i]=new Student(rs.get(i));
                        }
                        return stu;
                        }
                        }

我使用javabean来实现很多功能,这样可以减少在jsp中的java代码量,方便我们对界面的改进。我们要实现的功能为对学生的编辑,添加,删除和列表。这些功能定义在两个javabean中:下面是两个jsp文件的主要代码:
<%-- student.jsp --%>
                        <%@ page contentType="text/html;charset=GB2312" %>
                        <%@ page import="org.gjt.mm.mysql.*,manger.bean.*,manger.tools.*,manger.business.*" %>
                        <html>
                        <head>
                        <SCRIPT TYPE="text/javascript" LANGUAGE="JavaScript" >
                        <!--
                        function doDelete()
                        {
                        if(confirm('你确定删除吗?')) {
                        document.EditForm.event.value='delete';
                        document.EditForm.submit();
                        }
                        }
                        function doEdit()
                        {
                        if(confirm('你确定编辑吗?')) {
                        document.EditForm.event.value='showEdit';
                        document.EditForm.submit();
                        }
                        }
                        function showAddPage()
                        {
                        document.location='editstudent.jsp?event=showAdd';
                        }
                        </SCRIPT>
                        </head>
                        <body bgcolor="#FFFFFF" text="#000000">
                        <%
                        try {
                        Class.forName("org.gjt.mm.mysql.Driver").newInstance();
                        }
                        catch (Exception E) {
                        out.println("Unable to load driver.");
                        } %>
                        <jsp:useBean id="table" scope="page" class="manger.bean.ListStudentBean" />
                        <%
                        Student[] student=table.getStudent(pageContext);
                        int total=0;
                        int currPage=table.getCurPage();
                        int pageCount=table.getPageCount();
                        if(student!=null)
                        {total=student.length;}%>
                        <FORM NAME="EditForm" ACTION="editstudent.jsp">
                        <INPUT TYPE="HIDDEN" NAME="event" VALUE="">
                        <table width="75%" border="1">
                        <tr>
                        <td colspan="5">学生列表</td>
                        </tr>
                        <tr>
                        <td>学号</td>
                        <td>姓名</td>
                        <td>班级</td>
                        <td>备注一</td>
                        <td>选择</td>
                        </tr>
                        <%for (int i=0;i<total;i++){
                        Student current=student[i];%>
                        <tr>
                        <td><%=current.getId()%></td>
                        <td><%=current.getName()%></td>
                        <td><%=current.getDepart()%></td>
                        <td><%=current.getSex() %></td>
                        <td>
                        <input type="checkbox" name="id" value=<%=current.getId()%>>
                        </td>
                        <% } %>
                        </tr><tr>
                        <td colspan="5">
                        <INPUT TYPE="BUTTON" onclick="doEdit();" VALUE="编辑">
                        <INPUT TYPE="BUTTON" onclick="showAddPage()" VALUE="增加">
                        <INPUT TYPE="BUTTON" onclick="doDelete();" VALUE="删除">
                        </td>
                        </tr>
                        </table>
                        </form>
                        </html>

<%-- studentedit.jsp --%>
                        <jsp:useBean id="table" scope="page" class="manger.bean.EditStudentBean" />
                        <%table.processRequest(pageContext);%>
                        <p>_lt;/p>
                        <form name="EditForm" action="editstudent.jsp">
                        <INPUT TYPE="hidden" NAME="event" VALUE="<%=table.getEvent()%>" >
                        <table width="75%" border="1">
                        <tr>
                        <td colspan="2">
                        <div align="center"><b>编辑学生信息</b></div>
                        </td>
                        </tr>
                        <tr>
                        <td width="40%">学号:</td>
                        <td width="60%">
                        <input type="text" name="id" value="<%=table.getStudent().getId()%>">
                        </td>
                        </tr>
                        <%--下面的一些学生信息我们省略了--%>
                        <tr>
                        <td colspan="2">
                        <input type="submit" name="Submit" value="确定">
                        </td>
                        </tr>
                        </table>
                        </form>
                        

我的想法是在student.jsp中显示学生列表,从这个页面可以转到增加和编辑页面,也可以在这个页面中删除学生,这三个功能都是由editstudent.jsp完成的。在editstudent.jsp中非常关键的一行代码就是<%table.processRequest(pageContext);%>,这会调用EditStudentBean类的processRequest方法:下面是EditStudentBean类processRequest方法和addStudent方法的代码:
public void processRequest (PageContext pageContext)
                        throws SQLException,ServletException,IOException{
                        this.student.setId("");
                        this.student.setName("");
                        this.student.setPassword("");
                        this.student.setDepart("");
                        this.student.setSex("");
                        HttpServletRequest request=(HttpServletRequest)pageContext.getRequest();
                        String event=request.getParameter("event");
                        //this.event=event;
                        if(event==null || event.equals("")||event.equals("showAdd"))
                        {this.event="add";
                        }
                        else if(event.equals("showEdit"))
                        {this.event="edit";
                        String id=request.getParameter("id");
                        Student stu=StudentFactory.findStudentById(id);
                        this.student=stu;
                        }
                        else if(event.equals("add"))
                        {this.addStudent(pageContext);
                        }
                        else if(event.equals("delete"))
                        {this.deleteStudent(pageContext);
                        }
                        else if(event.equals("edit"))
                        {this.editStudent(pageContext);
                        }
                        }
                        public void addStudent(PageContext page)
                        throws SQLException,ServletException,IOException{
                        HttpServletRequest request=(HttpServletRequest)page.getRequest();
                        HttpServletResponse response=(HttpServletResponse)page.getResponse();
                        JspWriter out=page.getOut();
                        String id=request.getParameter("id");
                        String name=request.getParameter("name");
                        String sex=request.getParameter("sex");
                        String pass=request.getParameter("password");
                        String depart=request.getParameter("depart");
                        if (id!=null&&name!=null&&sex!=null&&pass!=null&&depart!=null
                        &&!id.equals("")&&!name.equals("")&&!sex.equals("")&&!pass.equals("")&&!depart.equals(""))
                        {Student s=new Student();
                        s.setId(id);
                        s.setName(name);
                        s.setSex(sex);
                        s.setPassword(pass);
                        s.setDepart(depart);
                        s.save(null);
                        response.sendRedirect("student.jsp");
                        }
                        else
                        {out.print("请添完所有信息");
                        }
                        }

processRequest方法的功能主要为获取提交给editstudent.jsp的event的值,根据不同的event调用不同的函数。例如event=add,则调用addStudent函数。

注意:在设置Student对象的各个属性值时,一定要按照表(见上面的SQL语句)中顺序来设置,例如在表中,id字段在name字段的前面,所以setId方法在setName方法前面,这主要是由于Table类中的putRow函数中执行插入操作的SQL语句有缺陷。在那个SQL语句中,它是按各属性在Row中的顺序来执行插入操作的。如过你不愿意按表中字段的顺序来设置Student的属性,可以对putRow函数更改如下:
String ss = "";
                        if (conditions==null) {
                        ss = "INSERT INTO "+name+'(";
                        for (int i=0;i<row.length();++i) {
                        String k = row.getKey( i );
                        ss += k;
                        if (i != row.length()-1)
                        ss += ", ";
                        }
                        ss +=") VALUES (";
                        for (int j=0; j<row.length(); ++j) {
                        String v = row.get( j );
                        ss += "'"+v+"'";
                        if (j != row.length()-1)
                        ss += ", ";
                        }
                        ss += ")";
                        }

限于篇幅,我省略了student.jsp文件中的一些内容,对这个jsp文件进行处理的bean为ListStudentBean,这个类主要实现一个函数getStudent,这个函数主要通过StudentFactory查寻到所有的学生,由于我在此函数中进行了分页处理,因此此函数只返回当前页需要的Student数组。具体的代码参看 附件

总之,我改进之后的这个JDBC封装器还是比较有效的,它能满足一般地需要,当然你也可以根据你的情况对其进行扩充,以更好地适应你的开发。

代码在Tomcat4.01+mysql3.23.43下测试通过。



关于作者

 

宗锋,男,西北大学计算机系硕士。兴趣主要集中在:java,linux,enhydra,barracuda。希望能与有共同爱好的朋友进行交流。E-mail: zong_feng@263.net.

posted @ 2007-07-03 17:22 和田雨 阅读(204) | 评论 (0)编辑 收藏

一个简单的 JDBC 包装器

一种简单程序的快速数据访问解决方案

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项



级别: 初级

Greg Travis (mito@panix.com), 自由程序员

2001 年 8 月 04 日

JDBC 提供了一种强大、全面的接口用来从 Java 程序访问数据库。对于较小的项目来说,使用 JDBC 似乎是理所当然的,它使一些程序员避免了一起使用数据库。本文描述了一种简单的包装器库,它让使用简单的数据库易如反掌。您会发现您已经开始想在编写的每一个程序中都使用 JDBC。

事情发生得很突然。您正在修改一个程序,您原以为它是个小程序,不料竟发现它已经迅速增长成为一个庞大的东西 ― 一个 项目― 而现在您已到了需要保存一些数据的时候了。

问题是,您并不是有意让程序发展到这种程度的。 您只是不由自主地做了一小部分修改。您并没有真正准备要存储或装入。而且,您的程序是个 applet,而 applet 是无法存储或装入的。

可以存储或装入它们吗?

实际上,JDBC API 允许任何 Java 程序 ― 甚至是 applet ― 连接到关系型数据库(RDBMS)上。 不幸的是,JDBC 对您的小程序来说可能有一点头重脚轻了。毕竟,如果您希望做的只是存储一点点数据的话,RDBMS 是一个强大、复杂的系统。

在本文中,我们将分析一个简单的使用 JDBC 的抽象层。对于简单的应用程序来说,它可以让您只用几行代码就实现存储和装入结构化数据。它不会处理复杂的 RDBMS 使用,但那正是我们要避免的,不是吗?

JDBC 的复杂性

JDBC 使用起来可能是一个复杂的 API。它不仅必须支持整个强大的 SQL 标准,还必须很好地隐藏不同数据库引擎之间的区别。

JDBC 的复杂还在于关系型数据库是基于 SQL 构建的,而 SQL 是要给人用的,而不是给程序用的。直接使用 JDBC 有点象同时用两种语言编程。

虽然 JDBC 的这些方面并不是什么坏事,但它们确实与我们的目标 ― 快速地存储少量数据相冲突。





回页首


简化的抽象

我们将创建一个简单的抽象层,让您不必顾虑所有繁琐的细节问题来直接使用 JDBC。如果您早已熟悉 JDBC 或关系型数据库了,那您一眼看到我们的类列表应该是很熟悉的:

  • Database
  • Table
  • RowSet
  • Row

我们这里不是在作任何实质性的事情;我们的数据模型本质上和关系型模型是一样的,但去掉了烦人的细节(同时去掉了强大的功能)。每一个类映射到一个基本的 RDBMS 概念上,同时也映射到一个 JDBC 类上。就是这种映射让我们的 API 可以在保持易用性的同时保留它的相关特性。

这种 API 的设计是基于对我们的数据存储需要的设想。如果您发现自己的程序需要一点不同的地方,您可以随意地改变这种抽象以适应您的情况。 这些类可以被认为是一种简化您工作的模式,而不是一成不变的规则。

如果您不熟悉 SQL 或者 RDBMS 技术,不必害怕。 下面的四节中,每一节都会帮助您熟悉我们的一个类,还有这些类映射到的 RDBMS 功能。

Database 类

当使用 JDBC 与数据库建立连接时,您必须告诉 JDBC 在何处可以找到实际的数据。 因为不同的数据库引擎有不同的访问方法和描述这些方法的不同语法,所以有不止一种方法来指定数据源。 在 JDBC 中,统一资源标识符(Uniform Resource Identifier,URI)字符串是用来指定数据源的,而这个字符串的结构是依赖于数据库的。

Database 类的主要目的就是封装这个字符串,还有建立连接操作时可能需要的任何用户名/密码信息。

下面是如何创建一个 Database 对象的方法:

  Database db =
                        new Database( "jdbc:postgresql://localhost/mito",
                        "mito", "" );
                        

构造函数的第一个参数是数据源的 URI。 在这个示例中,我使用了 PostgreSQL数据库引擎,而且在本机上访问了一个名为 mito 的数据库。 另外,我指定我的用户名 mito 和一个空的密码分别作为第二个和第三个参数。

一旦您创建了 Database 对象,您就可以使用它来访问数据,如我们在下一章可以看到的一样。

Table 类

我们在 API 中对简化的一个设想就是,当您从表的一行读取数据时,您会得到整行的数据。换句话说,表的一行是作为读写单独一块数据的最小单位。这并不十分有效,但效率不是我们方法中所首要考虑的。

Talbe 类让您可以读写这些行对象。您必须做的第一步是创建一个表对象,它简单得只要知道它的名称即可:

  Table table = db.getTable( "employee" );
                        

创建 Table 对象的操作实际上并没有做任何事,只是让对象记住自己的名称。要做一些实际的事,我们就需要实际地使用这个 Table 对象了。在这里,我们从表中读取一行。

  Row row = table.getRow( "id=101");
                        

注意,我们已经指定了我们只需要那些‘id’值设定为‘101’的行。通过使用 getRow() 方法,我们假定只有一行符合这个条件。在另外的情况下,我们可能需要多个行,那样我们就需要这样使用 getRows() 方法:

  RowSet rows = table.getRows( "id<103" );
                        

在这种情况下,返回的值是一个 RowSet ,而不是一个 Row。 RowSet 就是 Row 的一个集合。

在接下来的两节里,我们将讨论 RowRowSet 类。

Row 类

在我们的抽象中, Row 是在 RDBMS 中表示表中一行的名称/值对的集合。不同于 RDBMS 值可以是不同的类型, Row 仅包含一种类型,即字符串类型。 这还是为了让事情简单一点 ― 我们假定您不需要字符串类型提供的任何更强大的功能。

一旦您有了一个 Row ,就很容易从其中取出一个值,正如我们在清单 1 中看见的一样。



L清单 1. 从 Row 中获取值
  Row e = table.getRow( "id=100" );
                        String name = e.get( "name" );
                        System.out.println( "Employee name: "+name );
                        

还要注意的是 Row 是排序的,这样您就可以通过索引来取出名称/值的对。清单 2 中给出了这样的一个示例。



清单 2. 迭代整个 Row
  Row e = table.getRow( "id=100" );
                        for (int i=0; i<e.length(); ++i) {
                        String key = e.getKey( i );
                        String value = e.get( i );
                        System.out.println( key+" = "+value );
                        }
                        

当然,您还可以改变 Row 中的值。这是在数据库中更改数据所必需的 — 稍后我们会看到这一点。

RowSet 类

记住有一些查询可以返回多个 Row ,这样的话您就会得到一个 RowSetRowSet 有一点不同于基于 Vector 的包装器。你可以轻易地迭代 RowSet 中所有的 Row ,在清单 3 中可以看到这一点。



清单 3. 迭代整个 RowSet
  RowSet rs = table.get( "id<104" );
                        for (int i=0; i<rs.length(); ++i) {
                        Row row = rs.get( i );
                        // do something with the row....
                        }
                        

一个完整的示例

现在我们已经看过了所有的类,让我们来看一个完整的示例吧。在清单 4 中,我们将抽取出符合特定条件的一套记录,然后打印出它们的值。



清单 4. 一个读取数据的完整示例
    // First, get all rows meeting the criterion
                        RowSet rs = table.getRows( "id<103" );
                        // Iterate through the set
                        for (int i=0; i<rs.length(); ++i) {
                        // Grab each row in turn
                        Row row = rs.get( i );
                        // Get and print the value of the "name" field
                        String name = row.get( "name" );
                        System.out.println( "Name: "+name );
                        }
                        

如此容易!在下一节中,我们将看看怎样向数据库写入数据。





回页首


修改数据

正如前面所提到的,使用我们的 API 读写数据是以整个 为单位的。为了向数据库写入数据,您必须创建(或修改) Row 对象,然后向数据库写入那个 Row 对象。

向数据库写入数据是通过使用 Table 中的 putRow 方法。这种方法有两种变体:

  • public void putRow( Row row )
  • public void putRow( Row row, String conditions )

这两种变体分别对应于 SQL 中的 INSERTUPDATE 命令。

在第一个变体中,写一行意味着将一个全新的行插入表中。

在第二个变体中,写一行意味着修改一个现有的行。 conditions 参数使您能够指定您想要修改的是哪一行(哪些行)。

让我们来看看每种方法的一个示例。

插入一个新行

插入一个新行很简单,因为您不必指定要修改的行(一行或多行)。您只是简单地把行插入:

  table.putRow( row );
                        

您可以重新创建一个 Row ,如清单 5 所示。



清单 5. 重新创建一个 Row
  // Create an empty row object
                        Row row = new Row();
                        // Fill it up with data
                        row.put( "id", "200" );
                        row.put( "name", "Joey Capellino" );
                        

或者,您可以修改一个以前曾经从数据库中读取的一个现有的行,如清单 6 所示。



清单 6. 修改现有的 Row
  // Grab a row from the database
                        Row row = table.getRow( someConditions );
                        // Change some or all of the fields
                        row.put( "name", "Joey Capellino" );
                        

虽然通常是在插入时重新创建 Row ,更新时使用现有的 Row ,实际上您可以用任何方式来进行。

更新现有的行

正如前面的部分提到的,对于您如何 创建用来更新的 Row 是没有限制的。 但是,通常您是使用一个刚从数据库中读出的 Row

为了详细描述这一点,我们将使用一个示例(在该例子中我们读出一个员工的姓名),改变这个名字,然后将更改后的结果写回数据库,如清单 7 所示。



清单 7. 通过修改 Row 进行更新
    Row row = table.getRow( "id=104" );
                        row.put( "name", newName );
                        table.putRow( row, "id=104" );
                        

注意我们必须在调用 putRow() 中指定条件。这样才会使调用成为 更新,而不是 插入

注意,这个调用将更新 所有符合条件的行,而不是其中的一行。





回页首


结论

在本文中,我们初步认识了一种通过 JDBC 包提供一种简化的通往关系型数据库接口的 API。这种抽象保留了 JDBC 接口的很多基本关系型功能,但对其进行了简化,从而让使用非常地方便。这种简化是以效率为代价的,但当目标是简单性时,这并不是一个令人惊奇的结果。



参考资料



关于作者

 

Greg Travis 是居住在纽约的一个自由程序员。他对计算机的兴趣可能是源于“ Bionic Woman”中的一段情节 - Jamie 四处奔跑,试图逃离一所灯光和门都被邪恶的人工智能所控制的房子,人工智能还通过扬声器嘲笑她。他是一个传统观念的虔诚信徒 - 如果一个计算机程序能够工作,那完全是个巧合。可以通过 mito@panix.com与他联系。

posted @ 2007-07-03 17:16 和田雨 阅读(205) | 评论 (0)编辑 收藏

     摘要: 本文通过开发一个JSP 编辑器插件的示例,介绍了 Eclipse 中设置 JSP 断点的方法,以及如何远程调试 JSP。作为基础知识,本文的前两部分描述了 JAVA Debug 和 JSR-45 的基本原理。
  阅读全文
posted @ 2007-07-03 16:40 和田雨 阅读(378) | 评论 (0)编辑 收藏

JavaBeans的属性 

JavaBeans的属性与一般Java程序中所指的属性,或者说与所有面向对象的程序设计语言中对象的属性是一个概念,在程序中的具体体现就是类中的变量。在JavaBeans设计中,按照属性的不同作用又细分为四类:Simple, Index, Bound与Constrained属性。 

1. Simple属性 

一个简单属性表示一个伴随有一对get/set方法(C语言的过程或函数在Java程序中称为"方法")的变量。属性名与和该属性相关的get/set方法名对应。例如:如果有setX和getX方法,则暗指有一个名为"X"的属性。如果有一个方法名为isX,则通常暗指"X"是一个布尔属性(即X的值为true或false)。例如在下面这个程序中: 


public class alden1 extends Canvas { 
string ourString= "Hello"; //属性名为ourString,类型为字符串 
public alden1(){     //alden1()是alden1的构造函数, 
与C++中构造函数的意义相同 
setBackground(Color.red); 
setForeground(Color.blue); 

/* "set"属性*/ 
public void setString(String newString) { 
ourString=newString; 

/* "get"属性 */ 
public String getString() { 
return ourString; 






2. Indexed属性 

一个Indexed属性表示一个数组值。使用与该属性对应的set/get方法可取得数组中的数值。该属性也可一次设置或取得整个数组的值。例: 


public class alden2 extends Canvas { 
int[] dataSet={1,2,3,4,5,6}; // dataSet是一个indexed属性 
public alden2() { 
setBackground(Color.red); 
setForeground(Color.blue); 

/* 设置整个数组 */ 
public void setDataSet(int[] x){ 
dataSet=x; 

/* 设置数组中的单个元素值 */ 
public void setDataSet(int index, int x){ 
dataSet[index]=x; 

/* 取得整个数组值 */ 
public int[] getDataSet(){ 
return dataSet; 

/* 取得数组中的指定元素值 */ 
public int getDataSet(int x){ 
return dataSet[x]; 






3. Bound属性 

一个Bound属性是指当该种属性的值发生变化时,要通知其它的对象。每次属性值改变时,这种属性就点火一个PropertyChange事件(在Java程序中,事件也是一个对象)。事件中封装了属性名、属性的原值、属性变化后的新值。这种事件是传递到其它的Beans,至于接收事件的Beans应做什么动作由其自己定义。当PushButton的background属性与Dialog的background属性bind时,若PushButton的background属性发生变化时,Dialog的background属性也发生同样的变化。 例: 


public class alden3 extends Canvas{ 
String ourString= "Hello"; 
//ourString是一个bound属性 
private PropertyChangeSupport changes = new PropertyChangeSupport(this); 
/** 注:Java是纯面向对象的语言, 
如果要使用某种方法则必须指明是要使用哪个对象的方法, 
在下面的程序中要进行点火事件的操作, 
这种操作所使用的方法是在PropertyChangeSupport类中的。 
所以上面声明并实例化了一个changes对象, 
在下面将使用changes的firePropertyChange方法来点火ourString的属性改变事件。*/ 

public void setString(string newString){ 
String oldString = ourString; 
ourString = newString; 
/* ourString的属性值已发生变化,于是接着点火属性改变事件 */ 
changes.firePropertyChange("ourString",oldString,newString); 

public String getString(){ 
return ourString; 

/** 以下代码是为开发工具所使用的。 
我们不能预知alden3将与哪些其它的Beans组合成为一个应用, 
无法预知若alden3的ourString属性发生变化时有哪些其它的组件与此变化有关, 
因而alden3这个Beans要预留出一些接口给开发工具, 
开发工具使用这些接口, 
把其它的JavaBeans对象与alden3挂接。*/ 

public void addPropertyChangeListener(PropertyChangeLisener l){ 
changes.addPropertyChangeListener(l); 

public void removePropertyChangeListener(PropertyChangeListener l){ 
changes.removePropertyChangeListener(l); 





通过上面的代码, 

开发工具调用changes的addPropertyChangeListener方法 

把其它JavaBeans注册入ourString属性的监听者队列l中, 

l是一个Vector数组,可存储任何Java对象。 

开发工具也可使用changes的removePropertyChangeListener方法, 

从l中注销指定的对象, 

使alden3的ourString属性的改变不再与这个对象有关。 

当然,当程序员手写代码编制程序时, 

也可直接调用这两个方法, 

把其它Java对象与alden3挂接。 

4. Constrained属性 

一个JavaBeans的constrained属性,是指当这个属性的值要发生变化时,与这个属性已建立了某种连接的其它Java对象可否决属性值的改变。constrained属性的监听者通过抛出PropertyVetoException来阻止该属性值的改变。例:下面程序中的constrained属性是PriceInCents。 


public class JellyBeans extends Canvas{ 
private PropertyChangeSupport changes=new PropertyChangeSupport(this); 
private VetoableChangeSupport Vetos=new VetoableChangeSupport(this); 
/*与前述changes相同, 
可使用VetoableChangeSupport对象的实例Vetos中的方法, 
在特定条件下来阻止PriceInCents值的改变。*/ 


...... 
public void setPriceInCents(int newPriceInCents) throws PropertyVetoException { 
/*方法名中throws PropertyVetoException的作用是当有 
其它Java对象否决PriceInCents的改变时, 
要抛出例外。*/ 
/* 先保存原来的属性值*/ 

int oldPriceInCents=ourPriceInCents; 
/**点火属性改变否决事件*/ 
vetos.fireVetoableChange("priceInCents",new Integer(OldPriceInCents), 
new Integer(newPriceInCents)); 

/**若有其它对象否决priceInCents的改变, 
则程序抛出例外,不再继续执行下面的两条语句, 
方法结束。若无其它对象否决priceInCents的改变, 
则在下面的代码中把ourPriceIncents赋予新值, 
并点火属性改变事件*/ 

ourPriceInCents=newPriceInCents; 
changes.firePropertyChange("priceInCents", 
new Integer(oldPriceInCents), 
new Integer(newPriceInCents)); 


/**与前述changes相同, 
也要为PriceInCents属性预留接口, 
使其它对象可注册入PriceInCents否决改变监听者队列中, 
或把该对象从中注销 

public void addVetoableChangeListener(VetoableChangeListener l) 
{ vetos.addVetoableChangeListener(l); 

public void removeVetoableChangeListener(VetoableChangeListener l){ 
vetos.removeVetoableChangeListener(l); 

...... 





从上面的例子中可看到,一个constrained属性有两种监听者:属性变化监听者和否决属性改变的监听者。否决属性改变的监听者在自己的对象代码中有相应的控制语句,在监听到有constrained属性要发生变化时,在控制语句中判断是否应否决这个属性值的改变。 

总之,某个Beans的constrained属性值可否改变取决于其它的Beans或者是Java对象是否允许这种改变。允许与否的条件由其它的Beans或Java对象在自己的类中进行定义。 

JavaBeans的事件 

事件处理是JavaBeans体系结构的核心之一。通过事件处理机制,可让一些组件作为事件源,发出可被描述环境或其它组件接收的事件。这样,不同的组件就可在构造工具内组合在一起,组件之间通过事件的传递进行通信,构成一个应用。从概念上讲,事件是一种在"源对象"和"监听者对象"之间,某种状态发生变化的传递机制。事件有许多不同的用途,例如在Windows系统中常要处理的鼠标事件、窗口边界改变事件、键盘事件等。在Java和JavaBeans中则是定义了一个一般的、可扩充的事件机制,这种机制能够: 

对事件类型和传递的模型的定义和扩充提供一个公共框架,并适合于广泛的应用。 

与Java语言和环境有较高的集成度。 

事件能被描述环境捕获和点火。 

能使其它构造工具采取某种技术在设计时直接控制事件,以及事件源和事件监听者之间的联系。 

事件机制本身不依赖于复杂的开发工具。特别地,还应当: 

能够发现指定的对象类可以生成的事件。 

能够发现指定的对象类可以观察(监听)到的事件。 

提供一个常规的注册机制,允许动态操纵事件源与事件监听者之间的关系。 

不需要其它的虚拟机和语言即可实现。 

事件源与监听者之间可进行高效的事件传递。 

能完成JavaBeans事件模型与相关的其它组件体系结构事件模型的中立映射。 

JavaBeans事件模型的主要构成有: 事件从事件源到监听者的传递是通过对目标监听者对象的Java方法调用进行的。对每个明确的事件的发生,都相应地定义一个明确的Java方法。这些方法都集中定义在事件监听者(EventListener)接口中,这个接口要继承java.util.EventListener。实现了事件监听者接口中一些或全部方法的类就是事件监听者。 伴随着事件的发生,相应的状态通常都封装在事件状态对象中,该对象必须继承自java.util.EventObject。事件状态对象作为单参传递给应响应该事件的监听者方法中。 发出某种特定事件的事件源的标识是:遵从规定的设计格式为事件监听者定义注册方法,并接受对指定事件监听者接口实例的引用。 有时,事件监听者不能直接实现事件监听者接口,或者还有其它的额外动作时,就要在一个源与其它一个或多个监听者之间插入一个事件适配器类的实例,来建立它们之间的联系。 

事件状态对象(Event State Object) 

与事件发生有关的状态信息一般都封装在一个事件状态对象中,这种对象是java.util.EventObject的子类。按设计习惯,这种事件状态对象类的名应以Event结尾。例如: 


public class MouseMovedExampleEvent extends java.util.EventObject 

{ protected int x, y; 
/* 创建一个鼠标移动事件MouseMovedExampleEvent */ 
  MouseMovedExampleEvent(java.awt.Component source, Point location) { 
super(source); 
x = location.x; 
y = location.y; 

/* 获取鼠标位置*/ 
public Point getLocation() { 
return new Point(x, y); 
}} 




事件监听者接口(EventListener Interface)与事件监听者 

由于Java事件模型是基于方法调用,因而需要一个定义并组织事件操纵方法的方式。JavaBeans中,事件操纵方法都被定义在继承了java.util.EventListener类的EventListener接口中,按规定,EventListener接口的命名要以Listener结尾。任何一个类如果想操纵在EventListener接口中定义的方法都必须以实现这个接口方式进行。这个类也就是事件监听者。例如: 


/*先定义了一个鼠标移动事件对象*/ 
   public class MouseMovedExampleEvent 
extends java.util.EventObject { 
// 在此类中包含了与鼠标移动事件有关的状态信息 
     ... 
   } 
   /*定义了鼠标移动事件的监听者接口*/ 
   interface MouseMovedExampleListener 
extends java.util.EventListener { 
/*在这个接口中定义了鼠标移动事件监听者所应支持的方法*/ 
void mouseMoved(MouseMovedExampleEvent mme); 


在接口中只定义方法名, 
方法的参数和返回值类型。 
如:上面接口中的mouseMoved方法的 
具体实现是在下面的ArbitraryObject类中定义的。 

class ArbitraryObject implements MouseMovedExampleListener { 
    public void mouseMoved(MouseMovedExampleEvent mme) 
  { ... } 
} 
ArbitraryObject就是MouseMovedExampleEvent事件的监听者。 




事件监听者的注册与注销 

为了各种可能的事件监听者把自己注册入合适的事件源中,建立源与事件监听者间的事件流,事件源必须为事件监听者提供注册和注销的方法。在前面的bound属性介绍中已看到了这种使用过程,在实际中,事件监听者的注册和注销要使用标准的设计格式: 


public void add< ListenerType>(< ListenerType> listener); 
public void remove< ListenerType>(< ListenerType> listener); 




例如: 

首先定义了一个事件监听者接口: 


public interface 
ModelChangedListener extends java.util.EventListener { 
void modelChanged(EventObject e); 





接着定义事件源类: 


public abstract class Model { 
private Vector listeners = new Vector(); // 定义了一个储存事件监听者的数组 

/*上面设计格式中的< ListenerType>在此处即是下面的ModelChangedListener*/ 

public synchronized void addModelChangedListener(ModelChangedListener mcl) 
   { listeners.addElement(mcl); }//把监听者注册入listeners数组中 
public synchronized void removeModelChangedListener(ModelChangedListener mcl) 
     { listeners.removeElement(mcl); //把监听者从listeners中注销 
     } 
   /*以上两个方法的前面均冠以synchronized, 
是因为运行在多线程环境时, 
可能同时有几个对象同时要进行注册和注销操作, 
使用synchronized来确保它们之间的同步。 
开发工具或程序员使用这两个方法建立源与监听者之间的事件流*/ 

protected void notifyModelChanged() { 
/**事件源使用本方法通知监听者发生了modelChanged事件*/ 
    Vector l; 
     EventObject e = new EventObject(this); 
/* 首先要把监听者拷贝到l数组中, 
冻结EventListeners的状态以传递事件。 
这样来确保在事件传递到所有监听者之前, 
已接收了事件的目标监听者的对应方法暂不生效。*/ 
     synchronized(this) { 
       l = (Vector)listeners.clone(); 
     } 
     for (int i = 0; i < l.size(); i++) { 
     /* 依次通知注册在监听者队列中的每个监听者发生了modelChanged事件, 
     并把事件状态对象e作为参数传递给监听者队列中的每个监听者*/ 
((ModelChangedListener)l.elementAt(i)).modelChanged(e); 
     } 
    } 
    } 




在程序中可见事件源Model类显式地调用了接口中的modelChanged方法,实际是把事件状态对象e作为参数,传递给了监听者类中的modelChanged方法。 

适配类 

适配类是Java事件模型中极其重要的一部分。在一些应用场合,事件从源到监听者之间的传递要通过适配类来"转发"。例如:当事件源发出一个事件,而有几个事件监听者对象都可接收该事件,但只有指定对象做出反应时,就要在事件源与事件监听者之间插入一个事件适配器类,由适配器类来指定事件应该是由哪些监听者来响应。 

适配类成为了事件监听者,事件源实际是把适配类作为监听者注册入监听者队列中,而真正的事件响应者并未在监听者队列中,事件响应者应做的动作由适配类决定。目前绝大多数的开发工具在生成代码时,事件处理都是通过适配类来进行的。 

JavaBeans用户化 

JavaBeans开发者可以给一个Beans添加用户化器(Customizer)、属性编辑器(PropertyEditor)和BeansInfo接口来描述一个Beans的内容,Beans的使用者可在构造环境中通过与Beans附带在一起的这些信息来用户化Beans的外观和应做的动作。一个Beans不必都有BeansCustomizer、PrpertyEditor和BeansInfo,根据实际情况,这些是可选的,当有些Beans较复杂时,就要提供这些信息,以Wizard的方式使Beans的使用者能够用户化一个Beans。有些简单的Beans可能这些信息都没有,则构造工具可使用自带的透视装置,透视出Beans的内容,并把信息显示到标准的属性表或事件表中供使用者用户化Beans,前几节提到的Beans的属性、方法和事件名要以一定的格式命名,主要的作用就是供开发工具对Beans进行透视。当然也是给程序员在手写程序中使用Beans提供方便,使他能观其名、知其意。 

用户化器接口(Customizer Interface) 

当一个Beans有了自己的用户化器时,在构造工具内就可展现出自己的属性表。在定义用户化器时必须要实现java.Beanss.Customizer接口。例如,下面是一个"按钮"Beans的用户化一器: 


public class OurButtonCustomizer 
extends Panel implements Customizer { 
... ... 
/*当实现象OurButtonCustomizer这样的常规属性表时, 
一定要在其中实现addProperChangeListener 
和removePropertyChangeListener,这样, 
构造工具可用这些功能代码为属性事件添加监听者。*/ 
... ... 
private PropertyChangeSupport changes=new PropertyChangeSupport(this); 
public void addPropertyChangeListener(PropertyChangeListener l) { 
changes.addPropertyChangeListener(l); 
public void removePropertyChangeListener(PropertyChangeListener l) { 
changes.removePropertyChangeListener(l); 

... ... 




属性编辑器接口(PropertyEditor Interface) 

一个JavaBeans可提供PropertyEditor类,为指定的属性创建一个编辑器。这个类必须继承自java.Beanss.PropertyEditorSupport类。构造工具与手写代码的程序员不直接使用这个类,而是在下一小节的BeansInfo中实例化并调用这个类。例: 


public class MoleculeNameEditor extends java.Beanss.PropertyEditorSupport { 
public String[] getTags() { 
String resule[]={ 
"HyaluronicAcid","Benzene","buckmisterfullerine", 
"cyclohexane","ethane","water"}; 
return resule;} 





上例中是为Tags属性创建了属性编辑器,在构造工具内,可从下拉表格中选择MoleculeName的属性应是"HyaluronicAid"或是"water"。 

BeansInfo接口 

每个Beans类也可能有与之相关的BeansInfo类,在其中描述了这个Beans在构造工具内出现时的外观。BeansInfo中可定义属性、方法、事件,显示它们的名称,提供简单的帮助说明。 例如: 


public class MoleculeBeansInfo extends SimpleBeansInfo { 
public PropertyDescriptor[] getPropertyDescriptors() { 
try { 
PropertyDescriptor pd=new PropertyDescriptor("moleculeName",Molecule.class); 
/*通过pd引用了上一节的MoleculeNameEditor类,取得并返回moleculeName属性*/ 
pd.setPropertyEditorClass(MoleculeNameEditor.class); 
PropertyDescriptor result[]={pd}; 
return result; 
} catch(Exception ex) { 
System.err.println("MoleculeBeansInfo: unexpected exeption: "+ex); 
return null; 







JavaBeans持久化 

当一个JavaBeans在构造工具内被用户化,并与其它Beans建立连接之后,它的所有状态都应当可被保存,下一次被load进构造工具内或在运行时,就应当是上一次修改完的信息。为了能做到这一点,要把Beans的某些字段的信息保存下来,在定义Beans时要使它实现java.io.Serializable接口。例如: 

public class Button 
implements java.io.Serializable { 




实现了序列化接口的Beans中字段的信息将被自动保存。若不想保存某些字段的信息则可在这些字段前冠以transient或static关键字,transient和static变量的信息是不可被保存的。通常,一个Beans所有公开出来的属性都应当是被保存的,也可有选择地保存内部状态。 Beans开发者在修改软件时,可以添加字段,移走对其它类的引用,改变一个字段的private/protected/public状态,这些都不影响类的存储结构关系。然而,当从类中删除一个字段,改变一个变量在类体系中的位置,把某个字段改成transient/static,或原来是transient/static,现改为别的特性时,都将引起存储关系的变化。 

JavaBeans的存储格式 

JavaBeans组件被设计出来后,一般是以扩展名为jar的Zip格式文件存储,在jar中包含与JavaBeans有关的信息,并以MANIFEST文件指定其中的哪些类是JavaBeans。以jar文件存储的JavaBeans在网络中传送时极大地减少了数据的传输数量,并把JavaBeans运行时所需要的一些资源捆绑在一起,本章主要论述了JavaBeans的一些内部特性及其常规设计方法,参考的是JavaBeans规范1.0A版本。随着世界各大ISV对JavaBeans越来越多的支持,规范在一些细节上还在不断演化,但基本框架不会再有大的变动。
posted @ 2007-07-03 15:14 和田雨 阅读(227) | 评论 (0)编辑 收藏

一个读取xml文件内容的类 
package project.util.xml;

import java.io.*;
import java.util.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.jdom.*;
import org.jdom.input.*;

/**
* <p>Title: <font color="steelblue" size="10">读取xml文件信息</font></p>
* <p>Description: <font color="steelblue">从XML配置文件中获得配置信息。excerpt form jdom。</font></p>
* <p>Copyright: <font color="steelblue">Copyright (c) 2004</font></p>
* <p>Company: <font color="steelblue">Harmonious</font></p>
* @author <font color="steelblue">TZL</font>
* @version <font color="steelblue">1.0</font>
*/

public class XMLReader {
/*
#设置根的输出配置,格式为 "info [2004-05-01 22:35:30] [name]logname(b.c) [line] 86 msg-->log信息"
log4j.rootLogger=DEBUG, rootAppender
log4j.appender.rootAppender=org.apache.log4j.RollingFileAppender
log4j.appender.rootAppender.File=e:/MapXtremeSmpl.log
log4j.appender.rootAppender.MaxFileSize=1000KB
log4j.appender.rootAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.rootAppender.layout.ConversionPattern=%-5p [%d{yyyy-mm-dd HH:mm:ss}] [name] %c{2} [line] %L msg--> %m%n
*/
static public Logger log = Logger.getLogger(XMLReader.class);
protected Element m_RootElement = null;
protected String m_webAppPath = null;

/**
* <font color="orange">构造函数。</font>
* @param xmlFile <font color="steelblue">要读取的配置文件的绝对路径。</font>
*/
public XMLReader(String xmlFile) {
m_webAppPath = null;
try {
PatternLayout layout = new PatternLayout("%-5p %d{yyyy-MM-dd HH:mm:ss} [name] %c{2} [line] %L [msg] %m%n");
ConsoleAppender appender = new ConsoleAppender(/*new SimpleLayout(),*/layout, "System.err");
log.addAppender(appender);

SAXBuilder builder = new SAXBuilder();
document.nbspdoc = null;
doc = builder.build(new FileInputStream(xmlFile));
m_RootElement = doc.getRootElement();
}
catch (IOException ex) {
log.error("XMLReader构造时出现IO错误:" + ex.toString());
}
catch (JDOMException ex1) {
log.error("XMLReader构造时分析XML文件出错:" + ex1.toString());
}
catch (Exception ex) {
log.error("XMLReader 构造出错:" + ex.toString());
}
}

/**
* <font color="orange">构造函数。配置文件必须指定为发布的应用的根目录下的/XmlConfig/Config.xml。</font>
* @param servletObj <font color="steelblue">随便一个HttpServlet对象。</font>
*/
public XMLReader(HttpServlet servletObj) {
m_webAppPath = servletObj.getServletContext().getRealPath("/");
String configFileName = m_webAppPath + "XmlConfig/Config.xml";

try {
PatternLayout layout = new PatternLayout("%-5p %d{yyyy-MM-dd HH:mm:ss} [name] %c{2} [line] %L [msg] %m%n");
ConsoleAppender appender = new ConsoleAppender( /*new SimpleLayout(),*/layout, "System.err");
log.addAppender(appender);

SAXBuilder builder = new SAXBuilder();
document.nbspdoc = null;
doc = builder.build(new FileInputStream(configFileName));
m_RootElement = doc.getRootElement();
}
catch (IOException ex) {
log.error("XMLReader构造时出现IO错误(/XmlConfig/Config.xml):" + ex.toString());
}
catch (JDOMException ex1) {
log.error("XMLReader构造时分析XML文件出错(/XmlConfig/Config.xml):" + ex1.toString());
}
catch (Exception ex) {
log.error("XMLReader构造出错(/XmlConfig/Config.xml):" + ex.toString());
}
}

/**
* <font color="orange">web应用发布在web服务器的绝对路径根目录,最后已经有目录分割符。</font>
* @return <font color="tomato">返回web应用发布在web服务器的绝对路径的根目录。</font>
*/
public String getWebAppPath() {
return m_webAppPath;
}

/**
* <font color="orange">从配置文件中获得配置信息。</font>
* @param key <font color="steelblue">要获取的配置名称。</font>
* @param curRootName <font color="steelblue">查找的起始节点名称,如果为null从根开始查找。</font>
* @return <font color="tomato">配置的字符串。</font>
*/
public String getElementvalue(String curRootName, String key) {
String value = null;
Element curRoot = getElement(null, curRootName);
if (null == curRoot) {
curRoot = m_RootElement;
}
Element keyNode = getElement(curRoot, key);
if (null != keyNode) {
value = keyNode.getTextTrim();

}
return value;
}

/**
* <font color="orange">根据名字获得节点。广度遍历,递归调用。</font>
* @param nodeName <font color="steelblue">节点的名字。</font>
* @param curRoot <font color="steelblue"> 从开始查找的起始节点,如果为null从根开始查找。</font>
* @return <font color="tomato">返回从指定节点下找到的第一个节点。如果没有返回null。</font>
*/
private Element getElement(Element curRoot, String nodeName) {
Element retElement = null;

if (null == nodeName)
return m_RootElement;

if (null == curRoot) {
curRoot = m_RootElement;
}

if (null != curRoot) {
retElement = curRoot.getChild(nodeName);
if (null == retElement) {
List nestElements = curRoot.getChildren();
Iterator iterator = nestElements.iterator();
while (iterator.hasNext() && null == retElement) {
retElement = getElement( (Element) iterator.next(), nodeName);
}
}
}

return retElement;
}

/**
* <font color="orange">获得指定节点的属性。</font>
* @param elementName <font color="steelblue">节点的名称。</font>
* @param attName <font color="steelblue">要获得的属性的名称。</font>
* @return <font color="tomato">要查找的属性的值。</font>
*/
public String getElementAtrribute(String elementName, String attName)
{
Element el = getElement(null, elementName);
if (null == el)
return null;

return el.getAttributevalue(attName);
}

}
posted @ 2007-07-03 14:55 和田雨 阅读(424) | 评论 (0)编辑 收藏

引言
  在做无线项目的时候,与通讯公司的数据通讯有一部分是通过XML交互的,所以必须要动态抓取通讯公司提供的固定的Internet上的数据,便研究了一下如何抓取固定url上的数据,现与大家分享一下。

  类名GetPageCode,有一个方法GetSource,通过属性传递参数,入参控制的是要取得URL的地址,代理服务器的设置及输出方式的控制,这里大家可以再扩展自己的需要,我这里只提供了两种方式,一种是直接写到本地的某个文件中,另外一种就是返回字符串的。类里已经作了比较详细的注释,我想大家很容易就看明白了,如果实在不明白, 那就msn上问吧,MSN:yubo@x263.net。

  调用方式:
  #region 测试获取远程网页



GetPageCode gpc = new GetPageCode();
  gpc.Url="http://ppcode.com";
  gpc.ProxyState=1;//使用代理服务器,0为不使用,设置为1后下面的代理设置才起作用
  gpc.ProxyAddress="http://proxyName.com";//代理服务器地址
  gpc.ProxyPort="80";//代理服务器的端口
  gpc.ProxyAccount="proxy";//代理服务器账号
  gpc.ProxyPassword="password";//代理服务器密码
  gpc.ProxyDomain="bqc";//代理服务器域
  gpc.OutFilePath=filePath;//设置输出文件路径的地方,如果不设置,则返回字符串
  gpc.GetSource();//处理
  string tempErr=gpc.NoteMessage;//如果出错,这里会提示
  string tempCode=gpc.OutString;//返回的字符串
  #endregion
  类代码:
  using System;
  using System.Collections;
  using System.ComponentModel;
  using System.Data;
  using System.Drawing;
  using System.IO;
  using System.Net;
  using System.Text;
  using System.Web;
  namespace Test.Com
  {
   /// <summary>
   /// 功能:取得Internet上的URL页的源码
   /// 创建:2004-03-22
   /// 作者:Rexsp MSN:yubo@x263.net
  /// </summary>
   public class GetPageCode
   {
   #region 私有变量
  /// <summary>
  /// 网页URL地址
  /// </summary>
  private string url=null;
  /// <summary>
  /// 是否使用代码服务器:0 不使用  1 使用代理服务器
  /// </summary>
  private int proxyState=0;
  /// <summary>
  /// 代理服务器地址
  /// </summary>
  private string proxyAddress=null;
  /// <summary>
  /// 代理服务器端口
  /// </summary>
  private string proxyPort=null;
  /// <summary>
  /// 代理服务器用户名
  /// </summary>
  private string proxyAccount=null;
  /// <summary>
  /// 代理服务器密码
  /// </summary>
  private string proxyPassword=null;
  /// <summary>
  /// 代理服务器域
  /// </summary>
  private string proxyDomain=null;
 /// <summary>
  /// 输出文件路径
  /// </summary>
  private string outFilePath=null;
  /// <summary>
  /// 输出的字符串
  /// </summary>
  private string outString=null;
  /// <summary>
  /// 提示信息
  /// </summary>
  private string noteMessage;

  #endregion

  #region 公共属性
  /// <summary>
  /// 欲读取的URL地址
  /// </summary>
  public string Url
  {
   get{return url;}
   set{url=value;}
  }
  /// <summary>
  /// 是否使用代理服务器标志
  /// </summary>
  public int ProxyState
  {
   get{return proxyState;}
   set{proxyState=value;}
  }
  /// <summary>
  /// 代理服务器地址
  /// </summary>
  public string ProxyAddress
  {
   get{return proxyAddress;}
   set{proxyAddress=value;}
  }
  /// <summary>

  /// 代理服务器端口
  /// </summary>
  public string ProxyPort
  {
   get{return proxyPort;}
   set{proxyPort=value;}
  }
  /// <summary>
  /// 代理服务器账号
  /// </summary>
  public string ProxyAccount
  {
   get{return proxyAccount;}
   set{proxyAccount=value;}
  }
  /// <summary>
  /// 代理服务器密码
  /// </summary>
  public string ProxyPassword
  {
   get{return proxyPassword;}
   set{proxyPassword=value;}
  }
  /// <summary>
  /// 代理服务器域
  /// </summary>
  public string ProxyDomain
  {
   get{return proxyDomain;}
   set{proxyDomain=value;}
  }
  /// <summary>
  /// 输出文件路径
  /// </summary>
  public string OutFilePath
  {
   get{return outFilePath;}

  set{outFilePath=value;}
  }
  /// <summary>
  /// 返回的字符串
  /// </summary>
  public string OutString
  {
   get{return outString;}
   
  }
  /// <summary>
  /// 返回提示信息
  /// </summary>
  public string NoteMessage
  {
   get{return noteMessage;}
   
  }
  
  #endregion
  
  #region 构造函数
  public GetPageCode()
  {
  }
  #endregion

  #region 公共方法
  /// <summary>
  /// 读取指定URL地址,存到指定文件中
  /// </summary>
  public void GetSource() 
  { 
   WebRequest request = WebRequest.Create(this.url);
   //使用代理服务器的处理
   if(this.proxyState==1)
   {
    //默认读取80端口的数据

    if(this.proxyPort==null)
     this.ProxyPort="80";

    WebProxy myProxy=new WebProxy(); 
    myProxy = (WebProxy)request.Proxy; 
    myProxy.Address = new Uri(this.ProxyAddress+":"+this.ProxyPort); 
    myProxy.Credentials = new NetworkCredential(this.proxyAccount, this.proxyPassword, this.ProxyDomain);
    request.Proxy = myProxy; 
   }
   try
   
   {
    //请求服务
    WebResponse response = request.GetResponse();
    //返回信息
    Stream resStream = response.GetResponseStream(); 
    StreamReader sr = new StreamReader(resStream, System.Text.Encoding.Default);
    string tempCode= sr.ReadToEnd();
    resStream.Close(); 
    sr.Close();

    //如果输出文件路径为空,便将得到的内容赋给OutString属性
    if(this.outFilePath==null)
    {
     this.outString=tempCode;
    }
    else
    {

     FileInfo fi = new FileInfo(this.outFilePath);
     //如果存在文件则先干掉
     if(fi.Exists)
      fi.Delete();
     StreamWriter sw = new StreamWriter(this.outFilePath,true,Encoding.Default);
     sw.Write(tempCode);
     sw.Flush();
     sw.Close();
    }
   }
   catch
   {
    this.noteMessage="出错了,请检查网络是否连通;";
     }

      }
   #endregion

   }
  }
posted @ 2007-07-03 14:52 和田雨 阅读(233) | 评论 (0)编辑 收藏

<%@ page contentType="image/jpeg" import="java.awt.*, 
java.awt.image.*,java.util.*,javax.imageio.*" %> 
<% 
// 在内存中创建图象 
int width=60, height=20; 
BufferedImage image = new BufferedImage(width, height, 
BufferedImage.TYPE_INT_RGB); 

// 获取图形上下文 
Graphics g = image.getGraphics(); 

// 设定背景色 
g.setColor(new Color(0xDCDCDC)); 
g.fillRect(0, 0, width, height); 

//画边框 
g.setColor(Color.black); 
g.drawRect(0,0,width-1,height-1); 

// 取随机产生的认证码(4位数字) 
String rand = request.getParameter("rand"); 
rand = rand.substring(0,rand.indexOf(".")); 
switch(rand.length()) 

case 1: rand = "000"+rand; break; 
case 2: rand = "00"+rand; break; 
case 3: rand = "0"+rand; break; 
default: rand = rand.substring(0,4); break; 


// 将认证码存入SESSION 
session.setAttribute("rand",rand); 

// 将认证码显示到图象中 
g.setColor(Color.black); 
Integer tempNumber = new Integer(rand); 
String numberStr = tempNumber.toString(); 

g.setFont(new Font("Atlantic Inline",Font.PLAIN,18)); 
String Str = numberStr.substring(0,1); 
g.drawString(Str,8,17); 

Str = numberStr.substring(1,2); 
g.drawString(Str,20,15); 
Str = numberStr.substring(2,3); 
g.drawString(Str,35,18); 

Str = numberStr.substring(3,4); 
g.drawString(Str,45,15); 

// 随机产生88个干扰点,使图象中的认证码不易被其它程序探测到 
Random random = new Random(); 
for (int i=0;i<20;i++) 

int x = random.nextInt(width); 
int y = random.nextInt(height); 
g.drawOval(x,y,0,0); 


// 图象生效 
g.dispose(); 

// 输出图象到页面 
ImageIO.write(image, "JPEG", response.getOutputStream()); 
%>
posted @ 2007-07-03 14:51 和田雨 阅读(198) | 评论 (0)编辑 收藏

JSP/Servlet 中的汉字编码问题

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项



级别: 初级

IBM,

2001 年 3 月 01 日

网上就 JSP/Servlet 中 DBCS字符编码问题有许多优秀的文章和讨论,本文对它们作一些整理,并结合IBM WebSphere Application Server3.5(WAS)的解决方法作一些说明,希望它不是多余的。

问题的起源

每个国家(或区域)都规定了计算机信息交换用的字符编码集,如美国的扩展 ASCII码, 中国的 GB2312-80,日本的 JIS 等,作为该国家/区域内信息处理的基础,有着统一编码的重要作用。字符编码集按长度分为 SBCS(单字节字符集),DBCS(双字节字符集)两大类。早期的软件(尤其是操作系统),为了解决本地字符信息的计算机处理,出现了各种本地化版本(L10N),为了区分,引进了 LANG, Codepage 等概念。但是由于各个本地字符集代码范围重叠,相互间信息交换困难;软件各个本地化版本独立维护成本较高。因此有必要将本地化工作中的共性抽取出来,作一致处理,将特别的本地化处理内容降低到最少。这也就是所谓的国际化(I18N)。各种语言信息被进一步规范为 Locale 信息。处理的底层字符集变成了几乎包含了所有字形的 Unicode。

现在大部分具有国际化特征的软件核心字符处理都是以 Unicode 为基础的,在软件运行时根据当时的 Locale/Lang/Codepage 设置确定相应的本地字符编码设置,并依此处理本地字符。在处理过程中需要实现 Unicode 和本地字符集的相互转换,甚或以 Unicode 为中间的两个不同本地字符集的相互转换。这种方式在网络环境下被进一步延伸,任何网络两端的字符信息也需要根据字符集的设置转换成可接受的内容。

Java 语言内部是用 Unicode 表示字符的,遵守 Unicode V2.0。Java 程序无论是从/往文件系统以字符流读/写文件,还是往 URL 连接写 HTML 信息,或从 URL 连接读取参数值,都会有字符编码的转换。这样做虽然增加了编程的复杂度,容易引起混淆,但却是符合国际化的思想的。

从理论上来说,这些根据字符集设置而进行的字符转换不应该产生太多问题。而事实是由于应用程序的实际运行环境不同,Unicode 和各个本地字符集的补充、完善,以及系统或应用程序实现的不规范,转码时出现的问题时时困扰着程序员和用户。





回页首


GB2312-80,GBK,GB18030-2000 汉字字符集及 Encoding

其实解决 JAVA 程序中的汉字编码问题的方法往往很简单,但理解其背后的原因,定位问题,还需要了解现有的汉字编码和编码转换。

GB2312-80 是在国内计算机汉字信息技术发展初始阶段制定的,其中包含了大部分常用的一、二级汉字,和 9 区的符号。该字符集是几乎所有的中文系统和国际化的软件都支持的中文字符集,这也是最基本的中文字符集。其编码范围是高位0xa1-0xfe,低位也是 0xa1-0xfe;汉字从 0xb0a1 开始,结束于 0xf7fe;

GBK 是 GB2312-80 的扩展,是向上兼容的。它包含了 20902 个汉字,其编码范围是 0x8140-0xfefe,剔除高位 0x80 的字位。其所有字符都可以一对一映射到 Unicode 2.0,也就是说 JAVA 实际上提供了 GBK 字符集的支持。这是现阶段 Windows 和其它一些中文操作系统的缺省字符集,但并不是所有的国际化软件都支持该字符集,感觉是他们并不完全知道 GBK 是怎么回事。值得注意的是它不是国家标准,而只是规范。随着 GB18030-2000国标的发布,它将在不久的将来完成它的历史使命。

GB18030-2000(GBK2K) 在 GBK 的基础上进一步扩展了汉字,增加了藏、蒙等少数民族的字形。GBK2K 从根本上解决了字位不够,字形不足的问题。它有几个特点,

  • 它并没有确定所有的字形,只是规定了编码范围,留待以后扩充。
  • 编码是变长的,其二字节部分与 GBK 兼容;四字节部分是扩充的字形、字位,其编码范围是首字节 0x81-0xfe、二字节0x30-0x39、三字节 0x81-0xfe、四字节0x30-0x39。
  • 它的推广是分阶段的,首先要求实现的是能够完全映射到 Unicode 3.0 标准的所有字形。
  • 它是国家标准,是强制性的。

现在还没有任何一个操作系统或软件实现了 GBK2K 的支持,这是现阶段和将来汉化的工作内容。

Unicode 的介绍......就免了吧。

JAVA 支持的encoding中与中文编程相关的有:(有几个在JDK文档中未列出)

ASCII 7-bit, 同 ascii7
ISO8859-1 8-bit, 同 8859_1,ISO-8859-1,ISO_8859-1,latin1...
GB2312-80 同gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB......
GBK (注意大小写),同MS936
UTF8 UTF-8
GB18030 (现在只有IBM JDK1.3.?有支持), 同Cp1392,1392

JAVA 语言采用Unicode处理字符. 但从另一个角度来说,在java程序中也可以采用非Unicode的转码,重要的是保证程序入口和出口的汉字信息不失真。如完全采用ISO-8859-1来处理汉字也能达到正确的结果。网络上流行的许多解决方法,都属于这种类型。为了不致引起混淆,本文不对这种方法作讨论。





回页首


中文转码时'?'、乱码的由来

两个方向转换都有可能得到错误的结果:

  • Unicode-->Byte, 如果目标代码集不存在对应的代码,则得到的结果是0x3f.

    如:
    "\u00d6\u00ec\u00e9\u0046\u00bb\u00f9".getBytes("GBK") 的结果是 "?ìéF?ù", Hex 值是3fa8aca8a6463fa8b4.

    仔细看一下上面的结果,你会发现\u00ec被转换为0xa8ac, \u00e9被转换为\xa8a6... 它的实际有效位变长了!这是因为GB2312符号区中的一些符号被映射到一些公共的符号编码,由于这些符号出现在ISO-8859-1或其它一些SBCS字符集中,故它们在Unicode中编码比较靠前,有一些其有效位只有8位,和汉字的编码重叠(其实这种映射只是编码的映射,在显示时仔细不是一样的。Unicode 中的符号是单字节宽,汉字中的符号是双字节宽) . 在Unicode\u00a0--\u00ff 之间这样的符号有20个。了解这个特征非常重要!由此就不难理解为什么JAVA编程中,汉字编码的错误结果中常常会出现一些乱码(其实是符号字符), 而不全是'?'字符, 就比如上面的例子。

  • Byte-->Unicode, 如果Byte标识的字符在源代码集不存在,则得到的结果是0xfffd.

    如:
    Byte ba[] = {(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1}; new String(ba,"gb2312");

    结果是"?啊", hex 值是"\ufffd\u554a". 0x8140 是GBK字符,按GB2312转换表没有对应的值,取\ufffd. (请注意:在显示该uniCode时,因为没有对应的本地字符,所以也适用上一种情况,显示为一个"?".)

实际编程中,JSP/Servlet 程序得到错误的汉字信息,往往是这两个过程的叠加,有时甚至是两个过程叠加后反复作用的结果.





回页首


JSP/Servlet 汉字编码问题及在 WAS 中的解决办法

4.1 常见的 encoding 问题的现象

网上常出现的 JSP/Servlet encoding 问题一般都表现在 browser 或应用程序端,如:

  • 浏览器中看到的 Jsp/Servlet 页面中的汉字怎么都成了 ’?’ ?
  • 浏览器中看到的 Servlet 页面中的汉字怎么都成了乱码?
  • JAVA 应用程序界面中的汉字怎么都成了方块?
  • Jsp/Servlet 页面无法显示 GBK 汉字。
  • JSP 页面中内嵌在<%...%>,<%=...%>等Tag包含的 JAVA code 中的中文成了乱码,但页面的其它汉字是对的。
  • Jsp/Servlet 不能接收 form 提交的汉字。
  • JSP/Servlet 数据库读写无法获得正确的内容。

隐藏在这些问题后面的是各种错误的字符转换和处理(除第3个外,是因为 Java font 设置错误引起的)。解决类似的字符 encoding 问题,需要了解 Jsp/Servlet 的运行过程,检查可能出现问题的各个点。

4.2 JSP/Servlet web 编程时的 encoding 问题

运行于Java 应用服务器的 JSP/Servlet 为 Browser 提供 HTML 内容,其过程如下图所示:




其中有字符编码转换的地方有:

  • JSP 编译。Java 应用服务器将根据 JVM 的 file.encoding 值读取 JSP 源文件,编译生成 JAVA 源文件,再根据 file.encoding 值写回文件系统。如果当前系统语言支持 GBK,那么这时候不会出现 encoding 问题。如果是英文的系统,如 LANG 是 en_US 的 Linux, AIX 或 Solaris,则要将 JVM 的 file.encoding 值置成 GBK 。系统语言如果是 GB2312,则根据需要,确定要不要设置 file.encoding,将 file.encoding 设为 GBK 可以解决潜在的 GBK 字符乱码问题

  • Java 需要被编译为 .class 才能在 JVM 中执行,这个过程存在与a.同样的 file.encoding 问题。从这里开始 servlet 和 jsp 的运行就类似了,只不过 Servlet 的编译不是自动进行的。对于JSP程序, 对产生的JAVA 中间文件的编译是自动进行的(在程序中直接调用sun.tools.javac.Main类). 因此如果在这一步出现问题的话, 也要检查encoding和OS的语言环境,或者将内嵌在JSP JAVA Code 中的静态汉字转为 Unicode, 要么静态文本输出不要放在 JAVA code 中。对于Servlet, javac 编译时手工指定-encoding 参数就可以了。

  • Servlet 需要将 HTML 页面内容转换为 browser 可接受的 encoding 内容发送出去。依赖于各 JAVA App Server 的实现方式,有的将查询 Browser 的 accept-charset 和 accept-language 参数或以其它猜的方式确定 encoding 值,有的则不管。因此采用固定encoding 也许是最好的解决方法。对于中文网页,可在 JSP 或 Servlet 中设置 contentType="text/html; charset=GB2312";如果页面中有GBK字符,则设置为contentType="text/html; charset=GBK",由于IE 和 Netscape对GBK的支持程度不一样,作这种设置时需要测试一下。因为16位 JAVA char在网络传送时高8位会被丢弃,也为了确保Servlet页面中的汉字(包括内嵌的和servlet运行过程中得到的)是期望的内码,可以用 PrintWriter out=res.getWriter() 取代 ServletOutputStream out=res.getOutputStream(). PrinterWriter 将根据contentType中指定的charset作转换 (ContentType需在此之前指定!); 也可以用OutputStreamWriter封装 ServletOutputStream 类并用write(String)输出汉字字符串。对于 JSP,JAVA Application Server 应当能够确保在这个阶段将嵌入的汉字正确传送出去。

  • 这是解释 URL 字符 encoding 问题。如果通过 get/post 方式从 browser 返回的参数值中包含汉字信息, servlet 将无法得到正确的值。SUN的 J2SDK 中,HttpUtils.parseName 在解析参数时根本没有考虑 browser 的语言设置,而是将得到的值按 byte 方式解析。这是网上讨论得最多的 encoding 问题。因为这是设计缺陷,只能以 bin 方式重新解析得到的字符串;或者以 hack HttpUtils 类的方式解决。参考文章 2 均有介绍,不过最好将其中的中文 encoding GB2312、 CP1381 都改为 GBK,否则遇到 GBK 汉字时,还是会有问题。 Servlet API 2.3 提供一个新的函数 HttpServeletRequest.setCharacterEncoding 用于在调用 request.getParameter(“param_name”) 前指定应用程序希望的 encoding,这将有助于彻底解决这个问题。

4.3 IBM Websphere Application Server 中的解决方法

WebSphere Application Server 对标准的 Servlet API 2.x 作了扩展,提供较好的多语言支持。运行在中文的操作系统中,可以不作任何设置就可以很好地处理汉字。下面的说明只是对WAS是运行在英文的系统中,或者需要有GBK支持时有效。

上述c,d情况,WAS 都要查询 Browser 的语言设置,在缺省状况下, zh, zh-cn 等均被映射为 JAVA encoding CP1381(注意: CP1381 只是等同于 GB2312 的一个 codepage,没有 GBK 支持)。这样做我想是因为无法确认 Browser 运行的操作系统是支持GB2312, 还是 GBK,所以取其小。但是实际的应用系统还是要求页面中出现 GBK 汉字,最著名的是朱总理名字中的“?F"(rong2 ,0xe946,\u9555),所以有时还是需要将 Encoding/Charset 指定为 GBK。当然 WAS 中变更缺省的 encoding 没有上面说的那么麻烦,针对 a,b,参考文章 5,在 Application Server 的命令行参数中指定 -Dfile.encoding=GBK 即可; 针对 d,在 Application Server 的命令行参数中指定-Ddefault.client.encoding=GBK。如果指定了-Ddefault.client.encoding=GBK,那么c情况下可以不再指定charset。

上面列出的问题中还有一个关于Tag<%...%>,<%=...%>中的 JAVA 代码里包含的静态文本未能正确显示的问题,在WAS中的解决方法是除了设置正确的file.encoding, 还需要以相同方法设置-Duser.language=zh -Duser.region=CN。这与JAVA locale的设置有关。

4.4 数据库读写时的 encoding 问题

JSP/Servlet 编程中经常出现 encoding 问题的另一个地方是读写数据库中的数据。

流行的关系数据库系统都支持数据库 encoding,也就是说在创建数据库时可以指定它自己的字符集设置,数据库的数据以指定的编码形式存储。当应用程序访问数据时,在入口和出口处都会有 encoding 转换。对于中文数据,数据库字符编码的设置应当保证数据的完整性. GB2312,GBK,UTF-8 等都是可选的数据库 encoding;也可以选择 ISO8859-1 (8-bit),那么应用程序在写数据之前须将 16Bit 的一个汉字或 Unicode 拆分成两个 8-bit 的字符,读数据之后则需将两个字节合并起来,同时还要判别其中的 SBCS 字符。没有充分利用数据库 encoding 的作用,反而增加了编程的复杂度,ISO8859-1不是推荐的数据库 encoding。JSP/Servlet编程时,可以先用数据库管理系统提供的管理功能检查其中的中文数据是否正确。

然后应当注意的是读出来的数据的 encoding,JAVA 程序中一般得到的是 Unicode。写数据时则相反。

4.5 定位问题时常用的技巧

定位中文encoding问题通常采用最笨的也是最有效的办法――在你认为有嫌疑的程序处理后打印字符串的内码。通过打印字符串的内码,你可以发现什么时候中文字符被转换成Unicode,什么时候Unicode被转回中文内码,什么时候一个中文字成了两个 Unicode 字符,什么时候中文字符串被转成了一串问号,什么时候中文字符串的高位被截掉了……

取用合适的样本字符串也有助于区分问题的类型。如:”aa啊aa?@aa” 等中英相间、GB、GBK特征字符均有的字符串。一般来说,英文字符无论怎么转换或处理,都不会失真(如果遇到了,可以尝试着增加连续的英文字母长度)。





回页首


结束语

其实 JSP/Servlet 的中文encoding 并没有想像的那么复杂,虽然定位和解决问题没有定规,各种运行环境也各不尽然,但后面的原理是一样的。了解字符集的知识是解决字符问题的基础。不过,随着中文字符集的变化,不仅仅是 java 编程,中文信息处理中的问题还是会存在一段时间的。



参考资料



关于作者

 

IBM has authored this article

posted @ 2007-07-03 14:49 和田雨 阅读(253) | 评论 (0)编辑 收藏

仅列出标题
共9页: 上一页 1 2 3 4 5 6 7 8 9 下一页