<?xml version="1.0" encoding="UTF-8" ?>



<rss version="2.0" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

<channel>
<title><![CDATA[路漫漫其修远兮，吾将上下而求索]]> </title>
<description>
<![CDATA[耻下问者自满，知不足者好学  ]]>
</description>
<link>http://zshxts37.blog.bokee.net/</link>
<language>zh-cn</language>
<creator>zshxts37</creator>
<pubDate>Tue, 27 Mar 2007 11:05:44 CST </pubDate>
<generatorAgent rdf:resource="http://www.bokee.net"/>
<ttl>5</ttl>

<item>
<title>JAVA学习教材《java入门经典》1-17章</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/653016.html</link>
<description>
<![CDATA[课程主要以学习J2SE JDK1.5为主，课本以清华大学出版社出版的java程序设计教程(美)H.M.Deitel,P.J.Deitel著(ISBN:7-302-07892-0),以机械工业出版社java2入门经典(美)Ivor Horton著(ISBN:7-111-18089-5)为辅，课程下载请到http://bbs.java.ccidnet.com/read.php?tid=21283&fpage=1。
教程的格式为.PDG请大家用超星阅读
由于有人说是用超星打不开，特提供一个超星的版本下载，已经测试过，可以正常阅读
http://bbs.java.ccidnet.com/htm_data/32/0606/21377.html
]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/653016.html</guid>
<subject>网摘资料</subject>
<author>zshxts37</author>
<category>网摘资料</category>
<pubDate>Fri, 06 Apr 2007 09:39:38 CST </pubDate>
</item>

<item>
<title>怎么做需求分析</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/640186.html</link>
<description>
<![CDATA[<p><font face="宋体">在具体的研究需求分析之前，我们先了解一下软件工程这个概念。软件工程分为三个层次，过程层、方法层、工具层。在最基础的过程层，最重要的就是一组被称为关键过程区域（KPAs）的框架（KPA的概念在讨论CMM的书中有详细的概念说明）。关键过程区域构成了软件项目的管理控制的基础，并且确立了上下文各区域的关系，其中规定了技术方法的采用、工程产品的，模型、文档、数据、报告、表格等，等的产生、里程碑的建立、质量的保证及变化的适当管理。方法层主要是过程在技术上的实现。它解决的问题是如何做。软件工程方法涵盖了一系列的任务：需求分析、设计、编程、测试、维护。同时他还包括了一组基本原则，控制了每一个的关键过程区域。工具层就很好理解了，他对过程层和方法层提供了自动和半自动的支持。这些辅助工具就称为CASE。<br /><br />　　可以看到需求分析的位置，但是事实上需求分析是跨越了软件工程的三个层次的。这一点是和其他的过程是一样的。当然我们这里比较重点强调的是在软件工程的方法层，同时也涉及到一些过程层的思想，至于工具层则不再我们的讨论之列，但是会提到一些很适合在需求分析时应用的工具，诸如Word、Excel、Visio等。<br /><br /><font color="#ff0000">方法</font><br /><br />　　需求分析都包括了哪些方法呢？这里列举出在《需求分析》一书中推荐的一些方法，<br /><br />　　1) 绘制系统关联图，这种关联图是用于定义系统与系统外部实体间的界限和接口的简单模型。同时它也明确了通过接口的信息流和物质流。<br /><br />　　2) 创建用户接口原型，当开发人员或用户不能确定需求时，开发一个用户接口原型&mdash;一个可能的局部实现&mdash;这样使得许多概念和可能发生的事更为直观明了。用户通过评价原型将使项目参与者能更好地相互理解所要解决的问题。注意要找出需求文档与原型之间所有的冲突之处。<br /><br />　　3) 分析需求可行性，在允许的成本、性能要求下，分析每项需求实施的可行性，明确与每项需求实现相联系的风险，包括与其它需求的冲突，对外界因素的依赖和技术障碍。 <br />　　4) 确定需求的优先级别，应用分析方法来确定使用实例、产品特性或单项需求实现的优先级别。以优先级为基础确定产品版本将包括哪些特性或哪类需求。当允许需求变更时，在特定的版本中加入每一项变更，并在那个版本计划中作出需要的变更。<br /><br />　　5) 为需求建立模型，需求的图形分析模型是软件需求规格说明极好的补充说明。它们能提供不同的信息与关系以有助于找到不正确的、不一致的、遗漏的和冗余的需求。这样的模型包括数据流图、实体关系图、状态变换图、对话框图、对象类及交互作用图。<br /><br />　　6) 创建数据字典，数据字典是对系统用到的所有数据项和结构的定义，以确保开发人员使用统一的数据定义。在需求阶段，数据字典至少应定义客户数据项以确保客户与开发小组是使用一致的定义和术语。分析和设计工具通常包括数据字典组件。<br /><br />　　7) 使用质量功能调配，（QFD）是一种高级系统技术，它将产品特性、属性与对客户的重要性联系起来。该技术提供了一种分析方法以明确那些是客户最为关注的特性。QFD将需求分为三类：期望需求，即客户或许并未提及，但如若缺少会让他们感到不满意；普通需求；兴奋需求，即实现了会给客户带去惊喜，但若未实现也不会受到责备（Zultner <br />1993;Pardee 1996）。<br /><br />　　记住一点，不要试图在你的项目中把这些方法都用上去，四个现代化并不是一夜就可以实现的。同样，尝试着使用你认为对你很有帮助的方法，确实收到效果之后，在考虑继续学习方法。因为上面提到的都是需求分析的大方法，事实上还有很多很多的方法可以采用，例如，采用SRS模板、指明需求的来源、为每项需求注上标号、记录业务规范、创建需求跟踪能力矩阵、审查需求文档、以需求为依据编写测试用例、编写用户手册、确定合格的标准。<br /><br /><font color="#ff0000">业务建模</font><br /><br />　　很多人都没有意识到业务需求阶段应该做些什么事情，实际上业务建模是最重要的一件事情。不要觉得业务建模这个词很深奥，让人模不着头脑。其实所有做过需求分析的人都做过业务建模，比如你了解企业的运作模式就?是一种你脑海中的业务建模。但是大多数人都没有科学的、系统的、文档化的做过业务建模。<br /><br />　　业务建模的目的在于： <br /><br />　　了解目标组织（将要在其中部署系统的组织）的结构及机制。<br /><br />　　了解目标组织中当前存在的问题并确定改进的可能性。<br /><br />　　确保客户、最终用户和开发人员就目标组织达成共识。<br /><br />　　导出支持目标组织所需的业务需求。<br /><br />　　上面的话是不是很抽象呢，其实没有什么复杂的：人和电脑是完全不同的思想（思维方式）。所以，原先适合人的业务流程对于计算机来说可不一定合适的，为了最大限度的利用计算机，必须要了解原先的业务流程并对此加易改造（流程自动化），当然这些动作需要得到用户的许可。有些人认为说只有ERP这种大系统才需要对业务流程进行重组，但是实际上，不论是部门级的MIS系统，还是社会级的电子商务系统，都需要对业务流程进行改造，所不同的只是改造的程度。<br /><br />　　业务建模很重要的一点是在分析企业流程的同时分析出基础企业对象（Common Business Object）（这个词我翻译的不好，如果大家有更好的翻译，请告诉我）。任何企业都有最基础的一些元素，例如银行的CBO就有帐户，制造业的CBO就有订单等。有一次我的一个在企业应用方面研究多年的朋友告诉我一个秘诀，他说，企业的CBO无非是4个：客户、员工、产品和供应商（银行的供应商应该称为同业）。其他的所有CBO都是在这四个CBO的基础上发展起来的。比如说CBO中客户和产品是多对多的关系，根据关系数据的理论，任何多对多的关系都可以拆分成多个一对多或一对一的关系。你就可以在这两个类之间引入订单类，客户和订单之间是一对多，订单和产品之间又是一对多，这样一个多对多的关系就拆分成两个一对多的关系，而新的订单类也就顺理成章的产生了。在订单类产生时，你可能还会加入一个关联类：业务员类。而业务员类又是从员工类继承下来的。所以呢，企业的四种CBO通过不同的组合，不同的关系，能够形成企业运作的许许多多的CBO。<br /><br />　　CBO是做业务建模的基础，在此基础上，通过评估业务状态，说明当前业务，确定业务流程，改进业务流程的定义，设计业务流程实现，改进角色和职责，研究流程自动化，开发领域模型等一系列在RUP中定义的工作流程实现业务建模的目标。<br /><br /><font color="#ff0000">需求获取</font><br /><br />　　需求获取(requirement elicitation)是需求工程的主体。对于所建议的软件产品，获取需求是一个确定和理解不同用户类的需要和限制的过程。获取用户需求位于软件需求三个层次的中间一层。业务需求决定用户需求，它描述了用户利用系统需要完成的任务。从这些任务中，分析者能获得用于描述系统活动的特定的软件功能需求，这些系统活动有助于用户执行他们的任务。 <br />需求获取是在问题及其最终解决方案之间架设桥梁的第一步。获取需求的一个必不可少的结果是对项目中描述的客户需求的普遍理解。一旦理解了需求，分析者、开发者和客户就能探索出描述这些需求的多种解决方案。参与需求获取者只有在他们理解了问题之后才能开始设计系统，否则，对需求定义的任何改进，设计上都必须大量的返工。把需求获取集中在用户任务上&mdash;而不是集中在用户接口上&mdash;有助于防止开发组由于草率处理设计问题而造成的失误。 <br />需求获取、分析、编写需求规格说明和验证并不遵循线性的顺序，这些活动是相互隔开、增量和反复的。当你和客户合作时，你就将会问一些问题，并且取得他们所提供的信息（需求获取）。同时，你将处理这些信息以理解它们，并把它们分成不同的类别，还要把客户需求同可能的软件需求相联系（分析）。然后，你可以使客户信息结构化，并编写成文档和示意图（说明）。下一步，就可以让客户代表评审文档并纠正存在的错误（验证）。这四个过程贯穿着需求分析的整个阶段。 <br />需求获取可能是软件开发中最困难、最关键、最易出错及最需要交流的方面。需求获取只有通过有效的客户&mdash;开发者的合作才能成功。分析者必须建立一个对问题进行彻底探讨的环境，而这些问题与产品有关。为了方便清晰地进行交流，就要列出重要的小组，而不是假想所有的参与者都持有相同的看法。对需求问题的全面考察需要一种技术，利用这种技术不但考虑了问题的功能需求方面，还可讨论项目的非功能需求。确定用户已经理解：对于某些功能的讨论并不意味着即将在产品中实现它。对于想到的需求必须集中处理并设定优先级?，以避免一个不能带来任何益处的无限大的项目。 <br />需求获取是一个需要高度合作的活动，而并不是客户所说的需求的简单誊本。作为一个分析者，你必须透过客户所提出的表面需求理解他们的真正需求。询问一个可扩充（open-ended）的问题有助于你更好地理解用户目前的业务过程并且知道新系统如何帮助或改进他们的工作。调查用户任务可能遇到的变更，或者用户需要使用系统其它可能的方式。想像你自己在学习用户的工作，你需要完成什么任务？你有什么问题？从这一角度来指导需求的开发和利用。<br /><br />　　还有，探讨例外的情况：什么会妨碍用户顺利完成任务？对系统错误情况的反映，用户是如何想的？询问问题时，以&ldquo;还有什么能&rdquo; ,&rdquo;当?时，将会发生什么&rdquo;&ldquo;你有没有曾经想过&rdquo; <br />,&ldquo;有没有人曾经&rdquo;为开头。记下每一个需求的来源，这样向下跟踪直到发现特定的客户。<br /><br />　　有些时候，尝试着问一些&ldquo;愚蠢&rdquo;的问题也有助于客户打开话匣子。如果你直接要求客户写出业务是如何实现的，客户十有八九无法完成。但是如果你尝试着问一些实际的问题，例如：&ldquo;以我的理解，你们收到订单后，会...&rdquo;。客户立刻就会指出你的错误，并滔滔不绝的开始谈论业务，而你，就在一边仔细的聆听吧。这一招就叫做&ldquo;抛砖引玉&rdquo;。<br /><br />　　需求讨论会上必须要使用笔记本电脑，还要指定一个打字熟练的人把所有的讨论记录下来，记录的同时还要做一定的整理。如果不这样做，那么你结束会议的时候就会发现，所有的讨论只剩下一个模糊的印象，需求对你来说仍然是一件遥远的事情。在座谈讨论之后，记下所讨论的条目(item)，并请参与讨论的用户评论并更正。及早并经常进行座谈讨论是需求获取成功的一个关键途径，因为只有提供需求的人才能确定是否真正获取需求。进行深入收集和分析以消除任何冲突或不一致性。<br /><br />　　尽量把客户所持的假设解释清楚，特别是那些发生冲突的部分。从字里行间去理解以明确客户没有表达清楚但又想加入的特性或特征。Gause <br />和Weinberg（1989）提出使用&ldquo;上下文无关问题&rdquo;&mdash;这是一个高层次的问题，它可以获取业务问题和可能的解决方案的全部信息。客户对这些问题的回答诸如&ldquo;产品要求怎样的精确度&rdquo;或&ldquo;你能帮我解释一下你为什么不同意某人的回答吗？&rdquo;这些回答可以更直接地认识问题，而这是封闭（close-end）问题所不能做到的。<br /><br />　　需求获取利用了所有可用的信息来源，这些信息描述了问题域或在软件解决方案中合理的特性。一个研究表明：比起不成功的项目，一个成功的项目在开发者和客户之间采用了更多的交流方式（Kiel <br />and Carmel 1995）。与单个客户或潜在的用户组一起座谈，对于业务软件包或信息管理系统（MIS）的应用来说是一种传统的需求来源。直接聘请用户进行获取需求的过程是为项目获得支持和买入（buy-in）的一种方式。<br /><br />　　尽量理解用户用于表述他们需求的思维过程。充分研究用户执行任务时作出决策的过程，并提取出潜在的逻辑关系。流程图和决策树是描述这些逻辑决策途径的好方法。<br /><br />　　在需求获取的过程中，你可能会发现对项目范围的定义存在误差，不是太大就是太小。如果范围太大，你将要收集比真正需要更多的需求，以传递足够的业务和客户的值，此时获取过程将会拖延。如果项目范围太小，那么客户将会提出很重要的但又在当前产品范围之外的需求。当前的范围太小，以致不能提供一个令人满意的产品。需求的获取将导致修改项目的范围和任务，但作出这样具有深远影响的改变，一定要小心谨慎。 <br />正如经常所说的，需求主要是关于系统做什么，而解决方案如何实现是属于设计的范围。这样说虽然很简洁，但似乎过于简单化。需求的获取应该把重点放在&ldquo;做什么&rdquo;上，但在分析和设计之间还是存在一定的距离。你可以使用假设&ldquo;怎么做&rdquo;来分类并改善你对用户需求的理解。在需求的获取过程中，分析模型、屏幕图形和原型可以使概念表达得更加清楚，然后提供一个寻找错误和遗漏的办法。把你在需求开发阶段所形成的模型和屏幕效果看成是方便高效交流的概念性建议，而不应该看成是对设计者选择的一种限制。 <br />需求获取讨论会中如果参与者过多，就会减慢进度。人数大致控制在5到7人是最好的。这些人包括客户、系统设计者、开发者和可视化设计者等主要工程角色。相反地，从极少的代表那里收集信息或者只听到呼声最高、最有舆论影响的用户的声音，也会造成问题。这将导致忽视特定用户类的重要的需求，或者其需求不能代表绝大多数用户的需要。最?好的权衡在于选择一些授权为他们的用户类发言的产品代表者，他们也被同组用户类的其它代表所支持。 <br />没有一个简单、清楚的信号暗示你什么时候已完成需求获取。当客户和开发者与他们的同事聊天、阅读工业和商业上的文献及在早上沐浴时思考时，他们都将对潜在产品产生新的构思。你不可能全面收集需求，但是下列的提示将会暗示你在需求获取的过程中的返回点。<br /><br />　　1. 如果用户不能想出更多的使用实例，也许你就完成了收集需求的工作。用户总是按其重要性的顺序来确定使用实例的。<br /><br />　　2. 如果用户提出新的使用实例，但你可以从其它使用实例的相关功能需求中获得这些新的使用实例，这时也许你就完成了收集需求的工作。这些新的使用实例可能是你已获取的其它使用实例的可选过程。 <br />　　3. 如果用户开始重复原先讨论过的问题，此时，也许你就完成了收集需求的工作。<br /><br />　　4. 如果所提出的新需求比你已确定的需求的优先级都低时，也许你就完成了收集需求的工作。<br /><br />　　5. 如果用户提出对将来产品的要求，而不是现在我们讨论的特定产品，也许你就完成了收集需求的工作。<br /><br />　　以上知识大致上讨论需求分析应该如何做，实际上对于需求分析的方法有很多很多，已经形成了一定的理论，当然这种理论比较偏向与方法学，而方法学的应用主要还是要靠个人。所以，大家在实际应用的时候，不妨结合自己的实际，有选择性的采用一些方法，那你就是成功的</font></p>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/640186.html</guid>
<subject>网摘资料</subject>
<author>zshxts37</author>
<category>网摘资料</category>
<pubDate>Fri, 30 Mar 2007 16:18:07 CST </pubDate>
</item>

<item>
<title>以小博大 Java性能优化技巧集锦</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/639910.html</link>
<description>
<![CDATA[<p><font face="宋体">可供程序利用的资源(内存、CPU时间、网络带宽等)是有限的，优化的目的就是让程序用尽可能少的资源完成预定的任务。优化通常包含两方面的内容：减小代码的体积，提高代码的运行效率。本文讨论的主要是如何提高代码的效率。 </font></p>
<p><font face="宋体">　　<strong>一、通用篇 </strong></font></p>
<p><font face="宋体">　　&ldquo;通用篇&rdquo;讨论的问题适合于大多数Java应用。</font></p>
<p><font face="宋体">　　1.1 不用new关键词创建类的实例<img alt="" src="/common/js/fckeditor/editor/images/smiley/msn/wink_smile.gif" /></font></p>
<p><font face="宋体">　　用new关键词创建类的实例时，构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口，我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。</font></p>
<p><font face="宋体">　　在使用设计模式(Design Pattern)的场合，如果用Factory模式创建对象，则改用clone()方法创建新的对象实例非常简单。例如，下面是Factory模式的一个典型实现：</font></p>
<p><font face="宋体">　　 </font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">&nbsp;&nbsp;&nbsp; public static Credit getNewCredit() </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　return new Credit();</font></p>
            <p><font face="宋体">　　} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><font face="宋体">&nbsp;&nbsp;&nbsp;　　改进后的代码使用clone()方法，如下所示：</font></p>
<p><font face="宋体">　　 </font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">&nbsp;&nbsp;&nbsp; private static Credit BaseCredit = new Credit(); </font></p>
            <p><font face="宋体">　　public static Credit getNewCredit() </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　return (Credit) BaseCredit.clone();</font></p>
            <p><font face="宋体">　　} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><br /><font face="宋体">&nbsp;&nbsp;&nbsp;　　上面的思路对于数组处理同样很有用。</font></p>
<p><font face="宋体">　　1.2 使用非阻塞I/O</font></p>
<p><font face="宋体">　　版本较低的JDK不支持非阻塞I/O API。为避免I/O阻塞，一些应用采用了创建大量线程的办法(在较好的情况下，会使用一个缓冲池)。这种技术可以在许多必须支持并发I/O流的应用中见到，如Web服务器、报价和拍卖应用等。然而，创建Java线程需要相当可观的开销。</font></p>
<p><font face="宋体">　　JDK 1.4引入了非阻塞的I/O库(java.nio)。如果应用要求使用版本较早的JDK，在这里有一个支持非阻塞I/O的软件包。</font></p>
<p><font face="宋体">　　1.3 慎用异常</font></p>
<p><font face="宋体">　　异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法，fillInStackTrace()方法检查堆栈，收集调用跟踪信息。只要有异常被抛出，VM就必须调整调用堆栈，因为在处理过程中创建了一个新的对象。</font></p>
<p><font face="宋体">　　异常只能用于错误处理，不应该用来控制程序流程。</font></p>
<p><font face="宋体">　　1.4 不要重复初始化变量</font></p>
<p><font face="宋体">　　默认情况下，调用类的构造函数时， Java会把变量初始化成确定的值：所有的对象被设置成null，整数变量(byte、short、int、long)设置成0，float和double变量设置成0.0，逻辑值设置成false。当一个类从另一个类派生时，这一点尤其应该注意，因为用new关键词创建一个对象时，构造函数链中的所有构造函数都会被自动调用。</font></p>
<p><font face="宋体">　　1.5 尽量指定类的final修饰符</font></p>
<p><font face="宋体">　　带有final修饰符的类是不可派生的。在Java核心API中，有许多应用final的例子，例如java.lang.String。为String类指定final防止了人们覆盖length()方法。</font></p>
<p><font face="宋体">　　另外，如果指定一个类为final，则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。</font></p>
<p><font face="宋体">　　1.6 尽量使用局部变量</font></p>
<p><font face="宋体">　　调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中，速度较快。其他变量，如静态变量、实例变量等，都在堆(Heap)中创建，速度较慢。另外，依赖于具体的编译器/JVM，局部变量还可能得到进一步优化。请参见《尽可能使用堆栈变量》。</font></p>
<p><font face="宋体">　　1.7 乘法和除法</font></p>
<p><font face="宋体">　　考虑下面的代码：</font></p>
<p><font face="宋体">&nbsp; </font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">　　for (val = 0; val &lt; 100000; val +=5) </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　alterX = val * 8; </font></p>
            <p><font face="宋体">　　　myResult = val * 2; </font></p>
            <p><font face="宋体">　　} </font></p>
            <p><font face="宋体">　　用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代码：</font></p>
            <p><font face="宋体">　　for (val = 0; val &lt; 100000; val += 5) </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　alterX = val &lt;&lt; 3;</font></p>
            <p><font face="宋体">　　　myResult = val &lt;&lt; 1;</font></p>
            <p><font face="宋体">　　} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><br /></p>
<p><font face="宋体">　　修改后的代码不再做乘以8的操作，而是改用等价的左移3位操作，每左移1位相当于乘以2。相应地，右移1位操作相当于除以2。值得一提的是，虽然移位操作速度快，但可能使代码比较难于理解，所以最好加上一些注释。</font></p>
<p><font face="宋体">　　<strong>二、J2EE篇</strong></font></p>
<p><font face="宋体">　　前面介绍的改善性能技巧适合于大多数Java应用，接下来要讨论的问题适合于使用JSP、EJB或JDBC的应用。</font></p>
<p><font face="宋体">　　2.1 使用缓冲标记</font></p>
<p><font face="宋体">　　一些应用服务器加入了面向JSP的缓冲标记功能。例如，BEA的WebLogic Server从6.0版本开始支持这个功能，Open Symphony工程也同样支持这个功能。JSP缓冲标记既能够缓冲页面片断，也能够缓冲整个页面。当JSP页面执行时，如果目标片断已经在缓冲之中，则生成该片断的代码就不用再执行。页面级缓冲捕获对指定URL的请求，并缓冲整个结果页面。对于购物篮、目录以及门户网站的主页来说，这个功能极其有用。对于这类应用，页面级缓冲能够保存页面执行的结果，供后继请求使用。</font></p>
<p><font face="宋体">　　对于代码逻辑复杂的页面，利用缓冲标记提高性能的效果比较明显；反之，效果可能略逊一筹。</font></p>
<p><font face="宋体">　　2.2 始终通过会话Bean访问实体Bean</font></p>
<p><font face="宋体">　　直接访问实体Bean不利于性能。当客户程序远程访问实体Bean时，每一个get方法都是一个远程调用。访问实体Bean的会话Bean是本地的，能够把所有数据组织成一个结构，然后返回它的值。</font></p>
<p><font face="宋体">　　用会话Bean封装对实体Bean的访问能够改进事务管理，因为会话Bean只有在到达事务边界时才会提交。每一个对get方法的直接调用产生一个事务，容器将在每一个实体Bean的事务之后执行一个&ldquo;装入-读取&rdquo;操作。</font></p>
<p><font face="宋体">　　一些时候，使用实体Bean会导致程序性能不佳。如果实体Bean的唯一用途就是提取和更新数据，改成在会话Bean之内利用JDBC访问数据库可以得到更好的性能。</font></p>
<p><font face="宋体">　　2.3 选择合适的引用机制</font></p>
<p><font face="宋体">　　在典型的JSP应用系统中，页头、页脚部分往往被抽取出来，然后根据需要引入页头、页脚。当前，在JSP页面中引入外部资源的方法主要有两种：include指令，以及include动作。</font></p>
<p><font face="宋体">　　include指令：例如<!--include file="copyright.html"-->。该指令在编译时引入指定的资源。在编译之前，带有include指令的页面和指定的资源被合并成一个文件。被引用的外部资源在编译时就确定，比运行时才确定资源更高效。</font></p>
<p><font face="宋体">　　include动作：例如<jsp:include page="copyright.jsp"></jsp:include>。该动作引入指定页面执行后生成的结果。由于它在运行时完成，因此对输出结果的控制更加灵活。但时，只有当被引用的内容频繁地改变时，或者在对主页面的请求没有出现之前，被引用的页面无法确定时，使用include动作才合算。</font></p>
<p><font face="宋体">　　2.4 在部署描述器中设置只读属性</font></p>
<p><font face="宋体">　　实体Bean的部署描述器允许把所有get方法设置成&ldquo;只读&rdquo;。当某个事务单元的工作只包含执行读取操作的方法时，设置只读属性有利于提高性能，因为容器不必再执行存储操作。</font></p>
<p><font face="宋体">　　2.5 缓冲对EJB Home的访问</font></p>
<p><font face="宋体">　　EJB Home接口通过JNDI名称查找获得。这个操作需要相当可观的开销。JNDI查找最好放入Servlet的init()方法里面。如果应用中多处频繁地出现EJB访问，最好创建一个EJBHomeCache类。EJBHomeCache类一般应该作为singleton实现。</font></p>
<p><font face="宋体">　　2.6 为EJB实现本地接口</font></p>
<p><font face="宋体">　　本地接口是EJB 2.0规范新增的内容，它使得Bean能够避免远程调用的开销。请考虑下面的代码。</font></p>
<p><font face="宋体">　　PayBeanHome home = (PayBeanHome) javax.rmi.PortableRemoteObject.narrow (ctx.lookup (&quot;PayBeanHome&quot;), PayBeanHome.class); </font></p>
<p><font face="宋体">　　PayBean bean = (PayBean) javax.rmi.PortableRemoteObject.narrow (home.create(), PayBean.class); </font></p>
<p><font face="宋体">　　第一个语句表示我们要寻找Bean的Home接口。这个查找通过JNDI进行，它是一个RMI调用。然后，我们定位远程对象，返回代理引用，这也是一个RMI调用。第二个语句示范了如何创建一个实例，涉及了创建IIOP请求并在网络上传输请求的stub程序，它也是一个RMI调用。</font></p>
<p><font face="宋体">　　要实现本地接口，我们必须作如下修改：</font></p>
<p><font face="宋体">　　方法不能再抛出java.rmi.RemoteException异常，包括从RemoteException派生的异常，比如TransactionRequiredException、TransactionRolledBackException和NoSuchObjectException。EJB提供了等价的本地异常，如TransactionRequiredLocalException、TransactionRolledBackLocalException和NoSuchObjectLocalException。</font></p>
<p><font face="宋体">　　所有数据和返回值都通过引用的方式传递，而不是传递值。</font></p>
<p><font face="宋体">　　本地接口必须在EJB部署的机器上使用。简而言之，客户程序和提供服务的组件必须在同一个JVM上运行。</font></p>
<p><font face="宋体">　　如果Bean实现了本地接口，则其引用不可串行化。</font></p>
<p><font face="宋体">　　2.7 生成主键</font></p>
<p><font face="宋体">　　在EJB之内生成主键有许多途径，下面分析了几种常见的办法以及它们的特点。</font></p>
<p><font face="宋体">　　利用数据库内建的标识机制(SQL Server的IDENTITY或Oracle的SEQUENCE)。这种方法的缺点是EJB可移植性差。</font></p>
<p><font face="宋体">　　由实体Bean自己计算主键值(比如做增量操作)。它的缺点是要求事务可串行化，而且速度也较慢。</font></p>
<p><font face="宋体">　　利用NTP之类的时钟服务。这要求有面向特定平台的本地代码，从而把Bean固定到了特定的OS之上。另外，它还导致了这样一种可能，即在多CPU的服务器上，同一个毫秒之内生成了两个主键。</font></p>
<p><font face="宋体">　　借鉴Microsoft的思路，在Bean中创建一个GUID。然而，如果不求助于JNI，Java不能确定网卡的MAC地址；如果使用JNI，则程序就要依赖于特定的OS。</font></p>
<p><font face="宋体">　　还有其他几种办法，但这些办法同样都有各自的局限。似乎只有一个答案比较理想：结合运用RMI和JNDI。先通过RMI注册把RMI远程对象绑定到JNDI树。客户程序通过JNDI进行查找。下面是一个例子：</font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">　　public class keyGenerator extends UnicastRemoteObject implements Remote </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　private static long KeyValue = System.currentTimeMillis(); </font></p>
            <p><font face="宋体">&nbsp;&nbsp;&nbsp; public static synchronized long getKey() throws RemoteException { return KeyValue++; } </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><font face="宋体">　　2.8 及时清除不再需要的会话</font></p>
<p><font face="宋体">　　为了清除不再活动的会话，许多应用服务器都有默认的会话超时时间，一般为30分钟。当应用服务器需要保存更多会话时，如果内存容量不足，操作系统会把部分内存数据转移到磁盘，应用服务器也可能根据&ldquo;最近最频繁使用&rdquo;(Most Recently Used)算法把部分不活跃的会话转储到磁盘，甚至可能抛出&ldquo;内存不足&rdquo;异常。在大规模系统中，串行化会话的代价是很昂贵的。当会话不再需要时，应当及时调用HttpSession.invalidate()方法清除会话。HttpSession.invalidate()方法通常可以在应用的退出页面调用。</font></p>
<p><font face="宋体">　　2.9 在JSP页面中关闭无用的会话</font></p>
<p><font face="宋体">　　对于那些无需跟踪会话状态的页面，关闭自动创建的会话可以节省一些资源。使用如下page指令：</font></p>
<p><font face="宋体">　　＜%@ page session=&quot;false&quot;%＞ </font></p>
<p><font face="宋体">　　2.10 Servlet与内存使用</font></p>
<p><font face="宋体">　　许多开发者随意地把大量信息保存到用户会话之中。一些时候，保存在会话中的对象没有及时地被垃圾回收机制回收。从性能上看，典型的症状是用户感到系统周期性地变慢，却又不能把原因归于任何一个具体的组件。如果监视JVM的堆空间，它的表现是内存占用不正常地大起大落。</font></p>
<p><font face="宋体">　　解决这类内存问题主要有二种办法。第一种办法是，在所有作用范围为会话的Bean中实现HttpSessionBindingListener接口。这样，只要实现valueUnbound()方法，就可以显式地释放Bean使用的资源。</font></p>
<p><font face="宋体">　　另外一种办法就是尽快地把会话作废。大多数应用服务器都有设置会话作废间隔时间的选项。另外，也可以用编程的方式调用会话的setMaxInactiveInterval()方法，该方法用来设定在作废会话之前，Servlet容器允许的客户请求的最大间隔时间，以秒计。</font></p>
<p><font face="宋体">　　2.11 HTTP Keep-Alive</font></p>
<p><font face="宋体">　　Keep-Alive功能使客户端到服务器端的连接持续有效，当出现对服务器的后继请求时，Keep-Alive功能避免了建立或者重新建立连接。市场上的大部分Web服务器，包括iPlanet、IIS和Apache，都支持HTTP Keep-Alive。对于提供静态内容的网站来说，这个功能通常很有用。但是，对于负担较重的网站来说，这里存在另外一个问题：虽然为客户保留打开的连接有一定的好处，但它同样影响了性能，因为在处理暂停期间，本来可以释放的资源仍旧被占用。当Web服务器和应用服务器在同一台机器上运行时，Keep-Alive功能对资源利用的影响尤其突出。</font></p>
<p><font face="宋体">　　2.12 JDBC与Unicode</font></p>
<p><font face="宋体">　　想必你已经了解一些使用JDBC时提高性能的措施，比如利用连接池、正确地选择存储过程和直接执行的SQL、从结果集删除多余的列、预先编译SQL语句，等等。</font></p>
<p><font face="宋体">　　除了这些显而易见的选择之外，另一个提高性能的好选择可能就是把所有的字符数据都保存为Unicode(代码页13488)。Java以Unicode形式处理所有数据，因此，数据库驱动程序不必再执行转换过程。但应该记住：如果采用这种方式，数据库会变得更大，因为每个Unicode字符需要2个字节存储空间。另外，如果有其他非Unicode的程序访问数据库，性能问题仍旧会出现，因为这时数据库驱动程序仍旧必须执行转换过程。</font></p>
<p><font face="宋体">　　2.13 JDBC与I/O</font></p>
<p><font face="宋体">　　如果应用程序需要访问一个规模很大的数据集，则应当考虑使用块提取方式。默认情况下，JDBC每次提取32行数据。举例来说，假设我们要遍历一个5000行的记录集，JDBC必须调用数据库157次才能提取到全部数据。如果把块大小改成512，则调用数据库的次数将减少到10次。</font></p>
<p><font face="宋体">　　在一些情形下这种技术无效。例如，如果使用可滚动的记录集，或者在查询中指定了FOR UPDATE，则块操作方式不再有效。</font></p>
<p><font face="宋体">　　1.14 内存数据库</font></p>
<p><font face="宋体">　　许多应用需要以用户为单位在会话对象中保存相当数量的数据，典型的应用如购物篮和目录等。由于这类数据可以按照行/列的形式组织，因此，许多应用创建了庞大的Vector或HashMap。在会话中保存这类数据极大地限制了应用的可伸缩性，因为服务器拥有的内存至少必须达到每个会话占用的内存数量乘以并发用户最大数量，它不仅使服务器价格昂贵，而且垃圾收集的时间间隔也可能延长到难以忍受的程度。</font></p>
<p><font face="宋体">　　一些人把购物篮/目录功能转移到数据库层，在一定程度上提高了可伸缩性。然而，把这部分功能放到数据库层也存在问题，且问题的根源与大多数关系数据库系统的体系结构有关。对于关系数据库来说，运行时的重要原则之一是确保所有的写入操作稳定、可靠，因而，所有的性能问题都与物理上把数据写入磁盘的能力有关。关系数据库力图减少I/O操作，特别是对于读操作，但实现该目标的主要途径只是执行一套实现缓冲机制的复杂算法，而这正是数据库层第一号性能瓶颈通常总是CPU的主要原因。</font></p>
<p><font face="宋体">　　一种替代传统关系数据库的方案是，使用在内存中运行的数据库(In-memory Database)，例如TimesTen。内存数据库的出发点是允许数据临时地写入，但这些数据不必永久地保存到磁盘上，所有的操作都在内存中进行。这样，内存数据库不需要复杂的算法来减少I/O操作，而且可以采用比较简单的加锁机制，因而速度很快。</font></p>
<p><font face="宋体">　　<strong>三、GUI篇</strong></font></p>
<p><font face="宋体">　　这一部分介绍的内容适合于图形用户界面的应用(Applet和普通应用)，要用到AWT或Swing。</font></p>
<p><font face="宋体">　　3.1 用JAR压缩类文件</font></p>
<p><font face="宋体">　　Java档案文件(JAR文件)是根据JavaBean标准压缩的文件，是发布JavaBean组件的主要方式和推荐方式。JAR档案有助于减少文件体积，缩短下载时间。例如，它有助于Applet提高启动速度。一个JAR文件可以包含一个或者多个相关的Bean以及支持文件，比如图形、声音、HTML和其他资源。</font></p>
<p><font face="宋体">　　要在HTML/JSP文件中指定JAR文件，只需在Applet标记中加入ARCHIVE = &quot;name.jar&quot;声明。</font></p>
<p><font face="宋体">　　3.2 提示Applet装入进程</font></p>
<p><font face="宋体">　　你是否看到过使用Applet的网站，注意到在应该运行Applet的地方出现了一个占位符？当Applet的下载时间较长时，会发生什么事情？最大的可能就是用户掉头离去。在这种情况下，显示一个Applet正在下载的信息无疑有助于鼓励用户继续等待。</font></p>
<p><font face="宋体">　　下面我们来看看一种具体的实现方法。首先创建一个很小的Applet，该Applet负责在后台下载正式的Applet：</font></p>
<p><font face="宋体">&nbsp;</font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">&nbsp; </font></p>
            <p><font face="宋体">　　import java.applet.Applet;</font></p>
            <p><font face="宋体">　　import java.applet.AppletStub;</font></p>
            <p><font face="宋体">　　import java.awt.Label;</font></p>
            <p><font face="宋体">　　import java.awt.Graphics;</font></p>
            <p><font face="宋体">　　import java.awt.GridLayout; </font></p>
            <p><font face="宋体">　　public class PreLoader extends Applet implements Runnable, AppletStub</font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　String largeAppletName; </font></p>
            <p><font face="宋体">　　　Label label;</font></p>
            <p><font face="宋体">　　　public void init()</font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　// 要求装载的正式Applet </font></p>
            <p><font face="宋体">　　largeAppletName = getParameter(&quot;applet&quot;); </font></p>
            <p><font face="宋体">　　// &ldquo;请稍等&rdquo;提示信息 </font></p>
            <p><font face="宋体">　　label = new Label(&quot;请稍等...&quot; + largeAppletName); </font></p>
            <p><font face="宋体">　　add(label); </font></p>
            <p><font face="宋体">　　　}</font></p>
            <p><font face="宋体">　　　public void run()</font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　try </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　// 获得待装载Applet的类</font></p>
            <p><font face="宋体">　　　Class largeAppletClass = Class.forName(largeAppletName);</font></p>
            <p><font face="宋体">　　　// 创建待装载Applet的实例 </font></p>
            <p><font face="宋体">　　　Applet largeApplet = (Applet)largeAppletClass.newInstance(); </font></p>
            <p><font face="宋体">　　　// 设置该Applet的Stub程序 </font></p>
            <p><font face="宋体">　　　largeApplet.setStub(this); </font></p>
            <p><font face="宋体">　　　// 取消&ldquo;请稍等&rdquo;信息 </font></p>
            <p><font face="宋体">　　　remove(label);</font></p>
            <p><font face="宋体">　　　// 设置布局 </font></p>
            <p><font face="宋体">　　　setLayout(new GridLayout(1, 0)); </font></p>
            <p><font face="宋体">　　　add(largeApplet);</font></p>
            <p><font face="宋体">　　　// 显示正式的Applet </font></p>
            <p><font face="宋体">　　　largeApplet.init(); </font></p>
            <p><font face="宋体">　　　largeApplet.start(); </font></p>
            <p><font face="宋体">　　}</font></p>
            <p><font face="宋体">　　catch (Exception ex) </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　// 显示错误信息 </font></p>
            <p><font face="宋体">　　　label.setText(&quot;不能装入指定的Applet&quot;); </font></p>
            <p><font face="宋体">　　}</font></p>
            <p><font face="宋体">　　// 刷新屏幕 </font></p>
            <p><font face="宋体">　　validate();</font></p>
            <p><font face="宋体">　　　}</font></p>
            <p><font face="宋体">　　　public void appletResize(int width, int height) </font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　// 把appletResize调用从stub程序传递到Applet </font></p>
            <p><font face="宋体">　　resize(width, height); </font></p>
            <p><font face="宋体">　　　}</font></p>
            <p><font face="宋体">　　} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><font face="宋体">　　编译后的代码小于2K，下载速度很快。代码中有几个地方值得注意。首先，PreLoader实现了AppletStub接口。一般地，Applet从调用者判断自己的codebase。在本例中，我们必须调用setStub()告诉Applet到哪里提取这个信息。另一个值得注意的地方是，AppletStub接口包含许多和Applet类一样的方法，但appletResize()方法除外。这里我们把对appletResize()方法的调用传递给了resize()方法。</font></p>
<p><font face="宋体">　　3.3 在画出图形之前预先装入它</font></p>
<p><font face="宋体">　　ImageObserver接口可用来接收图形装入的提示信息。ImageObserver接口只有一个方法imageUpdate()，能够用一次repaint()操作在屏幕上画出图形。下面提供了一个例子。</font></p>
<p><font face="宋体">&nbsp;</font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">　　public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　if ((flags &amp; ALLBITS) !=0 { repaint(); </font></p>
            <p><font face="宋体">　　}</font></p>
            <p><font face="宋体">　　else if (flags &amp; (ERROR |ABORT )) != 0) </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　error = true; </font></p>
            <p><font face="宋体">　　　// 文件没有找到，考虑显示一个占位符 </font></p>
            <p><font face="宋体">　　　repaint();</font></p>
            <p><font face="宋体">　　}</font></p>
            <p><font face="宋体">　　return (flags &amp; (ALLBITS | ERROR| ABORT)) == 0;</font></p>
            <p><font face="宋体">　　} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><font face="宋体">　　当图形信息可用时，imageUpdate()方法被调用。如果需要进一步更新，该方法返回true；如果所需信息已经得到，该方法返回false。</font></p>
<p><font face="宋体">　　3.4 覆盖update方法</font></p>
<p><font face="宋体">　　update()方法的默认动作是清除屏幕，然后调用paint()方法。如果使用默认的update()方法，频繁使用图形的应用可能出现显示闪烁现象。要避免在paint()调用之前的屏幕清除操作，只需按照如下方式覆盖update()方法：</font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">&nbsp;&nbsp;&nbsp; public void update(Graphics g) { paint(g);} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><font face="宋体">　　更理想的方案是：覆盖update()，只重画屏幕上发生变化的区域，如下所示：</font></p>
<p><font face="宋体">　　</font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">&nbsp;&nbsp;&nbsp;&nbsp; public void update(Graphics g) </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　g.clipRect(x, y, w, h); </font></p>
            <p><font face="宋体">　　　paint(g);</font></p>
            <p><font face="宋体">　　} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><font face="宋体">　　3.5 延迟重画操作</font></p>
<p><font face="宋体">　　对于图形用户界面的应用来说，性能低下的主要原因往往可以归结为重画屏幕的效率低下。当用户改变窗口大小或者滚动一个窗口时，这一点通常可以很明显地观察到。改变窗口大小或者滚动屏幕之类的操作导致重画屏幕事件大量地、快速地生成，甚至超过了相关代码的执行速度。对付这个问题最好的办法是忽略所有&ldquo;迟到&rdquo;的事件。</font></p>
<p><font face="宋体">　　建议在这里引入一个数毫秒的时差，即如果我们立即接收到了另一个重画事件，可以停止处理当前事件转而处理最后一个收到的重画事件；否则，我们继续进行当前的重画过程。</font></p>
<p><font face="宋体">　　如果事件要启动一项耗时的工作，分离出一个工作线程是一种较好的处理方式；否则，一些部件可能被&ldquo;冻结&rdquo;，因为每次只能处理一个事件。下面提供了一个事件处理的简单例子，但经过扩展后它可以用来控制工作线程。</font></p>
<p><font face="宋体">&nbsp;</font></p>
<p><font face="宋体">　　 </font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">&nbsp; </font></p>
            <p><font face="宋体">　　public static void runOnce(String id, final long milliseconds) </font></p>
            <p><font face="宋体">　　{ </font></p>
            <p><font face="宋体">　　　synchronized(e_queue)</font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　// e_queue: 所有事件的集合 </font></p>
            <p><font face="宋体">　　if (!e_queue.containsKey(id))</font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　e_queue.put(token, new LastOne());</font></p>
            <p><font face="宋体">　　}</font></p>
            <p><font face="宋体">　　　}</font></p>
            <p><font face="宋体">　　　final LastOne lastOne = (LastOne) e_queue.get(token); </font></p>
            <p><font face="宋体">　　　final long time = System.currentTimeMillis(); </font></p>
            <p><font face="宋体">　　　// 获得当前时间 </font></p>
            <p><font face="宋体">　　　lastOne.time = time; </font></p>
            <p><font face="宋体">　　　(new Thread() </font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　public void run() </font></p>
            <p><font face="宋体">　　{</font></p>
            <p><font face="宋体">　　　if (milliseconds &gt; 0) </font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　　　try</font></p>
            <p><font face="宋体">　　　　{</font></p>
            <p><font face="宋体">　　　　　Thread.sleep(milliseconds);</font></p>
            <p><font face="宋体">　　　　}</font></p>
            <p><font face="宋体">　　　　// 暂停线程 </font></p>
            <p><font face="宋体">　　　　atch (Exception ex) {}</font></p>
            <p><font face="宋体">　　　}</font></p>
            <p><font face="宋体">　　　synchronized(lastOne.running) </font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　　　// 等待上一事件结束 </font></p>
            <p><font face="宋体">　　　　if (lastOne.time != time)</font></p>
            <p><font face="宋体">　　　　// 只处理最后一个事件 </font></p>
            <p><font face="宋体">　　　　　return; </font></p>
            <p><font face="宋体">　　　}</font></p>
            <p><font face="宋体">　　}}).start();</font></p>
            <p><font face="宋体">　　　}</font></p>
            <p><font face="宋体">　　　private static Hashtable e_queue = new Hashtable(); </font></p>
            <p><font face="宋体">　　　private static class LastOne </font></p>
            <p><font face="宋体">　　　{</font></p>
            <p><font face="宋体">　　public long time=0;</font></p>
            <p><font face="宋体">　　public Object running = new Object();</font></p>
            <p><font face="宋体">　　　} </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><font face="宋体">&nbsp;&nbsp;&nbsp; 3.6 使用双缓冲区</font></p>
<p><font face="宋体">　　在屏幕之外的缓冲区绘图，完成后立即把整个图形显示出来。由于有两个缓冲区，所以程序可以来回切换。这样，我们可以用一个低优先级的线程负责画图，使得程序能够利用空闲的CPU时间执行其他任务。下面的伪代码片断示范了这种技术。</font></p>
<p><font face="宋体">　　 </font></p>
<p>
<table style="BORDER-COLLAPSE: collapse" bordercolor="#cccccc" cellspacing="0" cellpadding="2" width="500" align="center" bgcolor="#ffffff" border="1">
    <tbody>
        <tr>
            <td>
            <p><font face="宋体">&nbsp;&nbsp;&nbsp; Graphics myGraphics; </font></p>
            <p><font face="宋体">　　Image myOffscreenImage = createImage(size().width, size().height);</font></p>
            <p><font face="宋体">　　Graphics offscreenGraphics = myOffscreenImage.getGraphics(); </font></p>
            <p><font face="宋体">　　offscreenGraphics.drawImage(img, 50, 50, this);</font></p>
            <p><font face="宋体">　　myGraphics.drawImage(myOffscreenImage, 0, 0, this); </font></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p><br /><font face="宋体">&nbsp;&nbsp;&nbsp; 3.7 使用BufferedImage</font></p>
<p><font face="宋体">　　Java JDK 1.2使用了一个软显示设备，使得文本在不同的平台上看起来相似。为实现这个功能，Java必须直接处理构成文字的像素。由于这种技术要在内存中大量地进行位复制操作，早期的JDK在使用这种技术时性能不佳。为解决这个问题而提出的Java标准实现了一种新的图形类型，即BufferedImage。</font></p>
<p><font face="宋体">　　BufferedImage子类描述的图形带有一个可访问的图形数据缓冲区。一个BufferedImage包含一个ColorModel和一组光栅图形数据。这个类一般使用RGB(红、绿、蓝)颜色模型，但也可以处理灰度级图形。它的构造函数很简单，如下所示：</font></p>
<p><font face="宋体">　　public BufferedImage (int width, int height, int imageType) </font></p>
<p><font face="宋体">　　ImageType允许我们指定要缓冲的是什么类型的图形，比如5-位RGB、8-位RGB、灰度级等。</font></p>
<p><font face="宋体">　　3.8 使用VolatileImage</font></p>
<p><font face="宋体">　　许多硬件平台和它们的操作系统都提供基本的硬件加速支持。例如，硬件加速一般提供矩形填充功能，和利用CPU完成同一任务相比，硬件加速的效率更高。由于硬件加速分离了一部分工作，允许多个工作流并发进行，从而缓解了对CPU和系统总线的压力，使得应用能够运行得更快。利用VolatileImage可以创建硬件加速的图形以及管理图形的内容。由于它直接利用低层平台的能力，性能的改善程度主要取决于系统使用的图形适配器。VolatileImage的内容随时可能丢失，也即它是&ldquo;不稳定的(volatile)&rdquo;。因此，在使用图形之前，最好检查一下它的内容是否丢失。VolatileImage有两个能够检查内容是否丢失的方法：</font></p>
<p><font face="宋体">　　public abstract int validate(GraphicsConfiguration gc);public abstract Boolean contentsLost(); </font></p>
<p><font face="宋体">　　每次从VolatileImage对象复制内容或者写入VolatileImage时，应该调用validate()方法。contentsLost()方法告诉我们，自从最后一次validate()调用之后，图形的内容是否丢失。</font></p>
<p><font face="宋体">　　虽然VolatileImage是一个抽象类，但不要从它这里派生子类。VolatileImage应该通过Component.createVolatileImage()或者GraphicsConfiguration.createCompatibleVolatileImage()方法创建。</font></p>
<p><font face="宋体">　　3.9 使用Window Blitting</font></p>
<p><font face="宋体">　　进行滚动操作时，所有可见的内容一般都要重画，从而导致大量不必要的重画工作。许多操作系统的图形子系统，包括WIN32 GDI、MacOS和X/Windows，都支持Window Blitting技术。Window Blitting技术直接在屏幕缓冲区中把图形移到新的位置，只重画新出现的区域。要在Swing应用中使用Window Blitting技术，设置方法如下：</font></p>
<p><font face="宋体">　　setScrollMode(int mode); </font></p>
<p><font face="宋体">　　在大多数应用中，使用这种技术能够提高滚动速度。只有在一种情形下，Window Blitting会导致性能降低，即应用在后台进行滚动操作。如果是用户在滚动一个应用，那么它总是在前台，无需担心任何负面影响。 </font></p>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/639910.html</guid>
<subject>学习札记</subject>
<author>zshxts37</author>
<category>学习札记</category>
<pubDate>Fri, 30 Mar 2007 14:47:05 CST </pubDate>
</item>

<item>
<title>十年之后　Java技术的新方向</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/639861.html</link>
<description>
<![CDATA[<font face="宋体"><span class="Code">
<p style="TEXT-INDENT: 2em"><font face="宋体">Java技术自问世时光已经过去了10个年头。作为一名一直关注其成长的记者，曾经一段时间有过这样的想法：&ldquo;Java技术已经成熟，是不是发展速度该放慢一些了呢&rdquo;。然而，这种想法错了。近来Java技术的进化相当显著。Java技术正在迎来&ldquo;又一次革命&rdquo;的风暴。这就是本文的结论。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">&ldquo;又一次&rdquo;指的是什么？&ldquo;革命&rdquo;指的又是什么？光看结论的话肯定是一头雾水。其实，笔者要讲的并不是变化这样一个事实，而是&ldquo;促进变化的原动力&rdquo;。是什么让Java技术发生变化？让我们从这一角度出发，先看一下Java的变化历程。</font></p>
<p style="TEXT-INDENT: 2em"><strong><font face="宋体">Java正处于转变期</font></strong></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">回顾Java技术的发展历程，人们一定会感慨：2004年到2005年发生的变化如此之大，可以说&ldquo;现在&rdquo;正是Java技术的转换期。Java技术由编程语言（Java语言）、运行环境（JVM：Java虚拟机）、框架（JavaAPI群）组成，目前在每一个层面上都发生着巨大的变化。</font></p>
<p style="TEXT-INDENT: 2em"><strong><font face="宋体">（1）编程语言的变化</font></strong></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">Java语言标准出现明显变化。在2004年9月发布的正式版本J2SE5.0（J2SE:Java2Platform,StandardEdition，Java技术的核心运行环境）中，对Java语言标准中的Generics与元数据进行了大幅扩展。出现了被认为是&ldquo;自Java问世以来的最大一次语言标准变化&rdquo;（美国SunMicrosystems）。这次语言标准的变化给许多JavaAPI带来了影响。许多企业API的新版本都引入了基于元数据的注解（程序中记录的附加信息），以较短的编码记述更多的信息。</font></p>
<p style="TEXT-INDENT: 2em"><strong><font face="宋体">（2）Java运行环境的变化</font></strong></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">在J2SE5.0中，大幅度强化了JVM的管理功能与实用性，换句话说就是具备了Java执行OS（操作系统）的特征。例如，原来在J2EE（Java2Platform,EnterpriseEdition，构筑企业系统的Java技术）领域，标准配备有作为管理功能的应用软件管理框架JMX。不仅如此，还配备有JVM自身监控功能（（JSR174:MonitoringandManagementSpecificationfortheJavaVirtualMachine）。在标准功能架构中可以实时监视JVM运行时的状态，也就是内存使用量、线程状态等。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">J2SE5.0中新追加的功能中包括并行处理实用程序（JSR166），其基础是纽约州立大学DougLea提供的程序库。也就是说，标准规格中采用了来自民间的程序库。</font></p>
<p style="TEXT-INDENT: 2em"><strong><font face="宋体">（3）框架的变化</font></strong></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">服务器端的Java框架也发生了巨大变化。企业级Java技术--J2EE的&ldquo;使用方法&rdquo;说明文件&ldquo;J2EEBlueprint&rdquo;中，提出了将应用软件分为Web层、EJB层（EnterpriseJavaBeans，将包括事务处理在内的业务进程模块化的框架）来构筑的思路。这两种层次都迎来了架构更替时期。Web层的新框架为JSF（JavaServerFaces，将模块组合起来构筑Web应用程序的框架），EJB层为标准方案中刚刚公布的下一代EJB规格&ldquo;EJB3.0&rdquo;。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">值得注意的是，促成框架发生变化的正是来自民间的源码开放软件。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">对JSF产生影响的是作为源码开放的Web层框架得到普及的ApacheStruts。JSF是对Struts的思路进行改进的产物，JSF的SpecLead（规格制定领袖）之一CraigR.McClanahan就是Struts的作者。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">对EJB3.0造成影响的也是民间源码开放软件。EJB3.0引入了DI（DependencyInjection，依赖注入）容器类（Container）与POJO（PlainOldJavaObject）持久类这些新功能，大大减轻了编程的复杂性。这些概念因PicoContainer、Spring等源码开放软件的导入而引人注目。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">其背景在于用户对&ldquo;目前的EJB过于复杂&rdquo;的批评。原本由EJB反对派提出的设计思想与源码开放软件，却变成了EJB3.0的中心概念，显出了巨大的影响力。</font></p>
<p style="TEXT-INDENT: 2em"><strong><font face="宋体">（4）脚本语言</font></strong></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">在Java技术标准中新增加了编程语言Groovy（JSR241）。这是一种可与Java语言无缝连接的脚本语言，有望以极短的程序完成相关处理。&ldquo;在Java技术中添加Java以外的语言&rdquo;，这听起来也许有些别扭，其实以前就有这样的呼声，希望将可以充分利用Java技术资源的脚本作为语言使用。Groovy本来是源码开放软件，最终得到认可，被采纳为标准规格。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">由上述可以看出，Java技术的构成要素正在发生巨大变化。就在不久以前，一提起服务器Java，&ldquo;Servlet、JSP、EJB是重要的API&rdquo;这样的说明还占主流，但现在基于JSF和EJB3.0的应用程序已经变成了&ldquo;面目全非&rdquo;的程序。而在运行短程序或测试时，甚至还出现了不仅是Java语言，连脚本语言都开始调用Java框架的情况。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体"></font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">这些变化从大的方面来看的话，可以说是进一步发挥了Java面向对象的优势。当然，也包括提高开发效率、提高运行稳定性、简化运行管理等业务上的优势。</font></p>
<p style="TEXT-INDENT: 2em"><strong><font face="宋体">开发者团体是真正的&ldquo;变革推动者&rdquo;</font></strong></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">那么，这些变化的原动力来自哪里呢？为什么说&ldquo;目前&rdquo;正面临着&ldquo;又一次变革&rdquo;呢？理由如下：</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">在Java技术的发展过程中，1999年到2000年是一个大的转折点。J2EE概念于1999年出现。J2EE也在随后2～3年内得到普及，但这一技术体系早在5年前就已经确立。在我们眼前，新一代Java技术的轮廓正逐渐显现出来。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">JCP（JavaCommunityProcess）2.0于2000年问世。以会员制的组织形式推进Java技术的规格制订、总体发展方向则以委员会的方式决定。从而形成了不依赖特定企业的规格制订流程。这一组织形式历经近5年的时间，逐渐发展成&ldquo;变革的推动者&rdquo;。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">J2EE此前一直饱受批评，认为&ldquo;Web层与EJB层的差距太大&rdquo;、&ldquo;EJB过于复杂&rdquo;，但这也是因为这是一项实际使用的技术。JCP同样也遇到很多批评，称其&ldquo;没有完全公开&rdquo;、&ldquo;制定的技术标准却不可思议地让Sun拥有知识产权&rdquo;，但JCP却作为一个团体不断发展壮大。</font></p>
<p style="TEXT-INDENT: 2em"><font face="宋体">直接推动Java技术变化的当事者为5年前形成的基于团体的标准制订流程--JCP，但真正将讨论与技术纳入JCP的却是包括Java技术批评者在内的众多Java开发者团体。他们也是早期开展Java技术变革的先行者。由此诞生的下一代技术将会在2～3年后逐渐波及主流企业用户。</font></p>
<div style="TEXT-INDENT: 2em"><font face="宋体">Java技术的&ldquo;变革推动者&rdquo;为开发者团体。不受制于特定企业，通过众多需要Java的开发者的建议，Java技术正在不断发展进步。</font></div>
</span></font>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/639861.html</guid>
<subject>网摘资料</subject>
<author>zshxts37</author>
<category>网摘资料</category>
<pubDate>Fri, 30 Mar 2007 14:24:32 CST </pubDate>
</item>

<item>
<title>Java中的this和super的用法</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/639806.html</link>
<description>
<![CDATA[<p style="TEXT-INDENT: 2em"><strong>一、基础知识</strong> </p>
<p style="TEXT-INDENT: 2em">1、super（参数）：调用基类中的某一个构造函数（应该为构造函数中的第一条语句） </p>
<p style="TEXT-INDENT: 2em">2、this（参数）：调用本类中另一种形成的构造函数（应该为构造函数中的第一条语句） </p>
<p style="TEXT-INDENT: 2em">3、super:　它引用当前对象的直接父类中的成员（用来访问直接父类中被隐藏的父类中成员数据或函数，基类与派生类中有相同成员定义时） </p>
<p style="TEXT-INDENT: 2em">如：super.变量名 <br />super.成员函数据名（实参） </p>
<p style="TEXT-INDENT: 2em">4、this：它代表当前对象名（在程序中易产生二义性之处，应使用this来指明当前对象；如果函数的形参与类中的成员数据同名，这时需用this来指明成员变量名） </p>
<p style="TEXT-INDENT: 2em"><strong>二、应用实例</strong> </p>
<p class="code">class Point <br />{ private int x,y; <br />public Point(int x,int y) <br />{ <br />this.x=x; //this它代表当前对象名 <br />this.y=y; <br />} <br />public void Draw() <br />{ <br />} <br />public Point() <br />{ <br />this(0,0); //this（参数）调用本类中另一种形成的构造函数 <br />} <br />} <br />class Circle extends Point <br />{ <br />private int radius; <br />public circle(int x0,int y0, int r ) <br />{ <br />super(x0,y0); //super（参数）调用基类中的某一个构造函数 <br />radius=r; <br />} <br />public void Draw() <br />{ <br />super.Draw(); //super它引用当前对象的直接父类中的成员 <br />drawCircle(); <br />}} </p>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/639806.html</guid>
<subject>学习札记</subject>
<author>zshxts37</author>
<category>学习札记</category>
<pubDate>Fri, 30 Mar 2007 14:00:58 CST </pubDate>
</item>

<item>
<title>Failed to load Main-Class manifest attribute from pig.jar</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/635108.html</link>
<description>
<![CDATA[<span class="Code">
<p><font face="Arial">运行打包好的jar，出现failed &nbsp; to &nbsp; load &nbsp; Main-Class &nbsp; manifest &nbsp; attribute &nbsp; from&nbsp;&nbsp; pig.jar &nbsp; <br />&nbsp; 我已经在manifest文件里加了Main-class:HitPigHead了，为什么还不能运行呢？ </font></p>
<p><font face="Arial">难道说Java小程序与Java应用程序的打包过程不一样吗？</font></p>
<p><font face="Arial"></font></p>
</span>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/635108.html</guid>
<subject>学习札记</subject>
<author>zshxts37</author>
<category>学习札记</category>
<pubDate>Wed, 28 Mar 2007 10:39:11 CST </pubDate>
</item>

<item>
<title>提高Java代码可重用性的三个措施(3)</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634874.html</link>
<description>
<![CDATA[<p>而Rectangular接口的定义是： </p>
<p>　　public interface Rectangular {Rectangle getBounds();} </p>
<p>　　现在，所有可以描述为矩形的类（即，实现了Rectangular接口的类）所创建的对象都可以作为提供给pRectangular.contains()的rect参数。通过放宽参数类型的限制，我们使方法具有更好的可重用性。 </p>
<p>　　不过，对于上面这个例子，Rectangular接口的getBounds方法返回Rectangle，你可能会怀疑这么做是否真正值得。换言之，如果我们知道传入过程的对象会在被调用时返回一个Rectangle，为什么不直接传入Rectangle取代接口类型呢？之所以不这么做，最重要的原因与集合有关。让我们假设有这样一个方法： </p>
<p>　　static public boolean areAnyOverlapping(Collection rects) {...} </p>
<p>　　该方法用于检查给定集合中的任意矩形对象是否重叠。在这个方法的内部，当我们用循环依次访问集合中的各个对象时，如果我们不能把对象cast成为Rectangular之类的接口类型，又如何能够访问对象的矩形区域呢？唯一的选择是把对象cast成为它特有的类形式（我们知道它有一个方法可以返回矩形），它意味着方法必须事先知道它所操作的对象类型，从而使得方法的重用只限于那几种对象类型。而这正是前面这个措施力图先行避免的问题！ </p>
<p>　　措施三：选择最简单的参数接口类型 <br />　　在实施第二个措施时，应该选用哪一种接口类型来取代给定的类形式？答案是哪一个接口完全满足过程对参数的需求，同时又具有最少的多余代码和数据。描述参数对象要求的接口越简单，其他类实现该接口的机会就越大&mdash;&mdash;由此，其对象能够作为参数使用的类也越多。从下面这个例子可以很容易地看出这一点： </p>
<p>　　static public boolean areOverlapping(Window window1, Window window2) {...} </p>
<p>　　这个方法用于检查两个窗口（假定是矩形窗口）是否重叠。如果这个方法只要求从参数获得两个窗口的矩形坐标，此时相应地简化这两个参数是一种更好的选择： </p>
<p>　　static public boolean areOverlapping(Rectangular rect1, Rectangular rect2) {...} </p>
<p>　　上面的代码假定Window类型实现了Rectangular接口。经过改动之后，对于任何矩形对象我们都可以重用该方法的功能。 </p>
<p>　　有些时候可能会出现描述参数需求的接口拥有太多方法的情况。此时，我们应该在全局名称空间中定义一个新的公共接口供其他面临同一问题的代码重用。 </p>
<p><br />　　当我们需要象使用C语言中的函数指针一样使用参数时，创建唯一的接口描述参数需求是最好的选择。例如，假设有下面这个过程： </p>
<p>　　static public void sort(List list, SortComparison comp) {...} </p>
<p>　　该方法运用参数中提供的比较对象comp，通过比较给定列表list中的对象排序list列表。sort对comp对象的唯一要求是要调用一个方法进行比较。因此，SortComparison应该是只带有一个方法的接口： </p>
<p>　　public interface SortComparison {<br />　　boolean comesBefore(Object a, Object b);<br />　　} </p>
<p>　　SortComparison接口的唯一目的在于为sort提供一个它所需功能的钩子，因此SortComparison接口不能在其他地方重用。 </p>
<p>　　总而言之，本文三个措施适合于改造现有的、按照面向对象惯例编写的代码。这三个措施与面向对象编程技术结合就得到了一种可在以后编写代码时使用的新式代码编写技术，它能够简化方法的复杂性和依赖关系，同时提高方法的可重用能力和内部凝聚力。 </p>
<p>　　当然，这里的三个措施不能用于那些天生就不适合重用的代码。不适合重用的代码通常出现在应用的表现层。例如，创建程序用户界面的代码，以及联结到输入事件的控制代码，都属于那种在程序和程序之间千差万别的代码，这种代码几乎不可能重用。 </p>
<p>&nbsp;</p>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634874.html</guid>
<subject>学习札记</subject>
<author>zshxts37</author>
<category>学习札记</category>
<pubDate>Wed, 28 Mar 2007 08:51:07 CST </pubDate>
</item>

<item>
<title>提高Java代码可重用性的三个措施(2)</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634870.html</link>
<description>
<![CDATA[<p>static public boolean <br />　　containsPoint(Polygon polygon, Point p) {...} <br />　　} </p>
<p>　　从类的名字pPolygon可以看出，该类所封装的过程主要与Polygon类型的对象有关。名字前面的p表示该类的唯一目的是组织公用静态过程。在Java中，类的名字以小写字母开头是一种非标准的做法，但象pPloygon这样的类事实上并不提供普通Java类的功能。也就是说，它并不代表着一类对象，它只是Java语言组织代码的一种机制。 </p>
<p>　　在上面这个例子中，改动代码的最终效果是使得应用Polygon功能的客户代码不必再从Polygon继承。Polygon类的功能现在已经由pPolygon类以过程为单位提供。客户代码只使用自己需要的代码，无需关心Polygon类中自己不需要的功能。但它并不意味着在这种新式过程化编程中类的作用有所削弱。恰恰相反，在组织和封装对象数据成员的过程中，类起到了不可或缺的作用，而且正如本文接下来所介绍的，类通过多重接口实现多态性的能力本身也带来了卓越的代码重用支持。然而，由于用实例方法封装代码功能并不是首选的代码重用手段，所以通过类继承达到代码重用和多态性支持也不是最理想的。 </p>
<p>　　措施二：把参数类型改成接口 <br />　　正如Allen Holub在《Build User Interfaces for Object-Oriented Systems》中所指出的，在面向对象编程中，代码重用真正的要点在于通过接口参数类型利用多态性，而不是通过类继承： </p>
<p>　　&ldquo;&hellip;&hellip;我们通过对接口而不是对类编程达到代码重用的目的。如果某个方法的所有参数都是对一些已知接口的引用，那么这个方法就能够操作这样一些对象：当我们编写方法的代码时，这些对象的类甚至还不存在。从技术上说，可重用的是方法，而不是传递给方法的对象。&rdquo; </p>
<p>　　在&ldquo;措施一&rdquo;得到的结果上应用Holub的看法，当某块代码能够编写为独立的全局过程时，只要把它所有类形式的参数改为接口形式，我们就可以进一步提高它的可重用能力。经过这个改动之后，过程的参数可以是实现了该接口的所有类的对象，而不仅仅是原来的类所创建的对象。由此，过程将能够对可能存在的大量的对象类型进行操作。 </p>
<p>　　例如，假设有这样一个全局静态方法： </p>
<p>　　static public boolean contains(Rectangle rect, int x, int y) {...} </p>
<p>　　这个方法用于检查指定的点是否包含在矩形里面。在这个例子中，rect参数的类型可以从Rectangle类改变为接口类型，如下所示： </p>
<p>　　static public boolean contains(Rectangular rect, int x, int y) {...} <br /></p>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634870.html</guid>
<subject>学习札记</subject>
<author>zshxts37</author>
<category>学习札记</category>
<pubDate>Wed, 28 Mar 2007 08:49:32 CST </pubDate>
</item>

<item>
<title>提高Java代码可重用性的三个措施(1)</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634868.html</link>
<description>
<![CDATA[<p>本文介绍了三种修改现有代码提高其可重用性的方法，它们分别是：改写类的实例方法，把参数类型改成接口，选择最简单的参数接口类型。 </p>
<p>　　措施一：改写类的实例方法 <br />　　通过类继承实现代码重用不是精确的代码重用技术，因此它并不是最理想的代码重用机制。换句话说，如果不继承整个类的所有方法和数据成员，我们无法重用该类里面的单个方法。继承总是带来一些多余的方法和数据成员，它们总是使得重用类里面某个方法的代码复杂化。另外，派生类对父类的依赖关系也使得代码进一步复杂化：对父类的改动可能影响子类；修改父类或者子类中的任意一个类时，我们很难记得哪一个方法被子类覆盖、哪一个方法没有被子类覆盖；最后，子类中的覆盖方法是否要调用父类中的对应方法有时并不显而易见。 </p>
<p>　　任何方法，只要它执行的是某个单一概念的任务，就其本身而言，它就应该是首选的可重用代码。为了重用这种代码，我们必须回归到面向过程的编程模式，把类的实例方法移出成为全局性的过程。为了提高这种过程的可重用性，过程代码应该象静态工具方法一样编写：它只能使用自己的输入参数，只能调用其他全局性的过程，不能使用任何非局部的变量。这种对外部依赖关系的限制简化了过程的应用，使得过程能够方便地用于任何地方。当然，由于这种组织方式总是使得代码具有更清晰的结构，即使是不考虑重用性的代码也同样能够从中获益。 </p>
<p>　　在Java中，方法不能脱离类而单独存在。为此，我们可以把相关的过程组织成为独立的类，并把这些过程定义为公用静态方法。 </p>
<p>　　例如，对于下面这个类： </p>
<p>　　class Polygon {<br />　　. <br />　　. <br />　　public int getPerimeter() {...} <br />　　public boolean isConvex() {...}<br />　　public boolean containsPoint(Point p) {...} <br />　　. <br />　　. <br />　　} </p>
<p>　　我们可以把它改写成： </p>
<p>　　class Polygon {<br />　　. <br />　　. <br />　　public int getPerimeter() {return pPolygon.computePerimeter(this);}<br />　　public boolean isConvex() {return pPolygon.isConvex(this);} <br />　　public boolean containsPoint(Point p) {return pPolygon.containsPoint(this, p);} <br />　　.<br />　　} </p>
<p>　　其中，pPolygon是： </p>
<p>　　class pPolygon { <br />　　static public int computePerimeter(Polygon polygon) {...} <br />　　static public boolean isConvex(Polygon polygon) {...} </p>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634868.html</guid>
<subject>学习札记</subject>
<author>zshxts37</author>
<category>学习札记</category>
<pubDate>Wed, 28 Mar 2007 08:46:50 CST </pubDate>
</item>

<item>
<title>JAVA的声音处理</title>
<link>http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634818.html</link>
<description>
<![CDATA[<p>数字音频格式有很多种，其质量与采样频率和采样精度两个参数有关。频率的表示单位为赫兹〔Hz〕，它表示每秒采样次数。采样频率越高，音质就越好。采样精度为每次采样所存储的数据数量，它决定每个数字信号所能够表示的离散振幅的数量。存储每个样本的数据越多，音质就越好。但是高品质的声音需要占用大量的内存和磁盘空间。考虑到网络带宽，在Internet连接上传输就需要花费很长的时间。对于Applet来说，保证声音文件的最小化是极为重要的。 </p>
<p>&nbsp;&nbsp; <br />&nbsp;Java能够支持以下四种声音格式： <br />&nbsp;&nbsp; <br />&nbsp;◆AU <br />◆AIFF <br />◆WAVE <br />◆MIDI </p>
<p>　　第一种声音格式AU为以前的Java 1.1版本支持的唯一的声音格式。采样频率为8000赫兹，采样精度为8位。AIFF和WAVE与AU格式一样，都用来表示数字化的声音。其中，WAVE格式提供了更宽范围的音质。MIDI格式专用于音乐，并且以音符与乐器而不是数字化的声音来描述声音的。 </p>
<p>　　关于声音的创建：声音关键工具为Windows9X中的标准应用程序&mdash;录音机。录制的格式为WAVE。另外还有很多专业水准的应用程序，采样和编辑数字化的声音，及格式的转换。 </p>
<p>&nbsp;&nbsp; <br />&nbsp;资源需求： <br />&nbsp;&nbsp; <br />&nbsp;■ 硬件资源需求： <br />⑴声卡 <br />⑵音箱 <br />＃如需要录制声音，这还需麦克风。 </p>
<p>■ 软件资源需求： <br />⑴windows9X操作系统 <br />⑵网络浏览器 <br />⑶Java插件/运行时间环境 </p>
<p>&nbsp;&nbsp; <br />&nbsp;处理方案一 <br />&nbsp;&nbsp; <br />&nbsp;　　JAVA2开始，爪哇语言能够支持上述四种声音格式。简单的方法为在java中通过Applet类的AudioClip()接口来装载声音。该接口风钻了有关声音片断的常用方法，具有对播放声音片断的最小支持。 </p>
<p>该接口定义了如下方法： <br />■ play() <br />语法：void play() <br />功能：开始播放声音片断。 <br />不论声音片断是否已经在播放，都从头播放。播放一遍但不重复。 </p>
<p>■ loop() <br />语法：void loop() <br />功能：循环播放声音片断。 <br />调用该方法是不论声音片断是否已在播放，都从头开始播放。</p>
<p>■ stop() <br />语法：void stop() <br />功能：停止播放声音片断。 <br />AudioClip接口可以通过getAudioClip()及getCodeBase()方法来获取声音片断及 <br />URL地址。可以利用此方法在WEB页中播放指定的声音片断。 <br />＃需求：基于JDK1.2以上版本的开发环境。如果只播放AU格式的声音文件，JDK1 <br />.1就可。 </p>
<p>附：soundtest.html源代码 <br />《HTML》 <br />《HEAD》 <br />《TITLE》 <br />HTML Test Page <br />《/TITLE》 <br />《/HEAD》 <br />《BODY》 <br />《APPLET <br />CODEBASE = &quot;&quot; CODE = &quot;test.class&quot; NAME = &quot;TestApplet&quot; WID <br />TH = 400 HEIGHT = 200 HSPACE = 0 VSPACE = 0 ALIGN = mi <br />ddle》 <br />《PARAM NAME = &quot;clip&quot; VALUE = &quot;sound.AU&quot;》 <br />《/APPLET》 <br />《/BODY》 <br />《/HTML》 <br />附：test,java源代码 <br />package SoundTest; <br />import java.awt.*; <br />import java.awt.event.*; <br />import java.applet.*; <br />import javax.swing.*; <br />import java.net.*; <br />public class test extends JApplet { <br />boolean isStandalone = false; <br />String sound; <br />JButton jButtonPlay = new JButton(); <br />JButton jButtonLoop = new JButton(); <br />JButton jButtonStop = new JButton(); <br />JLabel jLabel1 = new JLabel(); <br />AudioClip clip; <br />//Get a parameter value <br />public String getParameter(String key, String def) { <br />return isStandalone ? System.getProperty(key, def) : <br />(getParameter(key) != null ? getParameter(key) : def); <br />} <br />//Construct the applet <br />public test() { <br />} <br />//Initialize the applet <br />public void init() { <br />try { <br />jbInit(); <br />} <br />catch(Exception e) { <br />e.printStackTrace(); <br />} <br />} <br />//Component initialization <br />private void jbInit() throws Exception { <br />jButtonPlay.setText(&quot;Play&quot;); <br />jButtonPlay.setBounds(new Rectangle(50, 85, 80, 40)); <br />jButtonPlay.addMouseListener(new java.awt.event.MouseAdapter() { <br />public void mouseClicked(MouseEvent e) { <br />jButtonPlay_mouseClicked(e); <br />} <br />}); <br />this.setSize(new Dimension(400,200)); <br />this.getContentPane().setLayout(null); <br />jButtonLoop.setText(&quot;Loop&quot;); <br />jButtonLoop.setBounds(new Rectangle(150, 85, 80, 40)); <br />jButtonLoop.addMouseListener(new java.awt.event.MouseAdapter() { <br />public void mouseClicked(MouseEvent e) { <br />jButtonLoop_mouseClicked(e); <br />} <br />}); <br />jButtonStop.setText(&quot;Stop&quot;); <br />jButtonStop.setBounds(new Rectangle(250, 85, 80, 40)); <br />jButtonStop.addMouseListener(new java.awt.event.MouseAdapter() { </p>
<p>public void mouseClicked(MouseEvent e) { <br />jButtonStop_mouseClicked(e); <br />} <br />}); <br />jLabel1.setText(&quot;Sound Test Demo&quot;); <br />jLabel1.setBounds(new Rectangle(109, 28, 186, 28)); <br />this.getContentPane().add(jButtonPlay, null); <br />this.getContentPane().add(jButtonStop, null); <br />this.getContentPane().add(jButtonLoop, null); <br />this.getContentPane().add(jLabel1, null); <br />try { sound = this.getParameter(&quot;clip&quot;, &quot;sound.AU&quot;); } catch (Exce <br />ption e) { e.printStackTrace(); } <br />if(sound!=null){ <br />try{ <br />clip=JApplet.newAudioClip (new URL(getCodeBase(),sound)); </p>
<p>} <br />catch(MalformedURLException e){ <br />System.out.println (&quot;Bad URL&quot;); <br />} <br />} <br />} <br />//Get Applet information <br />public String getAppletInfo() { <br />return &quot;Applet Information&quot;; <br />} <br />//Get parameter info <br />public String[][] getParameterInfo() { <br />String[][] pinfo = <br />{ <br />{&quot;clip&quot;, &quot;String&quot;, &quot;sound.AU&quot;}, <br />}; <br />return pinfo; <br />} <br />void jButtonPlay_mouseClicked(MouseEvent e) { <br />clip.play(); <br />} <br />void jButtonLoop_mouseClicked(MouseEvent e) { <br />clip.loop(); <br />} <br />void jButtonStop_mouseClicked(MouseEvent e) { <br />clip.stop(); <br />} <br />} </p>
<p>&nbsp;&nbsp; <br />&nbsp;处理方案二 <br />&nbsp;&nbsp; <br />&nbsp;　　利用JAVA媒体框架中的Sound API来处理声音片断。Sound API 被包含在SUN公司的JAVA媒体框架中，也包含在JDK1.3版本中。编译源代码需要支持JDK1.3的开发环境和运行时间环境。 </p>
<p>　　可以利用Sound API在网页中加入Applet的方式达到要求。但浏览器需要通过Java插件或运行时间环境的支持。 </p>
<p>Sound API主要包括以下四部分： </p>
<p>Packages <br />javax.sound.midi 提供MIDI (Musical Instrument Digital Interface)的I/O、 <br />序列、合成的接口和类。 <br />javax.sound.midi.spi Supplies interfaces for service providers to impl <br />ement when offering new MIDI devices, MIDI file readers and writers, o <br />r sound bank readers. <br />javax.sound.sampled 为采样数字音频的捕获、处理、重放提供接口和类。 <br />javax.sound.sampled.spi Supplies abstract classes for service provider <br />s to subclass when offering new audio devices, sound file readers and <br />writers, or audio format converters. </p>
<p><br />&nbsp;&nbsp; <br />&nbsp;处理方案三 <br />&nbsp;&nbsp; <br />&nbsp;　　利用JAVA 3D中的sound类。该类似声音资源的抽象定义。每一个具体的声音对象都可以应用sound的方法。</p>
<p>Sound类的类关系如下： <br />java.lang.Object <br />+-javax.media.j3d.SceneGraphObject <br />+-javax.media.j3d.Node <br />+-javax.media.j3d.Leaf <br />+-javax.media.j3d.Sound</p>
<p>sound类的子类有： <br />⒈BackgroundSound <br />⒉PointSound <br />⒊ConeSound </p>
<p>使用上述三种声音的步骤如下：</p>
<p>⒈定义并生成一个MediaContainer对象，同时提供对象所需要的声音文件，通常 <br />要单独设置一个存放AU/WAV声音文件的目录。 </p>
<p>⒉定义一个BackgroundSound/ PointSound/ ConeSound对象，根据需要，设置声 <br />音的是否循环、强度大小等参数，设置它的作用范围并setEnable用使其开始作用。 </p>
<p>⒊根据具体的对象设置坐标系。 <br />＃JAVA 3D应用程序只能播放au/wav格式的声音文件。不能播放midi文件。 </p>]]>
</description>
<guid isPermaLink="false">http://www.bokee.net/blogmodule/weblogcomment_viewEntry/634818.html</guid>
<subject>学习札记</subject>
<author>zshxts37</author>
<category>学习札记</category>
<pubDate>Wed, 28 Mar 2007 08:18:40 CST </pubDate>
</item>

</channel>
</rss>
