﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-勇于挑战-随笔分类-JAVA</title><link>http://www.blogjava.net/gp213/category/39567.html</link><description>不畏艰辛</description><language>zh-cn</language><lastBuildDate>Tue, 23 Jun 2009 15:53:34 GMT</lastBuildDate><pubDate>Tue, 23 Jun 2009 15:53:34 GMT</pubDate><ttl>60</ttl><item><title>WebWork 2.1 / 2.2 与 Spring 集成方法总结</title><link>http://www.blogjava.net/gp213/archive/2009/06/23/283721.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Tue, 23 Jun 2009 04:37:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/06/23/283721.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/283721.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/06/23/283721.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/283721.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/283721.html</trackback:ping><description><![CDATA[<h4>第一部分：WebWork2.1与<a target="_self"><span style="text-decoration: underline"><strong>Spring</strong></span></a>集成</h4>
<p>两种方法：</p>
<h5>法一：先<a target="_self"><span style="text-decoration: underline"><strong><span class="hilite1">WebWork</span></strong></span></a>后Spring（利用webwork2-spring.jar集成）</h5>
<p>该方法的执行顺序是先<span class="hilite1">WebWork</span>，后Spring。<br />
即，首先在xwork.xml中找到待执行的action，如果该action依赖其他bean，再到applicationContext.xml中查找被依赖的bean。</p>
<p>WebWork2的IoC功能实现是在xwork.xml中，指定action类与其他bean的依赖关系。即：<br />
(1)xwork.xml文件<br />
&lt;action name="myAction" class="com.ryandaigle.web.actions.MyAction"&gt;<br />
&lt;external-ref name="DAO"&gt;myDAO&lt;/external-ref&gt;<br />
&lt;result name="success" type="dispatcher"&gt;<br />
&lt;param name="location"&gt;/success.jsp&lt;/param&gt;<br />
&lt;/result&gt;<br />
&lt;/action&gt;</p>
<p>(2)applicationContext.xml文件<br />
&lt;bean id="myDAO" class="com.ryandaigle.persistence.MyDAO" singleton="true" /&gt;</p>
<p>具体集成步骤如下：</p>
<p>1. <a target="_self"><span style="text-decoration: underline"><strong>下载</strong></span></a>以下文件：<br />
<span class="nobr"><a title="Visit page outside Confluence" href="http://www.ryandaigle.com/pebble/images/webwork2-spring.jar" rel="nofollow">http://www.ryandaigle.com/pebble/images/webwork2-spring.jar<sup><img class="rendericon" title="点击图片可在新窗口打开" style="cursor: pointer" height="7" alt="" src="http://hi.baidu.com/fc/editor/" width="7" align="absMiddle" border="0" /></sup></a></span></p>
<p>2. 配置xwork.xml</p>
<p>2.1 在xwork.xml中定义action时，采用external-ref来指定依赖的spring bean</p>
<p>&lt;action name="myAction" class="com.foo.Action"&gt;<br />
&lt;external-ref name="someDao"&gt;someDao&lt;/external-ref&gt;</p>
<p>&lt;result name="success" type="dispatcher"&gt;<br />
&lt;param name="location"&gt;/success.jsp&lt;/param&gt;<br />
&lt;/result&gt;<br />
&lt;/action&gt;</p>
<p>2.2 在&lt;package&gt;中指定外部引用解析器</p>
<p>&lt;package name="default" extends="<span class="hilite1">webwork</span>-default"<br />
externalReferenceResolver="com.atlassian.xwork.ext.SpringServletContextReferenceResolver"/&gt;</p>
<p>SpringServletContextReferenceResolver作用是在applicationContext.xml中解析外部引用的bean</p>
<p>2.3 增加一个拦截器，允许引用作为外部资源进行解析<br />
&lt;interceptors&gt;<br />
&lt;interceptor name="reference-resolver" class="com.opensymphony.xwork.interceptor.ExternalReferencesInterceptor"/&gt;<br />
&lt;interceptor-stack name="myDefaultWebStack"&gt;<br />
&lt;interceptor-ref name="defaultStack"/&gt;<br />
&lt;interceptor-ref name="reference-resolver"/&gt;<br />
&lt;/interceptor-stack&gt;<br />
&lt;/interceptors&gt;<br />
&lt;default-interceptor-ref name="myDefaultWebStack"/&gt;</p>
<p>3. 在web.xml中配置Spring与XWork的外部解析器在同一个Web Context中工作<br />
&lt;!--载入spring配置文件--&gt;<br />
&nbsp;&lt;context-param&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;param-value&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; classpath:/applicationContext.xml<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/param-value&gt;<br />
&nbsp;&nbsp;&nbsp; &lt;/context-param&gt;<br />
<br />
&lt;listener&gt;<br />
&lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;<br />
&lt;/listener&gt;</p>
<p>&lt;listener&gt;<br />
&lt;listener-class&gt;com.atlassian.xwork.ext.ResolverSetupServletContextListener&lt;/listener-class&gt;<br />
&lt;/listener&gt;</p>
<p>第一个侦听器是spring必需的，无论是否与<span class="hilite1">WebWork</span>集成，都要<a target="_self"><span style="text-decoration: underline"><strong>定义</strong></span></a>该侦听器；第二个侦听器是外部解析器所需要的，作用是通过ServletContext来取出Spring的ApplicationContext，作为<span class="hilite1">WebWork</span>与Spring的&#8220;桥梁&#8221;。</p>
<p>4.applicationContext.xml文件跟xwork.xml文件的存放路径一下，都在classpath下<br />
在applicationContext.xml中定义被引用的bean<br />
&lt;bean id="myDAO" class="com.ryandaigle.persistence.MyDAO" singleton="true" /&gt;</p>
<p>说明：<br />
如果不使用WebWork2，完全使用Spring，达到上述<a target="_self"><span style="text-decoration: underline"><strong>效果</strong></span></a>，在applicationContext.xml中采取类似定义：<br />
&lt;bean id="myAction" class="com.ryandaigle.web.actions.MyAction" singleton="false"&gt;<br />
&lt;property name="DAO"&gt;<br />
&lt;ref bean="myDAO"/&gt;<br />
&lt;/property&gt;<br />
&lt;bean id="myDAO" class="com.ryandaigle.persistence.MyDAO" singleton="true" /&gt;</p>
<p>&nbsp;</p>
<h5>法二：先Spring后<span class="hilite1">WebWork</span>（利用spring-xwork-integration.jar集成）</h5>
<p>该方法的执行顺序是先Spring，后<span class="hilite1">WebWork</span>。<br />
即，首先由Spring负责wire所有的依赖关系，再由XWork来执行action。</p>
<p>注意：因为XWork为每个action调用生成一个新的类实例，action在Spring的applicationContext.xml中应该<a target="_self"><span style="text-decoration: underline"><strong>配置</strong></span></a>为prototype。</p>
<p>1. 配置SpringObjectFactory</p>
<p>(0)下载<a href="https://xwork-optional.dev.java.net/files/documents/1475/11992/spring-xwork-integration.jar">https://xwork-optional.dev.java.net/files/documents/1475/11992/spring-xwork-integration.jar</a></p>
<p>(1) 可以在web.xml配置</p>
<p>&lt;listener&gt;<br />
&lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;<br />
&lt;/listener&gt;</p>
<p>&lt;listener&gt;<br />
&lt;listener-class&gt;com.opensymphony.xwork.spring.SpringObjectFactoryListener&lt;/listener-class&gt;<br />
&lt;/listener&gt;</p>
<p>(2) 或者：在applicationContext.xml配置<br />
&lt;bean id="springObjectFactory"<br />
class="com.opensymphony.xwork.spring.SpringObjectFactory"<br />
init-method="initObjectFactory"/&gt;</p>
<p>以上两种方法任选一种即可</p>
<p>2. 在applicationContext.xml中定义bean<br />
&lt;bean name="some-action" class="fully.qualified.class.name" singleton="false"&gt;<br />
&lt;property name="someProperty"&gt;&lt;ref bean="someOtherBean"/&gt;&lt;/property&gt;<br />
&lt;/bean&gt;</p>
<p>3. 在xwork.xml中定义action（注意action的class与bean的id相同）</p>
<p>&lt;action name="myAction" class="some-action"&gt;<br />
&lt;result name="success"&gt;view.jsp&lt;/result&gt;<br />
&lt;/action&gt;</p>
<p>说明：<br />
该方法与<span class="hilite1">WebWork</span> 2.2中的方法基本相同。</p>
<p>&nbsp;</p>
<h4>第二部分：WebWork2.2与Spring的集成</h4>
<p>Spring是WebWork2.2中唯一支持的IoC容器。</p>
<p>1 配置<span class="hilite1">webwork</span>.properties文件，指定spring作为<span class="hilite1">webwork</span>的IoC容器<br />
<span class="hilite1">webwork</span>.objectFactory = spring<br />
(1)默认的autowiring模式是：by name<br />
即如果applicationContext.xml文件中的bean id与xwork.xml文件中的action name相同，就<br />
(2)如果要改为其他模式：<br />
<span class="hilite1">webwork</span>.objectFactory.spring.autoWire = name|type|auto|constructor</p>
<p>2 配置web.xml文件，启动Spring侦听器<br />
&lt;listener&gt;<br />
&lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;<br />
&lt;/listener&gt;</p>
<p>3 在WEB-INF目录下增加applicationContext.xml文件<br />
例：<br />
&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br />
&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"&gt;<br />
&lt;beans default-autowire="autodetect"&gt;<br />
&lt;bean id="personManager" class="com.acme.PersonManager"/&gt;<br />
...<br />
&lt;/beans&gt;</p>
<p>4 设置由Spring来初始化action<br />
4.1&nbsp;&nbsp;&nbsp;&nbsp; 在Spring的applicationContext.xml文件中配置bean（即action类）<br />
4.2&nbsp;&nbsp;&nbsp;&nbsp; 将xwork.xml中的action的class属性，由class名改为Spring中定义的bean名<br />
例如：<br />
(1)applicationContext.xml中，定义bean id是bar<br />
&lt;beans default-autowire="autodetect"&gt;<br />
&lt;bean id="bar" class="com.my.BarClass" singleton="false"/&gt;<br />
...<br />
&lt;/beans&gt;<br />
(2)xwork.xml中，action的class="bar"，而不是通常的类名<br />
&lt;package name="secure" namespace="/secure" extends="default"&gt;<br />
&lt;action name="bar" class="bar"&gt;<br />
&lt;result&gt;bar.ftl&lt;/result&gt;<br />
&lt;/action&gt;<br />
&lt;/package&gt;</p>
<img src ="http://www.blogjava.net/gp213/aggbug/283721.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-06-23 12:37 <a href="http://www.blogjava.net/gp213/archive/2009/06/23/283721.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WebWork深入浅出 </title><link>http://www.blogjava.net/gp213/archive/2009/06/23/283697.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Tue, 23 Jun 2009 02:11:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/06/23/283697.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 前言 本篇文章并没有太多WebWork 的实战代码细节。本人非常希望能充当一名导游的角色，带领读者逐步游览WebWork的功能特性和原理。在第一章，我们将提出基于三层架构的Web层需要解决的10个问题，这是本文的纵轴。围绕着纵轴，我们按照横轴的顺序逐步描述讲解：WebWork简介、WebWork入门、WebWork原理、WebWork实战和技巧、展望WebWork未来、最后是本文的总结。基...&nbsp;&nbsp;<a href='http://www.blogjava.net/gp213/archive/2009/06/23/283697.html'>阅读全文</a><img src ="http://www.blogjava.net/gp213/aggbug/283697.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-06-23 10:11 <a href="http://www.blogjava.net/gp213/archive/2009/06/23/283697.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在 Java 应用程序中计划重复执行的任务</title><link>http://www.blogjava.net/gp213/archive/2009/05/19/271550.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Tue, 19 May 2009 07:58:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/19/271550.html</guid><description><![CDATA[<blockquote>所有类型的 Java 应用程序一般都需要计划重复执行的任务。企业应用程序需要计划每日的日志或者晚间批处理过程。一个 J2SE 或者 J2ME 日历应用程序需要根据用户的约定计划闹铃时间。不过，标准的调度类 <code>Timer</code> 和 <code>TimerTask</code> 没有足够的灵活性，无法支持通常需要的计划任务类型。在本文中，Java 开发人员 Tom White 向您展示了如何构建一个简单通用的计划框架，以用于执行任意复杂的计划任务。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p>我将把 <code>java.util.Timer</code> 和 <code>java.util.TimerTask</code> 统称为 <em>Java 计时器框架</em>，它们使程序员可以很容易地计划简单的任务（注意这些类也可用于 J2ME 中）。在 Java 2 SDK, Standard Edition, Version 1.3 中引入这个框架之前，开发人员必须编写自己的调度程序，这需要花费很大精力来处理线程和复杂的 <code>Object.wait()</code> 方法。不过，Java 计时器框架没有足够的能力来满足许多应用程序的计划要求。甚至一项需要在每天同一时间重复执行的任务，也不能直接使用 <code>Timer</code> 来计划，因为在夏令时开始和结束时会出现时间跳跃。 </p>
<p>本文展示了一个通用的 <code>Timer</code> 和 <code>TimerTask</code> 计划框架，从而允许更灵活的计划任务。这个框架非常简单 ―― 它包括两个类和一个接口 ―― 并且容易掌握。如果您习惯于使用 Java 定时器框架，那么您应该可以很快地掌握这个计划框架（有关 Java 定时器框架的更多信息，请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>）。</p>
<p><a name="1"><span class="atitle">计划单次任务</span></a></p>
<p>计划框架建立在 Java 定时器框架类的基础之上。因此，在解释如何使用计划框架以及如何实现它之前，我们将首先看看如何用这些类进行计划。</p>
<p>想像一个煮蛋计时器，在数分钟之后（这时蛋煮好了）它会发出声音提醒您。清单 1 中的代码构成了一个简单的煮蛋计时器的基本结构，它用 Java 语言编写：</p>
<br />
<a name="IDAFLSVB"><strong>清单 1. EggTimer 类</strong></a><br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="36" sizcache="1">
    <tbody sizset="36" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">package org.tiling.scheduling.examples;
            import java.util.Timer;
            import java.util.TimerTask;
            public class EggTimer {
            private final Timer timer = new Timer();
            private final int minutes;
            public EggTimer(int minutes) {
            this.minutes = minutes;
            }
            public void start() {
            timer.schedule(new TimerTask() {
            public void run() {
            playSound();
            timer.cancel();
            }
            private void playSound() {
            System.out.println("Your egg is ready!");
            // Start a new thread to play a sound...
            }
            }, minutes * 60 * 1000);
            }
            public static void main(String[] args) {
            EggTimer eggTimer = new EggTimer(2);
            eggTimer.start();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p><code>EggTimer</code> 实例拥有一个 <code>Timer</code> 实例，用于提供必要的计划。用 <code>start()</code> 方法启动煮蛋计时器后，它就计划了一个 <code>TimerTask</code>，在指定的分钟数之后执行。时间到了，<code>Timer</code> 就在后台调用 <code>TimerTask</code> 的 <code>start()</code> 方法，这会使它发出声音。在取消计时器后这个应用程序就会中止。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="37" sizcache="1">
    <tbody sizset="37" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="38" sizcache="1">
    <tbody sizset="39" sizcache="1">
        <tr align="right" sizset="39" sizcache="1">
            <td sizset="39" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="39" sizcache="1">
                <tbody sizset="39" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/j-schedule/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="2"><span class="atitle">计划重复执行的任务</span></a></p>
<p>通过指定一个固定的执行频率或者固定的执行时间间隔，<code>Timer</code> 可以对重复执行的任务进行计划。不过，有许多应用程序要求更复杂的计划。例如，每天清晨在同一时间发出叫醒铃声的闹钟不能简单地使用固定的计划频率 86400000 毫秒（24 小时），因为在钟拨快或者拨慢（如果您的时区使用夏令时）的那些天里，叫醒可能过晚或者过早。解决方案是使用日历算法计算每日事件下一次计划发生的时间。而这正是计划框架所支持的。考虑清单 2 中的 <code>AlarmClock</code> 实现（有关计划框架的源代码以及包含这个框架和例子的 JAR 文件，请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>）：</p>
<br />
<a name="IDABNSVB"><strong>清单 2. AlarmClock 类</strong></a><br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="40" sizcache="1">
    <tbody sizset="40" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">package org.tiling.scheduling.examples;
            import java.text.SimpleDateFormat;
            import java.util.Date;
            import org.tiling.scheduling.Scheduler;
            import org.tiling.scheduling.SchedulerTask;
            import org.tiling.scheduling.examples.iterators.DailyIterator;
            public class AlarmClock {
            private final Scheduler scheduler = new Scheduler();
            private final SimpleDateFormat dateFormat =
            new SimpleDateFormat("dd MMM yyyy HH:mm:ss.SSS");
            private final int hourOfDay, minute, second;
            public AlarmClock(int hourOfDay, int minute, int second) {
            this.hourOfDay = hourOfDay;
            this.minute = minute;
            this.second = second;
            }
            public void start() {
            scheduler.schedule(new SchedulerTask() {
            public void run() {
            soundAlarm();
            }
            private void soundAlarm() {
            System.out.println("Wake up! " +
            "It's " + dateFormat.format(new Date()));
            // Start a new thread to sound an alarm...
            }
            }, new DailyIterator(hourOfDay, minute, second));
            }
            public static void main(String[] args) {
            AlarmClock alarmClock = new AlarmClock(7, 0, 0);
            alarmClock.start();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>注意这段代码与煮蛋计时器应用程序非常相似。<code>AlarmClock</code> 实例拥有一个 <code>Scheduler</code> （而不是 <code>Timer</code>）实例，用于提供必要的计划。启动后，这个闹钟对<code> SchedulerTask</code> （而不是 <code>TimerTask</code>）进行调度用以发出报警声。这个闹钟不是计划一个任务在固定的延迟时间后执行，而是用 <code>DailyIterator</code> 类描述其计划。在这里，它只是计划任务在每天上午 7:00 执行。下面是一个正常运行情况下的输出：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="41" sizcache="1">
    <tbody sizset="41" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">Wake up! It's 24 Aug 2003 07:00:00.023
            Wake up! It's 25 Aug 2003 07:00:00.001
            Wake up! It's 26 Aug 2003 07:00:00.058
            Wake up! It's 27 Aug 2003 07:00:00.015
            Wake up! It's 28 Aug 2003 07:00:00.002
            ...
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p><code>DailyIterator</code> 实现了<code> ScheduleIterator</code>，这是一个将<code> SchedulerTask</code> 的计划执行时间指定为一系列 <code>java.util.Date</code> 对象的接口。然后 <code>next()</code> 方法按时间先后顺序迭代 <code>Date</code> 对象。返回值 <code>null</code> 会使任务取消（即它再也不会运行）―― 这样的话，试图再次计划将会抛出一个异常。清单 3 包含 <code>ScheduleIterator</code> 接口：</p>
<br />
<a name="listing3"><strong>清单 3. ScheduleIterator 接口</strong></a><br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="42" sizcache="1">
    <tbody sizset="42" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">package org.tiling.scheduling;
            import java.util.Date;
            public interface ScheduleIterator {
            public Date next();
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p><code>DailyIterator </code>的 <code>next()</code> 方法返回表示每天同一时间（上午 7:00）的 <code>Date</code> 对象，如清单 4 所示。所以，如果对新构建的 <code>next()</code> 类调用 <code>next()</code>，那么将会得到传递给构造函数的那个日期当天或者后面一天的 7:00 AM。再次调用 <code>next()</code> 会返回后一天的 7:00 AM，如此重复。为了实现这种行为，<code>DailyIterator</code> 使用了 <code>java.util.Calendar</code> 实例。构造函数会在日历中加上一天，对日历的这种设置使得第一次调用 <code>next()</code> 会返回正确的 <code>Date</code>。注意代码没有明确地提到夏令时修正，因为 <code>Calendar</code> 实现（在本例中是 <code>GregorianCalendar</code>）负责对此进行处理，所以不需要这样做。</p>
<br />
<a name="IDAHRSVB"><strong>清单 4. DailyIterator 类</strong></a><br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="43" sizcache="1">
    <tbody sizset="43" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">package org.tiling.scheduling.examples.iterators;
            import org.tiling.scheduling.ScheduleIterator;
            import java.util.Calendar;
            import java.util.Date;
            /**
            * A DailyIterator class returns a sequence of dates on subsequent days
            * representing the same time each day.
            */
            public class DailyIterator implements ScheduleIterator {
            private final int hourOfDay, minute, second;
            private final Calendar calendar = Calendar.getInstance();
            public DailyIterator(int hourOfDay, int minute, int second) {
            this(hourOfDay, minute, second, new Date());
            }
            public DailyIterator(int hourOfDay, int minute, int second, Date date) {
            this.hourOfDay = hourOfDay;
            this.minute = minute;
            this.second = second;
            calendar.setTime(date);
            calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
            calendar.set(Calendar.MINUTE, minute);
            calendar.set(Calendar.SECOND, second);
            calendar.set(Calendar.MILLISECOND, 0);
            if (!calendar.getTime().before(date)) {
            calendar.add(Calendar.DATE, -1);
            }
            }
            public Date next() {
            calendar.add(Calendar.DATE, 1);
            return calendar.getTime();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="44" sizcache="1">
    <tbody sizset="44" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="45" sizcache="1">
    <tbody sizset="46" sizcache="1">
        <tr align="right" sizset="46" sizcache="1">
            <td sizset="46" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="46" sizcache="1">
                <tbody sizset="46" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/j-schedule/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="3"><span class="atitle">实现计划框架</span></a></p>
<p>在上一节，我们学习了如何使用计划框架，并将它与 Java 定时器框架进行了比较。下面，我将向您展示如何实现这个框架。除了 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#listing3" cmimpressionsent="1">清单 3</a> 中展示的 <code>ScheduleIterator</code> 接口，构成这个框架的还有另外两个类 ―― <code>Scheduler</code> 和 <code>SchedulerTask</code> 。这些类实际上在内部使用 <code>Timer</code> 和 <code>SchedulerTask</code>，因为计划其实就是一系列的单次定时器。清单 5 和 6 显示了这两个类的源代码：</p>
<br />
<a name="IDASSSVB"><strong>清单 5. Scheduler</strong></a><br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="47" sizcache="1">
    <tbody sizset="47" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">package org.tiling.scheduling;
            import java.util.Date;
            import java.util.Timer;
            import java.util.TimerTask;
            public class Scheduler {
            class SchedulerTimerTask extends TimerTask {
            private SchedulerTask schedulerTask;
            private ScheduleIterator iterator;
            public SchedulerTimerTask(SchedulerTask schedulerTask,
            ScheduleIterator iterator) {
            this.schedulerTask = schedulerTask;
            this.iterator = iterator;
            }
            public void run() {
            schedulerTask.run();
            reschedule(schedulerTask, iterator);
            }
            }
            private final Timer timer = new Timer();
            public Scheduler() {
            }
            public void cancel() {
            timer.cancel();
            }
            public void schedule(SchedulerTask schedulerTask,
            ScheduleIterator iterator) {
            Date time = iterator.next();
            if (time == null) {
            schedulerTask.cancel();
            } else {
            synchronized(schedulerTask.lock) {
            if (schedulerTask.state != SchedulerTask.VIRGIN) {
            throw new IllegalStateException("Task already
            scheduled " + "or cancelled");
            }
            schedulerTask.state = SchedulerTask.SCHEDULED;
            schedulerTask.timerTask =
            new SchedulerTimerTask(schedulerTask, iterator);
            timer.schedule(schedulerTask.timerTask, time);
            }
            }
            }
            private void reschedule(SchedulerTask schedulerTask,
            ScheduleIterator iterator) {
            Date time = iterator.next();
            if (time == null) {
            schedulerTask.cancel();
            } else {
            synchronized(schedulerTask.lock) {
            if (schedulerTask.state != SchedulerTask.CANCELLED) {
            schedulerTask.timerTask =
            new SchedulerTimerTask(schedulerTask, iterator);
            timer.schedule(schedulerTask.timerTask, time);
            }
            }
            }
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>清单 6 显示了 <code>SchedulerTask</code> 类的源代码：</p>
<br />
<a name="IDACTSVB"><strong>清单 6. SchedulerTask</strong></a><br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="48" sizcache="1">
    <tbody sizset="48" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">package org.tiling.scheduling;
            import java.util.TimerTask;
            public abstract class SchedulerTask implements Runnable {
            final Object lock = new Object();
            int state = VIRGIN;
            static final int VIRGIN = 0;
            static final int SCHEDULED = 1;
            static final int CANCELLED = 2;
            TimerTask timerTask;
            protected SchedulerTask() {
            }
            public abstract void run();
            public boolean cancel() {
            synchronized(lock) {
            if (timerTask != null) {
            timerTask.cancel();
            }
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
            }
            }
            public long scheduledExecutionTime() {
            synchronized(lock) {
            return timerTask == null ? 0 : timerTask.scheduledExecutionTime();
            }
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>就像煮蛋计时器，<code>Scheduler</code> 的每一个实例都拥有 <code>Timer</code> 的一个实例，用于提供底层计划。<code>Scheduler</code> 并没有像实现煮蛋计时器时那样使用一个单次定时器，它将一组单次定时器串接在一起，以便在由 <code>ScheduleIterator</code> 指定的各个时间执行 <code>SchedulerTask</code> 类。</p>
<p>考虑 <code>Scheduler</code> 上的 public <code>schedule()</code> 方法 ―― 这是计划的入口点，因为它是客户调用的方法（在 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#3.1" cmimpressionsent="1">取消任务</a> 一节中将描述仅有的另一个 public 方法 <code>cancel()</code>）。通过调用 <code>ScheduleIterator</code> 接口的 <code>next()</code>，发现第一次执行 <code>SchedulerTask</code> 的时间。然后通过调用底层 <code>Timer</code> 类的单次 <code>schedule()</code> 方法，启动计划在这一时刻执行。为单次执行提供的 <code>TimerTask</code> 对象是嵌入的 <code>SchedulerTimerTask</code> 类的一个实例，它包装了任务和迭代器（iterator）。在指定的时间，调用嵌入类的 <code>run()</code> 方法，它使用包装的任务和迭代器引用以便重新计划任务的下一次执行。<code>reschedule()</code> 方法与 <code>schedule()</code> 方法非常相似，只不过它是 private 的，并且执行一组稍有不同的 <code>SchedulerTask</code> 状态检查。重新计划过程反复重复，为每次计划执行构造一个新的嵌入类实例，直到任务或者调度程序被取消（或者 JVM 关闭）。</p>
<p>类似于 <code>TimerTask</code>，<code>SchedulerTask</code> 在其生命周期中要经历一系列的状态。创建后，它处于 <code>VIRGIN</code> 状态，这表明它从没有计划过。计划以后，它就变为 <code>SCHEDULED</code> 状态，再用下面描述的方法之一取消任务后，它就变为 <code>CANCELLED</code> 状态。管理正确的状态转变 ―― 如保证不对一个非 <code>VIRGIN</code> 状态的任务进行两次计划 ―― 增加了 <code>Scheduler</code> 和 <code>SchedulerTask</code> 类的复杂性。在进行可能改变任务状态的操作时，代码必须同步任务的锁对象。</p>
<p><a name="N10202"><span class="smalltitle">取消任务</span></a></p>
<p>取消计划任务有三种方式。第一种是调用 <code>SchedulerTask</code> 的 <code>cancel()</code> 方法。这很像调用 <code>TimerTask</code> 的 <code>cancel()</code>方法：任务再也不会运行了，不过已经运行的任务<em>仍会</em>运行完成。 <code>cancel()</code> 方法的返回值是一个布尔值，表示如果没有调用 <code>cancel()</code> 的话，计划的任务是否还会运行。更准确地说，如果任务在调用 <code>cancel()</code> 之前是 <code>SCHEDULED</code> 状态，那么它就返回 <code>true</code>。如果试图再次计划一个取消的（甚至是已计划的）任务，那么 <code>Scheduler</code> 就会抛出一个 <code>IllegalStateException</code>。</p>
<p>取消计划任务的第二种方式是让 <code>ScheduleIterator</code> 返回 <code>null</code>。这只是第一种方式的简化操作，因为 <code>Scheduler</code> 类调用 <code>SchedulerTask</code> 类的 <code>cancel()</code>方法。如果您想用迭代器而不是任务来控制计划停止时间时，就用得上这种取消任务的方式了。</p>
<p>第三种方式是通过调用其 <code>cancel()</code> 方法取消整个 <code>Scheduler</code>。这会取消调试程序的所有任务，并使它不能再计划任何任务。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="49" sizcache="1">
    <tbody sizset="49" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="50" sizcache="1">
    <tbody sizset="51" sizcache="1">
        <tr align="right" sizset="51" sizcache="1">
            <td sizset="51" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="51" sizcache="1">
                <tbody sizset="51" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/j-schedule/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="4"><span class="atitle">扩展 cron 实用程序</span></a></p>
<p>可以将计划框架比作 UNIX 的 <code>cron</code> 实用程序，只不过计划次数的规定是强制性而不是声明性的。例如，在 <code>AlarmClock</code> 实现中使用的 <code>DailyIterator</code> 类，它的计划与 <code>cron</code> 作业的计划相同，都是由以 <code>0 7 * * *</code> 开始的 <code>crontab</code> 项指定的（这些字段分别指定分钟、小时、日、月和星期）。</p>
<p>不过，计划框架比 <code>cron</code> 更灵活。想像一个在早晨打开热水的 <code>HeatingController</code> 应用程序。我想指示它&#8220;在每个工作日上午 8:00 打开热水，在周未上午 9:00 打开热水&#8221;。使用 <code>cron</code>，我需要两个 <code>crontab</code> 项（<code>0 8 * * 1,2,3,4,5</code> 和<code> 0 9 * * 6,7</code>）。而使用 <code>ScheduleIterator</code> 的解决方案更简洁一些，因为我可以使用复合（composition）来定义单一迭代器。清单 7 显示了其中的一种方法：</p>
<br />
<a name="IDA51SVB"><strong>清单 7. 用复合定义单一迭代器</strong></a><br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="52" sizcache="1">
    <tbody sizset="52" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">    int[] weekdays = new int[] {
            Calendar.MONDAY,
            Calendar.TUESDAY,
            Calendar.WEDNESDAY,
            Calendar.THURSDAY,
            Calendar.FRIDAY
            };
            int[] weekend = new int[] {
            Calendar.SATURDAY,
            Calendar.SUNDAY
            };
            ScheduleIterator i = new CompositeIterator(
            new ScheduleIterator[] {
            new RestrictedDailyIterator(8, 0, 0, weekdays),
            new RestrictedDailyIterator(9, 0, 0, weekend)
            }
            );
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p><code>RestrictedDailyIterator</code> 类很像 <code>DailyIterator</code>，只不过它限制为只在一周的特定日子里运行，而一个 <code>CompositeIterator</code> 类取得一组 <code>ScheduleIterator</code>s，并将日期正确排列到单个计划中。这些类的源代码请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>。</p>
<p>有许多计划是 <code>cron</code> 无法生成的，但是 <code>ScheduleIterator</code> 实现却可以。例如，&#8220;每个月的最后一天&#8221;描述的计划可以用标准 Java 日历算法来实现（用 <code>Calendar</code> 类），而用 <code>cron</code> 则无法表达它。应用程序甚至无需使用 <code>Calendar</code> 类。在本文的源代码（请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>）中，我加入了一个安全灯控制器的例子，它按&#8220;在日落之前 15 分钟开灯&#8221;这一计划运行。这个实现使用了 Calendrical Calculations Software Package （请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>），用于计算当地（给定经度和纬度）的日落时间。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="53" sizcache="1">
    <tbody sizset="53" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="54" sizcache="1">
    <tbody sizset="55" sizcache="1">
        <tr align="right" sizset="55" sizcache="1">
            <td sizset="55" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="55" sizcache="1">
                <tbody sizset="55" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/j-schedule/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="5"><span class="atitle">实时保证</span></a></p>
<p>在编写使用计划的应用程序时，一定要了解框架在时间方面有什么保证。我的任务是提前还是延迟执行？如果有提前或者延迟，偏差最大值是多少？不幸的是，对这些问题没有简单的答案。不过在实际中，它的行为对于很多应用程序已经足够了。下面的讨论假设系统时钟是正确的（有关网络时间协议(Network Time Protocol)的信息，请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>）。</p>
<p>因为 <code>Scheduler</code> 将计划委托给 <code>Timer</code> 类，<code>Scheduler</code> 可以做出的实时保证与 <code>Timer</code> 的一样。<code>Timer</code> 用 <code>Object.wait(long)</code> 方法计划任务。当前线程要等待直到唤醒它，唤醒可能出于以下原因之一：</p>
<ol>
    <li>另一个线程调用对象的 <code>notify()</code> 或者 <code>notifyAll()</code> 方法。
    <li>线程被另一个线程中断。
    <li>在没有通知的情况下，线程被唤醒（称为 <em>spurious wakeup</em>，Joshua Bloch 的 <em>Effective Java Programming Language Guide</em> 一书中 Item 50 对其进行了描述 ―― 请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>）。
    <li>规定的时间已到。 </li>
</ol>
<p>对于 <code>Timer</code> 类来说，第一种可能性是不会发生的，因为对其调用 <code>wait()</code> 的对象是私有的。即便如此，<code>Timer</code> 实现仍然针对前三种提前唤醒的原因进行了保护，这样保证了线程在规定时间后才唤醒。目前，<code>Object.wait(long)</code> 的文档注释声明，它会在规定的时间&#8220;前后&#8221;苏醒，所以线程有可能提前唤醒。在本例中，<code>Timer</code> 会让另一个 <code>wait()</code> 执行（<code>scheduledExecutionTime - System.currentTimeMillis()</code>）毫秒，从而保证<em>任务永远不会提前执行</em>。</p>
<p>任务是否会延迟执行呢？会的。延迟执行有两个主要原因：线程计划和垃圾收集。</p>
<p>Java 语言规范故意没有对线程计划做严格的规定。这是因为 Java 平台是通用的，并针对于大范围的硬件及其相关的操作系统。虽然大多数 JVM 实现都有公平的线程调度程序，但是这一点没有任何保证 —— 当然，各个实现都有不同的为线程分配处理器时间的策略。因此，当 <code>Timer</code> 线程在分配的时间后唤醒时，它实际执行其任务的时间取决于 JVM 的线程计划策略，以及有多少其他线程竞争处理器时间。因此，要减缓任务的延迟执行，应该将应用程序中可运行的线程数降至最少。为了做到这一点，可以考虑在一个单独的 JVM 中运行调度程序。</p>
<p>对于创建大量对象的大型应用程序，JVM 花在垃圾收集（GC）上的时间会非常多。默认情况下，进行 GC 时，整个应用程序都必须等待它完成，这可能要有几秒钟甚至更长的时间（<code>Java</code> 应用程序启动器的命令行选项 <code>-verbose:gc</code> 将导致向控制台报告每一次 GC 事件）。要将这些由 GC 引起的暂停（这可能会影响快速任务的执行）降至最少，应该将应用程序创建的对象的数目降至最低。同样，在单独的 JVM 中运行计划代码是有帮助的。同时，可以试用几个微调选项以尽可能地减少 GC 暂停。例如，增量 GC 会尽量将主收集的代价分散到几个小的收集上。当然这会降低 GC 的效率，但是这可能是时间计划的一个可接受的代价（有关 GC 微调的更多提示，请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>）。</p>
<p><a name="N10360"><span class="smalltitle">我被计划到什么时候？</span></a></p>
<p>如果任务本身能监视并记录所有延迟执行的实例，那么对于确定任务是否能按时运行会很有帮助。<code>SchedulerTask</code> 类似于<code> TimerTask</code>，有一个 <code>scheduledExecutionTime()</code> 方法，它返回计划任务最近一次执行的时间。在任务的 <code>run()</code> 方法开始时，对表达式 <code>System.currentTimeMillis() - scheduledExecutionTime()</code> 进行判断，可以让您确定任务延迟了多久执行（以毫秒为单位）。可以记录这个值，以便生成一个关于延迟执行的分布统计。可以用这个值决定任务应当采取什么动作 ―― 例如，如果任务太迟了，那么它可能什么也不做。在遵循上述原则的情况下，如果应用程序需要更严格的时间保证，可参考 Java 的实时规范（更多信息请参阅 <a href="http://www.ibm.com/developerworks/cn/java/j-schedule/#resources" cmimpressionsent="1">参考资料</a>）。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="56" sizcache="1">
    <tbody sizset="56" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="57" sizcache="1">
    <tbody sizset="58" sizcache="1">
        <tr align="right" sizset="58" sizcache="1">
            <td sizset="58" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="58" sizcache="1">
                <tbody sizset="58" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/j-schedule/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="6"><span class="atitle">结束语</span></a></p>
<p>在本文中，我介绍了 Java 定时器框架的一个简单增强，它使得灵活的计划策略成为可能。新的框架实质上是更通用的 <code>cron</code> ―― 事实上，将 <code>cron</code> 实现为一个 <code>ScheduleIterator</code> 接口，用以替换单纯的 Java <code>cron</code>，这是非常有用的。虽然没有提供严格的实时保证，但是许多需要计划定期任务的通用 Java 应用程序都可以使用这一框架。</p>
 <img src ="http://www.blogjava.net/gp213/aggbug/271550.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-19 15:58 <a href="http://www.blogjava.net/gp213/archive/2009/05/19/271550.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>修正Java中wait方法超时语意模糊性的一种方案</title><link>http://www.blogjava.net/gp213/archive/2009/05/19/271534.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Tue, 19 May 2009 06:17:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/19/271534.html</guid><description><![CDATA[<blockquote>Java语言中内建了对于多线程的支持，可以非常方便的创建、控制线程以及在线程之间进行同步操作。另外，为了支持更为高级的线程间同步机制，比如：类似于POSIX中的条件变量，Java在Object类中提供了wait、notify和notifyAll方法，使得所有的类都隐式的继承了这些方法。特别地，为了提供对于程序健壮性方面的考虑，在Java中提供了对于wait方法超时语意的支持。但是Java在对于wait方法超时语意的支持方面存在模糊性，即在调用具有超时语意的wait方法返回时，无法区分是由于notify的通知还是由于超时触发的。因此应用开发者在构建需要具有超时语意的应用时，就必须负责对这种语意模糊性进行区分，本文将对这一问题进行剖析，并给出一个比较通用的解决方案。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name="1"><span class="atitle">问题提出</span></a></p>
<p>
<p>在作者所从事的项目中，要开发一个应用服务器，实现如下所述的功能：能够高效的处理来自多个客户端的并发请求。为了简化同步控制，分离并发逻辑和业务逻辑，我们采用了Active Object模式，具体的实现可以参见作者的另外一篇文章：《构建Java并发模型框架》（ <a href="http://www.ibm.com/developerworks/cn/java/l-multithreading/index.html" cmimpressionsent="1">http://www.ibm.com/developerworks/cn/java/l-multithreading/index.shtml</a>）。该设计中有一个核心部件ActiveQueue用于存放客户的请求。为了能够做到应用服务器的负载控制，我们对于ActiveQueue的大小进行了限制，如果当前的客户请求数量已经达到这个限制，就让后继的请求等待，具体的代码实现片断如下（为了简洁起见，省略了其他无关的代码）： </p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="37" sizcache="1">
    <tbody sizset="37" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">		class ActiveQueue {
            ...
            public synchronized void enqueue(ClientRequest cr) throws InterruptedException
            {
            while(isFull( ) ){  // ActiveQueue的大小达到上限
            wait();
            }
            // 把用户请求添加到处理队列中
            notifyAll();
            }
            ...
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>该方法刚开始工作的很好，但是随着项目开发的进展，在随后的测试中我们发现了两个较为严重的问题：1、当并发请求的客户端很多时，会造成某些客户端等待的时间过长，对于客户端的使用者来说非常不友好；2、由于系统中应用服务器的其他方面的异常同样会造成客户端请求的永久等待比如：应用服务器在处理完客户端请求后，由于异常没有正确的调用相应notify方法。所以为了改善程序的用户友好性以及健壮性，我们决定采用带有超时语意的wait方法。该方法的原型声明如下：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="38" sizcache="1">
    <tbody sizset="38" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">	public final void wait (long millisecTimeout)  throws InterruptedException;
            使用该方法改进后的代码实现片断如下：
            class ActiveQueue {
            ...
            public synchronized void enqueue(ClientRequest cr, long timeout)
            throws InterruptedException
            {
            while(isFull( ) ){  // ActiveQueue的大小达到上限
            wait(timeout);
            // 语意模糊性体现于此，当wait返回时
            // 我们无法区分是由于notify的通知还是超时触发的
            // 因此我们无法做出适当的处理
            }
            // 把用户请求添加到处理队列中
            notifyAll();
            }
            ...
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>可以看出，简单的使用一个具有超时语意的wait方法是不可行的，原因就在于wait方法超时语意的模糊性。在下面的小节会先给出一个初步的解决方案，随后我们将使用模式对于该方案进行重构从而构造出一个比较通用的方案。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="39" sizcache="1">
    <tbody sizset="39" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="40" sizcache="1">
    <tbody sizset="41" sizcache="1">
        <tr align="right" sizset="41" sizcache="1">
            <td sizset="41" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="41" sizcache="1">
                <tbody sizset="41" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/l-threadwait/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="2"><span class="atitle">初步解决方案</span></a></p>
<p>
<p>我们的初步解决方案采用了Doug Lea（Doug Lea为对象并发领域世界级的专家）给出的一个显式的判断算法（关于该算法更加详细、深入的论述请参考参考文献〔1〕），通过该算法来辨别是否已经超时。采用该算法的解决方案的代码实现片断如下：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="42" sizcache="1">
    <tbody sizset="42" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">	class ActiveQueue {
            ...
            public synchronized void enqueue(ClientRequest cr, long timeout)
            throws InterruptedException, TimeoutException
            {
            if (isFull ()) {  // 判断队列是否为满
            long start = System.currentTimeMillis ();
            long waitTime = timeout;
            for (;;) {         // 一直等待到队列不满被notify通知或者超时
            wait (waitTime);
            if (isFull ()) { //重新判断队列是否为满
            long now = System.currentTimeMillis ();
            long timeSoFar = now - start;    // 队列仍然为满，计算已经等待的时间
            if (timeSoFar &gt;= msecTimeout)  // 如果超时，抛出TimeoutException异常
            throw new TimeoutException ();
            else  // 没有超时，计算还要等待的时间
            waitTime = timeout - timeSoFar;
            }
            else  // 被notify唤醒，并且队列不为满
            break;
            }
            }
            // 把用户请求添加到处理队列中
            notifyAll();
            }
            ...
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>可以看出，这个算法非常的简单，核心思路就是在每次wait返回时，计算wait等待的时间，并比较该时间和设定的要等待的时间，如果大于设定的要等待的时间，即确定为超时，否则确定为被notify唤醒。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="43" sizcache="1">
    <tbody sizset="43" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="44" sizcache="1">
    <tbody sizset="45" sizcache="1">
        <tr align="right" sizset="45" sizcache="1">
            <td sizset="45" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="45" sizcache="1">
                <tbody sizset="45" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/l-threadwait/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="3"><span class="atitle">使解决方案一般化</span></a></p>
<p>
<p>上述的解决方案针对我们目前的要求已经可以很好的工作了，但是细心的读者一定会发现在上面给出的解决方案中我们把两个无关的概念揉合在了一起：队列是否为满的判断逻辑和是否超时的计算判断逻辑。为什么说这是两个无关的概念呢？因为队列是否为满是与我们开发的具体应用相关的，不同的应用会有不同类型的判断逻辑（比如：不使用队列的应用可能会有其他在概念上类似的判断逻辑），而计算判断超时的逻辑是和具体应用无关的。如果我们能够把这两个概念剥离开来，那么这二者就可以独立变化，我们的解决方案的可重用性就会增强。</p>
<p>我们使用Template Method模式（参见参考文献〔2〕）来指导我们的重构。首先，我们会根据和具体应用无关的超时计算判断的算法定义一个通用的算法框架，把和具体应用逻辑相关的条件判断作为一个抽象的hook方法，延迟到具体的应用中去实现，从而实现了和应用无关的超时计算判断逻辑的复用。实现该算法的抽象基类的关键实现代码如下：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="46" sizcache="1">
    <tbody sizset="46" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">		public abstract class WaitWithTiming
            {
            // wait方法要作用的对象，对于上述例子就是ActiveQueue
            protected Object object_;
            public WaitWithTiming (Object obj)
            {
            object_ = obj;
            }
            // 这是一个抽象的hook方法，由具体的应用实现，该方法由本算法框架调用
            public abstract boolean condition ();
            // 计算判断超时的算法框架实现
            public final void timedWait (long timeout)
            throws InterruptedException, TimeoutException
            {
            if (condition ()) {  //调用具体应用实现的hook方法
            long start = System.currentTimeMillis ();
            long waitTime = msecTimeout;
            for (;;) {
            object_.wait (waitTime);
            if (condition ()) {
            long now = System.currentTimeMillis ();
            long timeSoFar = now - start;
            if (timeSoFar &gt;= msecTimeout)
            throw new TimeoutException ();
            else
            waitTime = timeout - timeSoFar;
            }
            else
            break;
            }
            }
            }
            public final void announce() {
            object_.notifyAll ();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="47" sizcache="1">
    <tbody sizset="47" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="48" sizcache="1">
    <tbody sizset="49" sizcache="1">
        <tr align="right" sizset="49" sizcache="1">
            <td sizset="49" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="49" sizcache="1">
                <tbody sizset="49" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/l-threadwait/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="4"><span class="atitle">使用方法介绍</span></a></p>
<p>
<p>本小节我们将对上一节给出的抽象基类WaitWithTiming的使用方法进行详细的介绍。我们当然可以直接使得ActiveQueue继承自WaitWithTiming，并实现相应的抽象hook方法condition，但是这样做有一个弊端，就是对于ActiveQueue我们只能够实现仅仅一个condition，如果我们要添加针对dequeue时队列为空的条件判断逻辑就无能为力了，因为WaitWithWaiting仅仅只有一个condition方法（其实，即使有多个也没有办法做到通用，因为不能对具体的应用的需求进行假设）。</p>
<p>我们推荐的使用方法是，根据具体应用的需求，整理出需要的判断条件，创建相应的类来表示这些判断条件，使这些用来表示具体判断条件的类继承自WaitWithTiming，这些类中具体的条件判断逻辑的实现可以使用相应的具体的应用实体。比如：对于本文开始所列举的应用，我们需要的判断条件为队列为满，所以我们可以定义一个QueueFullCondition类继承自WaitWithTiming，在QueueFullCondition中实现抽象的hook方法condition的逻辑，在该逻辑中在使用ActiveQueue的isFull方法。使用这种委托的方法，我们就可以比较有效的解决一个对象同时需要多个判断条件的问题（不同的判断条件只需定义不同的子类即可）。相应的UML结构图和关键代码实现如下：</p>
<p>UML结构图：</p>
<br />
<img height="262" alt="" src="http://www.ibm.com/developerworks/cn/java/l-threadwait/1.gif" width="444" /> <br />
<p>关键代码片断：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="50" sizcache="1">
    <tbody sizset="50" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">class QueueFullCondition extends WaitWithTiming
            {
            public QueueFullCondition (ActiveQueue aq)
            { super (aq); }  // 为WaitWithTiming中的object_赋值
            public boolean condition () {
            ActiveQueue aq = (ActiveQueue) object_; //使用ActiveQueue来实现具体的判断逻辑
            return aq.isFull ();
            }
            }
            class ActiveQueue {
            ...
            public synchronized void enqueue(ClientRequest cr, long timeout)
            throws InterruptedException, TimeoutException
            {
            //具有时限控制的等待
            queueFullCondition_.timedWait (timeout);
            // 把用户请求添加进队列
            //唤醒等待在ActiveQueue上的线程
            queueFullCondition_.announce ();
            }
            ...
            private QueueFullCondition queueFullCondition_  =  new QueueFullCondition (this);
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="51" sizcache="1">
    <tbody sizset="51" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="52" sizcache="1">
    <tbody sizset="53" sizcache="1">
        <tr align="right" sizset="53" sizcache="1">
            <td sizset="53" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="53" sizcache="1">
                <tbody sizset="53" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/l-threadwait/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="5"><span class="atitle">要注意的问题</span></a></p>
<p>
<p>如果读者朋友仔细观察的话，就会觉察到在WaitWithTiming类中的timedWait方法的定义中没有添加synchronized关键字，这一点是非常关键的，因为是为了避免在编写并发的Java应用时一个常见的死锁问题：嵌套的monitor。下面对于这个问题进行简单的介绍，关于这一问题更为详细的论述请参见参考文献〔1〕。</p>
<p>什么是嵌套的monitor问题呢？嵌套的monitor是指：当一个线程获得了对象A的monitor锁，接着又获得了对象B的monitor锁，在还没有释放对象B的monitor锁时，调用了对象B的wait方法，此时，该线程释放对象B的monitor锁并等待在对象B的线程等待队列上，但是此时该线程还拥有对象A的monitor锁。如果该线程的唤起条件依赖于另一个线程首先要获得对象A的monitor锁的话，就会引起死锁，因为此时别的线程无法获得上述线程还没有释放的对象A的monitor锁，结果就出现了死锁情况。一般的解决方案是：在设计时线程不要获取对象B的monitor锁，而仅仅使用对象A的monitor锁。</p>
<p>针对我们前面列举的例子，ActiveQueu可以类比为对象A，queueFullContion_可以类比为对象B，如果我们在timedWait方法前面添加上synchronized关键字，就有可能会发生上述的死锁情况，因为当我们在调用ActiveQueu的enqueue方法中调用了queueFullContion_的timedWait方法后，如果队列为满，虽然我们释放了queueFullContion_的monitor锁，但是我们还持有ActiveQueue的monitor锁，并且我们的唤醒条件依赖于另外一个线程调用ActivcQueue的dequeue方法，但是因为此时我们没有释放ActiveQueue的monitor锁，所以另外的线程就无法调用ActiveQueu的dequeue方法，那么结果就是这两个线程就都只能够等待。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="54" sizcache="1">
    <tbody sizset="54" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="55" sizcache="1">
    <tbody sizset="56" sizcache="1">
        <tr align="right" sizset="56" sizcache="1">
            <td sizset="56" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="56" sizcache="1">
                <tbody sizset="56" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/l-threadwait/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="6"><span class="atitle">总结</span></a></p>
<p>
<p>本文对于Java中wait方法超时语意的模糊性进行了分析，并给出了一个比较通用的解决方案，本解决方案对于需要精确的超时语意的应用还是无法很好的适用的，因为方案中所给出的关于超时计算的算法是不精确的。还有一点就是有关嵌套monitor的问题，在编写多线程的Java程序时一定要特别注意，否则非常容易引起死锁。其实，本文所讲述的所有问题的根源都是由于Java对于wait方法超时语意实现的模糊性造成的，如果在后续的Java版本中对此进行了修正，那么本文给出的解决方案就是多余的了。</p>
  <img src ="http://www.blogjava.net/gp213/aggbug/271534.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-19 14:17 <a href="http://www.blogjava.net/gp213/archive/2009/05/19/271534.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java单例对象同步问题探讨</title><link>http://www.blogjava.net/gp213/archive/2009/05/19/271510.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Tue, 19 May 2009 04:44:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/19/271510.html</guid><description><![CDATA[<blockquote>在本文中，作者向大家讲述了Single Call 模式的原理，同时也介绍了Single Call 模式的实现问题。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p>单例对象（Singleton）是一种常用的设计模式。在Java应用中，单例对象能保证在一个JVM中，该对象只有一个实例存在。正是由于这个特点，单例对象通常作为程序中的存放配置信息的载体，因为它能保证其他对象读到一致的信息。例如在某个服务器程序中，该服务器的配置信息可能存放在数据库或文件中，这些配置数据由某个单例对象统一读取，服务进程中的其他对象如果要获取这些配置信息，只需访问该单例对象即可。这种方式极大地简化了在复杂环境下，尤其是多线程环境下的配置管理，但是随着应用场景的不同，也可能带来一些同步问题。</p>
<p>本文将探讨一下在多线程环境下，使用单例对象作配置信息管理时可能会带来的几个同步问题，并针对每个问题给出可选的解决办法。</p>
<p><a name="1"><span class="atitle">问题描述</span></a></p>
<p>
<p>在多线程环境下，单例对象的同步问题主要体现在两个方面，单例对象的初始化和单例对象的属性更新。</p>
<p>本文描述的方法有如下假设：</p>
<ol>
    <li>单例对象的属性（或成员变量）的获取，是通过单例对象的初始化实现的。也就是说，在单例对象初始化时，会从文件或数据库中读取最新的配置信息。
    <li>其他对象不能直接改变单例对象的属性，单例对象属性的变化来源于配置文件或配置数据库数据的变化。 </li>
</ol>
<p><a name="N10051"><span class="smalltitle">1.1 单例对象的初始化</span></a></p>
<p>
<p>首先，讨论一下单例对象的初始化同步。单例模式的通常处理方式是，在对象中有一个静态成员变量，其类型就是单例类型本身；如果该变量为null，则创建该单例类型的对象，并将该变量指向这个对象；如果该变量不为null，则直接使用该变量。</p>
<p>其过程如下面代码所示：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="31" sizcache="1">
    <tbody sizset="31" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">  public class GlobalConfig {
            private static GlobalConfig instance = null;
            private Vector properties = null;
            private GlobalConfig() {
            //Load configuration information from DB or file
            //Set values for properties
            }
            public static GlobalConfig getInstance() {
            if (instance == null) {
            instance = new GlobalConfig();
            }
            return instance;
            }
            public Vector getProperties() {
            return properties;
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>这种处理方式在单线程的模式下可以很好的运行；但是在多线程模式下，可能产生问题。如果第一个线程发现成员变量为null，准备创建对象；这是第二个线程同时也发现成员变量为null，也会创建新对象。这就会造成在一个JVM中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化，会造成多个单例类型实例的不一致，产生一些很奇怪的现象。例如，某服务进程通过检查单例对象的某个属性来停止多个线程服务，如果存在多个单例对象的实例，就会造成部分线程服务停止，部分线程服务不能停止的情况。</p>
<p><a name="N10066"><span class="smalltitle">1.2 单例对象的属性更新</span></a></p>
<p>
<p>通常，为了实现配置信息的实时更新，会有一个线程不停检测配置文件或配置数据库的内容，一旦发现变化，就更新到单例对象的属性中。在更新这些信息的时候，很可能还会有其他线程正在读取这些信息，造成意想不到的后果。还是以通过单例对象属性停止线程服务为例，如果更新属性时读写不同步，可能访问该属性时这个属性正好为空（null），程序就会抛出异常。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="32" sizcache="1">
    <tbody sizset="32" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="33" sizcache="1">
    <tbody sizset="34" sizcache="1">
        <tr align="right" sizset="34" sizcache="1">
            <td sizset="34" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="34" sizcache="1">
                <tbody sizset="34" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/l-singleton/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="2"><span class="atitle">解决方法</span></a></p>
<p>
<p><a name="N10079"><span class="smalltitle">2.1 单例对象的初始化同步</span></a></p>
<p>
<p>对于初始化的同步，可以通过如下代码所采用的方式解决。</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="35" sizcache="1">
    <tbody sizset="35" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">  public class GlobalConfig {
            private static GlobalConfig instance = null;
            private Vector properties = null;
            private GlobalConfig() {
            //Load configuration information from DB or file
            //Set values for properties
            }
            private static synchronized void syncInit() {
            if (instance == null) {
            instance = new GlobalConfig();
            }
            }
            public static GlobalConfig getInstance() {
            if (instance == null) {
            syncInit();
            }
            return instance;
            }
            public Vector getProperties() {
            return properties;
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>这种处理方式虽然引入了同步代码，但是因为这段同步代码只会在最开始的时候执行一次或多次，所以对整个系统的性能不会有影响。</p>
<p><a name="N1008B"><span class="smalltitle">2.2 单例对象的属性更新同步</span></a></p>
<p>
<p>为了解决第2个问题，有两种方法：</p>
<p>1，参照读者/写者的处理方式</p>
<p>设置一个读计数器，每次读取配置信息前，将计数器加1，读完后将计数器减1。只有在读计数器为0时，才能更新数据，同时要阻塞所有读属性的调用。代码如下。</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="36" sizcache="1">
    <tbody sizset="36" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">  public class GlobalConfig {
            private static GlobalConfig instance;
            private Vector properties = null;
            private boolean isUpdating = false;
            private int readCount = 0;
            private GlobalConfig() {
            //Load configuration information from DB or file
            //Set values for properties
            }
            private static synchronized void syncInit() {
            if (instance == null) {
            instance = new GlobalConfig();
            }
            }
            public static GlobalConfig getInstance() {
            if (instance==null) {
            syncInit();
            }
            return instance;
            }
            public synchronized void update(String p_data) {
            syncUpdateIn();
            //Update properties
            }
            private synchronized void syncUpdateIn() {
            while (readCount &gt; 0) {
            try {
            wait();
            } catch (Exception e) {
            }
            }
            }
            private synchronized void syncReadIn() {
            readCount++;
            }
            private synchronized void syncReadOut() {
            readCount--;
            notifyAll();
            }
            public Vector getProperties() {
            syncReadIn();
            //Process data
            syncReadOut();
            return properties;
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>2，采用"影子实例"的办法</p>
<p>具体说，就是在更新属性时，直接生成另一个单例对象实例，这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息；然后将这些配置信息直接赋值给旧单例对象的属性。如下面代码所示。</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="37" sizcache="1">
    <tbody sizset="37" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">  public class GlobalConfig {
            private static GlobalConfig instance = null;
            private Vector properties = null;
            private GlobalConfig() {
            //Load configuration information from DB or file
            //Set values for properties
            }
            private static synchronized void syncInit() {
            if (instance = null) {
            instance = new GlobalConfig();
            }
            }
            public static GlobalConfig getInstance() {
            if (instance = null) {
            syncInit();
            }
            return instance;
            }
            public Vector getProperties() {
            return properties;
            }
            public void updateProperties() {
            //Load updated configuration information by new a GlobalConfig object
            GlobalConfig shadow = new GlobalConfig();
            properties = shadow.getProperties();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>注意：在更新方法中，通过生成新的GlobalConfig的实例，从文件或数据库中得到最新配置信息，并存放到properties属性中。</p>
<p>上面两个方法比较起来，第二个方法更好，首先，编程更简单；其次，没有那么多的同步操作，对性能的影响也不大。</p>
  <img src ="http://www.blogjava.net/gp213/aggbug/271510.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-19 12:44 <a href="http://www.blogjava.net/gp213/archive/2009/05/19/271510.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Web服务的客户端缓冲技术的设计与实现</title><link>http://www.blogjava.net/gp213/archive/2009/05/18/271380.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Mon, 18 May 2009 10:36:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/18/271380.html</guid><description><![CDATA[<blockquote>本文通过设计和实现一个网上商品查询系统，来展示如何利用缓冲管理、业务代理等设计模式加速Web服务的调用效率。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name="0"><span class="atitle">概要</span></a></p>
<p>时至今日，SOAP (Simple Object Access Protocol)已由最初的实验性协议，走进了现实应用中。但是，当在分布式Java应用程序中引入SOAP作为底层通讯协议时，它所带来的繁重的网络开销成为令人头痛的问题。本文将通过设计和实现一个网上商品查询系统，来展示如何利用缓冲管理、业务代理等设计模式加速Web服务的调用效率。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="42" sizcache="1">
    <tbody sizset="42" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="43" sizcache="1">
    <tbody sizset="44" sizcache="1">
        <tr align="right" sizset="44" sizcache="1">
            <td sizset="44" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="44" sizcache="1">
                <tbody sizset="44" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="1"><span class="atitle">引子</span></a></p>
<p>众所周知，Web服务的革命早已开始。SOAP(简单对象访问协议)，一种XML RPC消息协议成为了Web服务的底层通信协议。从它的名字就可以知道，它是一种易于定义、易于使用的轻量级协议。SOAP使发送和接收XML，并把它转化为异构语言对象变得容易。</p>
<p>但是，在拥有易用性和灵活性的同时也是要付出效率上的代价的。协议的设计者把效率方面的复杂性留给了应用程序开发者。一个简单的SOAP实现往往会给企业级应用程序带来无状态、运行效率低下等大量的问题。大数据量，频繁的SOAP调用甚至可能会引起网络阻塞。</p>
<p>解决Web服务效率问题的方法，一般分为两个层次： <br />
<ol>
    <li>容器层。Java Web服务的服务端一般都是一个Servlet应用程序，我们所说的容器层就是指运行Servlet的Web Server。例如，Apache Axis的服务端就可以运行在Tomcat这个Web Server上。
    <li>应用程序层。一般是指用户自己编写的代码。这里可以是客户端的代码，也可以是服务器端提供Web服务的代码。解决的方法，一般包括缓冲数据、压缩信息量等等。 </li>
</ol>
<p>&nbsp;</p>
<p>下文将会介绍一种基于应用程序层的解决方案。通过缓冲模式，代理模式构造一个对商业逻辑透明的，提高Web服务效率的解决方案。本解决方案同时还支持自动同步缓冲区信息和UI自动更新。</p>
<p>在这里假设读者对SOAP已有一定的认识，并且懂得如何使用Apache Axis创建、发布Web服务。本文将不会描述SOAP的工作原理，但是会提供相关链接，在那里可以得到更多的信息。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="45" sizcache="1">
    <tbody sizset="45" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="46" sizcache="1">
    <tbody sizset="47" sizcache="1">
        <tr align="right" sizset="47" sizcache="1">
            <td sizset="47" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="47" sizcache="1">
                <tbody sizset="47" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="2"><span class="atitle">Web Service在应用程序中的问题</span></a></p>
<p>当开发基于SOAP通信协议的胖客户端Java应用程序时，必须注意三个问题：性能、性能、还是性能。如果客户端程序会经常的访问服务端的相同信息，然而实现方法采取每次访问都要进行Web服务的调用，这样效率肯定很低。但现实是成百上千的客户端应用程序会为了这相同数据同时访问服务器，并且它们的操作仅仅限于浏览，服务器性能下降的会更加明显，甚至瘫痪。初步估计，一次SOAP调用的代价与执行一次关系型数据库的SQL操作相当甚至更大(如果考虑到网络的因素)。如下图：</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image001.gif" /> <br />
<p>但是，事实上，通常一个SOAP调用总是伴随着一次SQL操作。所以，一个现实中的SOAP调用的代价包括：网络的延时、Server端CPU操作SOAP的时间和数据库服务器的SQL操作延时。</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image002.gif" width="600" /> <br />
<p>由上图可见，一次Web服务调用如果不考虑网络的因素，就已经需要将近两倍于数据库SQL操作的时间。这样的效率是肯定无法接受的。因此，有一种解决方法就是尽量减少进行Web服务调用的次数了。正统的解决方案，就是采取缓冲机制。其中，缓冲机制还可以分为3种：在Server端缓冲，在Client端缓冲和不缓冲（有些实际应用中，数据量很小。维护缓冲区比不维护的代价更大）。下文中的例子程序将会根据缓冲管理模式，设计一个简单的应用程序。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="48" sizcache="1">
    <tbody sizset="48" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="49" sizcache="1">
    <tbody sizset="50" sizcache="1">
        <tr align="right" sizset="50" sizcache="1">
            <td sizset="50" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="50" sizcache="1">
                <tbody sizset="50" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="3"><span class="atitle">具体问题举例 —— 网上商品价格查询系统</span></a></p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="51" sizcache="1">
    <tbody sizset="51" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="52" sizcache="1">
    <tbody sizset="53" sizcache="1">
        <tr align="right" sizset="53" sizcache="1">
            <td sizset="53" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="53" sizcache="1">
                <tbody sizset="53" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="4"><span class="atitle">简介</span></a></p>
<p>这是一个常见的程序，在很多的WebService开发包中，都有类似的例子。如Apache Axis、IBM的ETTK等。本例子，仅仅包括一个提供商品报价的服务类，它只提供了一些很简单的操作，如增加和删除商品信息，获取商品信息等等方法。通过WSDL描述这些操作，并且暴露给用户，使其可以方便的开发自己的客户端程序。本程序有一个特点，支持自动同步数据，准确的说应该是自动同步缓冲区数据。事实上，缓冲技术和自动同步技术并没有任何关系，可以分开使用。最后，本程序还提供了一个简单的GUI客户端程序，可以执行部分Web服务方法。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="54" sizcache="1">
    <tbody sizset="54" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="55" sizcache="1">
    <tbody sizset="56" sizcache="1">
        <tr align="right" sizset="56" sizcache="1">
            <td sizset="56" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="56" sizcache="1">
                <tbody sizset="56" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="5"><span class="atitle">整体架构</span></a></p>
<p><strong>客户端架构</strong> </p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image003.gif" /> <br />
<p>通过代理模式，很好的分离了GUI层和业务层逻辑。又利用监听器模式实现了透明的缓冲机制。该实例程序客户端包括两个主面板（panel）。上方面版（panel）显示所有可用的商品信息，而下方面板让用户可以添加和更新商品的信息。当客户端开始运行，它会利用线程阻塞机制在服务端注册一个数据变更监听器，监听所有的数据更新事件。然后，调用Get All Quotes 服务获取所有的商品信息。当任意一个客户机提交和更新了数据，该客户端的监视线程就会返回，于是一次SOAP调用结束。这同时使到服务器端的数据改变事件被触发，从而主动激活所有被阻塞的客户端监视线程，并且触发它们的缓冲区数据更新的SOAP调用，然后每个客户端又会在服务器端注册新的数据改变监视线程，如此反复。这样，只有更新数据的时候，才会发送SOAP调用，一般的读数据实际上是读缓冲区内的数据，而不会进行代价颇高的Web服务调用，从而提高了速度，又实现了数据自动同步功能。</p>
<p><strong>服务器端架构</strong> </p>
<p>在服务端设计中，充分利用接口（Interface）带来的便利。全部用接口来描述功能，彻底的隐藏了具体实现细节。下面介绍，Web服务端设计的小技巧：</p>
<p>根据接口（Interface）来产生你的WSDL文件</p>
<p>之所以要这样做，主要有两个原因： <br />
<ol>
    <li>将接口发布为Web服务可以避免将一些不需要的方法发布为Web服务。例如main(String [ ] args)
    <li>对接口编程可以保证灵活性。你可以提供不同的服务实现类，并且无缝的切换。 </li>
</ol>
<p>&nbsp;</p>
<p>Apache Axis为此提供了方便的工具（Java2WSDL、 WSDL2Java），可以通过这些工具迅速的产生整个服务端架构。下面是整个Server端的类关系图。</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image004.gif" /> <br />
<p>下面列出了整个演示程序所有的包以及每个包的重要组成类。</p>
<p><strong>demo.productinfo.clientside</strong> <br />
<ul>
    <li>ClientNotificationHandler：
    <p>客户端数据同步处理器。用于处理本地缓冲类实例。其包含一个内部线程类，用于实现客户端的线程阻塞。</p>
    </li>
</ul>
<p>&nbsp;</p>
<p><strong>demo.productinfo.clientside.businessdelegates</strong> <br />
<ul>
    <li>DataChangeListener： 定义数据改变监听器的行为。
    <li>NotificationServiceDelegate： 调用Web服务的代理类。
    <li>ProductInfoServiceDelegate： 用于调用Web服务的代理类。 </li>
</ul>
<p>&nbsp;</p>
<p><strong>demo.productinfo.clientside.cache</strong> <br />
<ul>
    <li>CacheExpiredListener：定义了缓冲过期监听器的行为。
    <li>SimpleCache： 简单缓冲模式的实现类。 </li>
</ul>
<p>&nbsp;</p>
<p><strong>demo.productinfo.clientside.soapstub</strong> <br />
这个包里面的所有类均由Apache Axis的内置工具Java2WSDL和WSDL2Java产生，这里就不对之进行说明了。 </p>
<p><strong>demo.productinfo.exception</strong> <br />
<ul>
    <li>ProductInfoException： 自定义的异常类。通常在Java程序开发过程中，不要把系统底层的异常抛给用户，而应该以更加有意义的异常类取而代之，这就是为什么要有这个类的原因。 </li>
</ul>
<p>&nbsp;</p>
<p><strong>demo.productinfo.serverside</strong> <br />
在这个包中，通过接口类定义了服务器端所有的行为。其下面的子包都是这些接口的具体实现。
<ul>
    <li>INotificationService： 定义了通告服务的服务方法。
    <li>IProduct： 定义了商品类的基本行为。
    <li>IProductInfoService： 定义了商品信息服务的方法。
    <li>NotificationServiceSoapBindingSkeleton： 该类是由Apache Axis工具产生的服务端的框架类。
    <li>ProductInfoServiceSoapBindingSkeleton： 该类是由Apache Axis工具产生的服务端的框架类。 </li>
</ul>
<p>&nbsp;</p>
<p><strong>demo.productinfo.serverside.product</strong> <br />
在这个包内全部是具体的商品类。
<ul>
    <li>ProductTemplate: 该类是实现了IProduct接口的模板类。它包含了一些最基本的参数和方法。提供给其他的具体商品类继承之用。还有许多的商品类，这里就不一一罗列了。 </li>
</ul>
<p>&nbsp;</p>
<p><strong>demo.productinfo.serverside.serviceimplement</strong> <br />
在这个包内是所有Web服务的具体实现类。
<ul>
    <li>NotificationServiceImp： 该类是数据同步服务的具体实现。
    <li>ProductInfoImp： 商品查询服务的具体实现。
    <li>ServerNotificationHandlerImp： 服务端数据同步处理器。 </li>
</ul>
<p>&nbsp;</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="57" sizcache="1">
    <tbody sizset="57" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="58" sizcache="1">
    <tbody sizset="59" sizcache="1">
        <tr align="right" sizset="59" sizcache="1">
            <td sizset="59" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="59" sizcache="1">
                <tbody sizset="59" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="7"><span class="atitle">业务逻辑代理模式的实现</span></a></p>
<p>根据J2EE系统设计模式，业务逻辑代理通过向表现层隐藏了低层的实现，有效的减低表现层和商务逻辑层的耦合度，例如查找和调用EJB的细节。然而在本程序中，这种业务逻辑代理类对外隐藏了查找、调用Web服务和缓冲数据的复杂细节。</p>
<p>IProductInfoService接口类为客户端提供了关于商品信息的所有服务方法的描述。每个客户端必须自己处理SOAP 调用的查找、调用和缓冲。ProductInfoServiceDelegate类，从它的名字可知，它就是客户端的代理类。（在例子中，这个代理类并没有实现所有的Web服务方法调用，有兴趣的朋友可以自己补充完整）。</p>
<p>ProductInfoServiceDelegate类首先初始化一个IProductInfoService接口的实例，并且通过它来处理一切与底层SOAP相关的操作。然后，实例化一个SimpleCache类的静态实例，通过在所有的ProductInfoServiceDelegate类实例间共享这个实例来处理所有缓冲信息的操作。同时ProductInfoServiceDelegate类还包含一个DataChangeListener引用，用于触发注册的UI控件的界面更新事件，这里只是一个简单的实现，因此，一个ProductInfoServiceDelegate仅仅对应一个UI 控件，可以扩展为一个EventListenerList类，从而一个ProductInfoServiceDelegate可以拥有多个UI监听（但是更新UI又是一件危险的事，要注意只能在UI事件处理线程上刷新UI）。最后，ProductInfoServiceDelegate还实现了CacheExpiredListener接口，使它可以作为ClientNotificationHandler类的监听者，监听缓冲更新事件，以及时触发DataChange事件。</p>
<p>下面是ProductInfoServiceDelegate类的一些关键代码：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="60" sizcache="1">
    <tbody sizset="60" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">/**
            *&lt;p&gt;
            *Add a staff to server database and update the local cache.
            *&lt;/p&gt;
            *
            *@param staff &lt;code&gt;IProdcut&lt;/code&gt;
            *@return true if this operation is succeed or false for fail.
            */
            public boolean addStaff(IProduct staff) {
            boolean ret = false;
            if(staff != null) {
            try {
            synchronized(cache) {
            log.info("ProductInfo Service Delegate : Add Quote -&gt; ["
            + staff.getName() + "]");
            ret = soap.addStaff(staff);
            if(ret == true) {
            log.info("ProductInfo Service Delegate : Updating Cache for [" +
            staff.getName() + "] ...");
            cache.addObject(String.valueOf(staff.getSN()), staff);
            }
            }
            }catch(ProductInfoException e) {
            log.error("ProductInfoException in addStaff: " + e.getMessage());
            }catch(RemoteException e) {
            log.error("RemoteException in addStaff: " + e.getMessage());
            }
            }
            return ret;
            }
            /**
            * &lt;p&gt;
            * Request the staff object. If we can find it from cache,then we got it and return. If
            * not we get it from the remote server and update the local cache.
            * &lt;/p&gt;
            *
            * @param sn &lt;code&gt;String&lt;/code&gt; - the staff's SN Number
            * @return The staff if we got it, or null for fail.
            */
            public IProduct getStaff(String sn) {
            IProduct staff = null;
            log.info(new Date() + "Prodcut Service Delegate : Getting Staff for [" + sn + "] ...");
            if(sn != null) {
            //Try to get this staff from the cache firstly.
            staff = (IProduct)cache.fetchObject(sn);
            //If we can not get it from local cache, do it again from the remote server.
            if(staff == null) {
            try {
            synchronized(cache) {
            //After the synchronized, we try it again to see if we can get it from cache.
            staff = (IProduct)cache.fetchObject(sn);
            if (staff != null) {
            return (staff);
            }
            staff = soap.getStaff(sn);
            if(staff != null) {
            cache.addObject(sn, staff);
            log.info("ProductInfo Service Delegate : Updating Cache for ["
            +staff.getName() + "] ...");
            }
            }
            }catch(RemoteException e) {
            log.error("RemoteException in getStaff: " + e.getMessage());
            }
            }
            }
            return staff;
            }
            /**
            * &lt;p&gt;
            * Get all of the staffs information.
            * &lt;/p&gt;
            *
            * @return &lt;code&gt;java.util.Map&lt;/code&gt; the map of all these staffs info.
            */
            public Map getAllStaffs() {
            log.info( new Date() + "ProductInfo Service Delegate : Getting All staffs...");
            if(cache == null || cache.isEmpty()) {
            try {
            synchronized (cache){
            if ( (cache != null) &amp;&amp; (!cache.isEmpty())) {
            return cache.getAllObjectsHashtable();
            }
            Map tempMap = soap.getAllStaff();
            if(tempMap != null) {
            for(Iterator i = tempMap.keySet().iterator(); i.hasNext(); ) {
            String sn = (String)i.next();
            Object o  = tempMap.get(sn);
            cache.addObject(sn, o);
            }
            }
            }
            }catch(RemoteException e) {
            log.error("RemoteException in getAllStaffs: " + e.getMessage());
            }
            }
            return cache.getAllObjectsHashtable();
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>下面是操作流程图：</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image005.gif" width="600" /> <br />
<p>由上可以清楚的看到，使用业务代理模式可以使程序员不必去关心底层的通信细节，而把注意力完全集中在实现商业逻辑上来。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="61" sizcache="1">
    <tbody sizset="61" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="62" sizcache="1">
    <tbody sizset="63" sizcache="1">
        <tr align="right" sizset="63" sizcache="1">
            <td sizset="63" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="63" sizcache="1">
                <tbody sizset="63" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="8"><span class="atitle">缓冲管理模式的实现</span></a></p>
<p>当调用业务代理类的getStaff ()方法时，会首先试图从缓冲区中读取，如果无法找到，再向服务器端发出SOAP请求。当一个客户端程序添加或者更新了商品信息，它会首先调用Web服务方法更新服务器上的信息。这会触发服务端的dataChanged事件，导致所有的客户端都要更新本地缓冲区。然后，它在自己的本地缓冲区添加或更新该记录。其它的服务方法，也是一样的。目标就是将发送SOAP请求的次数减到最少。</p>
<p>根据缓冲管理模式，SimpleCache类应该包含两个方法，分别是addObject()和fetchObject()。一个CacheManager类通过ObjectKey来管理缓冲的信息。在本实现中，业务代理类就相当于CacheManager，这是由于每一个业务代理管理属于自己的SimpleCache。而ProductTemplate类实例就相当于ObjectKey。至于Cache的内部数据结构，出于简单的原因，采用了Hashtable。</p>
<p>现实中的程序里，可以通过扩展SimpleCache类，用更加严谨的内存管理机制轻易的替换这种基于Hashtable的简单实现（例如每次仅仅过期那些很少使用的缓冲信息），可以通过统计信息的点击次数或者复杂度来决定将要清除那些缓冲信息。可以利用Java的特性，如弱引用等来提高JVM的垃圾回收机制对被缓冲对象的清理效率。还可以通过创建一个全局的缓冲实例来统一管理整个客户端应用程序的缓冲信息。</p>
<p>总之，优化的方法是有很多很多的。</p>
<p>下面是，Cache类的简单实现代码：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="64" sizcache="1">
    <tbody sizset="64" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">/**
            * Empty constructor
            */
            public SimpleCache(){
            cache = new Hashtable();
            }
            /**
            * Constructor
            * @param size int the nitial size of the cache
            */
            public SimpleCache(int size){
            cache = new Hashtable(size);
            }
            /**
            * puts the key value pair in the cache based on the storage mechanism.
            *
            * @param key Object representing the key against which the value will be stored.
            * @param Value Object
            */
            public synchronized void addObject(Object key, Object value){
            if (key != null &amp;&amp; value != null){
            cache.put(key, value);
            }
            }
            /**
            * fetchs the value from the cache based on the key.
            *
            * @param key Object representing the key against which the value will be found.
            * @returns Object
            */
            public Object fetchObject(Object key){
            if (key == null) {
            return null;
            }
            return cache.get(key);
            }
            /**
            * expire the value object from cache
            *
            * @param key Object representing the key against which the value will be found.
            */
            public synchronized void expire(Object key){
            if (key != null) {
            cache.remove(key);
            }
            }
            /**
            * expire the entire cache
            */
            public void expire(){
            cache.clear();
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>下面的时序图演示了数据是如何被缓冲和缓冲是如何被更新的：</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image006.gif" /> <br />
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="65" sizcache="1">
    <tbody sizset="65" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="66" sizcache="1">
    <tbody sizset="67" sizcache="1">
        <tr align="right" sizset="67" sizcache="1">
            <td sizset="67" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="67" sizcache="1">
                <tbody sizset="67" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="9"><span class="atitle">自动数据同步的实现</span></a></p>
<p>在这个例子程序中，所有的客户端都是自动同步数据的，也就是说一个客户端程序更新或添加了数据，马上就会在其它所有的客户端中体现出来，这个功能是通过通告处理机制实现的。在这个示例程序中，有两个数据同步处理类，分别是运行在服务器端的ServerNotificationHandlerImp和运行在客户端的ClientNotificationHandler。</p>
<p>下面将详细介绍，在这个程序中是怎样实现数据同步的。</p>
<p>众所周知，基于HTTP协议的SOAP是无法支持双向传播（Bi-directional Communication）的，可是又必须当服务端发生变化时，让所有的客户端程序接收到通告（例如，当其中一个客户端向服务端数据库添加了一条商品信息后，所有其它的客户端都应该收到通告，以更新自己的数据视图）。</p>
<p>根据SOAP 绑定协议可以轻易的将底层的通信协议换成双向传播协议，如BEEP (Blocks Extensible Exchange Protocol)。但是这样将失去HTTP协议的简单性，可扩展性和防火墙可穿透性。</p>
<p>因此，这里采用了阻塞的Web服务调用。为此专门提供了一个Web服务--NotificationService。所有调用该服务的客户端线程都会被服务端的ServerNotificationHandler Singleton类实例的isDataChanged方法阻塞，直到数据改变事件触发，该Web服务调用才会返回。在本例中，DataChanged是由另一个Web服务 --- ProductInfoImp触发的。</p>
<p>下面是ServerNotificationHandler类的两个关键方法：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="68" sizcache="1">
    <tbody sizset="68" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">public synchronized boolean isDataChanged() {
            while(true) {
            try {
            wait();
            break;
            }
            catch (Exception ignored) {
            }
            }
            return true;
            }
            public synchronized void dataChanged()  {
            notifyAll();
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>在客户端，一个Singleton的ClientNotificationHandler单独运行在一个名为ClientNotificationHandlerThread的线程上。每个客户端都会创建这样一个线程，在这个线程上通过NotificationServiceDelegate代理类调用服务端的INotificationService服务的isDataChanged方法，并且被阻塞，只有当ServerNotificationHandlerImp的dataChanged方法被调用时，执行了Java线程的notifyAll操作才能返回。当该调用返回时，ClientNotificationHandler将注册的本地Cache全部过期，也就是触发了缓冲更新的事件。</p>
<p>下面ClientNotificationHandlerThread类的run()方法：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="69" sizcache="1">
    <tbody sizset="69" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">public void run() {
            while (true)   {
            if ((nsbd != null) &amp;&amp; (nsbd.isDataChanged()))       {
            expireAllCaches();
            }
            else
            {
            // Sleep for some time before creating the
            // NotificationServiceDelegate.
            gotoSleep();
            initNotificationServiceBusinessDelegate();
            }
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>下面是通过Apache Axis自带的TCP Monitor监视SOAP Call的截图：</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image007.jpg" /> <br />
<p>由上图可以清晰的看到，有一个SOAP调用一直没有结束，总是保持Active的状态，这就是对NotificationService的调用。当它返回的时候，必然会触发一个getAllQuotes的SOAP调用，然后又会有一个Active的连接。</p>
<p>这种方法也是有一定代价的。主要体现在它要求每一个客户端都必须起一个线程来等待接收通告，而在服务器端对于每一个注册的客户端连结都有一个线程阻塞的等待发出通告。绝大多数的企业级应用程序服务器都可以游刃有余的对应几百个客户端。但当同时有上千个客户端连结的时候，那又是另一回事了。这时候，只有采用非阻塞调用，ServerNotificationHandler将通知事件存储到队列中去，同时客户端的ClientNotificationHandler周期性的调用NotificationService服务。并且出于演示的原因，这个例子的数据同步模块已经被尽量的简化。真正的实现并不会总是使所有客户端的Cache全部过期。一个更具鲁棒性的设计可以让服务端的ServerNotificationHandlerImp传递需要被更新Cache数据的信息，甚至可以详细到哪一个对象实例需要被更新。这样一来，ClientNotificationHandler可以调用更加有效的代理类方法（如expiredOneCache）,来更新局部的Cache信息。</p>
<p>下图展示了数据同步模块的工作流程图：</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image008.gif" width="600" /> <br />
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="70" sizcache="1">
    <tbody sizset="70" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="71" sizcache="1">
    <tbody sizset="72" sizcache="1">
        <tr align="right" sizset="72" sizcache="1">
            <td sizset="72" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="72" sizcache="1">
                <tbody sizset="72" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="10"><span class="atitle">客户端界面</span></a></p>
<p>客户端主要两个功能组件，第一个是StaffInfoViewPanel用于显示最新的商品信息。另一个是DataEditPanel用于更新和添加商品信息。这些GUI组件都是通过封装的很好的ProductInfoServiceDelegate代理类来与后台通信的。</p>
<p>下面是运行时界面截图：</p>
<br />
<img alt="" src="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/image009.jpg" /> <br />
<p>可以清楚的看出，当一个客户端程序更新后台数据库的时候，其它的客户端也会实时的更新自己的数据视图。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="73" sizcache="1">
    <tbody sizset="73" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="74" sizcache="1">
    <tbody sizset="75" sizcache="1">
        <tr align="right" sizset="75" sizcache="1">
            <td sizset="75" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="75" sizcache="1">
                <tbody sizset="75" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="11"><span class="atitle">具体实现中遇到的问题</span></a></p>
<p>本文所带的例子，是在Eclipse 2.1IDE上开发完成的，SOAP引擎选用了Apache Axis1.1 Final。这些工具提供了十分快捷的开发方式。下面仅对开发过程中遇到的一些代表性的问题进行简单分析。</p>
<ul sizset="76" sizcache="1">
    <li sizset="76" sizcache="1">
    <p>如何用SOAP传递Hashtable</p>
    <p>这个问题在Apache Axis的官方 <a href="http://marc.theaimsgroup.com/?l=axis-user&amp;w=2&amp;r=1&amp;s=hashtable&amp;q=b" cmimpressionsent="1">Mail List</a>上，一直是一个非常火的问题。由于本文的例子也采用Hashtable这种数据结构，所以在此描述一下如何解决这个问题。首先，本例中传送的Hashtable的key为字符串型，value为StockQuote类型，其中StockQuote类是符合标准JavaBean规则的。因此，可以使用Axis自带的BeanSerializer和BeanDeserializer，但是在发布WebService的时候，必须配置服务端的序列化器，如下所示： </p>
    <table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="76" sizcache="1">
        <tbody sizset="76" sizcache="0">
            <tr>
                <td class="code-outline">
                <pre class="displaycode">&lt;deployment xmlns="http://xml.apache.org/axis/wsdd/"
                xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"&gt;
                &lt;service name="ProductInfoService" provider="java:RPC" style="rpc" use="encoded"&gt;
                &lt;parameter name="wsdlTargetNamespace" value="http://serverside.productinfo.demo"/&gt;
                &lt;parameter name="wsdlServiceElement" value="IProductInfoServiceService"/&gt;
                &lt;parameter name="wsdlServicePort" value="ProductInfoService"/&gt;
                &lt;parameter name="className" value="demo.productinfo.serverside.ProductInfoServiceSoapBindingSkeleton"/&gt;
                <!-- code sample is too wide -->  &lt;parameter name="wsdlPortType" value="IProductInfoService"/&gt;
                &lt;parameter name="allowedMethods" value="*"/&gt;
                &lt;parameter name="scope" value="Application"/&gt;
                &lt;typeMapping
                xmlns:ns="http://product.serverside.productinfo.demo"
                qname="ns: ProductTemplate"
                type="java:demo.productinfo.serverside.product.ProductTemplate"
                serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
                deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
                encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"      /&gt;
                &lt;typeMapping
                xmlns:ns="http://exception.productinfo.demo"
                qname="ns:ProductInfoException"
                type="java:demo.productinfo.exception.ProductInfoException"
                serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
                deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
                encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                /&gt;
                &lt;/service&gt;
                &lt;/deployment&gt;
                </pre>
                </td>
            </tr>
        </tbody>
    </table>
    <br />
    <p>请注意其中的&lt;typeMapping/&gt;节点，必须显示的为ProductTemplate类指定序列化器和反序列化器。否则，服务端会抛出org.xml.sax.SAXException异常。</p>
    <p>在客户端，可以直接使用通过Axis自带的WSDL2JAVA工具产生的可序列化的ProductTemplate类，也可以使用自己的ProductTemplate类，只要严格符合JavaBean标准规范。</p>
    <li sizset="77" sizcache="1">
    <p>如何调整后台服务的生存周期</p>
    <p>由于本例子程序，没有采用数据库，而只是简单的使用Hashtable做为存储商品信息的数据结构。如何保证数据的持久性呢？一般的Web服务只提供无状态的服务，类似于每一次客户端的SOAP调用，服务器端都会创建一个新的类实例来进行处理，你可以把它想象成Stateless Session Bean。为此，Apache Axis支持简单的三个级别的后台服务，分别为：Request、Session和Application。Request级别是默认的选项，它代表每次SOAP请求，都会创建一个新的对象来处理。Session级别代表客户端的每个Session有一个对应的后台对象进行处理。Application级别代表所有客户端的SOAP请求都会共享同一个后台服务实例对象，并且要由程序员自己来保证其数据的同步问题。在本例子中就是采用了Application级的服务。</p>
    <p>实施的过程如下：</p>
    <p>服务端：在WSDD文件中的每个服务的描述下，添加这样一行：</p>
    <table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="77" sizcache="1">
        <tbody sizset="77" sizcache="0">
            <tr>
                <td class="code-outline">
                <pre class="displaycode">&lt;parameter name="scope" value="Application"/&gt;
                </pre>
                </td>
            </tr>
        </tbody>
    </table>
    <br />
    <li sizset="78" sizcache="1">
    <p>如何避免Session Timeout</p>
    <p>Apache Axis1.1 Final的一个极具争议性的改变就是将原本的无Session Timeout时间限制，默认设置为60秒。这对例子程序有很大的影响，因为例子中每个客户端都需要与Server保持一个长连接，如果有超时设定，那么这个假设将会被破坏。因此，必须取消超时限制。取消方法如下：</p>
    <table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="78" sizcache="1">
        <tbody sizset="78" sizcache="0">
            <tr>
                <td class="code-outline">
                <pre class="displaycode">FooServiceLocator loc = new FooServiceLocator();
                FooService binding = loc.getFooService();
                org.apache.axis.client.Stub s = (Stub) binding;
                s.setTimeout(0);
                // 1 second, in miliseconds
                </pre>
                </td>
            </tr>
        </tbody>
    </table>
    <br />
    <p>对于这个问题，Axis的 <a href="http://marc.theaimsgroup.com/?l=axis-user&amp;w=2&amp;r=1&amp;s=hashtable&amp;q=b" cmimpressionsent="1">Mail List</a>上也处在激烈的争论中，也许在1.2版中就会取消默认60秒的设定。让我们拭目以待吧！ </p>
    </li>
</ul>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="79" sizcache="1">
    <tbody sizset="79" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="80" sizcache="1">
    <tbody sizset="81" sizcache="1">
        <tr align="right" sizset="81" sizcache="1">
            <td sizset="81" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="81" sizcache="1">
                <tbody sizset="81" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/webservices/ws-soapcli/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="12"><span class="atitle">总结</span></a></p>
<p>Web服务虽然已经取得了长足的发展，涌现出大量设计优良的SOAP引擎，如Apache Axis等，它们采用最新的技术在底层效率上已经做出了很大提高，但是由于SOAP协议的一些固有的特性，例如序列化和反序列化效率，传输效率等等问题，整体的效果仍然不尽如人意。但也不需担心，利用设计模式，可以解决大部分的问题。我们有理由相信它美好的未来。</p>
<p>本文中，在设计模式的帮助下，实现了一个透明的客户端缓冲SOAP调用的机制。现实中的程序还必须充分考虑到数据库持久性，全局Cache管理，资源回收，与其他Web服务平台的互操作性，安全性和更加有效的异常处理等问题。</p>
  <img src ="http://www.blogjava.net/gp213/aggbug/271380.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-18 18:36 <a href="http://www.blogjava.net/gp213/archive/2009/05/18/271380.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 程序中的多线程</title><link>http://www.blogjava.net/gp213/archive/2009/05/18/271346.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Mon, 18 May 2009 07:39:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/18/271346.html</guid><description><![CDATA[<blockquote>
<p>&nbsp;</p>
<p>由于在语言级提供了线程支持，在 Java 语言中使用多线程要远比在 C 或 C++ 中来得简单。本文通过简单的程序示例展现了在 Java 程序中线程编程的简单性。在学习完本文后，用户应该能够编写简单、多线程的程序。</p>
</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><em>在 Java 程序中使用多线程要比在 C 或 C++ 中容易得多，这是因为 Java 编程语言提供了语言级的支持。本文通过简单的编程示例来说明 Java 程序中的多线程是多么直观。读完本文以后，用户应该能够编写简单的多线程程序。</em> </p>
<p><a name="1"><span class="atitle">为什么会排队等待？</span></a></p>
<p>下面的这个简单的 Java 程序完成四项不相关的任务。这样的程序有单个控制线程，控制在这四个任务之间线性地移动。此外，因为所需的资源 ― 打印机、磁盘、数据库和显示屏 -- 由于硬件和软件的限制都有内在的潜伏时间，所以每项任务都包含明显的等待时间。因此，程序在访问数据库之前必须等待打印机完成打印文件的任务，等等。如果您正在等待程序的完成，则这是对计算资源和您的时间的一种拙劣使用。改进此程序的一种方法是使它成为多线程的。</p>
<p><em>四项不相关的任务</em> </p>
<table cellspacing="0" cellpadding="0" width="500" border="0" sizset="38" sizcache="1">
    <tbody sizset="38" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">class myclass {
            static public void main(String args[]) {
            print_a_file();
            manipulate_another_file();
            access_database();
            draw_picture_on_screen();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>在本例中，每项任务在开始之前必须等待前一项任务完成，即使所涉及的任务毫不相关也是这样。但是，在现实生活中，我们经常使用多线程模型。我们在处理某些任务的同时也可以让孩子、配偶和父母完成别的任务。例如，我在写信的同时可能打发我的儿子去邮局买邮票。用软件术语来说，这称为多个控制（或执行）线程。</p>
<p>可以用两种不同的方法来获得多个控制线程：</p>
<ul>
    <li><em>多个进程</em> <br />
    在大多数操作系统中都可以创建多个进程。当一个程序启动时，它可以为即将开始的每项任务创建一个进程，并允许它们同时运行。当一个程序因等待网络访问或用户输入而被阻塞时，另一个程序还可以运行，这样就增加了资源利用率。但是，按照这种方式创建每个进程要付出一定的代价：设置一个进程要占用相当一部分处理器时间和内存资源。而且，大多数操作系统不允许进程访问其他进程的内存空间。因此，进程间的通信很不方便，并且也不会将它自己提供给容易的编程模型。
    <li><em>线程</em> <br />
    线程也称为轻型进程 (LWP)。因为线程只能在单个进程的作用域内活动，所以创建线程比创建进程要廉价得多。这样，因为线程允许协作和数据交换，并且在计算资源方面非常廉价，所以线程比进程更可取。线程需要操作系统的支持，因此不是所有的机器都提供线程。Java 编程语言，作为相当新的一种语言，已将线程支持与语言本身合为一体，这样就对线程提供了强健的支持。 <br />
    </li>
</ul>
<p><a name="2"><span class="atitle">使用 Java 编程语言实现线程</span></a></p>
<p>Java 编程语言使多线程如此简单有效，以致于某些程序员说它实际上是自然的。尽管在 Java 中使用线程比在其他语言中要容易得多，仍然有一些概念需要掌握。要记住的一件重要的事情是 main() 函数也是一个线程，并可用来做有用的工作。程序员只有在需要多个线程时才需要创建新的线程。</p>
<p><a name="N10076"><span class="smalltitle">Thread 类</span></a></p>
<p>Thread 类是一个具体的类，即不是抽象类，该类封装了线程的行为。要创建一个线程，程序员必须创建一个从 Thread 类导出的新类。程序员必须覆盖 Thread 的 run() 函数来完成有用的工作。用户并不直接调用此函数；而是必须调用 Thread 的 start() 函数，该函数再调用 run()。下面的代码说明了它的用法：</p>
<p><em>创建两个新线程</em> </p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="42" sizcache="1">
    <tbody sizset="42" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">import java.util.*;
            class TimePrinter extends Thread {
            int pauseTime;
            String name;
            public TimePrinter(int x, String n) {
            pauseTime = x;
            name = n;
            }
            public void run() {
            while(true) {
            try {
            System.out.println(name + ":" + new
            Date(System.currentTimeMillis()));
            Thread.sleep(pauseTime);
            } catch(Exception e) {
            System.out.println(e);
            }
            }
            }
            static public void main(String args[]) {
            TimePrinter tp1 = new TimePrinter(1000, "Fast Guy");
            tp1.start();
            TimePrinter tp2 = new TimePrinter(3000, "Slow Guy");
            tp2.start();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>在本例中，我们可以看到一个简单的程序，它按两个不同的时间间隔（1 秒和 3 秒）在屏幕上显示当前时间。这是通过创建两个新线程来完成的，包括 main() 共三个线程。但是，因为有时要作为线程运行的类可能已经是某个类层次的一部分，所以就不能再按这种机制创建线程。虽然在同一个类中可以实现任意数量的接口，但 Java 编程语言只允许一个类有一个父类。同时，某些程序员避免从 Thread 类导出，因为它强加了类层次。对于这种情况，就要 <em>runnable 接口</em>。 </p>
<p><a name="N1008F"><span class="smalltitle">Runnable 接口</span></a></p>
<p>此接口只有一个函数，run()，此函数必须由实现了此接口的类实现。但是，就运行这个类而论，其语义与前一个示例稍有不同。我们可以用 runnable 接口改写前一个示例。（不同的部分用黑体表示。） </p>
<p><em>创建两个新线程而不强加类层次</em> </p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="43" sizcache="1">
    <tbody sizset="43" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">import java.util.*;
            class TimePrinter
            <span class="boldcode">implements Runnable</span> {
            int pauseTime;
            String name;
            public TimePrinter(int x, String n) {
            pauseTime = x;
            name = n;
            }
            public void run() {
            while(true) {
            try {
            System.out.println(name + ":" + new
            Date(System.currentTimeMillis()));
            Thread.sleep(pauseTime);
            } catch(Exception e) {
            System.out.println(e);
            }
            }
            }
            static public void main(String args[]) {
            <span class="boldcode">Thread t1 = new Thread</span> (new TimePrinter(1000, "Fast Guy"));
            t1.start();
            <span class="boldcode">Thread t2 = new Thread</span> (new TimePrinter(3000, "Slow Guy"));
            t2.start();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>请注意，当使用 runnable 接口时，您不能直接创建所需类的对象并运行它；必须从 Thread 类的一个实例内部运行它。许多程序员更喜欢 runnable 接口，因为从 Thread 类继承会强加类层次。</p>
<p><a name="N100AE"><span class="smalltitle">synchronized 关键字</span></a></p>
<p>到目前为止，我们看到的示例都只是以非常简单的方式来利用线程。只有最小的数据流，而且不会出现两个线程访问同一个对象的情况。但是，在大多数有用的程序中，线程之间通常有信息流。试考虑一个金融应用程序，它有一个 Account 对象，如下例中所示：</p>
<p><em>一个银行中的多项活动</em> </p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="44" sizcache="1">
    <tbody sizset="44" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">public class Account {
            String holderName;
            float amount;
            public Account(String name, float amt) {
            holderName = name;
            amount = amt;
            }
            public void deposit(float amt) {
            amount += amt;
            }
            public void withdraw(float amt) {
            amount -= amt;
            }
            public float checkBalance() {
            return amount;
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>在此代码样例中潜伏着一个错误。如果此类用于单线程应用程序，不会有任何问题。但是，在多线程应用程序的情况中，不同的线程就有可能同时访问同一个 Account 对象，比如说一个联合帐户的所有者在不同的 ATM 上同时进行访问。在这种情况下，存入和支出就可能以这样的方式发生：一个事务被另一个事务覆盖。这种情况将是灾难性的。但是，Java 编程语言提供了一种简单的机制来防止发生这种覆盖。每个对象在运行时都有一个关联的锁。这个锁可通过为方法添加关键字 synchronized 来获得。这样，修订过的 Account 对象（如下所示）将不会遭受像数据损坏这样的错误：</p>
<p><em>对一个银行中的多项活动进行同步处理</em> </p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="45" sizcache="1">
    <tbody sizset="45" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">public class Account {
            String holderName;
            float amount;
            public Account(String name, float amt) {
            holderName = name;
            amount = amt;
            }
            public
            <span class="boldcode">synchronized</span> void deposit(float amt) {
            amount += amt;
            }
            public
            <span class="boldcode">synchronized</span> void withdraw(float amt) {
            amount -= amt;
            }
            public float checkBalance() {
            return amount;
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>deposit() 和 withdraw() 函数都需要这个锁来进行操作，所以当一个函数运行时，另一个函数就被阻塞。请注意， checkBalance() 未作更改，它严格是一个读函数。因为 checkBalance() 未作同步处理，所以任何其他方法都不会阻塞它，它也不会阻塞任何其他方法，不管那些方法是否进行了同步处理。</p>
<br />
<br />
<p><a name="3"><span class="atitle">Java 编程语言中的高级多线程支持</span></a></p>
<p><strong>线程组</strong> <br />
线程是被个别创建的，但可以将它们归类到 <em>线程组</em>中，以便于调试和监视。只能在创建线程的同时将它与一个线程组相关联。在使用大量线程的程序中，使用线程组组织线程可能很有帮助。可以将它们看作是计算机上的目录和文件结构。 </p>
<p><strong>线程间发信</strong> <br />
当线程在继续执行前需要等待一个条件时，仅有 synchronized 关键字是不够的。虽然 synchronized 关键字阻止并发更新一个对象，但它没有实现 <em>线程间发信</em> 。Object 类为此提供了三个函数：wait()、notify() 和 notifyAll()。以全球气候预测程序为例。这些程序通过将地球分为许多单元，在每个循环中，每个单元的计算都是隔离进行的，直到这些值趋于稳定，然后相邻单元之间就会交换一些数据。所以，从本质上讲，在每个循环中各个线程都必须等待所有线程完成各自的任务以后才能进入下一个循环。这个模型称为 <em>屏蔽同步</em>，下例说明了这个模型： </p>
<p><em>屏蔽同步</em> </p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="49" sizcache="1">
    <tbody sizset="49" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">public class BSync {
            int totalThreads;
            int currentThreads;
            public BSync(int x) {
            totalThreads = x;
            currentThreads = 0;
            }
            public synchronized void waitForAll() {
            currentThreads++;
            if(currentThreads &lt; totalThreads) {
            try {
            wait();
            } catch (Exception e) {}
            }
            else {
            currentThreads = 0;
            notifyAll();
            }
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<p>当对一个线程调用 wait() 时，该线程就被有效阻塞，只到另一个线程对同一个对象调用 notify() 或 notifyAll() 为止。因此，在前一个示例中，不同的线程在完成它们的工作以后将调用 waitForAll() 函数，最后一个线程将触发 notifyAll() 函数，该函数将释放所有的线程。第三个函数 notify() 只通知一个正在等待的线程，当对每次只能由一个线程使用的资源进行访问限制时，这个函数很有用。但是，不可能预知哪个线程会获得这个通知，因为这取决于 Java 虚拟机 (JVM) 调度算法。</p>
<p><strong>将 CPU 让给另一个线程</strong> <br />
当线程放弃某个稀有的资源（如数据库连接或网络端口）时，它可能调用 yield() 函数临时降低自己的优先级，以便某个其他线程能够运行。 </p>
<p><strong>守护线程</strong> <br />
有两类线程：用户线程和守护线程。 <em>用户线程</em>是那些完成有用工作的线程。 <em>守护线程</em> 是那些仅提供辅助功能的线程。Thread 类提供了 setDaemon() 函数。Java 程序将运行到所有用户线程终止，然后它将破坏所有的守护线程。在 Java 虚拟机 (JVM) 中，即使在 main 结束以后，如果另一个用户线程仍在运行，则程序仍然可以继续运行。 </p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="50" sizcache="1">
    <tbody sizset="50" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="4"><span class="atitle">避免不提倡使用的方法</span></a></p>
<p>不提倡使用的方法是为支持向后兼容性而保留的那些方法，它们在以后的版本中可能出现，也可能不出现。Java 多线程支持在版本 1.1 和版本 1.2 中做了重大修订，stop()、suspend() 和 resume() 函数已不提倡使用。这些函数在 JVM 中可能引入微妙的错误。虽然函数名可能听起来很诱人，但请抵制诱惑不要使用它们。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="53" sizcache="1">
    <tbody sizset="53" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="5"><span class="atitle">调试线程化的程序</span></a></p>
<p>在线程化的程序中，可能发生的某些常见而讨厌的情况是死锁、活锁、内存损坏和资源耗尽。</p>
<p><strong>死锁</strong> <br />
死锁可能是多线程程序最常见的问题。当一个线程需要一个资源而另一个线程持有该资源的锁时，就会发生死锁。这种情况通常很难检测。但是，解决方案却相当好：在所有的线程中按相同的次序获取所有资源锁。例如，如果有四个资源 ―A、B、C 和 D ― 并且一个线程可能要获取四个资源中任何一个资源的锁，则请确保在获取对 B 的锁之前首先获取对 A 的锁，依此类推。如果&#8220;线程 1&#8221;希望获取对 B 和 C 的锁，而&#8220;线程 2&#8221;获取了 A、C 和 D 的锁，则这一技术可能导致阻塞，但它永远不会在这四个锁上造成死锁。 </p>
<p><strong>活锁</strong> <br />
当一个线程忙于接受新任务以致它永远没有机会完成任何任务时，就会发生活锁。这个线程最终将超出缓冲区并导致程序崩溃。试想一个秘书需要录入一封信，但她一直在忙于接电话，所以这封信永远不会被录入。 </p>
<p><strong>内存损坏</strong> <br />
如果明智地使用 synchronized 关键字，则完全可以避免内存错误这种气死人的问题。 </p>
<p><strong>资源耗尽</strong> <br />
某些系统资源是有限的，如文件描述符。多线程程序可能耗尽资源，因为每个线程都可能希望有一个这样的资源。如果线程数相当大，或者某个资源的侯选线程数远远超过了可用的资源数，则最好使用 <em>资源池</em>。一个最好的示例是数据库连接池。只要线程需要使用一个数据库连接，它就从池中取出一个，使用以后再将它返回池中。资源池也称为 <em>资源库</em>。 </p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="56" sizcache="1">
    <tbody sizset="56" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="6"><span class="atitle">调试大量的线程</span></a></p>
<p>有时一个程序因为有大量的线程在运行而极难调试。在这种情况下，下面的这个类可能会派上用场：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="59" sizcache="1">
    <tbody sizset="59" sizcache="0">
        <tr>
            <td class="code-outline">
            <pre class="displaycode">public class Probe extends Thread {
            public Probe() {}
            public void run() {
            while(true) {
            Thread[] x = new Thread[100];
            Thread.enumerate(x);
            for(int i=0; i&lt;100; i++) {
            Thread t = x[i];
            if(t == null)
            break;
            else
            System.out.println(t.getName() + "\t" + t.getPriority()
            + "\t" + t.isAlive() + "\t" + t.isDaemon());
            }
            }
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<br />
<br />
<p><a name="7"><span class="atitle">限制线程优先级和调度</span></a></p>
<p>Java 线程模型涉及可以动态更改的线程优先级。本质上，线程的优先级是从 1 到 10 之间的一个数字，数字越大表明任务越紧急。JVM 标准首先调用优先级较高的线程，然后才调用优先级较低的线程。但是，该标准对具有相同优先级的线程的处理是随机的。如何处理这些线程取决于基层的操作系统策略。在某些情况下，优先级相同的线程分时运行；在另一些情况下，线程将一直运行到结束。请记住，Java 支持 10 个优先级，基层操作系统支持的优先级可能要少得多，这样会造成一些混乱。因此，只能将优先级作为一种很粗略的工具使用。最后的控制可以通过明智地使用 yield() 函数来完成。通常情况下，请不要依靠线程优先级来控制线程的状态。</p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0" sizset="63" sizcache="1">
    <tbody sizset="63" sizcache="0">
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right" sizset="64" sizcache="1">
    <tbody sizset="65" sizcache="1">
        <tr align="right" sizset="65" sizcache="1">
            <td sizset="65" sizcache="1"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0" sizset="65" sizcache="1">
                <tbody sizset="65" sizcache="0">
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/multithreading/#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="8"><span class="atitle">小结</span></a></p>
<p>本文说明了在 Java 程序中如何使用线程。像是否 <em>应该</em>使用线程这样的更重要的问题在很大程序上取决于手头的应用程序。决定是否在应用程序中使用多线程的一种方法是，估计可以并行运行的代码量。并记住以下几点： </p>
<ul>
    <li>使用多线程不会增加 CPU 的能力。但是如果使用 JVM 的本地线程实现，则不同的线程可以在不同的处理器上同时运行（在多 CPU 的机器中），从而使多 CPU 机器得到充分利用。
    <li>如果应用程序是计算密集型的，并受 CPU 功能的制约，则只有多 CPU 机器能够从更多的线程中受益。
    <li>当应用程序必须等待缓慢的资源（如网络连接或数据库连接）时，或者当应用程序是非交互式的时，多线程通常是有利的。 </li>
</ul>
<p>基于 Internet 的软件有必要是多线程的；否则，用户将感觉应用程序反映迟钝。例如，当开发要支持大量客户机的服务器时，多线程可以使编程较为容易。在这种情况下，每个线程可以为不同的客户或客户组服务，从而缩短了响应时间。</p>
<p>某些程序员可能在 C 和其他语言中使用过线程，在那些语言中对线程没有语言支持。这些程序员可能通常都被搞得对线程失去了信心。</p>
  <img src ="http://www.blogjava.net/gp213/aggbug/271346.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-18 15:39 <a href="http://www.blogjava.net/gp213/archive/2009/05/18/271346.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>synchronized  同步的几种方法</title><link>http://www.blogjava.net/gp213/archive/2009/05/15/270941.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Fri, 15 May 2009 14:14:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/15/270941.html</guid><description><![CDATA[1.synchronized是锁对象，根本不存在锁代码块的概念 <br />
2.synchronized锁定的对象&nbsp;<br />
&nbsp;&nbsp;<br />
&nbsp; &nbsp;(1)synchronized(obj){}-&gt; 这个当然是锁obj&nbsp;<br />
&nbsp;这种方式应该是最好的，效率最高，对于一个对象中有多个共享资源时很好使用。<br />
&nbsp;&nbsp; (2)public void synchronized function(){} -&gt; 这个锁的是function这个方法所在的对象，相当于 <br />
&nbsp;&nbsp;&nbsp;&nbsp; public void function() { <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(this){} <br />
&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;这种方式是最不提倡的，因为1、会占用更多的资源，当有多个方法使用同步时，而其使用的共享资源又不相同时，会使更多的线程阻塞，推荐&nbsp;&nbsp; 使用上面的方式实现。<br />
&nbsp;&nbsp; 举例：<br />
&nbsp;&nbsp;&nbsp; private count1 = 0;<br />
&nbsp;&nbsp;&nbsp; private count2 = 0;<br />
&nbsp;&nbsp;&nbsp; public void synchronized function1(){}&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp; public void function() { <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(this){}&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count1++;<br />
&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;&nbsp;&nbsp; public void synchronized function2(){}&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp; public void function() { <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(this){}&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count2++;<br />
&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;以上方式会使得调用同一对象的不同方法的两个对象，因为其中一个线程对共享对象的占用而进入等待队列。<br />
<br />
&nbsp;&nbsp; (3)public static void synchronized function() -&gt;对于静态方法，锁定的是当前类(假设是类TestClass)的Class对象,相当于 <br />
&nbsp;&nbsp;&nbsp;&nbsp; public static void function() { <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(TestClass.class) {} <br />
&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp; 使用此种方法会使得对任何访问该类的所有使用synchronized functoin方法的调用，会使得其他方法处于阻塞状态。<br />
<br />
<br />
(3)synchronized&nbsp;只是锁对象，不会锁方法，任何对该被锁住的对象的访问都会进入等待队列，所有没有涉及到该锁对象的代码和数据都可以有多个线程并发访问。<br />
&nbsp;&nbsp;&nbsp; 
  <img src ="http://www.blogjava.net/gp213/aggbug/270941.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-15 22:14 <a href="http://www.blogjava.net/gp213/archive/2009/05/15/270941.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java多线程编程总结(基础)</title><link>http://www.blogjava.net/gp213/archive/2009/05/15/270932.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Fri, 15 May 2009 13:32:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/15/270932.html</guid><description><![CDATA[<div>一、认识多任务、多进程、单线程、多线程</div>
<div>要认识多线程就要从操作系统的原理说起。</div>
<div>&nbsp;</div>
<div>以前古老的DOS操作系统（V 6.22）是单任务的，还没有线程的概念，系统在每次只能做一件事情。比如你在copy东西的时候不能rename文件名。为了提高系统的利用效率，采用批处理来批量执行任务。</div>
<div>&nbsp;</div>
<div>现在的操作系统都是多任务操作系统，每个运行的任务就是操作系统所做的一件事情，比如你在听歌的同时还在用MSN和好友聊天。听歌和聊天就是两个任务，这个两个任务是&#8220;同时&#8221;进行的。一个任务一般对应一个进程，也可能包含好几个进程。比如运行的MSN就对应一个MSN的进程，如果你用的是windows系统，你就可以在任务管理器中看到操作系统正在运行的进程信息。</div>
<div>&nbsp;</div>
<div>一般来说，当运行一个应用程序的时候，就启动了一个进程，当然有些会启动多个进程。启动进程的时候，操作系统会为进程分配资源，其中最主要的资源是内存空间，因为程序是在内存中运行的。在进程中，有些程序流程块是可以乱序执行的，并且这个代码块可以同时被多次执行。实际上，这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行的时候，这样的执行模式成为并发执行。</div>
<div>&nbsp;</div>
<div>多线程的目的是为了最大限度的利用CPU资源。</div>
<div>&nbsp;</div>
<div>Java编写程序都运行在在Java虚拟机（JVM）中，在JVM的内部，程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序，就会启动一个JVM进程。在同一个JVM进程中，有且只有一个进程，就是它自己。在这个JVM环境中，所有程序代码的运行都是以线程来运行。</div>
<div>&nbsp;</div>
<div>一般常见的Java应用程序都是单线程的。比如，用java命令运行一个最简单的HelloWorld的Java应用程序时，就启动了一个JVM进程，JVM找到程序程序的入口点main()，然后运行main()方法，这样就产生了一个线程，这个线程称之为主线程。当main方法结束后，主线程运行完成。JVM进程也随即退出 。</div>
<div>&nbsp;</div>
<div>对于一个进程中的多个线程来说，多个线程共享进程的内存块，当有新的线程产生的时候，操作系统不分配新的内存，而是让新线程共享原有的进程块的内存。因此，线程间的通信很容易，速度也很快。不同的进程因为处于不同的内存块，因此进程之间的通信相对困难。</div>
<div>&nbsp;</div>
<div>实际上，操作的系统的多进程实现了多任务并发执行，程序的多线程实现了进程的并发执行。多任务、多进程、多线程的前提都是要求操作系统提供多任务、多进程、多线程的支持。</div>
<div>&nbsp;</div>
<div>在Java程序中，JVM负责线程的调度。线程调度是值按照特定的机制为多个线程分配CPU的使用权。</div>
<div>调度的模式有两种：分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权，并平均分配每个线程占用CPU的时间；抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。</div>
<div>&nbsp;</div>
<div>所谓的&#8220;并发执行&#8221;、&#8220;同时&#8221;其实都不是真正意义上的&#8220;同时&#8221;。众所周知，CPU都有个时钟频率，表示每秒中能执行cpu指令的次数。在每个时钟周期内，CPU实际上只能去执行一条（也有可能多条）指令。操作系统将进程线程进行管理，轮流（没有固定的顺序）分配每个进程很短的一段是时间（不一定是均分），然后在每个线程内部，程序代码自己处理该进程内部线程的时间分配，多个线程之间相互的切换去执行，这个切换时间也是非常短的。因此多任务、多进程、多线程都是操作系统给人的一种宏观感受，从微观角度看，程序的运行是异步执行的。</div>
<div>&nbsp;</div>
<div>用一句话做总结：虽然操作系统是多线程的，但CPU每一时刻只能做一件事，和人的大脑是一样的，呵呵。</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>二、Java与多线程</div>
<div>&nbsp;</div>
<div>Java语言的多线程需要操作系统的支持。</div>
<div>&nbsp;</div>
<div>Java 虚拟机允许应用程序并发地运行多个执行线程。Java语言提供了多线程编程的扩展点，并给出了功能强大的线程控制API。 </div>
<div>&nbsp;</div>
<div>在Java中，多线程的实现有两种方式：</div>
<div>扩展java.lang.Thread类</div>
<div>实现java.lang.Runnable接口</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>每个线程都有一个优先级，高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时，该新线程的初始优先级被设定为创建线程的优先级，并且当且仅当创建线程是守护线程时，新线程才是守护程序。 </div>
<div>&nbsp;</div>
<div>当 Java 虚拟机启动时，通常都会有单个非守护线程（它通常会调用某个指定类的 main 方法）。Java 虚拟机会继续执行线程，直到下列任一情况出现时为止： </div>
<div>&nbsp;</div>
<div>调用了 Runtime 类的 exit 方法，并且安全管理器允许退出操作发生。 </div>
<div>非守护线程的所有线程都已停止运行，无论是通过从对 run 方法的调用中返回，还是通过抛出一个传播到 run 方法之外的异常。 </div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>三、扩展java.lang.Thread类</div>
<div>&nbsp;</div>
<div>/**</div>
<div>&nbsp;* File Name:&nbsp;&nbsp; TestMitiThread.java</div>
<div>&nbsp;* Created by:&nbsp; IntelliJ IDEA.</div>
<div>&nbsp;* Copyright:&nbsp;&nbsp; Copyright (c) 2003-2006</div>
<div>&nbsp;* Company:&nbsp;&nbsp;&nbsp;&nbsp; Lavasoft( <a href="http://lavasoft.blog.51cto.com/">[url]http://lavasoft.blog.51cto.com/[/url]</a>)</div>
<div>&nbsp;* Author:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; leizhimin</div>
<div>&nbsp;* Modifier:&nbsp;&nbsp;&nbsp; leizhimin</div>
<div>&nbsp;* Date Time:&nbsp;&nbsp; 2007-5-17 10:03:12</div>
<div>&nbsp;* Readme:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 通过扩展Thread类实现多线程</div>
<div>&nbsp;*/</div>
<div>public class TestMitiThread {</div>
<div>&nbsp;&nbsp;&nbsp; public static void main(String[] rags) {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName() + " 线程运行开始!");</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new MitiSay("A").start();</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new MitiSay("B").start();</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName() + " 线程运行结束!");</div>
<div>&nbsp;&nbsp;&nbsp; }</div>
<div>}</div>
<div>&nbsp;</div>
<div>class MitiSay extends Thread {</div>
<div>&nbsp;&nbsp;&nbsp; public MitiSay(String threadName) {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super(threadName);</div>
<div>&nbsp;&nbsp;&nbsp; }</div>
<div>&nbsp;</div>
<div>&nbsp;&nbsp;&nbsp; public void run() {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(getName() + " 线程运行开始!");</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; 10; i++) {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(i + " " + getName());</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sleep((int) Math.random() * 10);</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (InterruptedException e) {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(getName() + " 线程运行结束!");</div>
<div>&nbsp;&nbsp;&nbsp; }</div>
<div>}</div>
<div>&nbsp;</div>
<div>运行结果：</div>
<div>&nbsp;</div>
<div>main 线程运行开始!</div>
<div>main 线程运行结束!</div>
<div>A 线程运行开始!</div>
<div>0 A</div>
<div>1 A</div>
<div>B 线程运行开始!</div>
<div>2 A</div>
<div>0 B</div>
<div>3 A</div>
<div>4 A</div>
<div>1 B</div>
<div>5 A</div>
<div>6 A</div>
<div>7 A</div>
<div>8 A</div>
<div>9 A</div>
<div>A 线程运行结束!</div>
<div>2 B</div>
<div>3 B</div>
<div>4 B</div>
<div>5 B</div>
<div>6 B</div>
<div>7 B</div>
<div>8 B</div>
<div>9 B</div>
<div>B 线程运行结束!</div>
<div>&nbsp;</div>
<div>说明：</div>
<div>程序启动运行main时候，java虚拟机启动一个进程，主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法，另外两个线程也启动了，这样，整个应用就在多线程下运行。</div>
<div>&nbsp;</div>
<div>在一个方法中调用Thread.currentThread().getName()方法，可以获取当前线程的名字。在mian方法中调用该方法，获取的是主线程的名字。</div>
<div>&nbsp;</div>
<div>注意：start()方法的调用后并不是立即执行多线程代码，而是使得该线程变为可运行态（Runnable），什么时候运行是由操作系统决定的。</div>
<div>从程序运行的结果可以发现，多线程程序是乱序执行。因此，只有乱序执行的代码才有必要设计为多线程。</div>
<div>Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源，以留出一定时间给其他线程执行的机会。</div>
<div>实际上所有的多线程代码执行顺序都是不确定的，每次执行的结果都是随机的。</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>四、实现java.lang.Runnable接口</div>
<div>&nbsp;</div>
<div>/**</div>
<div>&nbsp;* 通过实现 Runnable 接口实现多线程</div>
<div>&nbsp;*/</div>
<div>public class TestMitiThread1 implements Runnable {</div>
<div>&nbsp;</div>
<div>&nbsp;&nbsp;&nbsp; public static void main(String[] args) {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName() + " 线程运行开始!");</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TestMitiThread1 test = new TestMitiThread1();</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread thread1 = new Thread(test);</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread thread2 = new Thread(test);</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thread1.start();</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thread2.start();</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName() + " 线程运行结束!");</div>
<div>&nbsp;&nbsp;&nbsp; }</div>
<div>&nbsp;</div>
<div>&nbsp;&nbsp;&nbsp; public void run() {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName() + " 线程运行开始!");</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; 10; i++) {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(i + " " + Thread.currentThread().getName());</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread.sleep((int) Math.random() * 10);</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (InterruptedException e) {</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName() + " 线程运行结束!");</div>
<div>&nbsp;&nbsp;&nbsp; }</div>
<div>}</div>
<div>&nbsp;</div>
<div>运行结果：</div>
<div>&nbsp;</div>
<div>main 线程运行开始!</div>
<div>Thread-0 线程运行开始!</div>
<div>main 线程运行结束!</div>
<div>0 Thread-0</div>
<div>Thread-1 线程运行开始!</div>
<div>0 Thread-1</div>
<div>1 Thread-1</div>
<div>1 Thread-0</div>
<div>2 Thread-0</div>
<div>2 Thread-1</div>
<div>3 Thread-0</div>
<div>3 Thread-1</div>
<div>4 Thread-0</div>
<div>4 Thread-1</div>
<div>5 Thread-0</div>
<div>6 Thread-0</div>
<div>5 Thread-1</div>
<div>7 Thread-0</div>
<div>8 Thread-0</div>
<div>6 Thread-1</div>
<div>9 Thread-0</div>
<div>7 Thread-1</div>
<div>Thread-0 线程运行结束!</div>
<div>8 Thread-1</div>
<div>9 Thread-1</div>
<div>Thread-1 线程运行结束!</div>
<div>&nbsp;</div>
<div>说明：</div>
<div>TestMitiThread1类通过实现Runnable接口，使得该类有了多线程类的特征。run（）方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。</div>
<div>在启动的多线程的时候，需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象，然后调用Thread对象的start()方法来运行多线程代码。</div>
<div>实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此，不管是扩展Thread类还是实现Runnable接口来实现多线程，最终还是通过Thread的对象的API来控制线程的，熟悉Thread类的API是进行多线程编程的基础。</div>
<div>&nbsp;</div>
<div>五、读解Thread类API</div>
<div>&nbsp;</div>
<div>static int MAX_PRIORITY </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 线程可以具有的最高优先级。 </div>
<div>static int MIN_PRIORITY </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 线程可以具有的最低优先级。 </div>
<div>static int NORM_PRIORITY </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 分配给线程的默认优先级。 </div>
<div>&nbsp;</div>
<div>构造方法摘要 </div>
<div>Thread(Runnable target) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 分配新的 Thread 对象。 </div>
<div>Thread(String name) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 分配新的 Thread 对象。 </div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>方法摘要 </div>
<div>static Thread currentThread() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回对当前正在执行的线程对象的引用。 </div>
<div>&nbsp;ClassLoader getContextClassLoader() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回该线程的上下文 ClassLoader。 </div>
<div>&nbsp;long getId() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回该线程的标识符。 </div>
<div>&nbsp;String getName() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回该线程的名称。 </div>
<div>&nbsp;int getPriority() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回线程的优先级。 </div>
<div>&nbsp;Thread.State getState() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回该线程的状态。 </div>
<div>&nbsp;ThreadGroup getThreadGroup() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回该线程所属的线程组。 </div>
<div>static boolean holdsLock(Object obj) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当且仅当当前线程在指定的对象上保持监视器锁时，才返回 true。 </div>
<div>&nbsp;void interrupt() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 中断线程。 </div>
<div>static boolean interrupted() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 测试当前线程是否已经中断。 </div>
<div>&nbsp;boolean isAlive() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 测试线程是否处于活动状态。 </div>
<div>&nbsp;boolean isDaemon() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 测试该线程是否为守护线程。 </div>
<div>&nbsp;boolean isInterrupted() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 测试线程是否已经中断。 </div>
<div>&nbsp;void join() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 等待该线程终止。 </div>
<div>&nbsp;void join(long millis) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 等待该线程终止的时间最长为 millis 毫秒。 </div>
<div>&nbsp;void join(long millis, int nanos) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。 </div>
<div>&nbsp;void resume() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 已过时。 该方法只与 suspend() 一起使用，但 suspend() 已经遭到反对，因为它具有死锁倾向。有关更多信息，请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对？。 </div>
<div>&nbsp;void run() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果该线程是使用独立的 Runnable 运行对象构造的，则调用该 Runnable 对象的 run 方法；否则，该方法不执行任何操作并返回。 </div>
<div>&nbsp;void setContextClassLoader(ClassLoader cl) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 设置该线程的上下文 ClassLoader。 </div>
<div>&nbsp;void setDaemon(boolean on) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将该线程标记为守护线程或用户线程。 </div>
<div>static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 设置当线程由于未捕获到异常而突然终止，并且没有为该线程定义其他处理程序时所调用的默认处理程序。 </div>
<div>&nbsp;void setName(String name) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 改变线程名称，使之与参数 name 相同。 </div>
<div>&nbsp;void setPriority(int newPriority) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 更改线程的优先级。 </div>
<div>&nbsp;void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 设置该线程由于未捕获到异常而突然终止时调用的处理程序。 </div>
<div>static void sleep(long millis) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在指定的毫秒数内让当前正在执行的线程休眠（暂停执行）。 </div>
<div>static void sleep(long millis, int nanos) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠（暂停执行）。 </div>
<div>&nbsp;void start() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使该线程开始执行；Java 虚拟机调用该线程的 run 方法。 </div>
<div>&nbsp;void stop() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 已过时。 该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器（作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果）。如果以前受这些监视器保护的任何对象都处于一种不一致的状态，则损坏的对象将对其他线程可见，这有可能导致任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量，并且如果该变量指示它要停止运行，则从其运行方法依次返回。如果目标线程等待很长时间（例如基于一个条件变量），则应使用 interrupt 方法来中断该等待。有关更多信息，请参阅《为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume？》。 </div>
<div>&nbsp;void stop(Throwable obj) </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 已过时。 该方法具有固有的不安全性。请参阅 stop() 以获得详细信息。该方法的附加危险是它可用于生成目标线程未准备处理的异常（包括若没有该方法该线程不太可能抛出的已检查的异常）。有关更多信息，请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对？。 </div>
<div>&nbsp;void suspend() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 已过时。 该方法已经遭到反对，因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁，则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用 resume 之前锁定该监视器，则会发生死锁。这类死锁通常会证明自己是&#8220;冻结&#8221;的进程。有关更多信息，请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对？。 </div>
<div>&nbsp;String toString() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回该线程的字符串表示形式，包括线程名称、优先级和线程组。 </div>
<div>static void yield() </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 暂停当前正在执行的线程对象，并执行其他线程。 </div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>六、线程的状态转换图</div>
<div>&nbsp;</div>
<div>线程在一定条件下，状态会发生变化。线程变化的状态转换图如下：</div>
<div><img onclick='window.open("http://blog.51cto.com/viewpic.php?refimg=" + this.src)' alt="" src="http://img1.51cto.com/attachment/200705/200705181179465004843.png" border="0" /></div>
<div>&nbsp;</div>
<div>1、新建状态（New）：新创建了一个线程对象。</div>
<div>2、就绪状态（Runnable）：线程对象创建后，其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中，变得可运行，等待获取CPU的使用权。</div>
<div>3、运行状态（Running）：就绪状态的线程获取了CPU，执行程序代码。</div>
<div>4、阻塞状态（Blocked）：阻塞状态是线程因为某种原因放弃CPU使用权，暂时停止运行。直到线程进入就绪状态，才有机会转到运行状态。阻塞的情况分三种：</div>
<div>（一）、等待阻塞：运行的线程执行wait()方法，JVM会把该线程放入等待池中。</div>
<div>（二）、同步阻塞：运行的线程在获取对象的同步锁时，若该同步锁被别的线程占用，则JVM会把该线程放入锁池中。</div>
<div>（三）、其他阻塞：运行的线程执行sleep()或join()方法，或者发出了I/O请求时，JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时，线程重新转入就绪状态。</div>
<div>5、死亡状态（Dead）：线程执行完了或者因异常退出了run()方法，该线程结束生命周期。</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>七、线程的调度</div>
<div>1、调整线程优先级：Java线程有优先级，优先级高的线程会获得较多的运行机会。</div>
<div>&nbsp;</div>
<div>Java线程的优先级用整数表示，取值范围是1~10，Thread类有以下三个静态常量：</div>
<div>static int MAX_PRIORITY </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 线程可以具有的最高优先级，取值为10。 </div>
<div>static int MIN_PRIORITY </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 线程可以具有的最低优先级，取值为1。 </div>
<div>static int NORM_PRIORITY </div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 分配给线程的默认优先级，取值为5。 </div>
<div>&nbsp;</div>
<div>Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。</div>
<div>&nbsp;</div>
<div>每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。</div>
<div>线程的优先级有继承关系，比如A线程中创建了B线程，那么B将和A具有相同的优先级。</div>
<div>JVM提供了10个线程优先级，但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中，应该仅仅使用Thread类有以下三个静态常量作为优先级，这样能保证同样的优先级采用了同样的调度方式。</div>
<div>&nbsp;</div>
<div>2、线程睡眠：Thread.sleep(long millis)方法，使线程转到阻塞状态。millis参数设定睡眠的时间，以毫秒为单位。当睡眠结束后，就转为就绪（Runnable）状态。sleep()平台移植性好。</div>
<div>&nbsp;</div>
<div>3、线程等待：Object类中的wait()方法，导致当前的线程等待，直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法，行为等价于调用 wait(0) 一样。</div>
<div>&nbsp;</div>
<div>4、线程让步：Thread.yield() 方法，暂停当前正在执行的线程对象，把执行机会让给相同或者更高优先级的线程。</div>
<div>&nbsp;</div>
<div>5、线程加入：join()方法，等待其他线程终止。在当前线程中调用另一个线程的join()方法，则当前线程转入阻塞状态，直到另一个进程运行结束，当前线程再由阻塞转为就绪状态。</div>
<div>&nbsp;</div>
<div>6、线程唤醒：Object类中的notify()方法，唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待，则会选择唤醒其中一个线程。选择是任意性的，并在对实现做出决定时发生。线程通过调用其中一个 wait 方法，在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定，才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争；例如，唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll()，唤醒在此对象监视器上等待的所有线程。</div>
<div>&nbsp;</div>
<div>注意：Thread中suspend()和resume()两个方法在JDK1.5中已经废除，不再介绍。因为有死锁倾向。</div>
<div>&nbsp;</div>
<div>7、常见线程名词解释</div>
<div>主线程：JVM调用程序mian()所产生的线程。</div>
<div>当前线程：这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。</div>
<div>后台线程：指为其他线程提供服务的线程，也称为守护线程。JVM的垃圾回收线程就是一个后台线程。</div>
<div>前台线程：是指接受后台线程服务的线程，其实前台后台线程是联系在一起，就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。</div>
  <img src ="http://www.blogjava.net/gp213/aggbug/270932.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-15 21:32 <a href="http://www.blogjava.net/gp213/archive/2009/05/15/270932.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java 反射机制运用场景</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270726.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 15:22:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270726.html</guid><description><![CDATA[<p>反射机制实现：<br />
</p>
<p><span style="font-family: Verdana"><br />
1、在运行时判断任意一个对象所属的类。<br />
Class cls = Class.forName("com.jdk");<br />
返回true<br />
System.out.print(cls.isInstance(new jdk())); <br />
<br />
2、在运行时构造任意一个类的对象。<br />
Class cls = Class.forName("com.jdk");<br />
jdk&nbsp;jdkobj = cls.newInstance();<br />
<br />
3、在运行时判断任意一个类所具有的成员变量和方法。<br />
Class cls = Class.forName("com.jdk");<br />
Methods methods[]= cls.getDecliedMethod();<br />
Fields&nbsp; fields[] =&nbsp;cls.getDeclieredFields();<br />
<br />
4、在运行时调用任意一个对象的方法<br />
Class cls = Class.forName("com.jdk");<br />
Methods methods[]= cls.getDecliedMethod();<br />
jdk jdkobj = new jdk();<br />
String returnvalue = methods.invoke(jdkobj,null)<br />
<br />
其中第四种方法最长使用：<br />
主要有：<br />
a.配置文件创建form对象。并初始化。<br />
&nbsp;&nbsp;&nbsp; 1、使用配置文件产生类的实例。通过配置文件中提供的名称生成类的实例。<br />
&nbsp;&nbsp;&nbsp; 2、使用类的实例，根据request中的参数生成一个properties文件，之后使用beanUtils.populte(form,pro);<br />
&nbsp;&nbsp;&nbsp; 对form的属性进行复制，其中就是使用reflect方法。<br />
b.当你不知道要创建什么类型的对象时，使用在运行时提供类的完整路径的方式来生成类的实例时，<br />
&nbsp;&nbsp;&nbsp; 例如：<br />
&nbsp;&nbsp;&nbsp; 在struts中对messageResources引用的生成中，使用到工厂方法的模式，对于他所要使用的工厂，他采用的是factoryClass参数来生成工厂，之后使用这个工厂的实例来生成messageResources对象。<br />
&nbsp;&nbsp;&nbsp;&nbsp;这其中就使用到了使用reflect产生类的实例。<br />
<br />
c.当对象的方法只有在运行时才知道那些方法被调用，那些方法不用调用时，且方法遵循一定的规律生成，而且方法很对，比如100个。<br />
<br />
例如：<br />
使用日历生成客户经理的月报时，因为一个月是1到31天（最长），我们通过在数据库中配置客户经理日历的表，其中有<br />
customerid,customercode,no1,no2,no3,no4,no5------,no31,remark,tag,status&nbsp;&nbsp; .......<br />
生成的bean和上面的表的字段相对性，如customerbean<br />
我们通过jdbc从数据库查询得到客户经理每个月的数据，通过画表格的方式生成客户经理月报，输出每天的记录时，调用bean的1-31天的方法，如果不采用reflect那么将会是恼人的代码，采用reflect则会节省很多的时间和空间，<br />
<br />
d.最有用的也是最经常使用的，通过回调的方式从resultset中查询数据，查询字段和类型的内容是动态的，查询的sql也是动态的即结果集是动态的。这时我们可以很好的使用反射机制。通过指定的字段和类型，使用反射的方法在结果集中查找，并封装成map传回。<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</span></p>
  <img src ="http://www.blogjava.net/gp213/aggbug/270726.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 23:22 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270726.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java中利用Reflection API优化代码</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270641.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 08:31:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270641.html</guid><description><![CDATA[<strong>摘要</strong><br />
<br />
　　开发者通过各种各样的方法来尝试避免单调冗余的编程。一些编程的规则例如继承、多态或者设计模型可以帮助开发者避免产生多余的代码。不过由于软件开发方面存在着不确定性，因此这些规则并不能消除代码维护和重新编写的需要。在很多时候维护都是不可避免的，只有不能运作的软件才是从不需要维护的。不过，这篇文章介绍了你可以使用Java的Reflection API的功能来减少单调的代码编写，并可以使用活动的代码产生来克服reflection的限制。<br />
<br />
　　数据配置（由外部的源头得到数据并且将它装载到一个Java对象中）可以利用reflection的好处来创建一个可重用的方案。问题是很简单的：将数据由一个文件装入到一个对象的字段中。现在假设用作数据的目标Java类每星期改变一次？有一个很直接的解决方法，不过你必须不断地维护载入的过程来反映任何的改变。在更复杂的环境下，同样的问题可能会令系统崩溃掉。对于一个处理过运用XML的大型系统的人来说，他就会遇到过这个问题。要编写一个载入的过程通常是非常单调乏味的，由于数据源或者目标Java类的改变，你需要经常更新和重新编写代码。在这里我要介绍另一个解决方案，那就是使用映射，它通常使用更少的编码，并且可以在目标Java类发生改变后更新自己。<br />
<br />
　　最初，我想介绍一个使用Reflection在运行期间配置数据的方案。在开始的时候，一个动态、基于映射的程序要比一个简单的方法更有吸引力多了。随后，我要揭示出运行时Reflection的复杂性和冒险性。这篇文章将介绍由运行时的Reflection到活动的代码产生。<br />
<br />
　　<strong>由简单到复杂</strong><br />
<br />
　　我的第一个方案使用一个载入类将数据从一个文件载入到对象中。我的源代码含有对StringTokenizer对象下一节点方法的多次调用。在修改多次后，我的编码逻辑变得非常的直接、系统化。该类构造了专用的代码。在这个初始方案中，我只需要使用3个基本的对象：<br />
<br />
　　1、Strings <br />
<br />
　　2、Objects <br />
<br />
　　3、Arrays of objects<br />
<br />
　　你可以影射类的对象来产生代码块，如下表所示：<br />
<br />
　　被影射来产生代码块的对象<br />
<br />
<table cellspacing="0" width="600" align="center" border="1">
    <tbody>
        <tr>
            <td width="129">Field type</td>
            <td width="461">Code block</td>
        </tr>
        <tr>
            <td width="129">String</td>
            <td width="461">fileIterator.nextString(); </td>
        </tr>
        <tr>
            <td width="129">Object[]</td>
            <td width="461">Vector collector = new Vector(); while(fileIterator.hasMoreDataForArray()){ Object data = initializeObject(fileIterator)collector.add(data); } Object[] objArray = new Object[collector.size()]; collector.copyInto(objArray); </td>
        </tr>
        <tr>
            <td width="129">Object</td>
            <td width="461">initializeObject(fileIterator);</td>
        </tr>
    </tbody>
</table>
　　　　　　　　　　　　　　　　　**************表一**************<br />
<br />
　　我已经使用这个方案作了几次编码，因此我在写代码之前我已经知道该方案和代码的结构。难点在于该类是变化的。类的名字、成份和结构在任何时候都可能发生变化，而任何的改变你都要重新编写代码。虽然会发生这些变化，但是结构和下载的流程仍然是一样的；在写代码前，我仍然知道代码的结构和成份。我需要一个方法，来将头脑中的编码流程转换为一个可重用的、自动的形式。由于我是一个有效率的编程者，我很快就厌倦了编写几乎一样的代码，这时我想到了映射。<br />
<br />
　　数据配置通常需要一个源到目的数据的影射。影射可以是一个图解、DTD（document type definition，文档类型定义），文件格式等。在这个例子中，映射将一个对象的类定义解释为我们要映射的流程。映射可以在运行时复制代码的功能。在需要重写代码时，我将载入的过程用映射来代替，它所需要的时间和重写是一样的。<br />
<br />
　　载入的工程可以概括为以下几步：<br />
<br />
　　1、解释：一个影射决定你在构造一个对象时需要些什么<br />
<br />
　　2、请求数据：要满足构造的需要，要进行一个调用来得到数据<br />
<br />
　　3、拖：数据由源中得到。<br />
<br />
　　4、推：数据被填充入一个对象的新实例<br />
<br />
　　5、如果必要的话，重复步骤1<br />
<br />
　　你需要以下的类来满足以上的步骤：<br />
<br />
　　．数据类（Data classes）：由ASCII文件中的数据实例化。类定义提供数据的影射。数据类必须满足以下的条件：<br />
<br />
　　.它们必须包含有一个构造器来接收全部必需的参数，以使用一个有效的状态来构造对象；<br />
<br />
　　.它们必须由对象构成，这些对象是reflective过程知道如何处理的<br />
<br />
　　．对象装载器（Object loader）：使用reflection和数据类作为一个影射来载入数据。产生数据请求。<br />
<br />
　　．载入管理器（Load manager）：作为对象装载器和数据源的中介层，将对数据的请求转换为一个数据指定的调用。这可以令对象载入器做到与数据源无关。通过它的接口和一个可载入的类对象通信。<br />
<br />
　　．数据循环接口（Data iterator interface）：载入管理器和载入类对象使用这个接口来由数据源中得到数据。<br />
<br />
　　一旦你创建了支持的类，你就可以使用以下的声明来创建和影射一个对象：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>FooFileIterator iter = new FooFileIterator(fileLocation, log);<br />
            LoadManager manager = new FooFileLoadManager(iter);<br />
            SubFooObject obj = <br />
            (SubFooObject)ReflectiveObjectLoader.initializeInstance(SubFooObject.class, manager,log);</td>
        </tr>
    </tbody>
</table>
<br />
　　通过这个处理，你就创建了一个包含有文件内容的SubFooObject实例。<br />
<br />
<div class="fontclear"></div>
<br />
<div class="left"><span class="span">作者：QQ新人类</span><span class="span">出处：YESKY</span><span class="span">责任编辑： 方舟 </span><span class="span">[ 2002-04-14 09:51 ]</span></div>
<div class="fontclear"></div>
<div class="left fontsize3">开发者通过各种各样的方法来尝试避免单调冗余的编程。一些编程的规则例如继承、多态或者设计模型可以帮助开发者避免产生多余的代码</div>
<div class="fontclear"></div>
<hr class="hr1" />
<center></center>
<div class="left fontsize4">
<div class="guanggao"></div>
　　<strong>局限</strong><br />
<br />
　　开发者必须决定使用哪个方案来解决问题是最好的；通常做出这个决定是最困难的部分。在考虑使用reflection作数据配置时，你要考虑到以下一些限制：<br />
<br />
　　1、不要令一个简单的问题复杂化。reflection是比较复杂的，因此在必要的时候才使用它。一旦开发者明白了reflection的能力，他就想使用它来解决所有的问题。如果你有更快、更简单的方案来解决问题时，你就不应该使用reflection（即使这个更好的方案可能使用更多的代码）。reflection是强大的，但也有一些风险。<br />
<br />
　　2、考虑性能。reflection对性能的影响比较大，因为要在运行时发现和管理类属性需要时间和内存。<br />
<br />
　　<strong>重新评估方案</strong><br />
<br />
　　如上所述，使用运行时reflection的第一个限制是&#8220;不要令简单的问题复杂化&#8221;。在使用reflection时，这是不可避免的。将reflection和递归结合起来是一个令人头痛的问题；重新看代码也是一件可怕的事情；而准确决定代码的功能也是非常复杂的。要知道代码的准确作用的唯一方法是使用一些取样数据，逐行地看，就象运行时一样。不过，对于每个可能的数据组合都使用这种方式几乎是不可能的。在这种情况下，使用单元测试代码可能有些帮助，不过也很可能出现错误。幸运的是，还有一个可选的方法。<br />
<br />
　　<strong>可选的方法</strong><br />
<br />
　　由上面列出的限制可以看到，在某些情况下，使用reflective载入过程可能是得不偿失的。代码产生提供了一个通用的选择方法。你也可以使用reflection来检查一个类并且为载入过程产生代码。<br />
<br />
　　Andrew Hunt和David Thomas介绍了两类的代码产生器，见The Pragmatic Programmer（http://www.javaworld.com/javaworld/jw-11-2001/jw-1102-codegen-p2.html#resources）<br />
<br />
　　1、Passive（被动）：被动的代码产生器在实现代码时需要人工的干预。许多的IDE（集成开发环境）都提供相应的向导来实现。<br />
<br />
　　2、Active（主动）：主动的代码产生指的是代码一旦创建，就不再需要修改了。如果有问题产生，这个问题也应该在代码产生器中解决，而不是在产生的源文件中解决。在理想的情况下，这个过程应该包含在编译的处理过程中，从而确保类不会过期。<br />
<br />
　　代码产生的优点和缺点包含有以下方面：<br />
<br />
　　优点：<br />
<br />
　　．简单：产生的代码通常是更便于开发者阅读和调试。<br />
<br />
　　．编译过程的错误：Reflexive在运行时出现错误的机会要比编译的期间多。例如，改变被载入的对象将有可能令产生的载入类抛出一个编译的错误，不过reflexive过程将不会看到任何的区别，直到在运行时遇到这个类。<br />
<br />
　　缺点：<br />
<br />
　　．维护：使用被动的代码产生，修改被载入的对象将需要更新或者重新产生载入的类。如果该类被重新产生，那么自定义的东西就会丢失。<br />
<br />
　　回头再来看看主动代码产生的好处<br />
<br />
　　在这里我们可以看到在运行时使用reflection是不可以接受的。主动的代码产生有着reflection的全部好处，但是没有它的限制。还可以继续使用reflection，不过只是在代码的产生过程，而不是运行的过程。理由如下：<br />
<br />
　　1、更少冒险：运行时的reflection明显是更冒险的，特别是问题变得复杂的时候。<br />
<br />
　　2、基于单元测试，但并不是编译器<br />
<br />
　　3、多功能性：产生的代码有着runtime reflection的全部好处，而且有着runtime reflection没有的好处。<br />
<br />
　　4、更易懂：虽然经过多次的处理，但是将递归和reflection结合仍然是很复杂的。产生源代码的方式更加容易解释和理解。代码产生过程需要递归和reflection，但得到的结果是可查看的源代码，而不是难以理解的东西。<br />
<br />
<div class="fontclear"></div>
<br />
<div class="left"><span class="span">作者：QQ新人类</span><span class="span">出处：YESKY</span><span class="span">责任编辑： 方舟 </span><span class="span">[ 2002-04-14 09:51 ]</span></div>
<div class="fontclear"></div>
<div class="left fontsize3">开发者通过各种各样的方法来尝试避免单调冗余的编程。一些编程的规则例如继承、多态或者设计模型可以帮助开发者避免产生多余的代码</div>
<div class="fontclear"></div>
<hr class="hr1" />
<center></center>
<div class="left fontsize4">
<div class="guanggao"></div>
　　<strong>写代码产生器</strong><br />
<br />
　　要写一个代码产生器，在思考的时候，你不能只是简单地编写一个方案来解决一个问题，你应该看得更远。代码产生器（以及reflection）需要你作更多的思考。如果你只是使用runtime reflection，你就不得不在运行时概念化问题，而不是使用简单、兼容性好的源代码来解决问题。代码产生要求你从两个方面来查看问题。代码产生过程会将抽象的概念转变为实际的源代码。Runtime reflection则一直是抽象的。<br />
<br />
　　代码产生过程将你的思考过程转变为代码，然后产生并且编译代码。编译器会让你知道你的思考过程在语法上是否正确；单元测试则可以验证代码在运行时的行为。就动态特性方面，runtime reflection就不能达到这个级别的安全性。<br />
<br />
　　<strong>代码产生器</strong><br />
<br />
　　在经历后几次失败的设计后，我认为最简单的方法是：在载入过程中，为每一种需要实例化的类产生一个方法。一个方法工厂产生每个特别类的正确方法。一个代码编译对象缓冲来自代码工厂的方法请求，以产生最终源代码文件的内容。<br />
<br />
　　MethodCode对象是代码产生过程的核心。以下就是一个int的代码产生对象的例子：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>public class MethodForInt extends MethodCode {<br />
            private final static MethodParameter param = new MethodParameter(SimpleFileIterator.class, "parser");<br />
            <br />
            public MethodForInt(Class type, CodeBuilder builder){<br />
            super(type, builder);<br />
            }<br />
            <br />
            public MethodParameter[] getInputParameters(){<br />
            return new MethodParameter[]{<br />
            param<br />
            };<br />
            }<br />
            <br />
            public MethodParameter[] getInstanceParameters(){<br />
            return getInputParameters();<br />
            }<br />
            <br />
            protected String getImplBody(CodeBuilder builder){<br />
            return "return " + param.getName() + ".nextInt();\n";<br />
            }<br />
            }</td>
        </tr>
    </tbody>
</table>
<br />
　　基类MethodCode完成全部的工作。在代码产生的过程中，MethodCode类决定方法名字以及用作实现的框架代码。MethodForInt类只需要为它的方法定义所有的数据规范。其中最重要的部分是getImplBody(CodeBuilder builder) 方法。这就是定义函数的地方。getInputParameters()和 getInstanceParameters()这两个方法定义函数的签名。函数签名不但声明了函数，而且还定义了如何在其它函数中调用它。MethodForInt类在代码产生时产生以下的代码：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>/** Generated Load method for int**/<br />
            final public static int loadint(com.thoughtworks.rettig.util.SimpleFileIterator parser){<br />
            return parser.nextInt();<br />
            }</td>
        </tr>
    </tbody>
</table>
<br />
　　<strong>无缝产生</strong><br />
<br />
　　在编译阶段，代码产生为源代码产生带来了额外的负担。你可以使用一个方便的配置编译工具（例如Ant）来处理这个问题。在这里，我要为这篇文章的例子产生代码，我创建了以下的任务：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td><target name="GenerateLoad"><br />
            <java <br classname="com.thoughtworks.rettig.loadGenerator.LoadWriter" />
            dir = "."<br />
            fork = "yes"&gt;<br />
            <arg value="com.thoughtworks.rettig.example.generated"  /><br />
            <arg value="com.thoughtworks.rettig.example.PurchaseOrder"  /><br />
            </java><br />
            </target></td>
        </tr>
    </tbody>
</table>
<br />
　　两个参数指定了源代码的目的包，以及用来创建载入过程的类。一旦定义好任务并且将它集成到编译的过程中，代码产生就会成为编译过程的一部分。<br />
<br />
　　<strong>对比工作方案</strong><br />
<br />
　　对于这两个工作方案，我们现在来回顾分析一下。<br />
<br />
　　当你在运行时遇到问题时，这些方案的真正不同之处是显而易见的。在runtime reflection的方案中，由于广泛地使用reflection和递归，你可能得到的是一个难懂的堆栈跟踪。产生代码的方式可让你得到一个简单的堆栈跟踪，这样你就可以回溯到产生的源代码作调试。<br />
<br />
　　以下就是一个例子，由同样的错误产生的两种堆栈跟踪。我将让你判断一下使用哪一种作调试。（要注意的是为了便于阅读，我已经移除了com.thoughtworks.rettig包的限定）<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>Runtime Reflection Exception:<br />
            java.lang.NumberFormatException: itemName<br />
            at java.lang.Integer.parseInt(Integer.java:409)<br />
            at java.lang.Integer.parseInt(Integer.java:458)<br />
            at ...util.SimpleFileIterator.nextInt(SimpleFileIterator.java:82)<br />
            at ...dataLoader.SimpleFileLoadManager$1.load(SimpleFileLoadManager.java:44)<br />
            at ...dataLoader.ReflectiveObjectLoader.initializeInstance(ReflectiveObjectLoader.java:129)<br />
            at ...dataLoader.ReflectiveObjectLoader.constructObject(ReflectiveObjectLoader.java, Compiled Code)<br />
            at ...dataLoader.ReflectiveObjectLoader.initializeInstance(ReflectiveObjectLoader.java:134)<br />
            at ...dataLoader.ReflectiveObjectLoader.constructObjectArray(ReflectiveObjectLoader.java, Compiled Code)<br />
            at ...dataLoader.ReflectiveObjectLoader.initializeArray(ReflectiveObjectLoader.java:39)<br />
            at ...dataLoader.ReflectiveObjectLoader.initializeInstance(ReflectiveObjectLoader.java:123)<br />
            at ...dataLoader.ReflectiveObjectLoader.constructObject(ReflectiveObjectLoader.java, Compiled Code)<br />
            at ...dataLoader.ReflectiveObjectLoader.initializeInstance(ReflectiveObjectLoader.java:134)<br />
            at ...dataLoader.ReflectiveObjectLoader.initializeInstance(ReflectiveObjectLoader.java:103)<br />
            </td>
        </tr>
    </tbody>
</table>
<br />
　　以下是产生代码的Exception<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>java.lang.NumberFormatException: itemName<br />
            at java.lang.Integer.parseInt(Integer.java:409)<br />
            at java.lang.Integer.parseInt(Integer.java:458)<br />
            at ...util.SimpleFileIterator.nextInt(SimpleFileIterator.java:82)<br />
            at ....example.generated.PurchaseOrderLoader.loadint(PurchaseOrderLoader.java:32)<br />
            at ....example.generated.PurchaseOrderLoader.loadLineItem(PurchaseOrderLoader.java:22)<br />
            at ....example.generated.PurchaseOrderLoader.loadLineItemArray(PurchaseOrderLoader.java, Compiled Code)<br />
            at ....example.generated.PurchaseOrderLoader.loadPurchaseOrder(PurchaseOrderLoader.java:27) <br />
            </td>
        </tr>
    </tbody>
</table>
<br />
　　对于runtime reflection，我们要分离出问题的话需要作很多的记录日志。在载入的过程中，大量地使用记录日志明显是不适合的。使用reflection，你可以令堆栈跟踪更加有意义，不过这会令已经复杂的环境更加复杂化。使用产生代码的方法时，得到的代码只是记下runtime reflection将如何处理这些情形。<br />
<br />
　　这两种实现方式在性能方面也有着区别。我惊奇地发现，在使用runtime reflection时，我的例子载入要慢4到7倍。<br />
<br />
　　一个典型的运行结果如下所示：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>java com.thoughtworks.rettig.example.TestPerformance<br />
            Number of Iterations: 100000<br />
            <br />
            Generated<br />
            Total time: 14481<br />
            Max Memory Used: 1337672<br />
            <br />
            Reflection<br />
            Total time: 89219<br />
            Max Memory Used: 1407944</td>
        </tr>
    </tbody>
</table>
<br />
　　这个延迟可以归结于在运行时，reflection需要时间来发现类的属性，而产生代码的方法只是由显式的调用构成。Runtime reflection使用的内存也要多一些，但并不是多很多。当然，reflection可以作更好的优化，但是该优化将会非常复杂，而且优化的结果可能也远远比不上一个直接的方案。<br />
<br />
　　相反地，产生代码方式的优化是一件轻而易举的事情。在以前的一项目中使用了类似的代码产生器，我通过优化载入的过程从而使用更少的内存。我只需要几分钟变可以将代码产生器修改好。在优化后代码产生了一个bug，不过堆栈跟踪很直接地指出了代码产生过程中的问题，我很快就改正过来了。在runtime reflection时，我将不会尝试作同样的优化，因为实在是太费劲了。<br />
<br />
　　<strong>运行源代码</strong><br />
<br />
　　如果你查看一下源代码，你将可以更好地掌握这里谈到的几个问题。要编译和运行源代码，这需要将其中的文件解压到一个空的目录，然后在命令行运行ant Install。这样将会使用Ant的编译脚本来产生、编译源代码，并且作单元测试。（这里假定你已经安装了Ant和JUnit 3.7）<br />
<br />
　　我创建了一个例子，它是一个简单的购买订单，该订单由几类对象组成。JUnit测试案例解释了你如何使用每个方法从一个文件创造一个购买订单。测试案例然后验证对象的内容，以确保数据被正确地装载。你可以由以下的包中得到测试的内容和所有支持的类：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>com.thoughtworks.rettig.example <br />
            com.thoughtworks.rettig.example.reflection <br />
            com.thoughtworks.rettig.example.generated</td>
        </tr>
    </tbody>
</table>
<br />
　　在两个测试案例之间最值得注意的不同是runtime reflection无需支持代码来装载数据。这就是reflection的神奇所在。它仅需要类定义和源数据的位置来载入数据，而产生代码的方式在它可以创建测试案例前，需要一个产生的载入类。<br />
<br />
　　在对象创建过程中，两者是非常相似的。以下就是reflection的代码：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>SimpleFileIterator iter = new SimpleFileIterator(fileLocation);<br />
            LoadManager manager = new SimpleFileLoadManager(iter);<br />
            PurchaseOrder obj = (PurchaseOrder) ReflectiveObjectLoader.initializeInstance(PurchaseOrder.class, manager);</td>
        </tr>
    </tbody>
</table>
<br />
　　以下就是产生的代码：<br />
<br />
<table width="100%" bgcolor="#ffffff">
    <tbody>
        <tr>
            <td>SimpleFileIterator iter = new SimpleFileIterator(file);<br />
            PurchaseOrder po = PurchaseOrderLoader.loadPurchaseOrder(iter);<br />
            </td>
        </tr>
    </tbody>
</table>
<br />
　　<strong>总结</strong><br />
<br />
　　reflection的好处是非常明显的。当与代码产生结合时，它就成为一个无价的、更重要的、安全的工具。通常没有其它的方式来进行许多表面上多余的任务。对于代码产生：我用得越多，就越喜欢它。通过不断地修改和改进功能，代码变得更为清晰易懂，而runtime reflection的效果则相反，我加入的功能越多，它就变得越复杂。<br />
<br />
　　所以，如果你感到将来要使用reflection来解决一个复杂的问题，要记得以下一条规律：不要在runtime时做。<br />
<br />
</div>
</div>
  <img src ="http://www.blogjava.net/gp213/aggbug/270641.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 16:31 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270641.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java Reflection (JAVA反射)详解 </title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270623.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 07:48:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270623.html</guid><description><![CDATA[Reflection是<a class="channel_keylink" href="http://java.chinaitlab.com/" target="_blank">Java</a> 程序开发语言的特征之一，它允许运行中的 <a class="channel_keylink" href="http://java.chinaitlab.com/" target="_blank">Java</a> 程序对自身进行检查，或者说"自审"，并能直接操作程序的内部属性。例如，使用它能获得 Java 类中各成员的名称并显示出来。
<p>　　Java 的这一能力在实际应用中也许用得不是很多，但是在其它的程序设计语言中根本就不存在这一特性。例如，Pascal、C 或者 <a class="channel_keylink" href="http://c.chinaitlab.com/" target="_blank">C++</a>&nbsp; 中就没有办法在程序中获得函数定义相关的信息。</p>
<p>　　JavaBean 是 reflection 的实际应用之一，它能让一些工具可视化的操作软件组件。这些工具通过 reflection 动态的载入并取得 Java 组件（类） 的属性。</p>
<p>　　1. 一个简单的例子</p>
<p>　　考虑下面这个简单的例子，让我们看看 reflection 是如何工作的。</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>
            <p class="code">import java.lang.reflect.*;<br />
            public class DumpMethods {<br />
            public static void main(String args[]) {<br />
            try {<br />
            Class c = Class.forName(args[0]);<br />
            Method m[] = c.getDeclaredMethods();<br />
            for (int i = 0; i &lt; m.length; i++)<br />
            System.out.println(m[i].toString());<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</p>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　按如下语句执行：</p>
<p>　　java DumpMethods java.util.Stack</p>
<p>　　它的结果输出为：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>public java.lang.Object java.util.Stack.push(java.lang.Object)<br />
            <br />
            public synchronized java.lang.Object java.util.Stack.pop()<br />
            <br />
            public synchronized java.lang.Object java.util.Stack.peek()<br />
            <br />
            public boolean java.util.Stack.empty()<br />
            <br />
            public synchronized int java.util.Stack.search(java.lang.Object)</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　这样就列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。</p>
<p>　　这个程序使用 Class.forName 载入指定的类，然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。</p>
<p>　　2.开始使用 Reflection</p>
<p>　　用于 reflection 的类，如 Method，可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤：第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中，用 java.lang.Class 类来描述类和接口等。</p>
<p>　　下面就是获得一个 Class 对象的方法之一：</p>
<p>　　Class c = Class.forName（"java.lang.String"）；</p>
<p>　　这条语句得到一个 String 类的类对象。还有另一种方法，如下面的语句：</p>
<p>　　Class c = int.class；</p>
<p>　　或者</p>
<p>　　Class c = Integer.TYPE；</p>
<p>　　它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 （如 Integer） 中预先定义好的 TYPE 字段。</p>
<p>　　第二步是调用诸如 getDeclaredMethods 的方法，以取得该类中定义的所有方法的列表。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一旦取得这个信息，就可以进行第三步了——使用 reflection API 来操作这些信息，如下面这段代码： </p>
<p>　　Class c = Class.forName（"java.lang.String"）；</p>
<p>　　Method m[] = c.getDeclaredMethods（）；</p>
<p>　　System.out.println（m[0].toString（））；</p>
<p>　　它将以文本方式打印出 String 中定义的第一个方法的原型。</p>
<p>　　在下面的例子中，这三个步骤将为使用 reflection 处理特殊应用程序提供例证。</p>
<p>　　模拟 instanceof 操作符</p>
<p>　　得到类信息之后，通常下一个步骤就是解决关于 Class 对象的一些基本的问题。例如，Class.isInstance 方法可以用于模拟 instanceof 操作符：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>class A {<br />
            }<br />
            <br />
            public class instance1 {<br />
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("A");<br />
            boolean b1 = cls.isInstance(new Integer(37));<br />
            System.out.println(b1);<br />
            boolean b2 = cls.isInstance(new A());<br />
            System.out.println(b2);<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　在这个例子中创建了一个 A 类的 Class 对象，然后检查一些对象是否是 A 的实例。Integer（37） 不是，但 new A（）是。</p>
<p>　　3.找出类的方法</p>
<p>　　找出一个类中定义了些什么方法，这是一个非常有价值也非常基础的 reflection 用法。下面的代码就实现了这一用法：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>
            <p class="code">import java.lang.reflect.*;<br />
            <br />
            public class method1 {<br />
            private int f1(Object p, int x) throws NullPointerException {<br />
            if (p == null)<br />
            throw new NullPointerException();<br />
            return x;<br />
            }<br />
            <br />
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("method1");<br />
            Method methlist[] = cls.getDeclaredMethods();<br />
            for (int i = 0; i &lt; methlist.length; i++) {<br />
            Method m = methlist[i];<br />
            System.out.println("name = " + m.getName());<br />
            System.out.println("decl class = " + m.getDeclaringClass());<br />
            Class pvec[] = m.getParameterTypes();<br />
            for (int j = 0; j &lt; pvec.length; j++)<br />
            System.out.println("param #" + j + " " + pvec[j]);<br />
            Class evec[] = m.getExceptionTypes();<br />
            for (int j = 0; j &lt; evec.length; j++)<br />
            System.out.println("exc #" + j + " " + evec[j]);<br />
            System.out.println("return type = " + m.getReturnType());<br />
            System.out.println("-----");<br />
            }<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</p>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　这个程序首先取得 method1 类的描述，然后调用 getDeclaredMethods 来获取一系列的 Method 对象，它们分别描述了定义在类中的每一个方法，包括 public 方法、protected 方法、package 方法和 private 方法等。如果你在程序中使用 getMethods 来代替 getDeclaredMethods，你还能获得继承来的各个方法的信息。<br />
<br />
取得了 Method 对象列表之后，要显示这些方法的参数类型、异常类型和返回值类型等就不难了。这些类型是基本类型还是类类型，都可以由描述类的对象按顺序给出。 </p>
<p>　　输出的结果如下：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>
            <p class="code">name = f1<br />
            <br />
            decl class = class method1<br />
            <br />
            </p>
            <div id="w_hzh">&nbsp;</div>
            param #0 class java.lang.Object<br />
            <br />
            param #1 int<br />
            <br />
            exc #0 class java.lang.NullPointerException<br />
            <br />
            return type = int<br />
            <br />
            -----<br />
            name = main<br />
            <br />
            decl class = class method1<br />
            <br />
            param #0 class [Ljava.lang.String;<br />
            <br />
            return type = void</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　4.获取构造器信息</p>
<p>　　获取类构造器的用法与上述获取方法的用法类似，如：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>import java.lang.reflect.*;<br />
            <br />
            public class constructor1 {<br />
            public constructor1() {<br />
            }<br />
            <br />
            protected constructor1(int i, double d) {<br />
            }<br />
            <br />
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("constructor1");<br />
            Constructor ctorlist[] = cls.getDeclaredConstructors();<br />
            for (int i = 0; i &lt; ctorlist.length; i++) {<br />
            Constructor ct = ctorlist[i];<br />
            System.out.println("name = " + ct.getName());<br />
            System.out.println("decl class = " + ct.getDeclaringClass());<br />
            Class pvec[] = ct.getParameterTypes();<br />
            for (int j = 0; j &lt; pvec.length; j++)<br />
            System.out.println("param #" + j + " " + pvec[j]);<br />
            Class evec[] = ct.getExceptionTypes();<br />
            for (int j = 0; j &lt; evec.length; j++)<br />
            System.out.println("exc #" + j + " " + evec[j]);<br />
            System.out.println("-----");<br />
            }<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　这个例子中没能获得返回类型的相关信息，那是因为构造器没有返回类型。</p>
<p>　　这个程序运行的结果是：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>
            <p class="code">name = constructor1<br />
            <br />
            decl class = class constructor1<br />
            <br />
            -----<br />
            name = constructor1<br />
            <br />
            decl class = class constructor1<br />
            <br />
            param #0 int<br />
            <br />
            param #1 double</p>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;5.获取类的字段（域）
<p>　　找出一个类中定义了哪些数据字段也是可能的，下面的代码就在干这个事情：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>
            <p class="code">import java.lang.reflect.*;<br />
            <br />
            public class field1 {<br />
            private double d;<br />
            public static final int i = 37;<br />
            String s = "testing";<br />
            <br />
            </p>
            <div id="w_hzh">&nbsp;</div>
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("field1");<br />
            Field fieldlist[] = cls.getDeclaredFields();<br />
            for (int i = 0; i &lt; fieldlist.length; i++) {<br />
            Field fld = fieldlist[i];<br />
            System.out.println("name = " + fld.getName());<br />
            System.out.println("decl class = " + fld.getDeclaringClass());<br />
            System.out.println("type = " + fld.getType());<br />
            int mod = fld.getModifiers();<br />
            System.out.println("modifiers = " + Modifier.toString(mod));<br />
            System.out.println("-----");<br />
            }<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　这个例子和前面那个例子非常相似。例中使用了一个新东西 Modifier，它也是一个 reflection 类，用来描述字段成员的修饰语，如&#8220;private int&#8221;。这些修饰语自身由整数描述，而且使用 Modifier.toString 来返回以&#8220;官方&#8221;顺序排列的字符串描述 （如&#8220;static&#8221;在&#8220;final&#8221;之前）。这个程序的输出是：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>
            <p class="code">name = d<br />
            <br />
            decl class = class field1<br />
            <br />
            type = double<br />
            <br />
            modifiers = private<br />
            <br />
            -----<br />
            name = i<br />
            <br />
            decl class = class field1<br />
            <br />
            type = int<br />
            <br />
            modifiers = public static final<br />
            <br />
            -----<br />
            name = s<br />
            <br />
            decl class = class field1<br />
            <br />
            type = class java.lang.String<br />
            <br />
            modifiers =</p>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　和获取方法的情况一下，获取字段的时候也可以只取得在当前类中申明了的字段信息 （getDeclaredFields），或者也可以取得父类中定义的字段 （getFields） .</p>
<p>　　6.根据方法的名称来执行方法</p>
<p>　　文本到这里，所举的例子无一例外都与如何获取类的信息有关。我们也可以用 reflection 来做一些其它的事情，比如执行一个指定了名称的方法。下面的示例演示了这一操作：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>
            <p class="code">import java.lang.reflect.*;<br />
            public class method2 {<br />
            public int add(int a, int b) {<br />
            return a + b;<br />
            }<br />
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("method2");<br />
            Class partypes[] = new Class[2];<br />
            partypes[0] = Integer.TYPE;<br />
            partypes[1] = Integer.TYPE;<br />
            Method meth = cls.getMethod("add", partypes);<br />
            method2 methobj = new method2();<br />
            Object arglist[] = new Object[2];<br />
            arglist[0] = new Integer(37);<br />
            arglist[1] = new Integer(47);<br />
            Object retobj = meth.invoke(methobj, arglist);<br />
            Integer retval = (Integer) retobj;<br />
            System.out.println(retval.intValue());<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</p>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　假如一个程序在执行的某处的时候才知道需要执行某个方法，这个方法的名称是在程序的运行过程中指定的 （例如，JavaBean 开发环境中就会做这样的事），那么上面的程序演示了如何做到。</p>
<p>　　上例中，getMethod用于查找一个具有两个整型参数且名为 add 的方法。找到该方法并创建了相应的Method 对象之后，在正确的对象实例中执行它。执行该方法的时候，需要提供一个参数列表，这在上例中是分别包装了整数 37 和 47 的两个 Integer 对象。执行方法的返回的同样是一个 Integer 对象，它封装了返回值 84.<br />
<br />
7.创建新的对象 </p>
<p>　　对于构造器，则不能像执行方法那样进行，因为执行一个构造器就意味着创建了一个新的对象 （准确的说，创建一个对象的过程包括分配内存和构造对象）。所以，与上例最相似的例子如下：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>import java.lang.reflect.*;<br />
            <br />
            public class constructor2 {<br />
            public constructor2() {<br />
            }<br />
            <br />
            public constructor2(int a, int b) {<br />
            System.out.println("a = " + a + " b = " + b);<br />
            }<br />
            <br />
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("constructor2");<br />
            Class partypes[] = new Class[2];<br />
            partypes[0] = Integer.TYPE;<br />
            partypes[1] = Integer.TYPE;<br />
            Constructor ct = cls.getConstructor(partypes);<br />
            Object arglist[] = new Object[2];<br />
            arglist[0] = new Integer(37);<br />
            arglist[1] = new Integer(47);<br />
            Object retobj = ct.newInstance(arglist);<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　根据指定的参数类型找到相应的构造函数并执行它，以创建一个新的对象实例。使用这种方法可以在程序运行时动态地创建对象，而不是在编译的时候创建对象，这一点非常有价值。</p>
<p>　　8.改变字段（域）的值</p>
<p>　　reflection 的还有一个用处就是改变对象数据字段的值。reflection 可以从正在运行的程序中根据名称找到对象的字段并改变它，下面的例子可以说明这一点：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>import java.lang.reflect.*;<br />
            <br />
            public class field2 {<br />
            public double d;<br />
            <br />
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("field2");<br />
            Field fld = cls.getField("d");<br />
            field2 f2obj = new field2();<br />
            System.out.println("d = " + f2obj.d);<br />
            fld.setDouble(f2obj, 12.34);<br />
            System.out.println("d = " + f2obj.d);<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　这个例子中，字段 d 的值被变为了 12.34.</p>
<p>　　9.使用数组</p>
<p>　　本文介绍的 reflection 的最后一种用法是创建的操作数组。数组在 Java 语言中是一种特殊的类类型，一个数组的引用可以赋给 Object 引用。观察下面的例子看看数组是怎么工作的：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>import java.lang.reflect.*;<br />
            <br />
            public class array1 {<br />
            public static void main(String args[]) {<br />
            try {<br />
            Class cls = Class.forName("java.lang.String");<br />
            Object arr = Array.newInstance(cls, 10);<br />
            Array.set(arr, 5, "this is a test");<br />
            String s = (String) Array.get(arr, 5);<br />
            System.out.println(s);<br />
            } catch (Throwable e) {<br />
            System.err.println(e);<br />
            }<br />
            }<br />
            }</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　例中创建了 10 个单位长度的 String 数组，为第 5 个位置的字符串赋了值，最后将这个字符串从数组中取得并打印了出来。</p>
<p>　　下面这段代码提供了一个更复杂的例子：</p>
<p align="center">
<table cellspacing="1" cellpadding="0" width="80%" bgcolor="#cccccc">
    <tbody>
        <tr>
            <td bgcolor="#f2f2f2">
            <pre>import java.lang.reflect.*;<br />
            <br />
            public class array2 {<br />
            public static void main(String args[]) {<br />
            int dims[] = new int[]{5, 10, 15};<br />
            Object arr = Array.newInstance(Integer.TYPE, dims);<br />
            Object arrobj = Array.get(arr, 3);<br />
            Class cls = arrobj.getClass().getComponentType();<br />
            System.out.println(cls);<br />
            arrobj = Array.get(arrobj, 5);<br />
            Array.setInt(arrobj, 10, 37);<br />
            int arrcast[][][] = (int[][][]) arr;<br />
            System.out.println(arrcast[3][5][10]);<br />
            }<br />
            }</pre>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>　　例中创建了一个 5 x 10 x 15 的整型数组，并为处于 [3][5][10] 的元素赋了值为 37.注意，多维数组实际上就是数组的数组，例如，第一个 Array.get 之后，arrobj 是一个 10 x 15 的数组。进而取得其中的一个元素，即长度为 15 的数组，并使用 Array.setInt 为它的第 10 个元素赋值。</p>
<p>　　注意创建数组时的类型是动态的，在编译时并不知道其类型。</p>
  <img src ="http://www.blogjava.net/gp213/aggbug/270623.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 15:48 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270623.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java反射机制(reflect)解决   （调用一个类中多个规律命名的方法的办法）</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270616.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 07:27:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270616.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270616.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270616.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270616.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270616.html</trackback:ping><description><![CDATA[在实际生产中常常会遇见这样一种情况，就是由于数据库设计或者页面设计导致的 <br />
批量操作大量规律命名的对象。 <br />
例如下面这样
<div class="dp-highlighter">
<ol class="dp-j">
    <li>
    <pre class="java" style="display: none" name="code">create table tt
    (
    Q1_CODE		VARCHAR2(3),
    Q2_CODE		VARCHAR2(3),
    .....
    Q100_CODE       VARCHAR2(3),
    );</pre>
    &nbsp; 恕本人抱怨一句，设计出这样的表的人八成是吃得有点饱。 <br />
    <br />
    呵呵于是利用工具生成了JavaBean一般就是Struts的form,有的叫做DTO,有的叫做Ao,bo,bao,dao..........众多名字。访问数据库的时候就用它们做容器。 <br />
    面对100个get方法，一一调用，太无聊了。有什么简便方法呢.如果有像JS的eval()的方法就会方便多了。可惜啊。 <br />
    不过！！<span class="hilite1">java</span>中有一种神奇的机制，娃哈哈<strong><span class="hilite1">Java</span> Reflection </strong><br />
    <br />
    以下是我参考的两个网址 <br />
    <a href="http://www-128.ibm.com/developerworks/cn/java/j-dyn0603/" target="_blank">http://www-128.ibm.com/developerworks/cn/<span class="hilite1">java</span>/j-dyn0603/</a> <br />
    <a href="http://dev.csdn.net/article/49/49876.shtm" target="_blank">http://dev.csdn.net/article/49/49876.shtm</a> <br />
    大家想要仔细了解就去看看吧。 <br />
    <br />
    现在介绍一下我的具体实现： <br />
    </li>
</ol>
</div>
<div class="dp-highlighter">
<ol class="dp-j">
    <li><span><span class="keyword">public</span><span>&nbsp;</span><span class="keyword">static</span><span>&nbsp;</span><span class="keyword">void</span><span>&nbsp;main(String&nbsp;args[])&nbsp;{ &nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">try</span><span>&nbsp;{ &nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;c&nbsp;=&nbsp;Class.forName(</span><span class="string">"com.TtDto"</span><span>); &nbsp;&nbsp;</span></span></li>
    <li><span class="comment">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //这里面的类名要写全，连包名一起写 </span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Method&nbsp;m[]&nbsp;=&nbsp;c.getDeclaredMethods(); &nbsp;&nbsp;</span></li>
    <li><span class="comment">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //method的方法有很多下面会讲一讲 </span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TtDto&nbsp;methobj&nbsp;=&nbsp;</span><span class="keyword">new</span><span>&nbsp;TtDto(); &nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>&nbsp;(</span><span class="keyword">int</span><span>&nbsp;i&nbsp;=&nbsp;</span><span class="number">0</span><span>;&nbsp;i&nbsp;&lt;&nbsp;m.length;&nbsp;i++)&nbsp;{ &nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(</span><span class="string">"name&nbsp;=&nbsp;"</span><span>&nbsp;+&nbsp;m[i].getName()); &nbsp;&nbsp;</span></span></li>
    <li><span class="comment">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //打印出来的名字同羊可以用来做处理，String向怎么处理就怎么处理。 </span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Object&nbsp;retobj&nbsp;=&nbsp;m[i].invoke(methobj,&nbsp;null); </span><span>&nbsp;&nbsp;</span></span></li>
    <li><span class="comment">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //这部才是非常重要的调用，但是你可以通过上一步的处理来决定是否进行调用来完成奇怪的调用。 </span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;</span><span class="keyword">catch</span><span>&nbsp;(Throwable&nbsp;e)&nbsp;{ &nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.err.println(e); &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>
</ol>
</div>
<br />
<br />
这样你就看到这个类下面的所有方法了。把注释掉的代码用上就可以调用你看到的方法，了哈哈，怎么样，很好玩吧。 <br />
下面讲两个方法：
<div class="quote_div">public Object <a href="http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/lang/reflect/Method.html#invoke(java.lang.Object,%20java.lang.Object...)url" target="_blank">invoke</a>(Object obj,Object... args) <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws IllegalAccessException, <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IllegalArgumentException</div>
如果基础方法是静态的，那么可以忽略指定的 obj 参数。该参数可以为 null。 <br />
<br />
如果基础方法所需的形参数为 0，则所提供的 args 数组长度可以为 0 或 null。 <br />
<br />
参数： <br />
obj - 从中调用基础方法的对象 <br />
args - 用于方法调用的参数 <br />
返回： <br />
使用参数 args 在 obj 上指派该对象所表示方法的结果 <br />
可以查阅JDK文档。 <br />
getDeclaredMethods和getMethods差不多都是返回一个方法的数组。当然了你也可以按照方法的名字来调用方法 <br />
<img src ="http://www.blogjava.net/gp213/aggbug/270616.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 15:27 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270616.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java中对HashMap的深度分析</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270595.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 06:25:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270595.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270595.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270595.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270595.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270595.html</trackback:ping><description><![CDATA[<p>在Java的世界里，无论类还是各种数据，其结构的处理是整个程序的逻辑以及性能的关键。由于本人接触了一个有关性能与逻辑同时并存的问题，于是就开始研究这方面的问题。找遍了大大小小的论坛，也把《Java 虚拟机规范》，《apress,.java.collections.(2001),.bm.ocr.6.0.shareconnector》，和《Thinking in Java》翻了也找不到很好的答案，于是一气之下把JDK的src 解压出来研究，扩然开朗，遂写此文，跟大家分享感受和顺便验证我理解还有没有漏洞。 这里就拿HashMap来研究吧。 </p>
<p>HashMap可谓JDK的一大实用工具，把各个Object映射起来，实现了&#8220;键－－值&#8221;对应的快速存取。但实际里面做了些什么呢？ </p>
<p>在这之前，先介绍一下负载因子和容量的属性。大家都知道其实一个 HashMap 的实际容量就 因子*容量，其默认值是 16&#215;0.75＝12； 这个很重要，对效率很一定影响！当存入HashMap的对象超过这个容量时，HashMap 就会重新构造存取表。这就是一个大问题，我后面慢慢介绍，反正，如果你已经知道你大概要存放多少个对象，最好设为该实际容量的能接受的数字。 </p>
<p><strong>两个关键的方法，put和get：</strong> </p>
<p>先有这样一个概念，HashMap是声明了 Map，Cloneable, Serializable 接口，和继承了 AbstractMap 类，里面的 Iterator 其实主要都是其内部类HashIterator 和其他几个 iterator 类实现，当然还有一个很重要的继承了Map.Entry 的 Entry 内部类，由于大家都有源代码，大家有兴趣可以看看这部分，我主要想说明的是 Entry 内部类。它包含了hash，value，key 和next 这四个属性，很重要。put的源码如下 </p>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#999999" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <pre>public Object put(Object key, Object value) {
            Object k = maskNull(key); </pre>
            </td>
        </tr>
    </tbody>
</table>
<p>这个就是判断键值是否为空，并不很深奥，其实如果为空，它会返回一个static Object 作为键值，这就是为什么HashMap允许空键值的原因。</p>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#999999" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <pre>int hash = hash(k);
            int i = indexFor(hash, table.length);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p>这连续的两步就是 HashMap 最牛的地方！研究完我都汗颜了，其中 hash 就是通过 key 这个Object的 hashcode 进行 hash，然后通过 indexFor 获得在Object table的索引值。 </p>
<p>table？不要惊讶，其实HashMap也神不到哪里去，它就是用 table 来放的。最牛的就是用 hash 能正确的返回索引。其中的hash算法，我跟JDK的作者 Doug 联系过，他建议我看看《The art of programing vol3》可恨的是，我之前就一直在找，我都找不到，他这样一提，我就更加急了，可惜口袋空空啊！ </p>
<p>不知道大家有没有留意 put 其实是一个有返回的方法，它会把相同键值的 put 覆盖掉并返回旧的值！如下方法彻底说明了 HashMap 的结构，其实就是一个表加上在相应位置的Entry的链表：</p>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#999999" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <pre>for (Entry e = table[i]; e != null; e = e.next) {
            if (e.hash == hash &amp;&amp; eq(k, e.key)) {
            Object oldvalue = e.value;
            e.value = value; //把新的值赋予给对应键值。
            e.recordAccess(this); //空方法，留待实现
            return oldvalue; //返回相同键值的对应的旧的值。
            }
            }
            modCount++; //结构性更改的次数
            addEntry(hash, k, value, i); //添加新元素，关键所在！
            return null; //没有相同的键值返回
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
我们把关键的方法拿出来分析： <br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#999999" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <pre>void addEntry(int hash, Object key, Object value, int bucketIndex) {
            table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
因为 hash 的算法有可能令不同的键值有相同的hash码并有相同的table索引，如：key＝&#8220;33&#8221;和key＝Object g的hash都是－8901334，那它经过indexfor之后的索引一定都为i，这样在new的时候这个Entry的next就会指向这个原本的table[i]，再有下一个也如此，形成一个链表，和put的循环对定e.next获得旧的值。到这里，HashMap的结构，大家也十分明白了吧？ <br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#999999" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <pre>if (size++ &gt;= threshold) //这个threshold就是能实际容纳的量
            resize(2 * table.length); //超出这个容量就会将Object table重构
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<p>所谓的重构也不神，就是建一个两倍大的table（我在别的论坛上看到有人说是两倍加1，把我骗了），然后再一个个indexfor进去！注意！！这就是效率！！如果你能让你的HashMap不需要重构那么多次，效率会大大提高！ </p>
<p>说到这里也差不多了，get比put简单得多，大家，了解put，get也差不了多少了。对于collections我是认为，它是适合广泛的，当不完全适合特有的，如果大家的程序需要特殊的用途，自己写吧，其实很简单。（作者是这样跟我说的，他还建议我用LinkedHashMap,我看了源码以后发现，LinkHashMap其实就是继承HashMap的，然后override相应的方法，有兴趣的同人，自己looklook）建个 Object table，写相应的算法，就ok啦。 </p>
<p>举个例子吧，像 Vector，list 啊什么的其实都很简单，最多就多了的同步的声明，其实如果要实现像Vector那种，插入，删除不多的，可以用一个Object table来实现，按索引存取，添加等。 </p>
<p>如果插入，删除比较多的，可以建两个Object table，然后每个元素用含有next结构的，一个table存，如果要插入到i，但是i已经有元素，用next连起来，然后size＋＋，并在另一个table记录其位置。</p>
<img src ="http://www.blogjava.net/gp213/aggbug/270595.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 14:25 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270595.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HashMap原理及冲突之简谈</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270586.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 05:32:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270586.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270586.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270586.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270586.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270586.html</trackback:ping><description><![CDATA[<p style="text-indent: 21pt"><span style="font-family: 宋体">了解</span>HashMap<span style="font-family: 宋体">原理对于日后的缓存机制多少有些认识。在网络中也有很多方面的帖子，但是很多都是轻描淡写，很少有把握的比较准确的信息，在这里试着不妨说解一二。</span></p>
<p>&nbsp;&nbsp;&nbsp;<span style="font-family: 宋体">对于</span>HashMap<span style="font-family: 宋体">主要以键值</span>(key-value)<span style="font-family: 宋体">的方式来体现，笼统的说就是采用</span>key<span style="font-family: 宋体">值的哈希算法来，外加取余最终获取索引，而这个索引可以认定是一种地址，既而把相应的</span>value<span style="font-family: 宋体">存储在地址指向内容中。这样说或许比较概念化，也可能复述不够清楚，来看列式更加清晰：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; hash=key.hashCode();//------------------------1</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; index=hash%table.lenth;//table<span style="font-family: 宋体">表示当前对象的长度</span>-----------------------2</p>
</div>
<p><span style="font-family: 宋体">其实最终就是这两个式子决定了值得存储位置。但是以上两个表达式还有欠缺。为什么这么说？例如在</span>key.hashCode()<span style="font-family: 宋体">后可能存在是一个负整数，你会问：是啊，那这个时候怎么办呢？所以在这里就需要进一步加强改造式子</span>2<span style="font-family: 宋体">了，修改后的：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; index=<span style="font-family: 宋体">（</span>hash&amp;Ox7FFFFFFF)%table.lenth;</p>
</div>
<p><span style="font-family: 宋体">到这里又迷惑了，为什么上面是这样的呢？对于先前我们谈到在</span>hash<span style="font-family: 宋体">有可能产生负数的情况，这里我们使用当前的</span>hash<span style="font-family: 宋体">做一个&#8220;与&#8221;操作，在这里需要和</span>int<span style="font-family: 宋体">最大的值相&#8220;与&#8221;。这样的话就可以保证数据的统一性，把有符号的数值给&#8220;与&#8221;掉。而一般这里我们把二进制的数值转换成</span>16<span style="font-family: 宋体">进制的就变成了：</span>Ox7FFFFFFF<span style="font-family: 宋体">。（注：与操作的方式为，不同为</span>0<span style="font-family: 宋体">，相同为</span>1<span style="font-family: 宋体">）。而对于</span>hashCode()<span style="font-family: 宋体">的方法一般有：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp; public int hashCode(){</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int hash=0,offset,len=count;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char[]&nbsp;var=value;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;len;i++){</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; h=31*hash+var[offset++];</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return hash;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
</div>
<p style="text-indent: 21pt"><span style="font-family: 宋体">说道这里大家可能会说，到这里算完事了吧。但是你可曾想到如果数据都采用上面的方式，最终得到的可能</span>index<span style="font-family: 宋体">会相同怎么办呢？如果你想到的话，那恭喜你</span>!<span style="font-family: 宋体">又增进一步了，这里就是要说到一个名词：冲突率。是的就是前面说道的一旦</span>index<span style="font-family: 宋体">有相同怎么办？数据又该如何存放呢，而且这个在数据量非常庞大的时候这个基率更大。这里按照算法需要明确的一点：每个键（</span>key<span style="font-family: 宋体">）被散列分布到任何一个数组索引的可能性相同，而且不取决于其它键分布的位置。这句话怎么理解呢？从概率论的角度，也就是说如果</span>key<span style="font-family: 宋体">的个数达到一个极限，每个</span>key<span style="font-family: 宋体">分布的机率都是均等的。更进一步就是：即便</span>key1<span style="font-family: 宋体">不等于</span>key2<span style="font-family: 宋体">也还是可能</span>key1.hashCode()=key2.hashCode()<span style="font-family: 宋体">。</span></p>
<p style="text-indent: 21pt"><span style="font-family: 宋体">对于早期的解决冲突的方法有折叠法（</span>folding)<span style="font-family: 宋体">，例如我们在做系统的时候有时候会采用部门编号附加到某个单据标号后，这里比如产生一个</span>9<span style="font-family: 宋体">～</span>11<span style="font-family: 宋体">位的编码。通过对半折叠做。</span></p>
<p><span style="font-family: 宋体">现在的策略有：</span></p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 宋体">键式散列</span>&nbsp;</p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 宋体">开放地址法</span></p>
<p><span style="font-family: 宋体">在了解这两个策略前，我们先定义清楚几个名词解释：</span></p>
<div style="border-right: windowtext 0.5pt solid; padding-right: 4pt; border-top: windowtext 0.5pt solid; padding-left: 4pt; background: #d9d9d9; padding-bottom: 1pt; border-left: windowtext 0.5pt solid; padding-top: 1pt; border-bottom: windowtext 0.5pt solid">
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">threshold[<span style="font-family: 宋体">阀值</span>]<span style="font-family: 宋体">，对象大小的边界值</span>;</p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">loadFactor[<span style="font-family: 宋体">加载因子</span>]=n/m<span style="font-family: 宋体">；其中</span>n<span style="font-family: 宋体">代表对象元素个数，</span>m<span style="font-family: 宋体">表示当前表的容积最大值</span></p>
<p style="border-right: medium none; padding-right: 0cm; border-top: medium none; padding-left: 0cm; padding-bottom: 0cm; border-left: medium none; padding-top: 0cm; border-bottom: medium none">threshold=(int)table.length*loadFactor</p>
</div>
<p><span style="font-family: 宋体">清晰了这几个定义，我们再来看具体的解决方式</span></p>
<p><span style="font-family: 宋体">键式散列：</span></p>
<p style="text-indent: 27pt"><span style="font-family: 宋体">我们直接看一个实例，这样就更加清晰它的工作方式，从而避免文字定义。我们这里还是来举一个图书编号的例子，下面比如有这样一些编号：</span></p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: black">78938</span><span style="color: black">-000</span><span style="color: black">0</span></p>
<p style="text-indent: 27pt"><span style="color: black">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>45678-0001</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 72678-0002</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 24678-0001</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;16678-0001</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 98678-0003</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 85678-0002</p>
<p style="text-indent: 27pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 45232-0004</p>
<p><span style="font-family: 宋体">步骤：</span></p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 宋体">把编号作为</span>key,<span style="font-family: 宋体">即：</span>int hash=key.hashCode();</p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>int index=hash%<span style="font-family: 宋体">表大小；</span></p>
<p style="margin-left: 18pt; text-indent: -18pt; tab-stops: list 18.0pt">3.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 宋体">逐步按顺序插入对象中</span></p>
<p><span style="font-family: 宋体">现在问题出现了：对于编号通过散列算法后很可能产生相同的索引值，意味着存在冲突。</span></p>
<p style="text-align: center" align="center"><img title="点击图片可在新窗口打开" style="cursor: pointer" height="436" alt="" src="http://space.itpub.net/attachments/2008/09/14734416_200809181404441.jpg" width="617" border="0" /></p>
<p><span style="font-family: 宋体">解释上面的操作：如果对于</span>key.hashCode()<span style="font-family: 宋体">产生了冲突（比如途中对于插入</span>24678-0001<span style="font-family: 宋体">对于通过哈希算法后可能产生的</span>index<span style="font-family: 宋体">或许也是</span>501<span style="font-family: 宋体">），既而把相应的前驱有相同的</span>index<span style="font-family: 宋体">的对象指向当前引用。这也就是大家认定的单链表方式。以此类推</span>&#8230;</p>
<p><span style="font-family: 宋体">而这里存在冲突对象的元素放在</span>Entry<span style="font-family: 宋体">对象中，</span>Entry<span style="font-family: 宋体">具有以下一些属性：</span></p>
<p>int hash;</p>
<p>Object key;</p>
<p>Entry next;</p>
<p><span style="font-family: 宋体">对于</span>Entry<span style="font-family: 宋体">对象就可以直接追溯到链表数据结构体中查阅。</span></p>
<p><span style="font-family: 宋体">开放地址法：</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; tab-stops: list 21.0pt">1.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 宋体">线性地址探测法：</span></p>
<p><span style="font-family: 宋体">如何理解这个概念呢，一句话：就是通过算法规则在对象地址</span>N+1<span style="font-family: 宋体">中查阅找到为</span>NULL<span style="font-family: 宋体">的索引内容。</span></p>
<p><span style="font-family: 宋体">处理方式：如果</span>index<span style="font-family: 宋体">索引与当前的</span>index<span style="font-family: 宋体">有冲突，即把当前的索引</span>index+1<span style="font-family: 宋体">。如果在</span>index+1<span style="font-family: 宋体">已经存在占位现象（</span>index+1<span style="font-family: 宋体">的内容不为</span>NULL<span style="font-family: 宋体">）试图接着</span>index+2<span style="font-family: 宋体">执行。。。直到找到索引为内容为</span>NULL<span style="font-family: 宋体">的为止。这种处理方式也叫：线性地址探测法</span>(offset-of-1)</p>
<p><span style="font-family: 宋体">如果采用线性地址探测法会带来一个效率的不良影响。现在我们来分析这种方式会带来哪些不良因素。大家试想下如果一个非常庞大的数据存储在</span>Map<span style="font-family: 宋体">中，假如在某些记录集中有一些数据非常相似（他们产生的索引在内存的某个块中非常的密集），也就是说他们产生的索引地址是一个连续数值，而造成数据成块现象。另一个致命的问题就是在数据删除后，如果再次查询可能无法定到下一个连续数字，这个又是一个什么概念呢？例如以下图片就很好的说明开发地址散列如何把数据按照算法插入到对象中：</span></p>
<p style="text-align: center" align="center"><img title="点击图片可在新窗口打开" style="cursor: pointer" height="502" alt="" src="http://space.itpub.net/attachments/2008/09/14734416_200809181404442.jpg" width="512" border="0" /></p>
<p><span style="font-family: 宋体">对于上图的注释步骤说明：</span></p>
<p style="line-height: 119%; text-align: center" align="center"><span style="font-family: 宋体">从数据&#8220;</span>78938-0000<span style="font-family: 宋体">&#8221;开始通过哈希算法按顺序依次插入到对象中，例如</span>78938-0000<span style="font-family: 宋体">通过换</span></p>
<p style="line-height: 119%"><span style="font-family: 宋体">算得到索引为</span>0<span style="font-family: 宋体">，当前所指内容为</span>NULL<span style="font-family: 宋体">所以直接插入；</span><span style="color: black">45678-0001</span><span style="color: black; font-family: 宋体">同样通过换算得到索引为地址</span><span style="color: black">501</span><span style="color: black; font-family: 宋体">所指内容，当前内容为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">所以也可以插入；</span><span style="color: black">72678-0002</span><span style="color: black; font-family: 宋体">得到索引</span><span style="color: black">502</span><span style="color: black; font-family: 宋体">所指内容，当前内容为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">也可以插入；请注意当</span><span style="color: black">24678-0001</span><span style="color: black; font-family: 宋体">得到索引也为</span><span style="color: black">501</span><span style="color: black; font-family: 宋体">，当前地址所指内容为</span><span style="color: black">45678-0001</span><span style="color: black; font-family: 宋体">。即表示当前数据存在冲突，则直接对地址</span><span style="color: black">501+1=502</span><span style="color: black; font-family: 宋体">所指向内容为</span><span style="color: black">72678-0002</span><span style="color: black; font-family: 宋体">不为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">也不允许插入，再次对索引</span><span style="color: black">502+1=503</span><span style="color: black; font-family: 宋体">所指内容为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">允许插入。。。依次类推只要对于索引存在冲突现象，则逐次下移位知道索引地址所指为</span><span style="color: black">NULL</span><span style="color: black; font-family: 宋体">；如果索引不冲突则还是按照算法放入内容。对于这样的对象是一种插入方式，接下来就是我们的删除</span><span style="color: black">(remove)</span><span style="color: black; font-family: 宋体">方法了。按照常理对于删除，方式基本区别不大。但是现在问题又出现了，如果删除的某个数据是一个存在冲突索引的内容，带来后续的问题又会接踵而来。那是什么问题呢？我们还是同样来看看图示的描述，对于图</span><span style="color: black">-2</span><span style="color: black; font-family: 宋体">中如果删除</span><span style="color: black">(remove)</span><span style="color: black; font-family: 宋体">数据</span><span style="color: black">24678-0001</span><span style="color: black; font-family: 宋体">的方法如下图所示：</span></p>
<p style="line-height: 119%; text-align: center" align="center"><img title="点击图片可在新窗口打开" style="cursor: pointer" height="502" alt="" src="http://space.itpub.net/attachments/2008/09/14734416_200809181404443.jpg" width="512" border="0" /></p>
<p style="line-height: 119%"><span style="font-family: 宋体">对于我们会想当然的觉得只要把指向数据置为</span>NULL<span style="font-family: 宋体">就可以</span>,<span style="font-family: 宋体">这样的做法对于删除来说当然是没有问题的。如果再次定位检索数据</span>16678-0001<span style="font-family: 宋体">不会成功，因为这个时候以前的链路已经堵上了，但是需要检索的数据事实上又存在。那我们如何来解决这个问题呢？对于</span>JDK<span style="font-family: 宋体">中的</span>Entry<span style="font-family: 宋体">类中的方法存在一个：</span>boolean markedForRemoval;<span style="font-family: 宋体">它就是一个典型的删除标志位，对于对象中如果需要删除时，我们只是对于它做一个&#8220;软删除&#8221;即置一个标志位为</span>true<span style="font-family: 宋体">就可以。而插入时，默认状态为</span>false<span style="font-family: 宋体">就可以。这样的话就变成以下图所示：</span></p>
<p style="line-height: 119%; text-align: center" align="center"><img title="点击图片可在新窗口打开" style="cursor: pointer" height="502" alt="" src="http://space.itpub.net/attachments/2008/09/14734416_200809181404444.jpg" width="512" border="0" /></p>
<p style="line-height: 119%"><span style="font-family: 宋体">通过以上方式更好的解决冲突地址删除数据无法检索其他链路数据问题了。</span></p>
<p style="margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt">2.<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-family: 宋体">双散列（余商法）</span></p>
<p style="margin-left: 21pt; text-indent: 21pt; line-height: 119%"><span style="font-family: 宋体">在了解开放地址散列的时候我们一直在说解决方法，但是大家都知道一个数据结构的完善更多的是需要高效的算法。这当中我们却没有涉及到。接下来我们就来看看在开放地址散列中它存在的一些不足以及如何改善这样的方法，既而达到无论是在方法的解决上还是在算法的复杂度上更加达到高效的方案。</span></p>
<p style="margin-left: 21pt; text-indent: 21pt; line-height: 119%"><span style="font-family: 宋体">在图</span>2-1<span style="font-family: 宋体">中类似这样一些数据插入进对象，存在冲突采用不断移位加一的方式，直到找到不为</span>NULL<span style="font-family: 宋体">内容的索引地址。也正是由于这样一种可能加大了时间上的变慢。大家是否注意到像图这样一些数据目前呈现出一种连续索引的插入，而且是一种成块是的数据。如果数据量非常的庞大，或许这种可能性更大。尽管它解决了冲突，但是对于数据检索的时间度来说，我们是不敢想象的。所有分布到同一个索引</span>index<span style="font-family: 宋体">上的</span>key<span style="font-family: 宋体">保持相同的路径：</span>index,index+1,index+2&#8230;<span style="font-family: 宋体">依此类推。更加糟糕的是索引键值的检索需要从索引开始查找。正是这样的原因，对于线性探索法我们需要更进一步的改进。而刚才所描述这种成块出现的数据也就定义成：簇。而这样一种现象称之为：主簇现象。</span></p>
<p style="line-height: 119%"><span style="font-family: 宋体">（主簇：就是冲突处理允许簇加速增长时出现的现象）而开放式地址冲突也是允许主簇现象产生的。那我们如何来避免这种主簇现象呢？这个方式就是我们要来说明的：双散列解决冲突法了。主要的方式为：</span></p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>int hash=key.hasCode();</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>int index=(hash&amp;Ox7FFFFFFF)%table.length;</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span style="font-family: 宋体">按照以上方式得到索引存在冲突，则开始对当前索引移位，而移位方式为：</span></p>
<p style="background: #e5e5e5; line-height: 119%">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ffset=(hash&amp;Ox7FFFFFFF)/table.length;</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span style="font-family: 宋体">如果第一次移位还存在同样的冲突，则继续：当前冲突索引位置（索引号</span>+<span style="font-family: 宋体">余数）</span>%<span style="font-family: 宋体">表</span>.length</p>
<p style="background: #e5e5e5; margin-left: 21pt; text-indent: -21pt; line-height: 119%; tab-stops: list 21.0pt"><span style="font-family: Wingdings">u<span style="font: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span style="font-family: 宋体">如果存在的余数恰好是表的倍数，则作偏移位置为一下移，依此类推</span></p>
<p style="text-indent: 21pt; line-height: 119%"><span style="font-family: 宋体">这样双散列冲突处理就避免了主簇现象。至于</span>HashSet<span style="font-family: 宋体">的原理基本和它是一致的，这里不再复述。在这里其实还是主要说了一些简单的解决方式，而且都是在一些具体参数满足条件下的说明，像一旦数据超过初始值该需要</span>rehash<span style="font-family: 宋体">，加载因子一旦大于</span>1.0<span style="font-family: 宋体">是何种情况等等。还有很多问题都可以值得我们更加进一步讨论的，比如：在</span><a onclick="javascript:tagshow(event, 'java');" href="javascript:;" target="_self"><u><strong>java</strong></u></a>.util.HashMap<span style="font-family: 宋体">中的加载因子为什么会是</span>0.75<span style="font-family: 宋体">，而它默认的初始大小为什么又是</span>16<span style="font-family: 宋体">等等这些问题都还值得说明。要说明这些问题可能又需要更加详尽的说明清楚。<br />
<br />
<br />
源码分析:HashMap<br />
<br />
</p>
<p>HashMap是Java新Collection Framework中用来代替HashTable的一个实现，HashMap和HashTable的区别是： HashMap是未经同步的，而且允许null值。HashTable继承Dictionary，而且使用了Enumeration，所以被建议不要使用。<br />
HashMap的声明如下：<br />
public class HashMap extends AbstractMap implements Map, Cloneable,Serializable <br />
有关AbstractMap：<a href="http://blog.csdn.net/treeroot/archive/2004/09/20/110343.aspx">http://blog.csdn.net/treeroot/archive/2004/09/20/110343.aspx</a> <br />
有关Map：<a href="http://blog.csdn.net/treeroot/archive/2004/09/20/110331.aspx">http://blog.csdn.net/treeroot/archive/2004/09/20/110331.aspx</a> <br />
有关Cloneable：<a href="http://blog.csdn.net/treeroot/archive/2004/09/07/96936.aspx">http://blog.csdn.net/treeroot/archive/2004/09/07/96936.aspx</a> <br />
这个类比较复杂，这里只是重点分析了几个方法，特别是后面涉及到很多内部类都没有解释<br />
不过都比较简单。</p>
<p>static final int DEFAULT_INITIAL_CAPACITY = 16; 默认初始化大小</p>
<p>static final int MAXIMUM_CAPACITY = 1 &lt;&lt; 30; 最大初始化大小</p>
<p>static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认加载因子</p>
<p>transient Entry[] table; 一个Entry类型的数组，数组的长度为2的指数。</p>
<p>transient int size; 映射的个数</p>
<p>int threshold; 下一次扩容时的值</p>
<p>final float loadFactor; 加载因子</p>
<p>transient volatile int modCount; 修改次数</p>
<p>public HashMap(int initialCapacity, float loadFactor) {<br />
　　 if (initialCapacity &lt; 0) <br />
　　　　 throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity); <br />
　　 if (initialCapacity &gt; MAXIMUM_CAPACITY) <br />
　　　　 initialCapacity = MAXIMUM_CAPACITY; <br />
　　 if (loadFactor &lt;= 0 || Float.isNaN(loadFactor)) <br />
　　　　 throw new IllegalArgumentException("Illegal load factor: " +loadFactor); <br />
　　 int capacity = 1;<br />
　　 while (capacity &lt; initialCapacity)<br />
　　　　 capacity &lt;&lt;= 1; <br />
　　 this.loadFactor = loadFactor; <br />
　　 threshold = (int)(capacity * loadFactor); <br />
　　 table = new Entry[capacity]; <br />
　　 init();<br />
}</p>
<p>public HashMap(int initialCapacity) { <br />
　　 this(initialCapacity, DEFAULT_LOAD_FACTOR);<br />
}</p>
<p>public HashMap() { <br />
　　 this.loadFactor = DEFAULT_LOAD_FACTOR; <br />
　　 threshold = (int)(DEFAULT_INITIAL_CAPACITY);<br />
　　　　　　注意：这里应该是一个失误！ 应该是：threshold =(int)(DEFAULT_INITIAL_CAPACITY * loadFactor); <br />
　　 table = new Entry[DEFAULT_INITIAL_CAPACITY]; <br />
　　　init(); <br />
}</p>
<p>public HashMap(Map m) { <br />
　　 this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), 　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　DEFAULT_LOAD_FACTOR); <br />
　　 putAllForCreate(m); <br />
}</p>
<p>void init() {}</p>
<p>static final Object NULL_KEY = new Object();</p>
<p>static Object maskNull(Object key){ <br />
　　 return (key == null ? NULL_KEY : key);<br />
}</p>
<p>static Object unmaskNull(Object key) {<br />
　　 return (key == NULL_KEY ? null : key);<br />
}</p>
<p>static int hash(Object x) {<br />
　　 int h = x.hashCode(); <br />
　　 h += ~(h &lt;&lt; 9);<br />
　　 h ^= (h &gt;&gt;&gt; 14);<br />
　　 h += (h &lt;&lt; 4);<br />
　　 h ^= (h &gt;&gt;&gt; 10);<br />
return h; <br />
}<br />
在HashTable中没有这个方法，也就是说HashTable中是直接用对象的hashCode值，但是HashMap做了改进 用这个算法来获得哈希值。</p>
<p>static boolean eq(Object x, Object y) {<br />
　 　return x == y || x.equals(y);<br />
}</p>
<p>static int indexFor(int h, int length) {<br />
　　 return h &amp; (length-1);<br />
}<br />
根据哈希值和数组的长度来返回该hash值在数组中的位置，只是简单的与关系。</p>
<p>public int size() {<br />
　　 return size; <br />
}</p>
<p>public boolean isEmpty() { <br />
　　return size == 0; <br />
}</p>
<p>public Object get(Object key) {<br />
　　 Object k = maskNull(key); <br />
　　 int hash = hash(k); <br />
　　 int i = indexFor(hash, table.length);<br />
　　 Entry e = table[i]; <br />
　　 while (true) {<br />
　　　　 if (e == null) return e; <br />
　　　　 if (e.hash == hash &amp;&amp; eq(k, e.key)) return e.value; <br />
　　　　 e = e.next;<br />
　　 }<br />
}<br />
<span class="code">这个方法是获取数据的方法，首先获得哈希值，这里把null值掩饰了，并且hash值经过函数hash()修正。 然后计算该哈希值在数组中的索引值。如果该索引处的引用为null，表示HashMap中不存在这个映射。 否则的话遍历整个链表，这里找到了就返回,如果没有找到就遍历到链表末尾，返回null。这里的比较是这样的：e.hash==hash &amp;&amp; eq(k,e.key) 也就是说如果hash不同就肯定认为不相等，eq就被短路了，只有在 hash相同的情况下才调用equals方法。现在我们该明白Object中说的如果两个对象equals返回true，他们的 hashCode应该相同的道理了吧。假如两个对象调用equals返回true，但是hashCode不一样，那么在HashMap 里就认为他们不相等。</span></p>
<p>public boolean containsKey(Object key) {<br />
　　 Object k = maskNull(key); <br />
　　 int hash = hash(k);<br />
　　 int i = indexFor(hash, table.length);<br />
　　 Entry e = table[i]; <br />
　　 while (e != null) {<br />
　　　　 if (e.hash == hash &amp;&amp; eq(k, e.key)) return true;<br />
　　　　 e = e.next;<br />
　　 }<br />
　　return false; <br />
}<br />
这个方法比上面的简单，先找到哈希位置，再遍历整个链表，如果找到就返回true。</p>
<p style="text-indent: 21pt; line-height: 119%">Entry getEntry(Object key) { <br />
　　 Object k = maskNull(key); <br />
　　 int hash = hash(k); <br />
　　 int i = indexFor(hash, table.length); <br />
　　 Entry e = table[i]; <br />
　　 while (e != null &amp;&amp; !(e.hash == hash &amp;&amp; eq(k, e.key))) <br />
　　　　 e = e.next;<br />
　　 return e; <br />
}<br />
这个方法根据key值返回Entry节点，也是先获得索引位置，再遍历链表，如果没有找到返回的是null。 </p>
<p>public Object put(Object key, Object value) { <br />
　　 Object k = maskNull(key);<br />
　　 int hash = hash(k);<br />
　　 int i = indexFor(hash, table.length); <br />
　　 for (Entry e = table[i]; e != null; e = e.next) { <br />
　　　　 if (e.hash == hash &amp;&amp; eq(k, e.key)) {<br />
　　　　　　 Object oldValue = e.value; <br />
　　　　　　 e.value = value;<br />
　　　　　　 e.recordAccess(this);<br />
　　　　　　 return oldValue;<br />
　　　　 } <br />
　　 }<br />
　　 modCount++; <br />
　　 addEntry(hash, k, value, i); <br />
　　 return null;<br />
}<br />
<span class="code">首先获得hash索引位置，如果该位置的引用为null，那么直接插入一个映射，返回null。如果此处的引用不是null，必须遍历链表，如果找到一个相同的key，那么就更新该value，同时返回原来的value值。如果遍历完了没有找到，说明该key值不存在，还是插入一个映射。如果hash值足够离散的话，也就是说该索引没有被使用的话，那么不不用遍历链表了。相反，如果hash值不离散，极端的说如果是常数的话，所有的映射都会在这一个链表上，效率会极其低下。这里举一个最简单的例子，写两<br />
个不同的类作为key插入到HashMap中，效率会远远不同。<br />
class Good{<br />
　　int i;<br />
　　public Good(int i){<br />
　　　this.i=i;<br />
　　}<br />
　　public boolean equals(Object o){<br />
　　　return (o instanceof Good) &amp;&amp; (this.i==((Good)o).i)<br />
　　}<br />
　　public int hashCode(){<br />
　　　return i;<br />
　　}<br />
} <br />
class Bad{<br />
　　int i;<br />
　　public Good(int i){<br />
　　　　this.i=i;<br />
　　}<br />
　　public boolean equals(Object o){<br />
　　　　return (o instanceof Good) &amp;&amp; (this.i==((Good)o).i)</p>
</span></span>
<img src ="http://www.blogjava.net/gp213/aggbug/270586.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 13:32 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270586.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java操作Excel、PDF文件</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270584.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 05:20:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270584.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270584.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270584.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270584.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270584.html</trackback:ping><description><![CDATA[<p>相关文章:&nbsp;&nbsp;<br />
<a onclick="javascript:tagshow(event, 'Java');" href="javascript:;" target="_self"><u><strong>Java</strong></u></a>操作Excel之理解JXL--读取Excel模板动态写入数据并生成Excel<br />
Java解析Excel文件<br />
Java操作Excel之理解JXL--读取Excel</p>
<p>推荐圈子: GT-Grid<br />
更多相关推荐 下面这些是在开发中用到的一些东西，有的代码贴的不是完整的，只是贴出了关于操作EXCEL的代码：</p>
<p><br />
jxl是一个*国人写的java操作excel的工具, 在<a onclick="javascript:tagshow(event, '%BF%AA%D4%B4');" href="javascript:;" target="_self"><u><strong>开源</strong></u></a>世界中，有两套比较有影响的API可供使用，一个是POI，一个是jExcelAPI。其中功能相对POI比较弱一点。但jExcelAPI对中文支持非常好，API是纯Java的， 并不依赖Windows系统，即使运行在Linux下，它同样能够正确的处理Excel文件。 另外需要说明的是，这套API对图形和图表的支持很有限，而且仅仅识别PNG格式。</p>
<p>使用如下：</p>
<p>搭建环境</p>
<p>将下载后的文件解包，得到jxl.jar，放入classpath，<a onclick="javascript:tagshow(event, '%B0%B2%D7%B0');" href="javascript:;" target="_self"><u><strong>安装</strong></u></a>就完成了。</p>
<p>基本操作</p>
<p>一、创建文件</p>
<p>拟生成一个名为&#8220;test.xls&#8221;的Excel文件，其中第一个工作表被命名为<br />
&#8220;第一页&#8221;，大致效果如下：<br />
Java代码<br />
package&nbsp; test;&nbsp;&nbsp;<br />
&nbsp;<br />
// 生成Excel的类&nbsp;&nbsp;&nbsp;<br />
import&nbsp; java.io.File;&nbsp;&nbsp;<br />
&nbsp;<br />
import&nbsp; jxl.Workbook;&nbsp;&nbsp;<br />
import&nbsp; jxl.write.Label;&nbsp;&nbsp;<br />
import&nbsp; jxl.write.WritableSheet;&nbsp;&nbsp;<br />
import&nbsp; jxl.write.WritableWorkbook;&nbsp;&nbsp;<br />
&nbsp;<br />
public&nbsp;&nbsp; class&nbsp; CreateExcel&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public&nbsp;&nbsp; static&nbsp;&nbsp; void&nbsp; main(String args[])&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 打开文件&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableWorkbook book&nbsp; =&nbsp; Workbook.createWorkbook( new&nbsp; File( " test.xls " ));&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 生成名为&#8220;第一页&#8221;的工作表，参数0表示这是第一页&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableSheet sheet&nbsp; =&nbsp; book.createSheet( " 第一页 " ,&nbsp; 0 );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 在Label对象的构造子中指名单元格位置是第一列第一行(0,0)&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 以及单元格内容为test&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Label label&nbsp; =&nbsp;&nbsp; new&nbsp; Label( 0 ,&nbsp; 0 ,&nbsp; " test " );&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 将定义好的单元格添加到工作表中&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sheet.addCell(label);&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /**/ /*&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 生成一个保存数字的单元格 必须使用Number的完整包路径，否则有语法歧义 单元格位置是第二列，第一行，值为789.123&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jxl.write.Number number&nbsp; =&nbsp;&nbsp; new&nbsp; jxl.write.Number( 1 ,&nbsp; 0 ,&nbsp; 555.12541 );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sheet.addCell(number);&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 写入数据并关闭文件&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.write();&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.close();&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; catch&nbsp; (Exception e)&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(e);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;</p>
<p>&nbsp;package&nbsp; test;</p>
<p>&nbsp;// 生成Excel的类<br />
&nbsp;import&nbsp; java.io.File;</p>
<p>&nbsp;import&nbsp; jxl.Workbook;<br />
&nbsp;import&nbsp; jxl.write.Label;<br />
&nbsp;import&nbsp; jxl.write.WritableSheet;<br />
&nbsp;import&nbsp; jxl.write.WritableWorkbook;</p>
<p>&nbsp;public&nbsp;&nbsp; class&nbsp; CreateExcel&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;&nbsp; static&nbsp;&nbsp; void&nbsp; main(String args[])&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try&nbsp;&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 打开文件<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableWorkbook book&nbsp; =&nbsp; Workbook.createWorkbook( new&nbsp; File( " test.xls " ));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 生成名为&#8220;第一页&#8221;的工作表，参数0表示这是第一页<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableSheet sheet&nbsp; =&nbsp; book.createSheet( " 第一页 " ,&nbsp; 0 );<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 在Label对象的构造子中指名单元格位置是第一列第一行(0,0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 以及单元格内容为test<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Label label&nbsp; =&nbsp;&nbsp; new&nbsp; Label( 0 ,&nbsp; 0 ,&nbsp; " test " );</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 将定义好的单元格添加到工作表中<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sheet.addCell(label);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /**/ /*<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 生成一个保存数字的单元格 必须使用Number的完整包路径，否则有语法歧义 单元格位置是第二列，第一行，值为789.123<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jxl.write.Number number&nbsp; =&nbsp;&nbsp; new&nbsp; jxl.write.Number( 1 ,&nbsp; 0 ,&nbsp; 555.12541 );<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sheet.addCell(number);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 写入数据并关闭文件<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.write();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.close();</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; catch&nbsp; (Exception e)&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(e);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;</p>
<p>&nbsp;&nbsp; 编译执行后，会产生一个Excel文件。</p>
<p>三、读取文件</p>
<p>以刚才我们创建的Excel文件为例，做一个简单的读取操作，程序代码如下：<br />
Java代码<br />
package&nbsp; test;&nbsp;&nbsp;<br />
&nbsp;<br />
// 读取Excel的类&nbsp;&nbsp;&nbsp;<br />
import&nbsp; java.io.File;&nbsp;&nbsp;<br />
&nbsp;<br />
import&nbsp; jxl.Cell;&nbsp;&nbsp;<br />
import&nbsp; jxl.Sheet;&nbsp;&nbsp;<br />
import&nbsp; jxl.Workbook;&nbsp;&nbsp;<br />
&nbsp;<br />
public&nbsp;&nbsp; class&nbsp; ReadExcel&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public&nbsp;&nbsp; static&nbsp;&nbsp; void&nbsp; main(String args[])&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Workbook book&nbsp; =&nbsp; Workbook.getWorkbook( new&nbsp; File( " test.xls " ));&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 获得第一个工作表对象&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sheet sheet&nbsp; =&nbsp; book.getSheet( 0 );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 得到第一列第一行的单元格&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cell cell1&nbsp; =&nbsp; sheet.getCell( 0 ,&nbsp; 0 );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String result&nbsp; =&nbsp; cell1.getContents();&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(result);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.close();&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; catch&nbsp; (Exception e)&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(e);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;</p>
<p>&nbsp;package&nbsp; test;</p>
<p>&nbsp;// 读取Excel的类<br />
&nbsp;import&nbsp; java.io.File;</p>
<p>&nbsp;import&nbsp; jxl.Cell;<br />
&nbsp;import&nbsp; jxl.Sheet;<br />
&nbsp;import&nbsp; jxl.Workbook;</p>
<p>&nbsp;public&nbsp;&nbsp; class&nbsp; ReadExcel&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;&nbsp; static&nbsp;&nbsp; void&nbsp; main(String args[])&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try&nbsp;&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Workbook book&nbsp; =&nbsp; Workbook.getWorkbook( new&nbsp; File( " test.xls " ));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 获得第一个工作表对象<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sheet sheet&nbsp; =&nbsp; book.getSheet( 0 );<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 得到第一列第一行的单元格<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cell cell1&nbsp; =&nbsp; sheet.getCell( 0 ,&nbsp; 0 );<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String result&nbsp; =&nbsp; cell1.getContents();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(result);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.close();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; catch&nbsp; (Exception e)&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(e);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;</p>
<p>&nbsp; 程序执行结果：test</p>
<p>四、修改文件<br />
利用jExcelAPI可以修改已有的Excel文件，修改Excel文件的时候，除了打开文件的方式不同之外，<br />
其他操作和创建Excel是一样的。下面的例子是在我们已经生成的Excel文件中添加一个工作表：<br />
Java代码<br />
package&nbsp; test;&nbsp;&nbsp;<br />
&nbsp;<br />
import&nbsp; java.io.File;&nbsp;&nbsp;<br />
&nbsp;<br />
import&nbsp; jxl.Workbook;&nbsp;&nbsp;<br />
import&nbsp; jxl.write.Label;&nbsp;&nbsp;<br />
import&nbsp; jxl.write.WritableSheet;&nbsp;&nbsp;<br />
import&nbsp; jxl.write.WritableWorkbook;&nbsp;&nbsp;<br />
&nbsp;<br />
public&nbsp;&nbsp; class&nbsp; UpdateExcel&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public&nbsp;&nbsp; static&nbsp;&nbsp; void&nbsp; main(String args[])&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; Excel获得文件&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Workbook wb&nbsp; =&nbsp; Workbook.getWorkbook( new&nbsp; File( " test.xls " ));&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 打开一个文件的副本，并且指定数据写回到原文件&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableWorkbook book&nbsp; =&nbsp; Workbook.createWorkbook( new&nbsp; File( " test.xls " ),&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wb);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 添加一个工作表&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableSheet sheet&nbsp; =&nbsp; book.createSheet( " 第二页 " ,&nbsp; 1 );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sheet.addCell( new&nbsp; Label( 0 ,&nbsp; 0 ,&nbsp; " 第二页的<a onclick="javascript:tagshow(event, '%B2%E2%CA%D4');" href="javascript:;" target="_self"><u><strong>测试</strong></u></a>数据 " ));&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.write();&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.close();&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; catch&nbsp; (Exception e)&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(e);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;</p>
<p>&nbsp;package&nbsp; test;</p>
<p>&nbsp;import&nbsp; java.io.File;</p>
<p>&nbsp;import&nbsp; jxl.Workbook;<br />
&nbsp;import&nbsp; jxl.write.Label;<br />
&nbsp;import&nbsp; jxl.write.WritableSheet;<br />
&nbsp;import&nbsp; jxl.write.WritableWorkbook;</p>
<p>&nbsp;public&nbsp;&nbsp; class&nbsp; UpdateExcel&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp;&nbsp; static&nbsp;&nbsp; void&nbsp; main(String args[])&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try&nbsp;&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; Excel获得文件<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Workbook wb&nbsp; =&nbsp; Workbook.getWorkbook( new&nbsp; File( " test.xls " ));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 打开一个文件的副本，并且指定数据写回到原文件<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableWorkbook book&nbsp; =&nbsp; Workbook.createWorkbook( new&nbsp; File( " test.xls " ),<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wb);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 添加一个工作表<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WritableSheet sheet&nbsp; =&nbsp; book.createSheet( " 第二页 " ,&nbsp; 1 );<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sheet.addCell( new&nbsp; Label( 0 ,&nbsp; 0 ,&nbsp; " 第二页的测试数据 " ));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.write();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.close();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; catch&nbsp; (Exception e)&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(e);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;</p>
<p>其他操作</p>
<p>一、 数据格式化</p>
<p>在Excel中不涉及复杂的数据类型，能够比较好的处理字串、数字和日期已经能够满足一般的应用。</p>
<p>1、 字串格式化</p>
<p>字符串的格式化涉及到的是字体、粗细、字号等元素，这些功能主要由WritableFont和<br />
WritableCellFormat类来负责。假设我们在生成一个含有字串的单元格时，使用如下语句，<br />
为方便叙述，我们为每一行命令加了编号：<br />
Java代码<br />
WritableFont font1 =&nbsp;&nbsp;&nbsp;<br />
&nbsp;new&nbsp; WritableFont(WritableFont.TIMES, 16 ,WritableFont.BOLD); ①&nbsp;&nbsp;<br />
&nbsp;<br />
WritableCellFormat format1 = new&nbsp; WritableCellFormat(font1); ②&nbsp;&nbsp;<br />
&nbsp;<br />
Label label = new&nbsp; Label( 0 , 0 ,&#8221;data&nbsp; 4&nbsp; test&#8221;,format1) ③&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;<br />
&nbsp;其中①指定了字串格式：字体为TIMES，字号16，加粗显示。WritableFont有非常丰富的&nbsp;&nbsp;<br />
构造子，供不同情况下使用，jExcelAPI的java-doc中有详细列表，这里不再列出。&nbsp;&nbsp;<br />
&nbsp;<br />
②处代码使用了WritableCellFormat类，这个类非常重要，通过它可以指定单元格的各种&nbsp;&nbsp;<br />
属性，后面的单元格格式化中会有更多描述。&nbsp;&nbsp;<br />
&nbsp;<br />
③处使用了Label类的构造子，指定了字串被赋予那种格式。&nbsp;&nbsp;<br />
&nbsp;<br />
在WritableCellFormat类中，还有一个很重要的方法是指定数据的对齐方式，比如针对我们&nbsp;&nbsp;<br />
上面的实例，可以指定：&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp; // 把水平对齐方式指定为居中&nbsp;&nbsp;&nbsp;<br />
&nbsp;format1.setAlignment(jxl.format.Alignment.CENTRE);&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;// 把垂直对齐方式指定为居中&nbsp;&nbsp;&nbsp;<br />
&nbsp;format1.setVerticalAlignment(jxl.format.VerticalAlignment.CENTRE);&nbsp;&nbsp;</p>
<p>&nbsp;WritableFont font1 =<br />
&nbsp; new&nbsp; WritableFont(WritableFont.TIMES, 16 ,WritableFont.BOLD); ①</p>
<p>&nbsp;WritableCellFormat format1 = new&nbsp; WritableCellFormat(font1); ②</p>
<p>&nbsp;Label label = new&nbsp; Label( 0 , 0 ,&#8221;data&nbsp; 4&nbsp; test&#8221;,format1) ③</p>
<p>&nbsp;<br />
&nbsp; 其中①指定了字串格式：字体为TIMES，字号16，加粗显示。WritableFont有非常丰富的<br />
&nbsp;构造子，供不同情况下使用，jExcelAPI的java-doc中有详细列表，这里不再列出。</p>
<p>&nbsp;②处代码使用了WritableCellFormat类，这个类非常重要，通过它可以指定单元格的各种<br />
&nbsp;属性，后面的单元格格式化中会有更多描述。</p>
<p>&nbsp;③处使用了Label类的构造子，指定了字串被赋予那种格式。</p>
<p>&nbsp;在WritableCellFormat类中，还有一个很重要的方法是指定数据的对齐方式，比如针对我们<br />
&nbsp;上面的实例，可以指定：</p>
<p>&nbsp;&nbsp; // 把水平对齐方式指定为居中<br />
&nbsp; format1.setAlignment(jxl.format.Alignment.CENTRE);</p>
<p>&nbsp; // 把垂直对齐方式指定为居中<br />
&nbsp; format1.setVerticalAlignment(jxl.format.VerticalAlignment.CENTRE);<br />
&nbsp;<br />
二、单元格操作</p>
<p>Excel中很重要的一部分是对单元格的操作，比如行高、列宽、单元格合并等，所幸jExcelAPI<br />
提供了这些支持。这些操作相对比较简单，下面只介绍一下相关的API。</p>
<p>1、 合并单元格<br />
Java代码<br />
WritableSheet.mergeCells( int&nbsp; m, int&nbsp; n, int&nbsp; p, int&nbsp; q);&nbsp;&nbsp;&nbsp;<br />
&nbsp;<br />
// 作用是从(m,n)到(p,q)的单元格全部合并，比如：&nbsp;&nbsp;&nbsp;<br />
WritableSheet sheet = book.createSheet(&#8220;第一页&#8221;, 0 );&nbsp;&nbsp;<br />
&nbsp;<br />
// 合并第一列第一行到第六列第一行的所有单元格&nbsp;&nbsp;&nbsp;<br />
sheet.mergeCells( 0 , 0 , 5 , 0 );&nbsp;&nbsp;</p>
<p>&nbsp; WritableSheet.mergeCells( int&nbsp; m, int&nbsp; n, int&nbsp; p, int&nbsp; q);</p>
<p>&nbsp; // 作用是从(m,n)到(p,q)的单元格全部合并，比如：<br />
&nbsp; WritableSheet sheet = book.createSheet(&#8220;第一页&#8221;, 0 );</p>
<p>&nbsp; // 合并第一列第一行到第六列第一行的所有单元格<br />
&nbsp; sheet.mergeCells( 0 , 0 , 5 , 0 );<br />
&nbsp;<br />
合并既可以是横向的，也可以是纵向的。合并后的单元格不能再次进行合并，否则会触发异常。</p>
<p>2、 行高和列宽<br />
Java代码<br />
&nbsp;WritableSheet.setRowView( int&nbsp; i, int&nbsp; height);&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;// 作用是指定第i+1行的高度，比如：&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;// 将第一行的高度设为200&nbsp;&nbsp;&nbsp;<br />
&nbsp;sheet.setRowView( 0 , 200 );&nbsp;&nbsp;<br />
&nbsp;<br />
WritableSheet.setColumnView( int&nbsp; i, int&nbsp; width);&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;// 作用是指定第i+1列的宽度，比如：&nbsp;&nbsp;<br />
&nbsp;<br />
&nbsp;// 将第一列的宽度设为30&nbsp;&nbsp;&nbsp;<br />
&nbsp;sheet.setColumnView( 0 , 30 );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;</p>
<p>&nbsp; WritableSheet.setRowView( int&nbsp; i, int&nbsp; height);</p>
<p>&nbsp; // 作用是指定第i+1行的高度，比如：</p>
<p>&nbsp; // 将第一行的高度设为200<br />
&nbsp; sheet.setRowView( 0 , 200 );</p>
<p>&nbsp;WritableSheet.setColumnView( int&nbsp; i, int&nbsp; width);</p>
<p>&nbsp; // 作用是指定第i+1列的宽度，比如：</p>
<p>&nbsp; // 将第一列的宽度设为30<br />
&nbsp; sheet.setColumnView( 0 , 30 );<br />
&nbsp;<br />
&nbsp;</p>
<p>jExcelAPI还有其他的一些功能，比如插入图片等，这里就不再一一介绍，读者可以自己探索。</p>
<p>其中：如果读一个excel，需要知道它有多少行和多少列，如下操作：<br />
Java代码<br />
Workbook book&nbsp; =&nbsp; Workbook.getWorkbook( new&nbsp; File( " 测试1.xls " ));&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 获得第一个工作表对象&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sheet sheet&nbsp; =&nbsp; book.getSheet( 0 );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 得到第一列第一行的单元格&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp; columnum&nbsp; =&nbsp; sheet.getColumns(); //&nbsp; 得到列数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp; rownum&nbsp; =&nbsp; sheet.getRows(); //&nbsp; 得到行数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(columnum);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(rownum);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for&nbsp; ( int&nbsp; i&nbsp; =&nbsp;&nbsp; 0 ; i&nbsp; &lt;&nbsp; rownum; i ++ ) //&nbsp; 循环进行读写&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for&nbsp; ( int&nbsp; j&nbsp; =&nbsp;&nbsp; 0 ; j&nbsp; &lt;&nbsp; columnum; j ++ )&nbsp;&nbsp; {&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cell cell1&nbsp; =&nbsp; sheet.getCell(j, i);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String result&nbsp; =&nbsp; cell1.getContents();&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print(result);&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print( " \t " );&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println();&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; book.close();&nbsp;&nbsp;<br />
</p>
<img src ="http://www.blogjava.net/gp213/aggbug/270584.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 13:20 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270584.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java 反射机制系列</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270583.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 05:17:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270583.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270583.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270583.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270583.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270583.html</trackback:ping><description><![CDATA[<a onclick="javascript:tagshow(event, 'Java');" href="javascript:;" target="_self"><u><strong>Java</strong></u></a> 反射机制是指Java程序可以在执行期载入，探知，使用编译期间完全未知的classes.这句话可能有点难以理解，我们可以通过一个例子来看。在Java程序中我们经常会用到这样一条语句来创建一个对象。Date date = new Date();在这条语句中date的类型(Java.util.Date)在编译时 已经确定。那么，有没有办法使我们把对象类型的确定时间由编译转到运行，答案是肯定的。这就是Java反射机制所提供的便利。而且它不单单可以生成对象还可以获取Field，对Field设值，及调用方法等。
<p>　　谈及Java反射机制就一定要知道一个名为&#8220;Class&#8221;的类，它是Java反射机制的基础。&#8220;Class&#8221;和其它类一样继承于Object类，它的实例对象用来描述Java运行时的一种类型，接口，或者原始类型(比如int).&#8220;Class&#8221;的实例要由JVM创建，它没有公用的构造方法。下面我们来看一下如何获得"Class"类实例。</p>
<p>　　主要有三种方法。</p>
<p>　　一，通过Class类的静态方法forName获取。Class cla = Class.forName("java.lang.String");</p>
<p>　　二，通过.Type或.class属性获得。Class cla = String.class;Class cla1 = int.Type;</p>
<p>　　三，通过实例变量的getClass方法获得。String s = ""; Class cla = s.getClass();</p>
<p>　　如上所示实例对象cla就是对String类型的描述，通过它我们就可以创建一个String实例，并调用其中的方法。下一篇我将通过一个例子来展示如何使用Java反射机制。</p>
下面我以顾客买相机为例来说明<a onclick="javascript:tagshow(event, 'Java');" href="javascript:;" target="_self"><u><strong>Java</strong></u></a>反射机制的应用。例子中涉及的类和接口有：
<p>　　Camera接口：定义了takePhoto()方法。</p>
<p>　　Camera01类：一种照相机的类型，实现Camera接口。</p>
<p>　　Camera02类：另一种照相机的类型，实现Camera接口。</p>
<p>　　Seller类：卖照相机。</p>
<p>　　Customer类：买相机，有main方法。</p>
<p>　　所有类都放在com包里</p>
<p>　　程序如下：</p>
<p>　　public interface Camera {</p>
<p>　　//声明照相机必须可以拍照</p>
<p>　　public void takePhoto();</p>
<p>　　}</p>
<p>　　public class Camera01 implements Camera {</p>
<p>　　private final int prefixs =300;//300万象素</p>
<p>　　private final double ptionZoom=3.5; //3.5倍变焦</p>
<p>　　public void takePhoto() {</p>
<p>　　System.out.println("Camera01 has taken a photo");</p>
<p>　　}</p>
<p>　　}</p>
<p>　　类似的有</p>
<p>　　public class Camera02 implements Camera {</p>
<p>　　private final int prefixs =400;</p>
<p>　　private final double ptionZoom=5;</p>
<p>　　public void takePhoto() {</p>
<p>　　System.out.println("Camera02 has taken a photo");</p>
<p>　　}</p>
<p>　　}</p>
<p>　　顾客出场了</p>
<p>　　public class Customer {</p>
<p>　　public static void main(String[] args){</p>
<p>　　//找到一个售货员</p>
<p>　　Seller seller = new Seller();</p>
<p>　　//向售货员询问两种相机的信息</p>
<p>　　seller.getDescription("com.Camera01");</p>
<p>　　seller.getDescription("com.Camera02");</p>
<p>　　//觉得Camera02比较好，叫售货员拿来看</p>
<p>　　Camera camera =(Camera)seller.getCamera("com.Camera02");</p>
<p>　　//让售货员拍张照试一下</p>
<p>　　seller.testFuction(camera, "takePhoto");</p>
<p>　　}</p>
<p>　　}</p>
<p>　　Seller类通过Java反射机制实现</p>
<p>　　import java.lang.reflect.Field;</p>
<p>　　import java.lang.reflect.Method;</p>
<p>　　public class Seller {</p>
<p>　　//向顾客描述商品信息</p>
<p>　　public void getDescription(String type){</p>
<p>　　try {</p>
<p>　　Class cla = Class.forName(type);</p>
<p>　　//生成一个实例对象，在编译时我们并不知道obj是什么类型。</p>
<p>　　Object bj = cla.newInstance();</p>
<p>　　//获得type类型所有已定义类变量及方法。</p>
<p>　　Field[] fileds = cla.getDeclaredFields();</p>
<p>　　Method[]methods = cla.getDeclaredMethods();</p>
<p>　　System.out.println("The arguments of this Camera is:");</p>
<p>　　for(int i=0;i<fileds.length;i++){< p>
<p>　　fileds[i].setAccessible(true);</p>
<p>　　//输出类变量的定义及obj实例中对应的值</p>
<p>　　System.out.println(fileds[i]+":"+fileds[i].get(obj));</p>
<p>　　}</p>
<p>　　System.out.println("The function of this Camera:");</p>
<p>　　for(int i=0;i<methods.length;i++){< p>
<p>　　//输出类中方法的定义</p>
<p>　　System.out.println(methods[i]);</p>
<p>　　}</p>
<p>　　System.out.println();</p>
<p>　　} catch (Exception e) {</p>
<p>　　System.out.println("Sorry , no such type");</p>
<p>　　}</p>
<p>　　}</p>
<p>　　//使用商品的某个功能</p>
<p>　　public void testFuction(Object obj,String function){</p>
<p>　　try {</p>
<p>　　Class cla = obj.getClass();</p>
<p>　　//获得cla类中定义的无参方法。</p>
<p>　　Method m = cla.getMethod(function, null);</p>
<p>　　//调用obj中名为function的无参方法。</p>
<p>　　m.invoke(obj, null);</p>
<p>　　} catch (Exception e) {</p>
<p>　　System.out.println("Sorry , no such function");</p>
<p>　　}</p>
<p>　　}</p>
<p>　　//拿商品给顾客</p>
<p>　　public Object getCamera(String type){</p>
<p>　　try {</p>
<p>　　Class cla = Class.forName(type);</p>
<p>　　Object bj = cla.newInstance();</p>
<p>　　return obj;</p>
<p>　　} catch (Exception e) {</p>
<p>　　System.out.println("Sorry , no such type");</p>
<p>　　return null;</p>
<p>　　}</p>
<p>　　}</p>
<p>　　}</p>
上一篇中，通过例子我们知道了如何利用反射机制创建对象，获得类变量和调用方法等。创建对象的语句是 Class cla = Class.forName(type); Object bj = cla.newInstance(); 这里newInstance()实际上是使用了该类的默认无参构造方法。如果我们要调用其它的构造方法就要稍微复杂一点。比如我们要创建一个StringBuffer对象，用new 操作符应该是StringBuffer br = new StringBuffer("example");用反射机制则要有以下步骤。
<p>　　首先，获得StringBuffer类的描述。</p>
<p>　　Class cla = Class.forName("<a onclick="javascript:tagshow(event, 'java');" href="javascript:;" target="_self"><u><strong>java</strong></u></a>.lang.StringBuffer");</p>
<p>　　其次，要创建参数类型数组Class[] 。</p>
<p>　　Class[] paraTypes = new Class[1];paraTypes[0]=String.class;</p>
<p>　　然后，通过cla 和 paraTypes 获得Constructor 对象。</p>
<p>　　Constructor constructor = cla.getConstructor(paraTypes);</p>
<p>　　接着，创建传入的参数列表Object[]。</p>
<p>　　Object[] paraLists = new Object[1]; paraLists[0]="color";</p>
<p>　　最后，得到我们所要得对象。Object bj = constructor.newInstance(paraLists);</p>
<p>　　如果我们paraTypes及paraLists设为null或长度为0，就可以用上述步骤调用StringBuffer的无参构造方法。类似地，我们可以调用对象中的有参方法。比如我们做如下操作br.insert(4, 'u');用反射机制实现如下。</p>
<p>　　Class[] paratypes = new Class[]{int.class,char.class};</p>
<p>　　Method method = cla.getMethod("insert", paratypes);</p>
<p>　　Object[] paralists = new Object[]{4,'u'};</p>
<p>　　method.invoke(obj, paralists);</p>
<p>　　反射机制给予我们运行时才确定对象类型的便利，然而它也有显著的缺点。</p>
<p>　　1，代码笨拙冗长。比如本来一句br.insert(4, 'u');可以解决的问题现在要用到四句。</p>
<p>　　2，损失了编译时类型检查的好处。这使得你要对付更多的异常。</p>
<p>　　3，性能损失。用反射机制运行的时间更久。</p>
<p>　　&lt;<effective Java>&gt;中给出的建议是&#8220;普通应用不应在运行时刻以映像方式访问对象，只是在很有限的情况下使用&#8220;。那么在什么地方会用到反射机制呢。已有的较熟悉应用是我们的IDE及一些框架。比如<a onclick="javascript:tagshow(event, 'eclipse');" href="javascript:;" target="_self"><u><strong>eclipse</strong></u></a>,编程时ctrl+space弹出的建议就是用到反射机制。比如Spring读取配置文件后生成对应的对象。还有RPC系统也会用到。对于一般的应用软件，你可以在工厂方法中用到它。</p>
<img src ="http://www.blogjava.net/gp213/aggbug/270583.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 13:17 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270583.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JSP连接数据库方法大全</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270578.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 04:54:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270578.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270578.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270578.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270578.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270578.html</trackback:ping><description><![CDATA[一、<a onclick="javascript:tagshow(event, 'jsp');" href="javascript:;" target="_self"><u><strong>jsp</strong></u></a>连接Oracle8/8i/9i数据库（用thin模式）<br />
　　testoracle.jsp如下：<br />
　　&lt;%@ page contentType="text/html;charset=gb2312"%&gt;<br />
　　&lt;%@ page import="<a onclick="javascript:tagshow(event, 'java');" href="javascript:;" target="_self"><u><strong>java</strong></u></a>.sql.*"%&gt;<br />
　　&lt;html&gt;<br />
　　&lt;body&gt;<br />
　　&lt;%Class.forName("<a onclick="javascript:tagshow(event, 'oracle');" href="javascript:;" target="_self"><u><strong>oracle</strong></u></a>.jdbc.driver.OracleDriver").newInstance();<br />
　　String url="jdbc:oracle:thin:@localhost:1521:orcl";<br />
　　//orcl为你的<a onclick="javascript:tagshow(event, '%CA%FD%BE%DD%BF%E2');" href="javascript:;" target="_self"><u><strong>数据库</strong></u></a>的SID<br />
　　String user="scott";<br />
　　String password="tiger";<br />
　　Connection conn= DriverManager.getConnection(url,user,password);<br />
　　Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);<br />
　　String <a onclick="javascript:tagshow(event, 'sql');" href="javascript:;" target="_self"><u><strong>sql</strong></u></a>="select * from test";<br />
　　ResultSet rs=stmt.executeQuery(sql);<br />
　　while(rs.next()) {%&gt;<br />
　　您的第一个字段内容为：&lt;%=rs.getString(1)%&gt;<br />
　　您的第二个字段内容为：&lt;%=rs.getString(2)%&gt;<br />
　　&lt;%}%&gt;<br />
　　&lt;%out.print("数据库操作成功，恭喜你\");%&gt;<br />
　　&lt;%rs.close();<br />
　　stmt.close();<br />
　　conn.close();<br />
　　%&gt;<br />
　　&lt;/body&gt;<br />
　　&lt;/html&gt;<br />
　　<br />
　　二、jsp连接Sql Server7.0/2000数据库<br />
　　testsqlserver.jsp如下：<br />
　　&lt;%@ page contentType="text/html;charset=gb2312"%&gt;<br />
　　&lt;%@ page import="java.sql.*"%&gt;<br />
　　&lt;html&gt;<br />
　　&lt;body&gt;<br />
　　&lt;%Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();<br />
　　String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=pubs";<br />
　　//pubs为你的数据库的<br />
　　String user="sa";<br />
　　String password="";<br />
　　Connection conn= DriverManager.getConnection(url,user,password);<br />
　　Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);<br />
　　String sql="select * from test";<br />
　　ResultSet rs=stmt.executeQuery(sql);<br />
　　while(rs.next()) {%&gt;<br />
　　您的第一个字段内容为：&lt;%=rs.getString(1)%&gt;<br />
　　您的第二个字段内容为：&lt;%=rs.getString(2)%&gt;<br />
　　&lt;%}%&gt;<br />
　　&lt;%out.print("数据库操作成功，恭喜你\");%&gt;<br />
　　&lt;%rs.close();<br />
　　stmt.close();<br />
　　conn.close();<br />
　　%&gt;<br />
　　&lt;/body&gt;<br />
　　&lt;/html&gt;<br />
　　<br />
　　三、jsp连接<a onclick="javascript:tagshow(event, 'DB2');" href="javascript:;" target="_self"><u><strong>DB2</strong></u></a>数据库<br />
　　testdb2.jsp如下：<br />
　　&lt;%@ page contentType="text/html;charset=gb2312"%&gt;<br />
　　&lt;%@ page import="java.sql.*"%&gt;<br />
　　&lt;html&gt;<br />
　　&lt;body&gt;<br />
　　&lt;%Class.forName("com.ibm.db2.jdbc.app.DB2Driver ").newInstance();<br />
　　String url="jdbc:db2://localhost:5000/sample";<br />
　　//sample为你的数据库名<br />
　　String user="admin";<br />
　　String password="";<br />
　　Connection conn= DriverManager.getConnection(url,user,password);<br />
　　Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);<br />
　　String sql="select * from test";<br />
　　ResultSet rs=stmt.executeQuery(sql);<br />
　　while(rs.next()) {%&gt;<br />
　　您的第一个字段内容为：&lt;%=rs.getString(1)%&gt;<br />
　　您的第二个字段内容为：&lt;%=rs.getString(2)%&gt;<br />
　　&lt;%}%&gt;<br />
　　&lt;%out.print("数据库操作成功，恭喜你\");%&gt;<br />
　　&lt;%rs.close();<br />
　　stmt.close();<br />
　　conn.close();<br />
　　%&gt;<br />
　　&lt;/body&gt;<br />
　　&lt;/html&gt;<br />
　　<br />
　　四、jsp连接Informix数据库<br />
　　testinformix.jsp如下：<br />
　　&lt;%@ page contentType="text/html;charset=gb2312"%&gt;<br />
　　&lt;%@ page import="java.sql.*"%&gt;<br />
　　&lt;html&gt;<br />
　　&lt;body&gt;<br />
　　&lt;%Class.forName("com.informix.jdbc.IfxDriver").newInstance();<br />
　　String url =<br />
　　"jdbc:informix-sqli://123.45.67.89:1533/testDB:INFORMIXSERVER=myserver;<br />
　　user=testuser;password=testpassword";<br />
　　//testDB为你的数据库名<br />
　　Connection conn= DriverManager.getConnection(url);<br />
　　Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);<br />
　　String sql="select * from test";<br />
　　ResultSet rs=stmt.executeQuery(sql);<br />
　　while(rs.next()) {%&gt;<br />
　　您的第一个字段内容为：&lt;%=rs.getString(1)%&gt;<br />
　　您的第二个字段内容为：&lt;%=rs.getString(2)%&gt;<br />
　　&lt;%}%&gt;<br />
　　&lt;%out.print("数据库操作成功，恭喜你\");%&gt;<br />
　　&lt;%rs.close();<br />
　　stmt.close();<br />
　　conn.close();<br />
　　%&gt;<br />
　　&lt;/body&gt;<br />
　　&lt;/html&gt;<br />
　　<br />
　　五、jsp连接Sybase数据库<br />
　　testmysql.jsp如下：<br />
　　&lt;%@ page contentType="text/html;charset=gb2312"%&gt;<br />
　　&lt;%@ page import="java.sql.*"%&gt;<br />
　　&lt;html&gt;<br />
　　&lt;body&gt;<br />
　　&lt;%Class.forName("com.sybase.jdbc.SybDriver").newInstance();<br />
　　String url =" jdbc:sybase:Tds:localhost:5007/tsdata";<br />
　　//tsdata为你的数据库名<br />
　　Properties sysProps = System.getProperties();<br />
　　SysProps.put("user","userid");<br />
　　SysProps.put("password","user_password");<br />
　　Connection conn= DriverManager.getConnection(url, SysProps);<br />
　　Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);<br />
　　String sql="select * from test";<br />
　　ResultSet rs=stmt.executeQuery(sql);<br />
　　while(rs.next()) {%&gt;<br />
　　您的第一个字段内容为：&lt;%=rs.getString(1)%&gt;<br />
　　您的第二个字段内容为：&lt;%=rs.getString(2)%&gt;<br />
　　&lt;%}%&gt;<br />
　　&lt;%out.print("数据库操作成功，恭喜你\");%&gt;<br />
　　&lt;%rs.close();<br />
　　stmt.close();<br />
　　conn.close();<br />
　　%&gt;<br />
　　&lt;/body&gt;<br />
　　&lt;/html&gt;<br />
　　<br />
　　六、jsp连接<a onclick="javascript:tagshow(event, 'MySQL');" href="javascript:;" target="_self"><u><strong>MySQL</strong></u></a>数据库<br />
　　testmysql.jsp如下：<br />
　　&lt;%@ page contentType="text/html;charset=gb2312"%&gt;<br />
　　&lt;%@ page import="java.sql.*"%&gt;<br />
　　&lt;html&gt;<br />
　　&lt;body&gt;<br />
　　&lt;%Class.forName("org.gjt.mm.mysql.Driver").newInstance();<br />
　　String url ="jdbc:mysql://localhost/softforum?user=soft&amp;password=soft1234&amp;useUnicode=true&amp;characterEncoding=8859_1"<br />
　　//testDB为你的数据库名<br />
　　Connection conn= DriverManager.getConnection(url);<br />
　　Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);<br />
　　String sql="select * from test";<br />
　　ResultSet rs=stmt.executeQuery(sql);<br />
　　while(rs.next()) {%&gt;<br />
　　您的第一个字段内容为：&lt;%=rs.getString(1)%&gt;<br />
　　您的第二个字段内容为：&lt;%=rs.getString(2)%&gt;<br />
　　&lt;%}%&gt;<br />
　　&lt;%out.print("数据库操作成功，恭喜你\");%&gt;<br />
　　&lt;%rs.close();<br />
　　stmt.close();<br />
　　conn.close();<br />
　　%&gt;<br />
　　&lt;/body&gt;<br />
　　&lt;/html&gt;<br />
　　<br />
　　七、jsp连接PostgreSQL数据库<br />
　　testmysql.jsp如下：<br />
　　&lt;%@ page contentType="text/html;charset=gb2312"%&gt;<br />
　　&lt;%@ page import="java.sql.*"%&gt;<br />
　　&lt;html&gt;<br />
　　&lt;body&gt;<br />
　　&lt;%Class.forName("org.postgresql.Driver").newInstance();<br />
　　String url ="jdbc:postgresql://localhost/soft"<br />
　　//soft为你的数据库名<br />
　　String user="myuser";<br />
　　String password="mypassword";<br />
　　Connection conn= DriverManager.getConnection(url,user,password);<br />
　　Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);<br />
　　String sql="select * from test";<br />
　　ResultSet rs=stmt.executeQuery(sql);<br />
　　while(rs.next()) {%&gt;<br />
　　您的第一个字段内容为：&lt;%=rs.getString(1)%&gt;<br />
　　您的第二个字段内容为：&lt;%=rs.getString(2)%&gt;<br />
　　&lt;%}%&gt;<br />
　　&lt;%out.print("数据库操作成功，恭喜你\");%&gt;<br />
　　&lt;%rs.close();<br />
　　stmt.close();<br />
　　conn.close();<br />
　　%&gt;<br />
　　&lt;/body&gt;<br />
　　&lt;/html&gt; <br />
<img src ="http://www.blogjava.net/gp213/aggbug/270578.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 12:54 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270578.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 中 Vector、ArrayList、List 使用深入剖析</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270573.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 03:43:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270573.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270573.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270573.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270573.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270573.html</trackback:ping><description><![CDATA[<p>线性表，链表，哈希表是常用的数据结构，在进行<a onclick="javascript:tagshow(event, 'Java');" href="javascript:;" target="_self"><u><strong>Java</strong></u></a>开发时，JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述，向读者阐述各个类的作用以及如何正确使用这些类。</p>
<p>Collection<br />
├List<br />
│├LinkedList<br />
│├ArrayList<br />
│└Vector<br />
│　└Stack<br />
└Set<br />
Map<br />
├Hashtable<br />
├HashMap<br />
└WeakHashMap</p>
<p>Collection接口<br />
　　Collection是最基本的集合接口，一个Collection代表一组Object，即Collection的元素（Elements）。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类， Java SDK提供的类都是继承自Collection的&#8220;子接口&#8221;如List和Set。<br />
　　所有实现Collection接口的类都必须提供两个标准的构造函数：无参数的构造函数用于创建一个空的Collection，有一个Collection参数的构造函数用于创建一个新的 Collection，这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。<br />
　　如何遍历Collection中的每一个元素？不论Collection的实际类型如何，它都支持一个iterator()的方法，该方法返回一个迭代子，使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下：<br />
　　　　Iterator it = collection.iterator(); // 获得一个迭代子<br />
　　　　while(it.hasNext()) {<br />
　　　　　　Object bj = it.next(); // 得到下一个元素<br />
　　　　}<br />
　　由Collection接口派生的两个接口是List和Set。</p>
<p>List接口<br />
　　List是有序的Collection，使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引（元素在List中的位置，类似于数组下标）来访问List中的元素，这类似于Java的数组。<br />
和下面要提到的Set不同，List允许有相同的元素。<br />
　　除了具有Collection接口必备的iterator()方法外，List还提供一个listIterator()方法，返回一个 ListIterator接口，和标准的Iterator接口相比，ListIterator多了一些add()之类的方法，允许添加，删除，设定元素，还能向前或向后遍历。<br />
　　实现List接口的常用类有LinkedList，ArrayList，Vector和Stack。</p>
<p>LinkedList类<br />
　　LinkedList实现了List接口，允许null元素。此外LinkedList提供额外的get，remove，insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈（stack），队列（queue）或双向队列（deque）。<br />
　　注意LinkedList没有同步方法。如果多个线程同时访问一个List，则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List：<br />
　　　　List list = Collections.synchronizedList(new LinkedList(...));</p>
<p>ArrayList类<br />
　　ArrayList实现了可变大小的数组。它允许所有元素，包括null。ArrayList没有同步。<br />
size，isEmpty，get，set方法运行时间为常数。但是add方法开销为分摊的常数，添加n个元素需要O(n)的时间。其他的方法运行时间为线性。<br />
　　每个ArrayList实例都有一个容量（Capacity），即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加，但是增长算法并没有定义。当需要插入大量元素时，在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。<br />
　　和LinkedList一样，ArrayList也是非同步的（unsynchronized）。</p>
<p>Vector类<br />
　　Vector非常类似ArrayList，但是Vector是同步的。由Vector创建的Iterator，虽然和ArrayList创建的 Iterator是同一接口，但是，因为Vector是同步的，当一个Iterator被创建而且正在被使用，另一个线程改变了Vector的状态（例如，添加或删除了一些元素），这时调用Iterator的方法时将抛出ConcurrentModificationEx<wbr>ception，因此必须捕获该异常。</p>
<p>Stack 类<br />
　　Stack继承自Vector，实现一个后进先出的堆栈。Stack提供5个额外的方法使得 Vector得以被当作堆栈使用。基本的push和pop方法，还有peek方法得到栈顶的元素，empty方法<a onclick="javascript:tagshow(event, '%B2%E2%CA%D4');" href="javascript:;" target="_self"><u><strong>测试</strong></u></a>堆栈是否为空，search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。</p>
<p>Set接口<br />
　　Set是一种不包含重复的元素的Collection，即任意的两个元素e1和e2都有e1.equals(e2)=false，Set最多有一个null元素。<br />
　　很明显，Set的构造函数有一个约束条件，传入的Collection参数不能包含重复的元素。<br />
　　请注意：必须小心操作可变对象（Mutable Object）。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。</p>
<p>Map接口<br />
　　请注意，Map没有继承Collection接口，Map提供key到value的映射。一个Map中不能包含相同的key，每个key只能映射一个 value。Map接口提供3种集合的视图，Map的内容可以被当作一组key集合，一组value集合，或者一组key-value映射。</p>
<p>Hashtable类<br />
　　Hashtable继承Map接口，实现一个key-value映射的哈希表。任何非空（non-null）的对象都可作为key或者value。<br />
　　添加数据使用put(key, value)，取出数据使用get(key)，这两个基本操作的时间开销为常数。<br />
Hashtable 通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大，这会影响像get和put这样的操作。<br />
使用Hashtable的简单示例如下，将1，2，3放到Hashtable中，他们的key分别是&#8221;one&#8221;，&#8221;two&#8221;，&#8221;three&#8221;：<br />
　　　　Hashtable numbers = new Hashtable();<br />
　　　　numbers.put(&#8220;one&#8221;, new Integer(1));<br />
　　　　numbers.put(&#8220;two&#8221;, new Integer(2));<br />
　　　　numbers.put(&#8220;three&#8221;, new Integer(3));<br />
　　要取出一个数，比如2，用相应的key：<br />
　　　　Integer n = (Integer)numbers.get(&#8220;two&#8221;);<br />
　　　　System.out.println(&#8220;two = &#8221; + n);<br />
　　由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置，因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object，如果你用自定义的类当作key的话，要相当小心，按照散列函数的定义，如果两个对象相同，即obj1.equals(obj2)=true，则它们的hashCode必须相同，但如果两个对象不同，则它们的hashCode不一定不同，如果两个不同对象的hashCode相同，这种现象称为冲突，冲突会导致操作哈希表的时间开销增大，所以尽量定义好的hashCode()方法，能加快哈希表的操作。<br />
　　如果相同的对象有不同的hashCode，对哈希表的操作会出现意想不到的结果（期待的get方法返回null），要避免这种问题，只需要牢记一条：要同时复写equals方法和hashCode方法，而不要只写其中一个。<br />
　　Hashtable是同步的。</p>
<p>HashMap类<br />
　　HashMap和Hashtable类似，不同之处在于HashMap是非同步的，并且允许null，即null value和null key。，但是将HashMap视为Collection时（values()方法可返回Collection），其迭代子操作时间开销和HashMap的容量成比例。因此，如果迭代操作的性能相当重要的话，不要将HashMap的初始化容量设得过高，或者load factor过低。</p>
<p>WeakHashMap类<br />
　　WeakHashMap是一种改进的HashMap，它对key实行&#8220;弱引用&#8221;，如果一个key不再被外部所引用，那么该key可以被GC回收。</p>
<p>总结<br />
　　如果涉及到堆栈，队列等操作，应该考虑用List，对于需要快速插入，删除元素，应该使用LinkedList，如果需要快速随机访问元素，应该使用ArrayList。<br />
　　如果程序在单线程环境中，或者访问仅仅在一个线程中进行，考虑非同步的类，其效率较高，如果多个线程可能同时操作一个类，应该使用同步的类。<br />
　　要特别注意对哈希表的操作，作为key的对象要正确复写equals和hashCode方法。<br />
　　尽量返回接口而非实际的类型，如返回List而非ArrayList，这样如果以后需要将ArrayList换成LinkedList时，客户端<a onclick="javascript:tagshow(event, '%B4%FA%C2%EB');" href="javascript:;" target="_self"><u><strong>代码</strong></u></a>不用改变。这就是针对抽象<a onclick="javascript:tagshow(event, '%B1%E0%B3%CC');" href="javascript:;" target="_self"><u><strong>编程</strong></u></a>。</p>
<p>同步性<br />
Vector 是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的，因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率，所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择，这样可以避免由于同步带来的不必要的性能开销。</p>
<p>数据增长<br />
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候，如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度，Vector缺省情况下自动增长原来一倍的数组长度， ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用 Vector有一些优势，因为你可以通过设置集合的初始化大小来避免不必要的资源开销。</p>
<p>使用模式<br />
在ArrayList和Vector中，从一个指定的位置（通过索引）查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的，这个时间我们用 O(1)表示。但是，如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长：O(n-i)，其中n代表集合中元素的个数，i代表元素增加或移除元素的索引位置。为什么会这样呢？以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢？</p>
<p>这意味着，你只是查找特定位置的元素或只在集合的末端增加、移除元素，那么使用Vector或ArrayList都可以。如果是其他操作，你最好选择其他的集合操作类。比如，LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1)，但它在索引一个元素的使用缺比较慢－O(i),其中i是索引的位置.使用 ArrayList也很容易，因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象，所有你要明白它也会带来额外的开销。</p>
<p>最后，在《Practical Java》一书中Peter Haggar建议使用一个简单的数组（Array）来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。</p>
<p>Java 中 Vector、ArrayList、List 使用深入剖析 - 程序杂烩</p>
<img src ="http://www.blogjava.net/gp213/aggbug/270573.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 11:43 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270573.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java Native Method初涉</title><link>http://www.blogjava.net/gp213/archive/2009/05/14/270569.html</link><dc:creator>郭鹏</dc:creator><author>郭鹏</author><pubDate>Thu, 14 May 2009 03:37:00 GMT</pubDate><guid>http://www.blogjava.net/gp213/archive/2009/05/14/270569.html</guid><wfw:comment>http://www.blogjava.net/gp213/comments/270569.html</wfw:comment><comments>http://www.blogjava.net/gp213/archive/2009/05/14/270569.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/gp213/comments/commentRss/270569.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/gp213/services/trackbacks/270569.html</trackback:ping><description><![CDATA[今天花了两个小时把一份关于什么是<span class="hilite1">Native</span> Method的英文文章好好了读了一遍，以下是我依据原文的理解。<br />
<br />
一. 什么是<span class="hilite1">Native</span> Method<br />
&nbsp;&nbsp; 简单地讲，一个<span class="hilite1">Native</span> Method就是一个<span class="hilite2">java</span>调用非<span class="hilite2">java</span>代码的接口。一个<span class="hilite1">Native</span> Method是这样一个<span class="hilite2">java</span>的方法：该方法的实现由非<span class="hilite2">java</span>语言实现，比如C。这个特征并非<span class="hilite2">java</span>所特有，很多其它的编程语言都有这一机制，比如在C＋＋中，你可以用extern "C"告知C＋＋编译器去调用一个C的函数。<br />
&nbsp;&nbsp; "A <span class="hilite1">native</span> method is a <span class="hilite2">Java</span> method whose implementation is provided by non-<span class="hilite2">java</span> code."<br />
&nbsp;&nbsp; 在定义一个<span class="hilite1">native</span> method时，并不提供实现体（有些像定义一个<span class="hilite2">java</span> interface），因为其实现体是由非<span class="hilite2">java</span>语言在外面实现的。，下面给了一个示例：&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; public class IHaveNatives<br />
&nbsp;&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="hilite1">native</span> public void Native1( int x ) ;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="hilite1">native</span> static public long Native2() ;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="hilite1">native</span> synchronized private float Native3( Object o ) ;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="hilite1">native</span> void Native4( int[] ary ) throws Exception ;<br />
&nbsp;&nbsp;&nbsp; } <br />
&nbsp;&nbsp;&nbsp; 这些方法的声明描述了一些非<span class="hilite2">java</span>代码在这些<span class="hilite2">java</span>代码里看起来像什么样子（view）.<br />
&nbsp;&nbsp;&nbsp; 标识符<span class="hilite1">native</span>可以与所有其它的<span class="hilite2">java</span>标识符连用，但是abstract除外。这是合理的，因为<span class="hilite1">native</span>暗示这些方法是有实现体的，只不过这些实现体是非<span class="hilite2">java</span>的，但是abstract却显然的指明这些方法无实现体。<span class="hilite1">native</span>与其它<span class="hilite2">java</span>标识符连用时，其意义同非<span class="hilite1">Native</span> Method并无差别，比如<span class="hilite1">native</span> static表明这个方法可以在不产生类的实例时直接调用，这非常方便，比如当你想用一个<span class="hilite1">native</span> method去调用一个C的类库时。上面的第三个方法用到了<span class="hilite1">native</span> synchronized，JVM在进入这个方法的实现体之前会执行同步锁机制（就像<span class="hilite2">java</span>的多线程。）<br />
&nbsp; &nbsp; 一个<span class="hilite1">native</span> method方法可以返回任何<span class="hilite2">java</span>类型，包括非基本类型，而且同样可以进行异常控制。这些方法的实现体可以制一个异常并且将其抛出，这一点与<span class="hilite2">java</span>的方法非常相似。当一个<span class="hilite1">native</span> method接收到一些非基本类型时如Object或一个整型数组时，这个方法可以访问这非些基本型的内部，但是这将使这个<span class="hilite1">native</span>方法依赖于你所访问的<span class="hilite2">java</span>类的实现。有一点要牢牢记住：我们可以在一个<span class="hilite1">native</span> method的本地实现中访问所有的<span class="hilite2">java</span>特性，但是这要依赖于你所访问的<span class="hilite2">java</span>特性的实现，而且这样做远远不如在<span class="hilite2">java</span>语言中使用那些特性方便和容易。<br />
&nbsp;&nbsp;&nbsp; <span class="hilite1">native</span> method的存在并不会对其他类调用这些本地方法产生任何影响，实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。JVM将控制调用本地方法的所有细节。需要注意当我们将一个本地方法声明为final的情况。用<span class="hilite2">java</span>实现的方法体在被编译时可能会因为内联而产生效率上的提升。但是一个<span class="hilite1">native</span> final方法是否也能获得这样的好处却是值得怀疑的，但是这只是一个代码优化方面的问题，对功能实现没有影响。<br />
&nbsp;&nbsp;&nbsp; 如果一个含有本地方法的类被继承，子类会继承这个本地方法并且可以用<span class="hilite2">java</span>语言重写这个方法（这个似乎看起来有些奇怪），同样的如果一个本地方法被fianl标识，它被继承后不能被重写。<br />
&nbsp;&nbsp; 本地方法非常有用，因为它有效地扩充了jvm.事实上，我们所写的<span class="hilite2">java</span>代码已经用到了本地方法，在sun的<span class="hilite2">java</span>的并发（多线程）的机制实现中，许多与操作系统的接触点都用到了本地方法，这使得<span class="hilite2">java</span>程序能够超越<span class="hilite2">java</span>运行时的界限。有了本地方法，<span class="hilite2">java</span>程序可以做任何应用层次的任务。<br />
<br />
<br />
二.为什么要使用<span class="hilite1">Native</span> Method<br />
&nbsp;&nbsp; <span class="hilite2">java</span>使用起来非常方便，然而有些层次的任务用<span class="hilite2">java</span>实现起来不容易，或者我们对程序的效率很在意时，问题就来了。<br />
&nbsp;&nbsp; 与<span class="hilite2">java</span>环境外交互：<br />
&nbsp;&nbsp; 有时<span class="hilite2">java</span>应用需要与<span class="hilite2">java</span>外面的环境交互。这是本地方法存在的主要原因，你可以想想<span class="hilite2">java</span>需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制：它为我们提供了一个非常简洁的接口，而且我们无需去了解<span class="hilite2">java</span>应用之外的繁琐的细节。<br />
&nbsp;&nbsp; 与操作系统交互：<br />
&nbsp;&nbsp; JVM支持着<span class="hilite2">java</span>语言本身和运行时库，它是<span class="hilite2">java</span>程序赖以生存的平台，它由一个解释器（解释字节码）和一些连接到本地代码的库组成。然而不管怎 样，它毕竟不是一个完整的系统，它经常依赖于一些底层（underneath在下面的）系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法，我们得以用<span class="hilite2">java</span>实现了jre的与底层系统的交互，甚至JVM的一些部分就是用C写的，还有，如果我们要使用一些<span class="hilite2">java</span>语言本身没有提供封装的操作系统的特性时，我们也需要使用本地方法。<br />
&nbsp;&nbsp;&nbsp; Sun's <span class="hilite2">Java</span><br />
&nbsp;&nbsp;&nbsp; Sun的解释器是用C实现的，这使得它能像一些普通的C一样与外部交互。jre大部分是用<span class="hilite2">java</span>实现的，它也通过一些本地方法与外界交互。例如：类<span class="hilite2">java</span>.lang.Thread 的 setPriority()方法是用<span class="hilite2">java</span>实现的，但是它实现调用的是该类里的本地方法setPriority0()。这个本地方法是用C实现的，并被植入JVM内部，在Windows 95的平台上，这个本地方法最终将调用Win32 SetPriority() API。这是一个本地方法的具体实现由JVM直接提供，更多的情况是本地方法由外部的动态链接库（external dynamic link library）提供，然后被JVM调用。<br />
<br />
<br />
三.JVM怎样使<span class="hilite1">Native</span> Method跑起来：<br />
&nbsp;&nbsp;&nbsp; 我们知道，当一个类第一次被使用到时，这个类的字节码会被加载到内存，并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list，这些方法描述符包含这样一些信息：方法代码存于何处，它有哪些参数，方法的描述符（public之类）等等。<br />
&nbsp;&nbsp;&nbsp; 如果一个方法描述符内有<span class="hilite1">native</span>，这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内，但是它们会被操作系统加载到<span class="hilite2">java</span>程序的地址空间。当一个带有本地方法的类被加载时，其相关的DLL并未被加载，因此指向方法实现的指针并不会被设置。当本地方法被调用之前，这些DLL才会被加载，这是通过调用<span class="hilite2">java</span>.system.loadLibrary()实现的。<br />
&nbsp;&nbsp; <br />
&nbsp;&nbsp; 最后需要提示的是，使用本地方法是有开销的，它丧失了<span class="hilite2">java</span>的很多好处。如果别无选择，我们可以选择使用本地方法。<br />
<img src ="http://www.blogjava.net/gp213/aggbug/270569.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/gp213/" target="_blank">郭鹏</a> 2009-05-14 11:37 <a href="http://www.blogjava.net/gp213/archive/2009/05/14/270569.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>