﻿<?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-苹果的成长日记-随笔分类-J2EE/JAVA学习</title><link>http://www.blogjava.net/Apple/category/1255.html</link><description>我还是个青苹果呀！</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 19:18:16 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 19:18:16 GMT</pubDate><ttl>60</ttl><item><title>[zz]初学者如何开发出一个高质量的J2EE系统</title><link>http://www.blogjava.net/Apple/archive/2005/08/16/10186.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Tue, 16 Aug 2005 01:00:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/08/16/10186.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/10186.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/08/16/10186.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/10186.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/10186.html</trackback:ping><description><![CDATA[<H3 align=left><FONT size=3>这篇文章对于J2EE整体上做了一个很好的概括</FONT>：</H3>
<H3 align=center>初学者如何开发出一个高质量的J2EE系统</H3>
<P align=center><A href="http://www.jdon.com/aboutme.htm">板桥里人</A> http://www.jdon.com 2005/06/20</P>
<P>　　J2EE学习者越来越多，J2EE本身技术不断在发展，涌现出各种概念，本文章试图从一种容易理解的角度对这些概念向初学者进行解释，以便掌握学习J2EE学习方向。</P>
<P>　　首先我们需要知道Java和J2EE是两个不同概念，Java不只是指一种语言，已经代表与微软不同的另外一个巨大阵营，所以Java有时是指一种软件系统的流派，当然目前主要是.NET和Java两大主流体系。</P>
<P>　　J2EE可以说指Java在数据库信息系统上实现，数据库信息系统从早期的dBase、到Delphi/VB等C/S结构，发展到B/S（Browser浏览器/Server服务器）结构，而J2EE主要是指B/S结构的实现。</P>
<P>　　J2EE又是一种框架和标准，框架类似API、库的概念，但是要超出它们。如果需要详细了解框架，可先从<A href="http://www.jdon.com/designpatterns/index.htm" target=_blank>设计模式</A>开始学习。</P>
<P>　　J2EE是一个虚的大的概念，J2EE标准主要有三种子技术标准：WEB技术、EJB技术和JMS，谈到J2EE应该说最终要落实到这三个子概念上。</P>
<P>　　这三种技术的每个技术在应用时都涉及两个部分：容器部分和应用部分，Web容器也是指Jsp/Servlet容器，你如果要开发一个Web应用，无论是编译或运行，都必须要有Jsp/Servlet库或API支持（除了JDK/J2SE以外）。</P>
<P>　　Web技术中除了Jsp/Servlet技术外，还需要JavaBeans或Java Class实现一些功能或者包装携带数据，所以Web技术最初裸体简称为Jsp/Servlet+JavaBeans系统。</P>
<P>　　谈到JavaBeans技术，就涉及到组件构件技术（component），这是Java的核心基础部分，很多软件设计概念（设计模式）都是通过JavaBeans实现的。</P>
<P>　　JavaBeans不属于J2EE概念范畴中，如果一个JavaBeans对象被Web技术（也就是Jsp/Servlet）调用，那么JavaBeans就运行在J2EE的Web容器中；如果它被EJB调用，它就运行在EJB容器中。</P>
<P>　　EJB（企业JavaBeans）是普通JavaBeans的一种提升和规范，因为企业信息系统开发中需要一个可伸缩的性能和事务、安全机制，这样能保证企业系统平滑发展，而不是发展到一种规模重新更换一套软件系统。</P>
<P>　　至此，JavaBeans组件发展到EJB后，并不是说以前的那种JavaBeans形式就消失了，这就自然形成了两种JavaBeans技术：EJB和POJO，POJO完全不同于EJB概念，指的是普通JavaBeans，而且这个JavaBeans不依附某种框架，或者干脆可以说：这个JavaBeans是你为这个应用程序单独开发创建的。</P>
<P>　　J2EE应用系统开发工具有很多：如JBuilder、Eclipse等，这些IDE首先是Java开发工具，也就是说，它们首要基本功能是可以开发出JavaBeans或Java class，但是如果要开发出J2EE系统，就要落实到要么是Web技术或EJB技术，那么就有可能要一些专门模块功能(如eclipse需要lomboz插件)，最重要的是，因为J2EE系统区分为容器和应用两个部分，所以，在任何开发工具中开发J2EE都需要指定J2EE容器。</P>
<P>　　J2EE容器分为WEB容器和EJB容器，Tomcat/Resin是Web容器；JBoss是EJB容器+Web容器等，其中Web容器直接使用Tomcat实现的。所以你开发的Web应用程序可以在上面两种容器运行，而你开发的Web+EJB应用则只可以在JBoss服务器上运行，商业产品Websphere/Weblogic等和JBoss属于同一种性质。</P>
<P>　　J2EE容器也称为J2EE服务器，大部分时它们概念是一致的。</P>
<P>　　如果你的J2EE应用系统的数据库连接是通过JNDI获得，也就是说是从容器中获得，那么你的J2EE应用系统基本与数据库无关，如果你在你的J2EE应用系统耦合了数据库JDBC驱动的配置，那么你的J2EE应用系统就有数据库概念色彩，作为一个成熟需要推广的J2EE应用系统，不推荐和具体数据库耦合，当然这其中如何保证J2EE应用系统运行性能又是体现你的设计水平了。</P>
<P>　　衡量J2EE应用系统设计开发水平高低的标准就是：解耦性；你的应用系统各个功能是否能够彻底脱离？是否不相互依赖，也只有这样，才能体现可维护性、可拓展性的软件设计目标。</P>
<P>　　为了达到这个目的，诞生各种框架概念，J2EE框架标准将一个系统划分为WEB和EJB主要部分，当然我们有时不是以这个具体技术区分，而是从设计上抽象为表现层、服务层和持久层，这三个层次从一个高度将J2EE分离开来，实现解耦目的。</P>
<P>　　因此，我们实际编程中，也要将自己的功能向这三个层次上靠，做到大方向清楚，泾渭分明，但是没有技术上约束限制要做到这点是很不容易的，因此我们还是必须借助J2EE具体技术来实现，这时，你可以使用EJB规范实现服务层和持久层，Web技术实现表现层；</P>
<P>　　EJB为什么能将服务层从Jsp/Servlet手中分离出来，因为它对JavaBeans编码有强制的约束，现在有一种对JavaBeans弱约束，使用Ioc模式实现的（当然EJB 3.0也采取这种方式），在Ioc模式诞生前，一般都是通过工厂模式来对JavaBeans约束，形成一个服务层，这也是是Jive这样开源论坛设计原理之一。</P>
<P>　　由此，将服务层从表现层中分离出来目前有两种可选架构选择：管理普通JavaBeans（POJO）框架(如Spring、<A href="http://www.jdon.com/jdonframework/index.htm" target=_blank>JdonFramework</A>)以及管理EJB的EJB框架，因为EJB不只是框架，还是标准，而标准可以扩展发展，所以，这两种区别将来是可能模糊，被纳入同一个标准了。　但是，个人认为：标准制定是为某个目的服务的，总要牺牲一些换取另外一些，所以，这两种架构会长时间并存。</P>
<P>　　这两种架构分歧也曾经诞生一个新名词：完全POJO的系统也称为轻量级系统(lightweight)，其实这个名词本身就没有一个严格定义，更多是一个吸引人的招牌，轻量是指容易学习容易使用吗？按照这个定义，其实轻量Spring等系统并不容易学习；而且EJB 3.0（依然叫EJB）以后的系统是否可称为轻量级了呢？</P>
<P>　　前面谈了服务层框架，使用服务层框架可以将JavaBeans从Jsp/Servlet中分离出来，而使用表现层框架则可以将Jsp中剩余的JavaBeans完全分离，这部分JavaBeans主要负责显示相关，一般是通过标签库（taglib）实现，不同框架有不同自己的标签库，Struts是应用比较广泛的一种表现层框架。</P>
<P>　　这样，表现层和服务层的分离是通过两种框架达到目的，剩余的就是持久层框架了，通过持久层的框架将数据库存储从服务层中分离出来是其目的，持久层框架有两种方向：直接自己编写JDBC等SQL语句（如iBatis）；使用O/R Mapping技术实现的Hibernate和JDO技术；当然还有EJB中的实体Bean技术。</P>
<P>　　持久层框架目前呈现百花齐放，各有优缺点的现状，所以正如表现层框架一样，目前没有一个框架被指定为标准框架，当然，表现层框架现在又出来了一个JSF，它代表的页面组件概念是一个新的发展方向，但是复杂的实现让人有些忘而却步。</P>
<P>　　在所有这些J2EE技术中，虽然SUN公司发挥了很大的作用，不过总体来说：网络上有这样一个评价：SUN的理论天下无敌；SUN的产品用起来撞墙；对于初学者，特别是那些试图通过或已经通过SUN认证的初学者，赶快摆脱SUN的阴影，立即开溜，使用开源领域的产品来实现自己的应用系统。</P>
<P>　　最后，你的J2EE应用系统如果采取上面提到的表现层、服务层和持久层的框架实现，基本你也可以在无需深刻掌握设计模式的情况下开发出一个高质量的应用系统了。</P>
<P>　　还要注意的是: 开发出一个高质量的J2EE系统还需要正确的业务需求理解，那么域建模提供了一种比较切实可行的正确理解业务需求的方法，相关详细知识可从UML角度结合理解。</P>
<P>　　当然，如果你想设计自己的行业框架，那么第一步从设计模式开始吧，因为设计模式提供你一个实现JavaBeans或类之间解耦参考实现方法，当你学会了系统基本单元JavaBean或类之间解耦时，那么系统模块之间的解耦你就可能掌握，进而你就可以实现行业框架的提炼了，这又是另外一个发展方向了。</P>
<P>　　以上理念可以总结为一句话：<BR>J2EE开发三件宝: Domain Model（域建模）、patterns（模式）和framework（框架）。</P>
<P>　　推荐一套高质量的J2EE开源系统： <A href="http://www.jdon.com/jdonframework/app.htm">JPestore</A> <BR></P><img src ="http://www.blogjava.net/Apple/aggbug/10186.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-08-16 09:00 <a href="http://www.blogjava.net/Apple/archive/2005/08/16/10186.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学习Swing的一点体会</title><link>http://www.blogjava.net/Apple/archive/2005/08/07/9505.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Sun, 07 Aug 2005 04:08:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/08/07/9505.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/9505.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/08/07/9505.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/9505.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/9505.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 从7月14日开始来上海实习已经3个星期了,项目还没正式开始,前期工作准备了很多,而我主要是负责GUI这块,工具是Swing,所以陪伴Swing也已经快一个月的日子了.项目下个星期正式启动,所以对前面的体会作个小小的总结.<BR>&nbsp;&nbsp;&nbsp; 以前在inforsense公司的KDE平台上已经有一个Table Editor,点击主界面上含有表格数据的节点,可以打开表格,然后可以对各种表格进行编辑,增删等简单操作,而同时这些操作也会反映到主界面上的工作流模型中.现在的目标是对这个工具进一步扩展其功能,不仅融如Excel spreadsheet的功能(过滤,对cell进行编辑等),还有将树图与表格视图连接起来,可以进行两种视图之间的拖拽(dnd),切换等.现在更要与化学专业结合起来,本来这款软件是为化学家设计的,目的使他们操作起来更方便.所以还要把扩展后的表格编辑器和Interactive Browser结合起来,做到对同一组数据的多种视图,而且它们是同步的.比如表格中会有Structure(化学分子结构),分子量这样的特定的域,而点击后可启动特定的编辑化学分子结构的软件进行编辑,同时变化反映在表格数据中.<BR>&nbsp;&nbsp;&nbsp; 而我接触的都是Swing,它给我的感觉虽然好象仅仅是在AWT的类前面都加上了个J,但仔细研究,里面有各种设计模式的存在,这一点让我兴奋不已,正好借这个机会学习设计模式.我大部分时间接触的都是JTable和JTree.所以主要谈谈他们.Swing基本是就是个MVC的设计架构,就拿JTable来说,JTable就是View的部分,而TableModel就是M的部分.下面一点点讲讲实现的细节:<BR>1.Filter(过滤器):<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 要在表格中实现过滤的功能,而实际上不影响原来的模型,可以考虑在原来的模型增加一个过滤器.它其实上也是一个TableModel(可以子类化TableModel的实现框架AbstractTableModel.),它把原来的TM作为自己的成员,任何实际的操作如getColumnCount(),getRowCount(),getColumnName()等都交给原来的TM来完成(调用TM的相应方法),只是在应该控制的地方控制一下,比如,getValueAt(i,j)就通过控制i,j来只返回过滤器想显示的行或列的数据,而具体的返回数据的操作还是由TM来完成.对setValueAt(),isCellEditable()也是同样的道理.我具体的做法就是用一个List把我想显示的行(列)号保存下来,在getValueAt(i,j)中,i的取值范围就是这个List了.这其实是一种Adapter模式的思想.同样,实现Sort也可以用这种方式.<BR>2)Selection:<BR>&nbsp;&nbsp;&nbsp; JTable中的选择都是由ListSelectionModel来完成的,行列都有默认的选择模型,访问行的SelectionModel的方式是getSelectionModel(),访问列的SelectionModel的方式是getColumnModel().getSelectionModel().你也可以实现自己的选择模型.可以通过<BR>getRowSelectionAllowed()和getColumnSelectionAllowed()获取现在行列是否可选的信息,如果都可选,则在Cell级别是可选的.这就是为什么在行列都可选的情况下,设置i行被选中setRowSelectionInterval(i),同时设置j列被选中setColumnSelectionInterval(j),这样只有(i,j)的Cell单元被选中得到原因.但是反过来,如果我只想使(i,j)的Cell不被选中,而仅仅靠removeColumnSelectionInterval(j)和removeRowSelectionInterval(i)是实现不了的.这难道是Swing的漏洞?<BR>&nbsp;&nbsp;&nbsp; 前面已经讲到,设置改变选择状态主要是通过行列SelectionModel的setSelectionInterval(),addSelectionInterval(),removeSelectionInterval()三个方式实现的.<BR>3)header<BR>&nbsp;&nbsp;&nbsp; 表的行,列的表头着实让我头痛了一阵.尤其是row header.我的row header是用一个JTable实现的,关键是要和表格同步起来.可以考虑与表格共用一个Filter,关键是改写getValueAt()和getRowCount()这两个方法.这样表格过滤留下的行也是表头这个JTable中所需要留下的行.而选择的同步则是覆盖changeSelection()这个方法实现的.而操作的方法就是在2)中提到的那几个方法.设置rowHeader为表头只需要在JScrollPane中用setRowHeaderView()指定即可,而表格最左上角的单元(行表头的表头)用setCorner()指定.<BR>&nbsp;&nbsp;&nbsp; ColumnHeader其实在JTable中已有实现,如果要通过单击列头来选择全列的话,实现的方法可通过在列头上添加一个MouseListener,然后在它的MouseClicked方法中进行选择的同步,其余步骤与行在changeSelection()中的类似,有一点值得注意,要获取单击的列的索引是通过getTableHeader()后得到的tableHeader.columnAtPoint(e.getPoint())得到的,这里e是MouseEvent,也就是这个单击的动作事件.<BR>具体的控制代码如下:<BR>/**<BR>&nbsp; * once click on the header, that column should be selected<BR>&nbsp; */<BR>&nbsp;public void mouseClicked(MouseEvent e) {<BR>&nbsp;&nbsp;JTableHeader header = table.getTableHeader();<BR>&nbsp;&nbsp;TableColumnModel columns = header.getColumnModel();<BR>&nbsp;&nbsp;if(!columns.getColumnSelectionAllowed())<BR>&nbsp;&nbsp;&nbsp;return;<BR>&nbsp;&nbsp;//get the column index being clicked<BR>&nbsp;&nbsp;int column = header.columnAtPoint(e.getPoint());<BR>&nbsp;&nbsp;if(column == -1)<BR>&nbsp;&nbsp;&nbsp;return;<BR>&nbsp;&nbsp;int count = table.getRowCount();<BR>&nbsp;&nbsp;//set the entire column to be selected<BR>&nbsp;&nbsp;if(count != 0)<BR>&nbsp;&nbsp;&nbsp;table.setRowSelectionInterval(0,count-1);<BR>&nbsp;&nbsp;ListSelectionModel selection = columns.getSelectionModel();<BR>&nbsp;&nbsp;//if the shift modifier is pushed down, need to select multiple columns<BR>&nbsp;&nbsp;if(e.isShiftDown()) {<BR>&nbsp;&nbsp;&nbsp;int anchor = selection.getAnchorSelectionIndex();// the first index<BR>&nbsp;&nbsp;&nbsp;int lead = selection.getLeadSelectionIndex();//the last index<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;if(anchor != -1) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;boolean old = selection.getValueIsAdjusting();<BR>&nbsp;&nbsp;&nbsp;&nbsp;selection.setValueIsAdjusting(true);<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;boolean anchorSelected = selection.isSelectedIndex(anchor);<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;if(lead != -1) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(anchorSelected)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;selection.removeSelectionInterval(anchor,lead);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;selection.addSelectionInterval(anchor,lead);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;if(anchorSelected)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;selection.addSelectionInterval(anchor,column);<BR>&nbsp;&nbsp;&nbsp;&nbsp;else <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;selection.removeSelectionInterval(anchor,column);<BR>&nbsp;&nbsp;&nbsp;&nbsp;selection.setValueIsAdjusting(old);<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;else <BR>&nbsp;&nbsp;&nbsp;&nbsp;//select single column<BR>&nbsp;&nbsp;&nbsp;&nbsp;selection.setSelectionInterval(column,column);<BR>&nbsp;&nbsp; }<BR>&nbsp;&nbsp;else if(e.isControlDown()) {<BR>&nbsp;&nbsp;&nbsp;if(selection.isSelectedIndex(column))<BR>&nbsp;&nbsp;&nbsp;&nbsp;selection.removeSelectionInterval(column,column);//unselect this column<BR>&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;selection.setSelectionInterval(column,column);<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;else {<BR>&nbsp;&nbsp;&nbsp; selection.setSelectionInterval(column,column);<BR>&nbsp;&nbsp;}<BR>&nbsp;}<BR>4)dnd:<BR>&nbsp;&nbsp; 构造一个Transferable对象,保存传送的数据.而两方分别实现自己的TransferHandler即可.<BR>5)表示器和编辑器.<BR>&nbsp;&nbsp; 如果想在JTree中添加JCheckbox,其实只需要实现自己的CellRenderer和CellEditor,在getTreeCellRendererComponent(Object value)和setTreeCellRendererComponent(Object value)中返回或设置一个JCheckBox(value.toString())即可.value就是Tree中节点node的UserObject.如果你想更改树中显示的文字,比如在父节点中显示子节点的数量,只需要在TreeNode类中(子类化DefaultMutableTreeNode)改写toString()方法即可.<BR>&nbsp;&nbsp; 目前的代码可以在"文件"中下载.<img src ="http://www.blogjava.net/Apple/aggbug/9505.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-08-07 12:08 <a href="http://www.blogjava.net/Apple/archive/2005/08/07/9505.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[zz至八进制]国际化你的应用程序（上）</title><link>http://www.blogjava.net/Apple/archive/2005/08/04/9166.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 04 Aug 2005 02:11:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/08/04/9166.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/9166.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/08/04/9166.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/9166.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/9166.html</trackback:ping><description><![CDATA[<DIV class=postText>
<P>原文地址：<A href="http://www.cnblogs.com/bjzhanghao/archive/2004/08/08/31262.aspx">http://www.cnblogs.com/bjzhanghao/archive/2004/08/08/31262.aspx</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 记得几年前汉化软件一般是用二进制编辑工具在编译后的文件中查找和替换英文单词，这个过程需要使用很多技巧，非常的麻烦。而现在，在开发时对应用程序进行国际化处理已经越来越成为一个必不可少的步骤了。例如这次我参与的<A href="http://www.cnblogs.com/bjzhanghao/category/7075.aspx"><FONT color=#000080>项目</FONT></A>是给台湾客户做的，他们要求英文和繁体中文两个版本，幸好我们使用的开发工具是Eclipse，利用它的国际化功能可以很方便的将写在代码里的字符串提出到独立的资源文件中，这里用一个简单的例子说明一下这个过程。</P>
<P>在Eclipse里建立一个名为nls-test的工程（也可以国际化已有工程），新建一个类NLSTest，内容如下：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">public&nbsp;class&nbsp;NLSTest&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;public&nbsp;NLSTest()&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;String&nbsp;str1</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">Hello&nbsp;world!</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">;<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;System.out.println(str1);<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;}<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;public&nbsp;static&nbsp;</SPAN><SPAN style="COLOR: #0000ff">void</SPAN><SPAN style="COLOR: #000000">&nbsp;main(String[]&nbsp;args)&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;NLSTest();<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;}<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>}</SPAN></DIV></DIV>
<P>现在，这个类的输出是在代码里写死的，如果要改变就必须修改代码然后重新编译。下面我们利用Eclipse解决这个问题。在导航器（Package Explorer）里右键单击这个文件，选择Source -&gt; Externalize Strings，就会打开一个对话框，在这里列出了该类中尚未国际化的字符串，见下图。</P>
<P align=center><IMG height=488 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/eclipse-nls1.gif" width=636 border=0><BR><BR><FONT size=2>图1 国际化向导第一步</FONT></P>
<P>单击每个字符串前面的小方块可以选择对该串1、进行国际化处理2、永不进行国际化处理3、这次不处理。我们选择要对这个字符串进行国际化处理，并把它的Key修改为比较好理解的名称hello，注意在对话框最上方可以指定一个通用的前缀，这个值会加在每个Key前面作为最终写在资源文件（扩展名是.properties）里的Key名称。好，按下一步继续。</P>
<P>在这里要指定properties文件的名称，如果需要的话要指定一个用于从properties文件中取资源的类，一般是XXXMessages的格式，再按下一步，会显示一个所作更改的确认列表，确认后按Finish按钮完成向导。</P>
<P>可以看到Eclipse为我们生成了NLSTestMessages.java和NLSTest.properties，打开后者会看到里面只有这么一句：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">NLSTest.hello</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">Hello&nbsp;world</SPAN><SPAN style="COLOR: #000000">!</SPAN></DIV></DIV>
<P>而前者NLSTestMessages的作用是根据参数Key从后者取对应的字符串值，看一看现在的NLSTest.java就知道了，它的内容现在是这样的（只列出构造方法，main方法同上）：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">&nbsp;public&nbsp;NLSTest()&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;String&nbsp;str1</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">NLSTestMessages.getString(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">NLSTest.hello</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">);&nbsp;</SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">$NON-NLS-1$</SPAN><SPAN style="COLOR: #008000"><BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top></SPAN><SPAN style="COLOR: #000000">&nbsp;&nbsp;System.out.println(str1);<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;}</SPAN></DIV></DIV>
<P>后面的注释是Eclipse自己用的，标有这个注释的字符串在国际化将被忽略。注释中的数字1表示要忽略的是该行中第一个字符串，如果一行语句里有多个字符串被忽略，将会有多个这样的注释，但数字会各不相同，像这样：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000"><IMG src="http://www.cnblogs.com/Images/dot.gif">&nbsp;</SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">$NON-NLS-1$&nbsp;//$NON-NLS-2$&nbsp;//$NON-NLS-3$&nbsp;<IMG src="http://www.cnblogs.com/Images/dot.gif"></SPAN></DIV></DIV>
<P>好了，现在这个类的国际化处理就算完成了。要想让这个类可以根据用户所在地区输出不同语言的结果，可以在NLSTest.properties同一目录下创建名为NLSTest_XX.properties的文件，其中XX表示国家名称，例如中国是zh_CN或tw_CN，法国是FR等等。新创建的文件里也要有和原来文件相同的名值对，但值是不同语言的，NLSTestMessages类会根据用户机器的地区设置值自动从不同的资源文件里取值，这样就达到了国际化的目的。要在自己的机器上测试运行结果，可以在Eclipse的运行设置里面加上这样的参数：-nl tw_CN，这样就不用费力气设置区域了。</P>
<P>国际化的原理很简单，Eclipse提供的这个功能使国际化变得更容易了。不过关于国际化还有一些细节问题，包括对含参数资源的处理，字符编码处理等等，下篇将对它们进行讨论。</P></DIV><img src ="http://www.blogjava.net/Apple/aggbug/9166.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-08-04 10:11 <a href="http://www.blogjava.net/Apple/archive/2005/08/04/9166.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载至竹笋炒肉]log4j学习笔记(1)</title><link>http://www.blogjava.net/Apple/archive/2005/07/29/8751.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 29 Jul 2005 09:05:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/07/29/8751.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/8751.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/07/29/8751.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/8751.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/8751.html</trackback:ping><description><![CDATA[<P><A href="http://www.infomall.cn/cgi-bin/mallgate/20031008/http://hedong.3322.org/archives/pics/log4jlogo.jpg"><IMG height=66 alt=log4jlogo.jpg src="http://hedong.3322.org/archives/pics/log4jlogo-thumb.jpg" width=98 align=left border=0></A>原文地址:http://www.infomall.cn/cgi-bin/mallgate/20031008/http://hedong.3322.org/archives/000193.html<BR>说实话，除了log4j的功能外，我更喜欢它的logo.<BR>　　下面的这篇笔记，主要是"borrow from"Log4J的随机文档"Short introduction to log4j"，由Ceki Gülcü 写于March 2002，其它参考文档见文后。<BR></P><A name=more></A>
<P>1、log4j已经被移植到C, C++, C#, Perl, Python, Ruby, Eiffel 几种语言。<BR>2、log4j有三种主要的<ACRONYM title=component>组件</ACRONYM>：<ACRONYM title=logger>记录器</ACRONYM>,<ACRONYM title=appender>存放器</ACRONYM>,<ACRONYM title=layout>布局</ACRONYM><BR>3、记录器（记录器可不关心log数据存放的事哟）<BR>　　log4j允许程序员定义多个记录器，每个记录器有自己的名字，记录器之间通过名字来表明隶属关系（或家族关系）。列如，记录器a.b,与记录器a.b.c之间是父子关系，而记录器a与a.b.c之间是祖先与后代的关系，父子关系是祖先与后代关系的特例。通过这种关系，可以描述不同记录器之间的逻辑关系。<BR>　　有一个记录器叫根记录器，它永远存在，且不能通过名字检索或引用，可以通过Logger.getRootLogger()方法取得它，而一般记录器通过Logger.getLogger(String name)方法。下面是Logger类的基本方法。 
<DIV class=code>package org.apache.log4j; 
<P></P>
<P>public class Logger {</P>
<P>// Creation &amp; retrieval methods:<BR>public static Logger getRootLogger();<BR>public static Logger getLogger(String name);</P>
<P>// printing methods:<BR>public void debug(Object message);<BR>public void info(Object message);<BR>public void warn(Object message);<BR>public void error(Object message);<BR>public void fatal(Object message);</P>
<P>// generic printing method:<BR>public void log(Level l, Object message);<BR>}</P></DIV><BR>　　记录器还有一个重要的属性，就是级别。（这好理解，就象一个家庭中，成员间存在辈份关系，但不同的成员的身高可能不一样，且身高与辈份无关）程序员可以给不同的记录器赋以不同的级别，如果某个成员没有被明确值，就自动继承最近的一个有级别长辈的级别值。根记录器总有级别值。例如：<BR>
<DIV class=code>
<TABLE border=1>
<TBODY>
<TR>
<TD>记录器名</TD>
<TD>赋予的级别值</TD>
<TD>继承的级别值</TD></TR>
<TR>
<TD>root</TD>
<TD>Proot</TD>
<TD>Proot</TD></TR>
<TR>
<TD>X </TD>
<TD>Px </TD>
<TD>Px </TD></TR>
<TR>
<TD>X.Y </TD>
<TD>none </TD>
<TD>Px </TD></TR>
<TR>
<TD>X.Y.Z</TD>
<TD>none </TD>
<TD>Px </TD></TR></TBODY></TABLE></DIV><BR>　　程序员可以自由定义级别。级别值之间存在偏序关系，如上面几种级别就有关系DEBUG<INFO<WARN<ERROR<FATAL.<BR /> 　　每一条要输出的log信息，也有一个级别值。<BR>　　前面的Logger类中，就预定义了 DEBUG, INFO, WARN, ERROR ，FATAL几种级别，由于与方法绑定，让人易产生误解，其实这几个方法只不过表明了要记录的log信息的级别。当调用log()方法时，log信息的级别就需要在通过参数明确指定。<BR>　　如果一条log信息的级别，大于等于记录器的级别值，那么记录器就会记录它。如果你觉得难以理解，可参考下例。 
<DIV class=code>&nbsp;&nbsp;&nbsp;//&nbsp;get&nbsp;a&nbsp;logger&nbsp;instance&nbsp;named&nbsp;"com.foo"<BR>&nbsp;&nbsp;&nbsp;Logger&nbsp;&nbsp;logger&nbsp;=&nbsp;Logger.getLogger("com.foo"); 
<P></P>
<P>&nbsp;&nbsp;&nbsp;//&nbsp;Now&nbsp;set&nbsp;its&nbsp;level.&nbsp;Normally&nbsp;you&nbsp;do&nbsp;not&nbsp;need&nbsp;to&nbsp;set&nbsp;the<BR>&nbsp;&nbsp;&nbsp;//&nbsp;level&nbsp;of&nbsp;a&nbsp;logger&nbsp;programmatically.&nbsp;This&nbsp;is&nbsp;usually&nbsp;done<BR>&nbsp;&nbsp;&nbsp;//&nbsp;in&nbsp;configuration&nbsp;files.<BR>&nbsp;&nbsp;&nbsp;logger.setLevel(Level.INFO);</P>
<P>&nbsp;&nbsp;&nbsp;Logger&nbsp;barlogger&nbsp;=&nbsp;Logger.getLogger("com.foo.Bar");</P>
<P>&nbsp;&nbsp;&nbsp;//&nbsp;This&nbsp;request&nbsp;is&nbsp;enabled,&nbsp;because&nbsp;WARN&nbsp;&gt;=&nbsp;INFO.<BR>&nbsp;&nbsp;&nbsp;logger.warn("Low&nbsp;fuel&nbsp;level.");</P>
<P>&nbsp;&nbsp;&nbsp;//&nbsp;This&nbsp;request&nbsp;is&nbsp;disabled,&nbsp;because&nbsp;DEBUG&nbsp;&lt;&nbsp;INFO.<BR>&nbsp;&nbsp;&nbsp;logger.debug("Starting&nbsp;search&nbsp;for&nbsp;nearest&nbsp;gas&nbsp;station.");</P>
<P>&nbsp;&nbsp;&nbsp;//&nbsp;The&nbsp;logger&nbsp;instance&nbsp;barlogger,&nbsp;named&nbsp;"com.foo.Bar",<BR>&nbsp;&nbsp;&nbsp;//&nbsp;will&nbsp;inherit&nbsp;its&nbsp;level&nbsp;from&nbsp;the&nbsp;logger&nbsp;named<BR>&nbsp;&nbsp;&nbsp;//&nbsp;"com.foo"&nbsp;Thus,&nbsp;the&nbsp;following&nbsp;request&nbsp;is&nbsp;enabled<BR>&nbsp;&nbsp;&nbsp;//&nbsp;because&nbsp;INFO&nbsp;&gt;=&nbsp;INFO.<BR>&nbsp;&nbsp;&nbsp;barlogger.info("Located&nbsp;nearest&nbsp;gas&nbsp;station.");</P>
<P>&nbsp;&nbsp;&nbsp;//&nbsp;This&nbsp;request&nbsp;is&nbsp;disabled,&nbsp;because&nbsp;DEBUG&nbsp;&lt;&nbsp;INFO.<BR>&nbsp;&nbsp;&nbsp;barlogger.debug("Exiting&nbsp;gas&nbsp;station&nbsp;search");</P></DIV><BR>　　有几个有趣的情况，一是当一个记录器实例化后，再一次用相同的名字调用getLogger()会返回对它的引用，这非常有利于用同一个记录器在不同代码或类中记录log信息，另一个是与自然界中祖先先于后代出现不同，一个记录器的祖先可以比后代记录出现的晚，但会自动根据名字之间的关系建立这种家族关系。 
<P></P>
<P>4、存放器<BR>　　在log4j中，log信息通过存放器输出到目的地。支持的存放器有console, files, GUI components, remote socket servers, JMS, NT Event Loggers, remote UNIX Syslog daemons。通过file存放器，log信息可以被输出到不同的文件中（即不同的目的地）。log信息可被异步存放。<BR>　　一个记录器可以有多个存放器，可以通过方法addAppender来增加存放器。一条blog信息如果可被这个记录器处理，则记录器会把这条信息送往每个它所拥有的存放器。<BR>　　每个记录器有一个<ACRONYM title="additivity flag ">继承开关</ACRONYM>,其开关决定记录器是/否继承其父记录器的存放器，注意，如果继承则只继承其父记录器，而不考虑更远的祖先的情况。参考下表：<BR>
<TABLE border=1>
<TBODY>
<TR>
<TD>记录器</TD>
<TD>增加的存放器</TD>
<TD>继承的存放器</TD>
<TD>输出的目的地</TD>
<TD>备注</TD></TR>
<TR>
<TD>root</TD>
<TD>A1</TD>
<TD>not applicable</TD>
<TD>A1</TD>
<TD>The root logger is anonymous but can be accessed with the Logger.getRootLogger() method. There is no default appender attached to root.</TD></TR>
<TR>
<TD>x</TD>
<TD>A-x1, A-x2</TD>
<TD>TRUE</TD>
<TD>A1, A-x1, A-x2</TD>
<TD>Appenders of "x" and root.</TD></TR>
<TR>
<TD>x.y</TD>
<TD>none</TD>
<TD>TRUE</TD>
<TD>A1, A-x1, A-x2</TD>
<TD>Appenders of "x" and root.</TD></TR>
<TR>
<TD>x.y.z</TD>
<TD>A-xyz1</TD>
<TD>TRUE</TD>
<TD>A1, A-x1, A-x2, A-xyz1</TD>
<TD>Appenders in "x.y.z", "x" and root.</TD></TR>
<TR>
<TD>security</TD>
<TD>A-sec</TD>
<TD>FALSE</TD>
<TD>A-sec</TD>
<TD>No appender accumulation since the additivity flag is set to false.</TD></TR>
<TR>
<TD>security.access</TD>
<TD>none</TD>
<TD>TRUE</TD>
<TD>A-sec</TD>
<TD>Only appenders of "security" because the additivity flag in "security" is set to false.</TD></TR></TBODY></TABLE>　　<BR>　　<BR>5、布局<BR>　　布局负责格式化输出的log信息。log4j的PatternLayout可以让程序以类似C语言printf的格式化模板来定义格式。</P>
<P>6、log4j可据程序员制定的标准自动提供一些log信息，这对那类需要频繁log的对象的情况很帮助。对象的自动log，具有继承性.</P><BR>参考文献：<BR>1、log4j--新的日志操作方法，scriptskychen ，http://www.cn-java.com/target/news.php?news_id=2590<img src ="http://www.blogjava.net/Apple/aggbug/8751.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-07-29 17:05 <a href="http://www.blogjava.net/Apple/archive/2005/07/29/8751.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[zz至八进制]国际化Eclipse插件</title><link>http://www.blogjava.net/Apple/archive/2005/07/26/8472.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Tue, 26 Jul 2005 05:38:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/07/26/8472.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/8472.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/07/26/8472.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/8472.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/8472.html</trackback:ping><description><![CDATA[<FONT size=2>转载地址：</FONT><A href="http://www.cnblogs.com/bjzhanghao/archive/2004/08/23/36011.html"><FONT size=2>http://www.cnblogs.com/bjzhanghao/archive/2004/08/23/36011.html</FONT></A><BR>
<P>在本文的<A href="http://www.cnblogs.com/bjzhanghao/archive/2004/08/08/31262.aspx"><FONT color=#000080>上篇</FONT></A>里，介绍了使用Eclipse的国际化工具对程序中的字符串进行外向化处理（Extenalize），可以看出步骤是十分简单的。实在是很喜欢Eclipse这样的工具，它可以为你做很多事情，干净漂亮，但绝不会在未经你同意的情况下做任何动作，所谓“利器”也！</P>
<P>现在说说在资源中含有参数的情况怎样处理。比如在对话框中要显示信息：“帐户目前还有 900 元，截止日期为 2004-9-1，谢谢！”，因为中间的数字和日期是动态的，所以不能直接放在资源文件中。但是请放心，大可不必为这条信息指定三个资源（被数字和日期分开的三个字符串），可以在资源文件（.properties）中指定资源为这个样子：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">my.resource.key=帐户目前还有&nbsp;{0}&nbsp;元，截止日期为&nbsp;{1}，谢谢！</SPAN></DIV></DIV>
<P>其中{0}和{1}表示将替换为动态的值，然后在程序里写：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">Float&nbsp;amount</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.cnblogs.com/Images/dot.gif">;<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>Date&nbsp;dt</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000"><IMG src="http://www.cnblogs.com/Images/dot.gif">;<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>String&nbsp;msg</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">MessageFormat.format(Messages.getString(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">my.resource.key</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">),&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;Object[]{amount,dt});</SPAN></DIV></DIV>
<P>这样，msg变量里就是动态生成的提示信息了。你很可能希望对日期进行格式化处理，要实现这个功能也很简单，只要稍微修改一下资源，如下：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">帐户目前还有&nbsp;{0}&nbsp;元，截止日期为&nbsp;{1,date,yyyy-MM-dd}，谢谢！</SPAN></DIV></DIV>
<P>确实简单吧，不知道你用没用过这个写法，我是在PPP项目中才第一次使用的，所以赶紧介绍一下了，呵呵。</P>
<P>接下来的问题是编码，资源文件写为英文是没有问题的，可以正常显示，但汉字是不能直接写在资源文件里的，要转换为unicode才可以。jdk本身提供了native2ascii工具，可以实现这个功能，但用命令行总是不太方便，虽然也有人很喜欢使用命令行的感觉……如果你愿意Eclipse为你服务，大可以使用我下面介绍的两个插件，利用它们，你根本不需要显式转换编码这一步了。</P>
<P>第一个是<A href="http://propedit.sourceforge.jp/index_en.html"><FONT color=#000080>Properties Editor</FONT></A>，好象是日本人写的，安装后它会与扩展名为.properties的文件相关联，使用它打开资源文件，可以在本地语言与unicode视图之间切换，一般情况下编辑本地语言就可以了，保存时会自动转换为unicode。当需要查找某个资源（值）并修改时这个编辑器非常方便，例如想把资源里所有“你好”改为“您好”，如果面对的是一堆unicode码还真是头疼。安装这个插件需要2.1.1版本以上的Eclipse。</P>
<P>另一个我们在项目中经常使用的插件叫……我还真不知道它全名叫什么，在我这里的打包文件名是“29 Localization Editor”，它是一个非常方便的国际化翻译工具。用它打开扩展名为.properties的文件后，可以新建key，或者新建语言，你要做的只是在表格中把尚未翻译成新语言的资源值填下而已，可以选择只显示未翻译的条目或是全部条目。不过很对不起，我还没找到网上的下载地址，如果需要请和我<A href="mailto:bjzhanghao@21cn.com"><FONT color=#000080>联系</FONT></A>吧。</P>
<P align=center><IMG height=417 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/LocalizationEditor.gif" width=631 border=0><BR><BR><FONT size=2>图1 Localization Editor使用界面</FONT></P>
<P align=justify>好了，关于Eclipse的国际化先介绍到这里了。如果你的应用程序是Eclipse插件，还可以更进一步：把资源文件打成语言包。关于这种方式（Fragment）的介绍，以后有时间再写吧。</P><img src ="http://www.blogjava.net/Apple/aggbug/8472.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-07-26 13:38 <a href="http://www.blogjava.net/Apple/archive/2005/07/26/8472.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对象和集合</title><link>http://www.blogjava.net/Apple/archive/2005/06/26/6720.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Sun, 26 Jun 2005 07:07:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/26/6720.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6720.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/26/6720.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6720.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6720.html</trackback:ping><description><![CDATA[&nbsp; 主要有以下几个方面需要注意：<BR>1.重写equals()和hashCode()<BR>&nbsp; 当真正需要了解两个引用是否完全相同时，使用＝＝。但是，当需要了解对象本身是否相等时，使用equals()。实际上是要比较两个不同对象的<STRONG>内容</STRONG>在意义上是否等价，除了String类和封装器类（已重写了equals()），需要重写equals().Object内的equals()的方法只使用==运算符进行比较，因此，除非重写equals()，否则只有当两个引用引用相同的对象时，这两个对象才被看做是相等的。另外，如果要使类对象能够用作散列表的键（或者任何数据结构中用于搜索和/或检索对象的元素)，则必须重写equals()，使两个不同实例能够被看做是相同的。<BR>&nbsp; 我们一般这样重写：<BR><FONT color=#ff0000>&nbsp; public</FONT> boolean equals(<FONT color=#ff0000>Object o</FONT>) {<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(<FONT color=#ff0000>o instanceof&nbsp; Moof</FONT>) &amp;&amp; ((Moof)o).getMoofValue() ==&nbsp; this.moofValue)) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true;<BR>&nbsp;&nbsp; } else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<BR>&nbsp; }<BR>}<BR>上面执行了两个操作：<BR>&nbsp;1）确保所测试对象的类型是正确的！用instanceof<BR>&nbsp;2）比较我们关心的属性<BR>注意： equals()、hashCode()和toString()方法都是公有的。下面这种重写是无效的，<BR>&nbsp;&nbsp;&nbsp; class Foo {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; boolean equals(Object o) { }<BR>}<BR>&nbsp;一定还要注意参数类型。下面方法是对equals()方法的重载，而不是重写。<BR>&nbsp;&nbsp;&nbsp; class Foo {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public boolean equals(Foo b) { }<BR>}<BR>一定要非常熟悉重写规则，要能够识别类中对Object方法的重写、重载还是非法重新声明。在Foo类中，equals()方法把参数从Object修改为Foo，因此，它是一个重载方法，除非从你自己的代码（它知道这个新的不同方法，而且碰巧被命名为equals)中调用，否则，它不会被调用。<BR>java API中，在equals()方法内定义了一种约定。java约定是一套应该遵守的规则，或者说是想提供其他人所期望的“正确”设计时必须要遵守的内容。有5点：<BR>&nbsp;1.自反性： x.equals(x)应该返回true；<BR>&nbsp;2.对称性：当且仅当y.equals(x)返回true时，x.equals(y）才返回true。<BR>3.可传递性<BR>4.一致性<BR>5.任何非空引用值x，x.equals(null）应该返回false.<BR>如果使用equals(）方法，两个对象被认为是相等的，则它们必须具有相同的散列码值。因为Object类的默认散列码总是为每个对象产生一个唯一号，即使重写的equals()方法认为两个或多个对象是相等的，也是这样。所以，为了安全起见，<STRONG><FONT color=#ff0000>如果重写equals(),一定也要重写<BR>hashCode().</FONT></STRONG><img src ="http://www.blogjava.net/Apple/aggbug/6720.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-26 15:07 <a href="http://www.blogjava.net/Apple/archive/2005/06/26/6720.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【读书笔记：java数据结构与面向对象编程基础】抽象数据类容器</title><link>http://www.blogjava.net/Apple/archive/2005/06/25/6706.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Sat, 25 Jun 2005 08:31:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/25/6706.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6706.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/25/6706.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6706.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6706.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 容器(Container)在生活中处处可见。每种容器的类型都有各自定制、访问实体的一系列规则。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 区别容器对象本身和该容器包含的对象是很重要的。从某种意义上说，研究数据结构也就是研究容器。这里，将给出容器这种抽象数据类型的行为框架，并且创建实现这种抽象数据类型的基本原型。根据一下的特性可以区别不同类型的容器：<BR>&nbsp;&nbsp;&nbsp; <STRONG>（1）容器中的对象是否可排序。</STRONG>比如：按容器的一个继承属性排序，或按容器中的对象的一个属性排序。<BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>（2）是否允许对象的复制（是否允许重复）。<BR>&nbsp;&nbsp;&nbsp;&nbsp;（3）容器中的对象可被限制为一个特定的类型。<BR>&nbsp;&nbsp;&nbsp;&nbsp;（4）容器中的对象可基于索引来访问。<BR>&nbsp;&nbsp;&nbsp;&nbsp;（5）容器中的对象可基于相对位置来访问。<BR>&nbsp;&nbsp;&nbsp;&nbsp;（6）容器中的对象可基于值访问。<BR>&nbsp;&nbsp;&nbsp; （7）容器可根据其连通性（线性、非线性）来区别。</STRONG><BR>这里以Container接口为根接口来表示所有容器最一般的形式。<BR><BR>1.1 顶层的Container接口<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在开发新的java类时，我们应该先考虑是否需要可串行化。一个可串行化的对象可以很容易的从作为对象的文件流中来读/写的。最一般的容器具有一下特性：<BR>&nbsp;&nbsp;&nbsp; 1）不考虑它所包含的对象的类型；<BR>&nbsp;&nbsp;&nbsp; <FONT color=#800080>2）所包含的对象没有排序的要求；<BR></FONT>&nbsp;&nbsp; 3）接受复制的对象；<BR>&nbsp;&nbsp; 4）支持可串行化；<BR>&nbsp;&nbsp; 5）接受使本身为空的命令；<BR>&nbsp;&nbsp; 6）接受一下查询：返回所包含的对象的个数、返回容器是否为空。<BR>我们所定义的Container接口如下：<BR>&nbsp; /** Interface Container - top level container*/<BR>&nbsp;package foundations;<BR>&nbsp;import java.io.serializable;<BR><STRONG>&nbsp;public interface Container extends Serializable {<BR></STRONG>// Commands - see subinferfaces<BR>/** Remove all objects from the container if found<BR>*/<BR><STRONG>public void makeEmpty ();<BR></STRONG>//Queries<BR>/** Return true if the container is empty<BR>*/<BR><STRONG>public boolean isEmpty();<BR></STRONG>/** Return the number of objects in the container<BR>*/<BR><STRONG>public int size();<BR></STRONG>}<BR><BR>1.2 最简单的容器－－堆栈和队列<BR>&nbsp;&nbsp;&nbsp; 这里定义最简单的容器：堆栈（Stack）和队列（Queue）<BR>&nbsp;&nbsp;&nbsp; 堆栈使一个容器，并具有以下特性：<BR>&nbsp;1) 堆栈是有次序的，这也是堆栈的一个属性，与堆栈所包含的对象无关。堆栈中对象的次序依赖于其插入、删除的顺序。后进先出。<BR>&nbsp;2）对堆栈的操作只能在一个名为top的位置上。用Push方法在堆栈顶部增加新的对象,用pop方法删除堆栈顶部的对象，用top方法查询堆栈顶部的对象。<BR>&nbsp;3）用MakeEmpty方法可以清除堆栈的对象，也可用isEmpty来查询该堆栈是否为空，以及查询堆栈的大小（size()）。<BR>&nbsp;&nbsp;&nbsp; 接口Stack是对Container的扩展，因此它继承了Container所有的方法，并增加了新的方法:push、pop、以及查询顶部对象的方法。<BR>&nbsp;&nbsp;/** Interface Stack - a first-in last-out container<BR>&nbsp;*/<BR>package foundations;<BR><STRONG>public interface Stack extends Container {<BR></STRONG>//Commands<BR>/** Add an object onto the top of the stack<BR>*/<BR><STRONG>public void push(object obj);</STRONG><BR><BR>/** Remove an object form the top of the stack<BR>* Throw NoSuchElementException if stack is empty<BR>*/<BR><STRONG>public void pop();<BR></STRONG>//Queries<BR><BR>/** Return without removing, the top object on the stack<BR>* Throw NoSuchElementException if stack is empty<BR>*/<BR><STRONG>public object top();<BR></STRONG>}<BR>队列也是一个容器，并具有以下特性：<BR>1）队列有次序，这也是它的一个属性，与队列所包含的对象无关。队列中对象的次序依赖于其插入、删除的顺序。队列中的次序关系的主要特点是先进先出。<BR>2）对队列的操作限制在两个位置上，即front（对头）、rear（队尾）。我们可以在尾部增加对象，在头部删除对象，或查询头部的对象。<BR>3）可以用MakeEmpty方法可以清除队列的对象，也可用isEmpty来查询该队列是否为空，以及查询队列的大小（size()）。<BR>接口Queue是对Container的扩展，因此它继承了Container所有的方法，并增加了新的方法:add、remove、以及查询头部对象的方法。<BR>/** Interface Queue<BR>*/<BR><STRONG>public interface Queue extends Container {<BR></STRONG>//Commands<BR>/** Add an object at the rear of the queue<BR>*/<BR><STRONG>public void add(Object obj);<BR></STRONG><BR>/** Remove an object from the front of the queue<BR>* Throws NoSuchElementException if queue is empty<BR>*/<BR><STRONG>public void remove();<BR></STRONG><BR>//Queries<BR>/** Return without removing, the front object in the queue<BR>* Throws NoSuchElementException if queue is empty<BR>*/<BR><STRONG>public object front();<BR></STRONG>}<BR><BR>1.3 辅助性接口和类－－<EM><FONT color=#006400>Comparable</FONT></EM>（可比性）和<EM><FONT color=#006400>Association</FONT></EM>（关联性）<BR>&nbsp;&nbsp; 有序容器包含了一些对象，这些对象在容器中的位置（或次序）是根据它的某个属性的重要性来决定的，更确切的说，有序容器中的对象是可比较的，这个属性可以通过要求对象类来实现Comparable接口来获得。Comparable接口包含唯一一个查询方法CompareTo。<BR>&nbsp; 查询CompareTo返回一个整数值。<BR>/** Interface Comparable<BR>*/<BR><STRONG>public Interface Comparable {<BR></STRONG>//Queries<BR><BR>/** Return -1 if the receiver is less than obj,<BR>* 0 if the receiver equals obj and<BR>* 1 if the receiver is greater than obj<BR>*/<BR><STRONG>public int compareTo (Object obj);<BR></STRONG>}<BR><BR>&nbsp;&nbsp; 类Association允许将值和键(Key)组合起来，即在键和其值之间有一个关联。类Association在研究一个需要键－值（key－value）对的容器数据结构时起到什么重要的作用。<BR>&nbsp;&nbsp; 在字典类容器中就包含Association类的实例，字典是由键组成的，也就是说，我们查找字典中的对象时，只是查找它的键。如果字典按某种顺序（根据所包含的键的相对大小）排列的，那么我们必须保证任何输入到OrderdDictionary（有序字典）中的关联对的键也是可以比较的（Comparable）。同时，我们要求关联也必须是可串行化的，这样才能保证前面所提到的所有容器都要求是可串行化的。Association是一个类，而不是接口。<BR>/** Class Assocation<BR>* An instance must initialize a key on creation.<BR>* If used as a comparable Assocation,keys must be comparable and comparions is based on keys only.<BR>* Note that equals() does not enforce the comparable feature and requires equality of both key and value.<BR>*/<BR>package foundations;<BR>import java.io.serializable;<BR><BR>public class Association extends Object implements Comparable,Serializable {<BR>//Fields<BR>private Object key;<BR>private Object value;<BR><BR>/** Create an instance with specified key and null value<BR>*/<BR>public Assocation (Object key) {<BR>&nbsp;this(key,null);<BR>}<BR><BR>/** Create an instance with specified key and value<BR>*/<BR>public Association(Object key,Object value) {<BR>&nbsp; this.key=key;<BR>&nbsp; this.value=value;<BR>}<BR><BR>/** Set the value<BR>*/<BR>public void setValue(Object value) {<BR>&nbsp; this.value = value;<BR>}<BR><BR>/** return key<BR>*/<BR>public Object key() {<BR>&nbsp; return key;<BR>}<BR><BR>/** return value<BR>*/<BR>public Object value() {<BR>return value;<BR>}<BR><BR>/**Return a string representation.<BR>* Return a String of the form &lt;key:value&gt;<BR>*/<BR>public String toString() {<BR>&nbsp;return "&lt;"+key+":"+value+"&gt;";<BR>}<BR><BR>/** Override inherited object method equals()<BR>*/<BR>public boolean&nbsp;&nbsp;&nbsp;equals (Object obj) {<BR>&nbsp; if(obj instanceof Assocation)]<BR>&nbsp;&nbsp;&nbsp; return (key.equals((Assocation)obj).key) &amp;&amp; value.equals((Assocation)obj).value));<BR>&nbsp; else return false;<BR>}<BR><BR>/**Implement Comparable method compareTo<BR>* Compare based only on key; key must be comparable<BR>*/<BR>public int compareTo (Object obj) {<BR>&nbsp;return ((Comparable)key).compareTo((Association)obj.key());<BR>}<BR><BR>/** Override inherited Object method hashCode().<BR>*Return a unique int representing this object<BR>*/<BR>public int hashCode () {<BR>&nbsp; int bits1 = key.hashCode();<BR>&nbsp; int bits2 = value.hashCode();<BR>&nbsp;return (bits1 &lt;&lt;8)^(bits2&gt;&gt;8);<BR>&nbsp;}<BR>}<BR><BR>未完，待续....<BR><img src ="http://www.blogjava.net/Apple/aggbug/6706.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-25 16:31 <a href="http://www.blogjava.net/Apple/archive/2005/06/25/6706.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【zz至键行天下】java编程100例</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6695.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 13:10:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6695.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6695.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6695.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6695.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6695.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]001、Hello,你好Java</STRONG></FONT></TD></TR>
<TR>
<TD>
<P><FONT color=#000000>public class HelloWorld {<BR>&nbsp; public static void main(String[] args) {<BR>&nbsp;&nbsp;&nbsp; System.out.println("Hello Java World!");<BR>&nbsp; }<BR>&nbsp;}</FONT></P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]002、Java流程控制</STRONG></FONT></TD></TR>
<TR>
<TD><FONT color=#000000>public class flowDemo{<BR>&nbsp;&nbsp; public static void main(String[] arges){<BR>&nbsp;&nbsp;&nbsp;&nbsp; int iPara1,iPara2,iEnd;<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(arges.length!=3)<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("USE :java flowDome parameter1 parameter2 circle");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("parameter1 : 比较条件1，数字类型");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("parameter2 : 比较条件2，数字类型");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("circle ：循环次数");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("ego:java flowDome 1 2 5");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<BR>&nbsp;&nbsp;&nbsp;&nbsp; }else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iPara1 = Integer.parseInt(arges[0]);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iPara2 = Integer.parseInt(arges[1]);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iEnd = Integer.parseInt(arges[2]);<BR>&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp; //if语句<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(iPara2&gt;iPara1)<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("if 条件满足！");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("第2个数比第1个数大！");<BR>&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp; else<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("if 条件不满足！");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("第2个数比第1个数小！");<BR>&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp; //for循环操作<BR>&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;iEnd;i++)<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("这是for 第"+i+"次循环");<BR>&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp; //while循环操作<BR>&nbsp;&nbsp;&nbsp;&nbsp; int i=0;<BR>&nbsp;&nbsp;&nbsp;&nbsp; while(i&lt;iEnd)<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("这是while 第"+i+"次循环");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i++;<BR>&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp; //do-while循环操作<BR>&nbsp;&nbsp;&nbsp;&nbsp; int j=0;<BR>&nbsp;&nbsp;&nbsp;&nbsp; do<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("这是do-while 第"+j+"次循环");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j++;<BR>&nbsp;&nbsp;&nbsp;&nbsp; }while(j&lt;iEnd);<BR>&nbsp;&nbsp; }<BR>&nbsp;}</FONT></TD></TR></TBODY></TABLE></P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]003、数组数据操作</STRONG></FONT></TD></TR>
<TR>
<TD><FONT color=#000000>public class&nbsp; myArray{<BR>&nbsp;&nbsp; //初始化数组变量<BR>&nbsp;&nbsp; char[] cNum = {'1','2','3','4','5','6','7','8','9','0'};<BR>&nbsp;&nbsp; char[] cStr = {'a','b','c','d','e','f','g','h',<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'i','j','k','l','m','n','o','p',<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'q','r','s','t','u','v','w','x','y','z'};<BR>&nbsp;&nbsp; int[] iMonth = {31,28,31,30,31,30,31,31,30,31,30,31};<BR>&nbsp;&nbsp; String[] sMail = {"@","."};<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：校验电子邮件<BR>&nbsp;*&lt;br&gt;输入参数：String sPara 被校验的电子邮件字符<BR>&nbsp;*&lt;br&gt;返回类型：boolean 如果校验的格式符合电子邮件格式返回true；否则返回false<BR>&nbsp;*/&nbsp;&nbsp; <BR>&nbsp;&nbsp; public boolean isMail(String sPara){<BR>&nbsp;&nbsp; &nbsp;for(int i=0;i&lt;sMail.length;i++){<BR>&nbsp;&nbsp; &nbsp;&nbsp; if(sPara.indexOf(sMail[i])==-1)<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return false;&nbsp;&nbsp; &nbsp;&nbsp; <BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;return true;<BR>&nbsp;&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：判断是否是数字<BR>&nbsp;*&lt;br&gt;输入参数：String sPara。 需要判断的字符串<BR>&nbsp;*&lt;br&gt;返回类型：boolean。如果都是数字类型，返回true；否则返回false<BR>&nbsp;*/&nbsp;&nbsp; <BR>&nbsp;&nbsp; public boolean isNumber(String sPara){<BR>&nbsp;&nbsp; &nbsp; int iPLength = sPara.length();<BR>&nbsp;&nbsp; &nbsp; for(int i=0;i&lt;iPLength;i++){<BR>&nbsp;&nbsp; &nbsp;&nbsp; char cTemp = sPara.charAt(i);<BR>&nbsp;&nbsp; &nbsp;&nbsp; boolean bTemp = false;<BR>&nbsp;&nbsp; &nbsp;&nbsp; for(int j=0;j&lt;cNum.length;j++){<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; if(cTemp==cNum[j]){<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bTemp = true;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp; &nbsp;&nbsp; }<BR>&nbsp;&nbsp; &nbsp;&nbsp; if(!bTemp) return false; <BR>&nbsp;&nbsp; &nbsp; }<BR>&nbsp;&nbsp;&nbsp; return true;<BR>&nbsp;&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：判断是否都是英文字符<BR>&nbsp;*&lt;br&gt;输入参数：String sPara。要检查的字符<BR>&nbsp;*&lt;br&gt;返回类型：boolean。如果都是字符返回true；反之为false<BR>&nbsp;*/&nbsp;&nbsp; <BR>&nbsp;&nbsp; public boolean isString(String sPara){<BR>&nbsp;&nbsp; &nbsp; int iPLength = sPara.length();<BR>&nbsp;&nbsp; &nbsp; for(int i=0;i&lt;iPLength;i++){<BR>&nbsp;&nbsp; &nbsp;&nbsp; char cTemp = sPara.charAt(i);<BR>&nbsp;&nbsp; &nbsp;&nbsp; boolean bTemp = false;<BR>&nbsp;&nbsp; &nbsp;&nbsp; for(int j=0;j&lt;cStr.length;j++){<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; if(cTemp==cStr[j]){<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bTemp = true;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp; &nbsp;&nbsp; }<BR>&nbsp;&nbsp; &nbsp;&nbsp; if(!bTemp) return false; <BR>&nbsp;&nbsp; &nbsp; }<BR>&nbsp;&nbsp;&nbsp; return true;<BR>&nbsp;&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：判断是否是闰年<BR>&nbsp;*&lt;br&gt;输入参数：int iPara。要判断的年份<BR>&nbsp;*&lt;br&gt;返回类型：boolean。如果是闰年返回true，否则返回false<BR>&nbsp;*/&nbsp;&nbsp; <BR>&nbsp;&nbsp; public boolean chickDay(int iPara){<BR>&nbsp;&nbsp;&nbsp;&nbsp; return iPara%100==0&amp;&amp;iPara%4==0;<BR>&nbsp;&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：检查日期格式是否正确<BR>&nbsp;*&lt;br&gt;输入参数：String sPara。要检查的日期字符<BR>&nbsp;*&lt;br&gt;返回类型：int。0 日期格式正确，-1 月或这日不合要求， -2 年月日格式不正确 <BR>&nbsp;*/<BR>&nbsp;&nbsp; public int chickData(String sPara){<BR>&nbsp;&nbsp; &nbsp;boolean bTemp = false;<BR>&nbsp;&nbsp; &nbsp;//所输入日期长度不正确<BR>&nbsp;&nbsp; &nbsp;if(sPara.length()!=10) return -2;<BR>&nbsp;&nbsp; &nbsp;//获取年<BR>&nbsp;&nbsp; &nbsp;String sYear = sPara.substring(0,4);<BR>&nbsp;&nbsp; &nbsp;//判断年是否为数字<BR>&nbsp;&nbsp; &nbsp;if(!isNumber(sYear)) return -2;<BR>&nbsp;&nbsp; &nbsp;//获取月份<BR>&nbsp;&nbsp; &nbsp;String sMonth = sPara.substring(5,7);<BR>&nbsp;&nbsp; &nbsp;//判断月份是否为数字<BR>&nbsp;&nbsp; &nbsp;if(!isNumber(sMonth)) return -2;<BR>&nbsp;&nbsp; &nbsp;//获取日<BR>&nbsp;&nbsp; &nbsp;String sDay = sPara.substring(8,10);<BR>&nbsp;&nbsp; &nbsp;//判断日是否为数字<BR>&nbsp;&nbsp; &nbsp;if(!isNumber(sDay)) return -2;<BR>&nbsp;&nbsp; &nbsp;//将年、月、日转换为数字<BR>&nbsp;&nbsp; &nbsp;int iYear = Integer.parseInt(sYear);<BR>&nbsp;&nbsp; &nbsp;int iMon = Integer.parseInt(sMonth);<BR>&nbsp;&nbsp; &nbsp;int iDay = Integer.parseInt(sDay);<BR>&nbsp;&nbsp; &nbsp;if(iMon&gt;12) return -1;<BR>&nbsp;&nbsp; &nbsp;//闰年二月处理<BR>&nbsp;&nbsp; &nbsp;if(iMon==2&amp;&amp;chickDay(iYear)){<BR>&nbsp;&nbsp; &nbsp;&nbsp; if(iDay&gt;29) return 2;<BR>&nbsp;&nbsp; &nbsp;}else{<BR>&nbsp;&nbsp; &nbsp;&nbsp; if(iDay&gt;iMonth[iMon-1]) return -1;<BR>&nbsp;&nbsp; &nbsp;}<BR>&nbsp;&nbsp; &nbsp;return 0;<BR>&nbsp;&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：主方法，测试用<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/ <BR>&nbsp;&nbsp; public static void main(String[] arges){<BR>&nbsp;&nbsp;&nbsp;&nbsp; myArray mA = new myArray();<BR>&nbsp;&nbsp;&nbsp;&nbsp; //校验邮件地址<BR>&nbsp;&nbsp;&nbsp;&nbsp; boolean bMail = mA.isMail("</FONT><A href="mailto:tom@163.com"><FONT color=#000000>tom@163.com</FONT></A><FONT color=#000000>");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("1 bMail is "+bMail);<BR>&nbsp;&nbsp;&nbsp;&nbsp; bMail = mA.isMail("</FONT><A href="mailto:tom@163com"><FONT color=#000000>tom@163com</FONT></A><FONT color=#000000>");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("2 bMail is "+bMail);<BR>&nbsp;&nbsp;&nbsp;&nbsp; //演示是否是数字<BR>&nbsp;&nbsp;&nbsp;&nbsp; boolean bIsNum = mA.isNumber("1234");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("1：bIsNum="+bIsNum);<BR>&nbsp;&nbsp;&nbsp;&nbsp; bIsNum = mA.isNumber("123r4");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("2：bIsNum="+bIsNum);<BR>&nbsp;&nbsp;&nbsp;&nbsp; //演示是否是英文字符<BR>&nbsp;&nbsp;&nbsp;&nbsp; boolean bIsStr = mA.isString("wer");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("1：bIsStr="+bIsStr);<BR>&nbsp;&nbsp;&nbsp;&nbsp; bIsStr = mA.isString("wer3");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("2：bIsStr="+bIsStr);<BR>&nbsp;&nbsp;&nbsp;&nbsp; //演示检查日期<BR>&nbsp;&nbsp;&nbsp;&nbsp; int iIsTime = mA.chickData("2003-12-98");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("1：iIsTime="+iIsTime);<BR>&nbsp;&nbsp;&nbsp;&nbsp; iIsTime = mA.chickData("2003-111-08");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("2：iIsTime="+iIsTime);<BR>&nbsp;&nbsp;&nbsp;&nbsp; iIsTime = mA.chickData("2003-10-08");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("3：iIsTime="+iIsTime);<BR>&nbsp;&nbsp;&nbsp;&nbsp; iIsTime = mA.chickData("2000-02-30");<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("4：iIsTime="+iIsTime);<BR>&nbsp;&nbsp; }<BR>&nbsp;}</FONT></TD></TR></TBODY></TABLE></P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]004、矢量(Vector)对象的操作</STRONG></FONT></TD></TR>
<TR>
<TD><FONT color=#000000>public class operateVector <BR>{<BR>/*<BR>*&lt;br&gt;方法说明：生成一个4*4的二维Vector，供使用。<BR>*&lt;br&gt;输入参数：<BR>*&lt;br&gt;输出变量：Vector<BR>*&lt;br&gt;其它说明：<BR>*/<BR>&nbsp;public Vector buildVector(){<BR>&nbsp;&nbsp;&nbsp; Vector vTemps = new Vector();<BR>&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;4;i++){<BR>&nbsp;&nbsp;&nbsp; Vector vTemp = new Vector();<BR>&nbsp;&nbsp;&nbsp; for (int j=0;j&lt;4;j++){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vTemp.addElement("Vector("+i+")("+j+")");<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; vTemps.addElement(vTemp);<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; return vTemps;<BR>&nbsp;}<BR>/*<BR>*&lt;br&gt;方法说明：插入数据<BR>*&lt;br&gt;输入参数：Vector vTemp 待插入的数据对象<BR>*&lt;br&gt;输入参数：int iTemp 插入数据的位置<BR>*&lt;br&gt;输入参数：Object oTemp 插入数据值<BR>*&lt;br&gt;输出变量：Vector 结果<BR>*&lt;br&gt;其它说明：如果插入位置超出实例实际的位置将返回null<BR>*/<BR>&nbsp;public Vector insert(Vector vTemp,int iTemp,Object oTemp){<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(iTemp&gt;vTemp.size()){<BR>&nbsp;&nbsp;&nbsp;print("数据超界!");<BR>&nbsp;&nbsp;&nbsp;return null;<BR>&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp; vTemp.insertElementAt(oTemp,iTemp);<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;return vTemp;<BR>&nbsp;}<BR>/*<BR>*&lt;br&gt;方法说明：移除数据<BR>*&lt;br&gt;输入参数：Vector vTemp 待删除矢量对象<BR>*&lt;br&gt;输入参数：int iTemp 删除数据的位置<BR>*&lt;br&gt;输出变量：Vector<BR>*&lt;br&gt;其它说明：如果删除超界的数据，将返回null<BR>*/<BR>&nbsp;public Vector delete(Vector vTemp,int iTemp){<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(iTemp&gt;vTemp.size()){<BR>&nbsp;&nbsp;&nbsp;print("数据超界!");<BR>&nbsp;&nbsp;&nbsp;return null;<BR>&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp; vTemp.removeElementAt(iTemp);<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;return vTemp;<BR>&nbsp;}<BR>/*<BR>*&lt;br&gt;方法说明：修改数据<BR>*&lt;br&gt;输入参数：Vector vTemp 待修改矢量对象<BR>*&lt;br&gt;输入参数：int iTemp 修改数据的位置<BR>*&lt;br&gt;输入参数：Object oTemp 修改数据值<BR>*&lt;br&gt;输出变量：Vector<BR>*&lt;br&gt;其它说明：如果修改位置超界的数据，将返回null<BR>*/<BR>&nbsp;public Vector updata(Vector vTemp,int iTemp,Object oTemp){<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(iTemp&gt;vTemp.size()){<BR>&nbsp;&nbsp;&nbsp;print("数据超界!");<BR>&nbsp;&nbsp;&nbsp;return null;<BR>&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp; vTemp.setElementAt(oTemp,iTemp);<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;return vTemp;<BR>&nbsp;}<BR>/*<BR>*&lt;br&gt;方法说明：输出信息<BR>*&lt;br&gt;输入参数：String sTemp 输出信息名称<BR>*&lt;br&gt;输入参数：Object oTemp 输出信息值<BR>*&lt;br&gt;返回变量：无<BR>*/<BR>&nbsp;public void print(String sTemp,Vector oTemp){<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(sTemp+"数据：");<BR>&nbsp;&nbsp;&nbsp;&nbsp; this.print(oTemp);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：打印输出（过载）<BR>&nbsp;*&lt;br&gt;输入参数：Object oPara 输出的对象<BR>&nbsp;*&lt;br&gt;返回类型：无<BR>&nbsp;*/<BR>&nbsp;public void print(Object oPara){<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(oPara);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：打印输出（过载）<BR>&nbsp;*&lt;br&gt;输入参数：Vector vPara 显示输出矢量对象<BR>&nbsp;*&lt;br&gt;返回类型：无<BR>&nbsp;*/<BR>&nbsp;public void print(Vector vPara){<BR>&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;vPara.size();i++){<BR>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(vPara.elementAt(i));<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：主方法，程序入口<BR>&nbsp;*&lt;br&gt;输入参数：String[] args<BR>&nbsp;*&lt;br&gt;返回类型：无<BR>&nbsp;*/<BR>&nbsp;public static void main(String[] args) <BR>&nbsp;{<BR>&nbsp;&nbsp;operateVector ov = new operateVector();<BR>&nbsp;&nbsp;Vector vTemp = ov.buildVector();<BR>&nbsp;&nbsp;ov.print("vTemp0",vTemp);<BR>&nbsp;&nbsp;Vector vResult = ov.insert(vTemp,2,"添加的数据");<BR>&nbsp;&nbsp;ov.print("vResult",vResult);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector vResultup = ov.updata(vResult,2,"修改的数据");<BR>&nbsp;&nbsp;ov.print("vResultmp",vResultup);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector vResultnow = ov.delete(vResultup,2);<BR>&nbsp;&nbsp;ov.print("vResultnow",vResultnow);<BR>&nbsp;}<BR>}<BR></FONT></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]005、哈希表(Hashtable)和枚举器</STRONG></FONT></TD></TR>
<TR>
<TD><FONT color=#000000>public class RoleRight<BR>&nbsp;{<BR>&nbsp;private static Hashtable rightList = new Hashtable();<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：初始化数据<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp;public void init()<BR>&nbsp;{<BR>&nbsp;&nbsp;&nbsp; String[] accRoleList = {"admin","satrap","manager","user","guest"};<BR>&nbsp;&nbsp;&nbsp; String[] rightCodeList = {"10001","10011","10021","20011","24011"};<BR>&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;accRoleList.length;i++)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rightList.put(accRoleList[i],rightCodeList[i]);<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：获取角色权限代码<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;返回类型：String 权限代码<BR>&nbsp;*/<BR>&nbsp;public String getRight(String accRole)<BR>&nbsp;{<BR>&nbsp;&nbsp;&nbsp; if(rightList.containsKey(accRole))<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (String)rightList.get(accRole);<BR>&nbsp;&nbsp;&nbsp; else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return null;<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：添加角色和代码信息<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;输入参数：String rightCode 角色权限代码 <BR>&nbsp;*&lt;br&gt;返回类型：void （无）<BR>&nbsp;*/<BR>&nbsp;public void insert(String accRole,String rightCode)<BR>&nbsp;{<BR>&nbsp;&nbsp; rightList.put(accRole,rightCode);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：删除角色权限<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;返回类型：void（无）<BR>&nbsp;*/<BR>&nbsp;public void delete(String accRole)<BR>&nbsp;{<BR>&nbsp;&nbsp; if(rightList.containsKey(accRole))<BR>&nbsp;&nbsp;&nbsp;&nbsp; rightList.remove(accRole);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：修改角色权限代码<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;输入参数：String rightCode 角色权限代码 <BR>&nbsp;*&lt;br&gt;返回类型：void（无）<BR>&nbsp;*/<BR>&nbsp;public void update(String accRole,String rightCode)<BR>&nbsp;{<BR>&nbsp;&nbsp; //this.delete(accRole);<BR>&nbsp;&nbsp; this.insert(accRole,rightCode);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：打印哈希表中角色和代码对应表<BR>&nbsp;*&lt;br&gt;输入参数：无<BR>&nbsp;*&lt;br&gt;返回类型：无<BR>&nbsp;*/<BR>&nbsp;public void print()<BR>&nbsp;{<BR>&nbsp;&nbsp;Enumeration RLKey = rightList.keys();<BR>&nbsp;&nbsp;while(RLKey.hasMoreElements())<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;String accRole = RLKey.nextElement().toString();<BR>&nbsp;&nbsp;&nbsp;print(accRole+"="+this.getRight(accRole));<BR>&nbsp;&nbsp;}<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：打印信息（过载）<BR>&nbsp;*&lt;br&gt;输入参数：Object oPara 打印的信息内容<BR>&nbsp;*&lt;br&gt;返回类型：无<BR>&nbsp;*/<BR>&nbsp;public void print(Object oPara)<BR>&nbsp;{<BR>&nbsp;&nbsp;System.out.println(oPara);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：主方法，<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp;public static void main(String[] args)<BR>&nbsp;{<BR>&nbsp;&nbsp;RoleRight RR = new RoleRight();<BR>&nbsp;&nbsp;RR.init();<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;&nbsp;RR.print("___________________________");<BR>&nbsp;&nbsp;RR.insert("presider","10110");<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;&nbsp;RR.print("___________________________");<BR>&nbsp;&nbsp;RR.update("presider","10100");<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;&nbsp;RR.print("___________________________");<BR>&nbsp;&nbsp;RR.delete("presider");<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;} <BR>&nbsp;}//end:)~</FONT></TD></TR></TBODY></TABLE><BR><FONT color=#000000>public class RoleRight<BR>&nbsp;{<BR>&nbsp;private static Hashtable rightList = new Hashtable();<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：初始化数据<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp;public void init()<BR>&nbsp;{<BR>&nbsp;&nbsp;&nbsp; String[] accRoleList = {"admin","satrap","manager","user","guest"};<BR>&nbsp;&nbsp;&nbsp; String[] rightCodeList = {"10001","10011","10021","20011","24011"};<BR>&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;accRoleList.length;i++)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rightList.put(accRoleList[i],rightCodeList[i]);<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：获取角色权限代码<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;返回类型：String 权限代码<BR>&nbsp;*/<BR>&nbsp;public String getRight(String accRole)<BR>&nbsp;{<BR>&nbsp;&nbsp;&nbsp; if(rightList.containsKey(accRole))<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (String)rightList.get(accRole);<BR>&nbsp;&nbsp;&nbsp; else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return null;<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：添加角色和代码信息<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;输入参数：String rightCode 角色权限代码 <BR>&nbsp;*&lt;br&gt;返回类型：void （无）<BR>&nbsp;*/<BR>&nbsp;public void insert(String accRole,String rightCode)<BR>&nbsp;{<BR>&nbsp;&nbsp; rightList.put(accRole,rightCode);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：删除角色权限<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;返回类型：void（无）<BR>&nbsp;*/<BR>&nbsp;public void delete(String accRole)<BR>&nbsp;{<BR>&nbsp;&nbsp; if(rightList.containsKey(accRole))<BR>&nbsp;&nbsp;&nbsp;&nbsp; rightList.remove(accRole);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：修改角色权限代码<BR>&nbsp;*&lt;br&gt;输入参数：String accRole 角色名称<BR>&nbsp;*&lt;br&gt;输入参数：String rightCode 角色权限代码 <BR>&nbsp;*&lt;br&gt;返回类型：void（无）<BR>&nbsp;*/<BR>&nbsp;public void update(String accRole,String rightCode)<BR>&nbsp;{<BR>&nbsp;&nbsp; //this.delete(accRole);<BR>&nbsp;&nbsp; this.insert(accRole,rightCode);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：打印哈希表中角色和代码对应表<BR>&nbsp;*&lt;br&gt;输入参数：无<BR>&nbsp;*&lt;br&gt;返回类型：无<BR>&nbsp;*/<BR>&nbsp;public void print()<BR>&nbsp;{<BR>&nbsp;&nbsp;Enumeration RLKey = rightList.keys();<BR>&nbsp;&nbsp;while(RLKey.hasMoreElements())<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;String accRole = RLKey.nextElement().toString();<BR>&nbsp;&nbsp;&nbsp;print(accRole+"="+this.getRight(accRole));<BR>&nbsp;&nbsp;}<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：打印信息（过载）<BR>&nbsp;*&lt;br&gt;输入参数：Object oPara 打印的信息内容<BR>&nbsp;*&lt;br&gt;返回类型：无<BR>&nbsp;*/<BR>&nbsp;public void print(Object oPara)<BR>&nbsp;{<BR>&nbsp;&nbsp;System.out.println(oPara);<BR>&nbsp;}<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：主方法，<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp;public static void main(String[] args)<BR>&nbsp;{<BR>&nbsp;&nbsp;RoleRight RR = new RoleRight();<BR>&nbsp;&nbsp;RR.init();<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;&nbsp;RR.print("___________________________");<BR>&nbsp;&nbsp;RR.insert("presider","10110");<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;&nbsp;RR.print("___________________________");<BR>&nbsp;&nbsp;RR.update("presider","10100");<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;&nbsp;RR.print("___________________________");<BR>&nbsp;&nbsp;RR.delete("presider");<BR>&nbsp;&nbsp;RR.print();<BR>&nbsp;} <BR>&nbsp;}//end:)~</FONT></P></TD></TR></TBODY></TABLE>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]006、类的继承（java100例）</STRONG></FONT></TD></TR>
<TR>
<TD>
<P><FONT color=#000000>class tree<BR>{<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：树的树根<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public void root()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; String sSite = "土壤中";<BR>&nbsp;&nbsp;&nbsp; String sFunction = "吸收养份";<BR>&nbsp;&nbsp;&nbsp; print("位置："+sSite);<BR>&nbsp;&nbsp;&nbsp; print("功能："+sFunction);<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：树的树干<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public void bolo()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; String sSite = "地面";<BR>&nbsp;&nbsp;&nbsp; String sFunction = "传递养份";<BR>&nbsp;&nbsp;&nbsp; print("位置："+sSite);<BR>&nbsp;&nbsp;&nbsp; print("功能："+sFunction);<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：树的树枝<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public void branch()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; String sSite = "树干上";<BR>&nbsp;&nbsp;&nbsp; String sFunction = "传递养份";<BR>&nbsp;&nbsp;&nbsp; print("位置："+sSite);<BR>&nbsp;&nbsp;&nbsp; print("功能："+sFunction);<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：树的叶子<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public void leaf()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; String sSite = "树梢";<BR>&nbsp;&nbsp;&nbsp; String sFunction = "光合作用";<BR>&nbsp;&nbsp;&nbsp; String sColor = "绿色";<BR>&nbsp;&nbsp;&nbsp; print("位置："+sSite);<BR>&nbsp;&nbsp;&nbsp; print("功能："+sFunction);<BR>&nbsp;&nbsp;&nbsp; print("颜色："+sColor);<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：显示信息<BR>&nbsp;*&lt;br&gt;输入参数：Object oPara 显示的信息<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public void print(Object oPara)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; System.out.println(oPara);<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：主方法<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public static void&nbsp; main(String[] arges)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; tree t = new tree();<BR>&nbsp;&nbsp;&nbsp; t.print("描述一棵树：");<BR>&nbsp;&nbsp;&nbsp; t.print("树根：");<BR>&nbsp;&nbsp;&nbsp; t.root();<BR>&nbsp;&nbsp;&nbsp; t.print("树干：");<BR>&nbsp;&nbsp;&nbsp; t.bolo();<BR>&nbsp;&nbsp;&nbsp; t.print("树枝：");<BR>&nbsp;&nbsp;&nbsp; t.branch();<BR>&nbsp;&nbsp;&nbsp; t.print("树叶：");<BR>&nbsp;&nbsp;&nbsp; t.leaf();<BR>&nbsp; }<BR>}<BR>/**<BR>&nbsp;* &lt;p&gt;Title: 柳树参数&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Description: 描述柳树的参数&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Copyright: Copyright (c) 2003&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Filename: &lt;/p&gt;<BR>&nbsp;* @author 杜江<BR>&nbsp;* @version 1.0<BR>&nbsp;*/<BR>class osier extends tree<BR>{<BR>&nbsp;/**<BR>&nbsp;*&lt;br&gt;方法说明：过载树的树叶<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public void leaf()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; super.leaf();<BR>&nbsp;&nbsp;&nbsp; String sShape = "长形";<BR>&nbsp;&nbsp;&nbsp; super.print("形状："+sShape);<BR>&nbsp; }<BR>&nbsp; /**<BR>&nbsp;*&lt;br&gt;方法说明：扩展树的花<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public void flower()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; print("哈哈，柳树没有花！！");<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：主方法<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public static void&nbsp; main(String[] args)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; osier o = new osier();<BR>&nbsp;&nbsp;&nbsp; o.print("柳树树根：");<BR>&nbsp;&nbsp;&nbsp; o.root();<BR>&nbsp;&nbsp;&nbsp; o.print("柳树树干：");<BR>&nbsp;&nbsp;&nbsp; o.bolo();<BR>&nbsp;&nbsp;&nbsp; o.print("柳树树枝：");<BR>&nbsp;&nbsp;&nbsp; o.branch();<BR>&nbsp;&nbsp;&nbsp; o.print("柳树树叶：");<BR>&nbsp;&nbsp;&nbsp; o.leaf();<BR>&nbsp;&nbsp;&nbsp; o.print("柳树花：");<BR>&nbsp;&nbsp;&nbsp; o.flower();<BR>&nbsp; }<BR>}</FONT></P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]007、接口和抽象类（java100例）</STRONG></FONT></TD></TR>
<TR>
<TD>
<P><FONT color=#000000>//接口 <BR>interface player<BR>{<BR>&nbsp;int flag = 1;<BR>&nbsp;void play();//播放<BR>&nbsp;void pause();//暂停<BR>&nbsp;void stop();//停止<BR>}//end :)</FONT></P>
<P><FONT color=#000000>//抽象类<BR>abstract class playing<BR>{<BR>&nbsp;public void display(Object oPara)<BR>&nbsp;{<BR>&nbsp;&nbsp; System.out.println(oPara);&nbsp; <BR>&nbsp;}<BR>&nbsp;abstract void winRun();<BR>}//end :)</FONT></P>
<P><FONT color=#000000>//继承了playing抽象类和实现类player接口<BR>public class newPlay extends playing implements player<BR>{<BR>&nbsp; public void play()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; display("newPlay.play()");//这里只是演示，去掉了代码。<BR>&nbsp; }<BR>&nbsp; public void pause()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp; display("newPlay.pause()");//这里只是演示，去掉了代码。<BR>&nbsp; }<BR>&nbsp; public void stop()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; display("newPlay.stop()");//这里只是演示，去掉了代码。<BR>&nbsp; }<BR>&nbsp; void winRun()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; display("newPlay.winRun()");//这里只是演示，去掉了代码。<BR>&nbsp; }<BR>&nbsp; public static void main(String[] args)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; newPlay p = new newPlay();<BR>&nbsp;&nbsp;&nbsp; p.play();<BR>&nbsp;&nbsp;&nbsp; p.pause();<BR>&nbsp;&nbsp;&nbsp; p.stop();<BR>&nbsp;&nbsp;&nbsp; p.winRun();<BR>&nbsp; }<BR>}//end </FONT></P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]008、类的标识和访问控制（java100例）</STRONG></FONT></TD></TR>
<TR>
<TD>
<P><FONT color=#000000>package e8.com;<BR>/**<BR>&nbsp;* &lt;p&gt;Title: 标识符&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Description: 演示标识符对类的访问控制&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Copyright: Copyright (c) 2003&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Filename: &lt;/p&gt;<BR>&nbsp;* @version 1.0<BR>&nbsp;*/<BR>public class classDemo1<BR>{<BR>//公有方法<BR>&nbsp;public void mechod1()<BR>&nbsp;{<BR>&nbsp;&nbsp; System.out.println("这是一个公有的方法！任何类都可以访问。");<BR>&nbsp;}<BR>//授保护的方法<BR>&nbsp;protected void mechod2()<BR>&nbsp;{<BR>&nbsp;&nbsp; System.out.println("这是一个受到保护的方法！只有子类可以访问。");<BR>&nbsp;}<BR>//私有的方法<BR>&nbsp;private void mechod3()<BR>&nbsp;{<BR>&nbsp;&nbsp; System.out.println("这是一个私有的方法！只有类本身才可以访问。");<BR>&nbsp;}<BR>&nbsp;public static void main(String[] args){<BR>&nbsp;&nbsp; classDemo1 d = new classDemo1();<BR>&nbsp;&nbsp; d.mechod1();<BR>&nbsp;&nbsp; d.mechod2();<BR>&nbsp;&nbsp; d.mechod3();<BR>&nbsp;}&nbsp; <BR>}//end&nbsp;</FONT></P>
<P><FONT color=#000000>package e8.com;<BR>/**<BR>&nbsp;* &lt;p&gt;Title: 标识符&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Description: 演示标识符对类的访问控制&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Copyright: Copyright (c) 2003&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Filename: &lt;/p&gt;<BR>&nbsp;* @version 1.0<BR>&nbsp;*/<BR>public class classPlay<BR>{<BR>&nbsp; public static void main(String[] args){<BR>&nbsp;&nbsp;&nbsp; classDemo1 d = new classDemo1();<BR>&nbsp;&nbsp;&nbsp; d.mechod1();<BR>&nbsp;&nbsp;&nbsp; d.mechod2();<BR>&nbsp;&nbsp;&nbsp; d.mechod3();<BR>&nbsp; }<BR>}</FONT></P>
<P><FONT color=#000000>&nbsp;package e8.net;</FONT></P>
<P><FONT color=#000000>import e8.com.*;<BR>/**<BR>&nbsp;* &lt;p&gt;Title: 标识符&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Description: 演示标识符对类的访问控制&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Copyright: Copyright (c) 2003&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Filename: &lt;/p&gt;<BR>&nbsp;* @version 1.0<BR>&nbsp;*/<BR>public class classPlay<BR>{<BR>&nbsp; public static void main(String[] args){<BR>&nbsp;&nbsp;&nbsp; classDemo1 d = new classDemo1();<BR>&nbsp;&nbsp;&nbsp; d.mechod1();<BR>&nbsp;&nbsp;&nbsp; d.mechod2();<BR>&nbsp;&nbsp;&nbsp; d.mechod3();<BR>&nbsp; }<BR>}</FONT></P></TD></TR></TBODY></TABLE></P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]009、异常的捕获和实现自己的异常类（java100例）</STRONG></FONT></TD></TR>
<TR>
<TD>
<P><FONT color=#000000>/**<BR>&nbsp;* &lt;p&gt;Title: 捕获异常和实现自己的异常类&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Description: 通过继承Exception类来实现自己的异常类。并使用try－catch来捕获这个异常。&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Copyright: Copyright (c) 2003&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Filename: &lt;/p&gt;<BR>&nbsp;* @version 1.0<BR>&nbsp;*/<BR>class MyException extends Exception {<BR>&nbsp; public MyException() {}<BR>&nbsp; public MyException(String msg) {<BR>&nbsp;&nbsp;&nbsp; super(msg);<BR>&nbsp; }<BR>&nbsp; public MyException(String msg, int x) {<BR>&nbsp;&nbsp;&nbsp; super(msg);<BR>&nbsp;&nbsp;&nbsp; i = x;<BR>&nbsp; }<BR>&nbsp; public int val() { return i; }<BR>&nbsp; private int i;<BR>}</FONT></P>
<P><FONT color=#000000>public class DemoException {<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：使用MyException类中默认的构造器<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public static void a() throws MyException {<BR>&nbsp;&nbsp;&nbsp; System.out.println(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Throwing MyException from a()");<BR>&nbsp;&nbsp;&nbsp; throw new MyException();<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：使用MyException类中带信息的构造器<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public static void b() throws MyException {<BR>&nbsp;&nbsp;&nbsp; System.out.println(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Throwing MyException from b()");<BR>&nbsp;&nbsp;&nbsp; throw new MyException("Originated in b()");<BR>&nbsp; }<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：使用了MyException中有编码的构造器<BR>&nbsp;*&lt;br&gt;输入参数：<BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public static void c() throws MyException {<BR>&nbsp;&nbsp;&nbsp; System.out.println(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Throwing MyException from c()");<BR>&nbsp;&nbsp;&nbsp; throw new MyException(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Originated in c()", 47);<BR>&nbsp; }<BR>&nbsp; public static void main(String[] args) {<BR>&nbsp;&nbsp;&nbsp; try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a();<BR>&nbsp;&nbsp;&nbsp; } catch(MyException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.getMessage();<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b();<BR>&nbsp;&nbsp;&nbsp; } catch(MyException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.toString();<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c();<BR>&nbsp;&nbsp;&nbsp; } catch(MyException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("error code: " + e.val());<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp; }<BR>} //end :)</FONT></P></TD></TR></TBODY></TABLE></P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT color=#000000><STRONG>[JAVA100例]010、创建一个窗体（java100例）</STRONG></FONT></TD></TR>
<TR>
<TD>
<P><FONT color=#000000>import javax.swing.*;<BR>import java.awt.*;</FONT></P>
<P><FONT color=#000000>/**<BR>&nbsp;* &lt;p&gt;Title: 创建自己的窗体&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Description: &lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Copyright: Copyright (c) 2003&lt;/p&gt;<BR>&nbsp;* &lt;p&gt;Filename: mainFrame.java&lt;/p&gt;<BR>&nbsp;* @version 1.0<BR>&nbsp;*/<BR>public class mainFrame extends JFrame<BR>{<BR>/**<BR>&nbsp;*&lt;br&gt;方法说明：构造器，通过传递参数来完成窗体的绘制。<BR>&nbsp;*&lt;br&gt;输入参数：String sTitle 窗体标题<BR>&nbsp;*&lt;br&gt;输入参数：int iWidth 窗体的宽度<BR>&nbsp;*&lt;br&gt;输入参数：int iHeight 窗体的高度 <BR>&nbsp;*&lt;br&gt;返回类型：<BR>&nbsp;*/<BR>&nbsp; public mainFrame(String sTitle,int iWidth,int iHeight)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();//获取屏幕尺寸<BR>&nbsp;&nbsp;&nbsp; ImageIcon ii = new ImageIcon("images/middle.gif");<BR>&nbsp;&nbsp;&nbsp; setTitle(sTitle);//设置窗体标题<BR>&nbsp;&nbsp;&nbsp; setIconImage(ii.getImage());//设置窗体的图标<BR>&nbsp;&nbsp;&nbsp; setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);//设置但关闭窗体时退出程序<BR>&nbsp;&nbsp;&nbsp; setSize(iWidth,iHeight);//设置窗体大小<BR>&nbsp;&nbsp;&nbsp; int w = getSize().width;//获取窗体宽度<BR>&nbsp;&nbsp;&nbsp; int h = getSize().height;//获取窗体高度<BR>&nbsp;&nbsp;&nbsp; System.out.println("窗体宽："+w+" 窗体高："+h);<BR>&nbsp;&nbsp;&nbsp; int x = (dim.width-w)/2;<BR>&nbsp;&nbsp;&nbsp; int y = (dim.height-h)/2;<BR>&nbsp;&nbsp;&nbsp; setLocation(x,y);//将窗体移到屏幕中间<BR>&nbsp;&nbsp;&nbsp; setVisible(true);//显示窗体<BR>&nbsp; }<BR>&nbsp; public static void main(String[] args)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; JFrame.setDefaultLookAndFeelDecorated(true);//使用最新的SWING外观<BR>&nbsp;&nbsp;&nbsp; mainFrame mF = new mainFrame("main Frame Demo",400,300);<BR>&nbsp; }<BR>}</FONT></P></TD></TR></TBODY></TABLE></P></TD></TR></TBODY></TABLE></P></TD></TR></TBODY></TABLE></P><img src ="http://www.blogjava.net/Apple/aggbug/6695.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 21:10 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6695.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】java对象序列化</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6694.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 12:57:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6694.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6694.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6694.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6694.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6694.html</trackback:ping><description><![CDATA[<P>序列化概述：</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;简单来说序列化就是一种用来处理对象流的机制，所谓对象流也就是将对象的内容进行流化，流的概念这里不用多说(就是I/O)，我们可以对流化后的对象进行读写操作，也可将流化后的对象传输于网络之间(注：要想将对象传输于网络必须进行流化)！在对对象流进行读写操作时会引发一些问题，而序列化机制正是用来解决这些问题的！</P>
<P>问题的引出：</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如上所述，读写对象会有什么问题呢？比如：我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗？别急，<STRONG><FONT color=#800080>其中一个最大的问题就是对象引用！</FONT></STRONG>举个例子来说：假如我有两个类，分别是A和B，B类中含有一个指向A类对象的引用，现在我们对两个类进行实例化{ A a = new A(); B b = new B(); }，这时在内存中实际上分配了两个空间，一个存储对象a，一个存储对象b，接下来我们想将它们写入到磁盘的一个文件中去，就在写入文件时出现了问题！因为对象b包含对对象a的引用，所以系统会自动的将a的数据复制一份到b中，这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时，内存分配了三个空间，而对象a同时在内存中存在两份，想一想后果吧，如果我想修改对象a的数据的话，那不是还要搜索它的每一份拷贝来达到对象数据的一致性，这不是我们所希望的！</P>
<P>以下序列化机制的解决方案：</P>
<P>1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)</P>
<P>2.当要保存一个对象时，先检查该对象是否被保存了。</P>
<P>3.如果以前保存过，只需写入"与已经保存的具有序列号x的对象相同"的标记，否则，保存该对象</P>
<P>通过以上的步骤序列化机制解决了对象引用的问题！</P>
<P>序列化的实现：</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;将需要被序列化的类实现Serializable接口，该接口没有需要实现的方法，implements Serializable只是为了标注该对象是可被序列化的，然后使用一个输出流(如：FileOutputStream)来构造一个ObjectOutputStream(对象流)对象，接着，使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态)，要恢复的话则用输入流。</P>
<P>例子：</P>
<P>import java.io.*;</P>
<P>public class Test <BR>{<BR>&nbsp;&nbsp;&nbsp;&nbsp;public static void main(String[] args) <BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Employee harry = new Employee("Harry Hacker", 50000);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Manager manager1 = new Manager("Tony Tester", 80000);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;manager1.setSecretary(harry);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Employee[] staff = new Employee[2];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;staff[0] = harry;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;staff[1] = manager1;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#ff0000>ObjectOutputStream out = new ObjectOutputStream(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new FileOutputStream("employee.dat"));<FONT color=#000000> //输出到文件employee.dat</FONT><BR></FONT>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.writeObject(staff);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.close();<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;&nbsp;ObjectInputStream in = new ObjectInputStream(<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new FileInputStream("employee.dat"));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Employee[] newStaff = (Employee[])in.readObject();//重新构造，恢复对象<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;in.close();<BR>&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;&nbsp; *通过harry对象来加薪<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *将在secretary上反映出来<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;&nbsp;newStaff[0].raiseSalary(10);<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;&nbsp;for (int i = 0; i &lt; newStaff.length; i++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(newStaff<EM>);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>}</EM></P>
<P><EM>class Employee implements Serializable<BR>{<BR>&nbsp;&nbsp;&nbsp;&nbsp;public Employee(String n, double s)<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name = n;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;salary = s;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;/**<BR>&nbsp;&nbsp;&nbsp;&nbsp; *加薪水<BR>&nbsp;&nbsp;&nbsp;&nbsp; */<BR>&nbsp;&nbsp;&nbsp;&nbsp;public void raiseSalary(double byPercent)<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;double raise = salary * byPercent / 100;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;salary += raise;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;public String toString()<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return getClass().getName()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ "[name = "+ name<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ ",salary = "+ salary<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ "]";<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;private String name;<BR>&nbsp;&nbsp;&nbsp;&nbsp;private double salary;<BR>}</EM></P>
<P><EM>class Manager extends Employee<BR>{<BR>&nbsp;&nbsp;&nbsp;&nbsp;public Manager(String n, double s)<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(n, s);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;secretary = null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;/**<BR>&nbsp;&nbsp;&nbsp;&nbsp; *设置秘书<BR>&nbsp;&nbsp;&nbsp;&nbsp; */<BR>&nbsp;&nbsp;&nbsp;&nbsp;public void setSecretary(Employee s)<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;secretary = s;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;public String toString()<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return super.toString()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ "[secretary = "+ secretary<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ "]";<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;//secretary代表秘书<BR>&nbsp;&nbsp;&nbsp;&nbsp;private Employee secretary; <BR>}</EM></P>
<P>修改默认的序列化机制：&nbsp;&nbsp; </P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在序列化的过程中，有些数据字段我们不想将其序列化，对于此类字段我们只需要在定义时给它加上<FONT color=#ff0000>transient</FONT>关键字即可，对于transient字段序列化机制会跳过不会将其写入文件，当然也不可被恢复。但有时我们想将某一字段序列化，但它在SDK中的定义却是不可序列化的类型，<FONT color=#ff0000>这样的话我们也必须把他标注为transient，</FONT>可是不能写入又怎么恢复呢？好在序列化机制为包含这种特殊问题的类提供了如下的方法定义：</P>
<P><EM>private void readObject(ObjectInputStream in) throws </EM></P>
<P><EM>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IOException, ClassNotFoundException;</EM></P>
<P><EM>private void writeObject(ObjectOutputStream out) throws</EM></P>
<P><EM>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IOException;</EM></P>
<P><EM>(注：这些方法定义时必须是私有的，因为不需要你显示调用，序列化机制会自动调用的)</EM></P>
<P>使用以上方法我们可以手动对那些你又想序列化又不可以被序列化的数据字段进行写出和读入操作。</P>
<P><EM>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#800080>下面是一个典型的例子，java.awt.geom包中的Point2D.Double类就是不可序列化的，因为该类没有实现Serializable接口</FONT>，在我的例子中将把它当作LabeledPoint类中的一个数据字段，并演示如何将其序列化！</EM></P>
<P><EM>import java.io.*;<BR>import java.awt.geom.*;</EM></P>
<P><EM>public class TransientTest <BR>{<BR>&nbsp;&nbsp;&nbsp;&nbsp;public static void main(String[] args) <BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(label);//写入前<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ObjectOutputStream out = new ObjectOutputStream(new<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileOutputStream("Label.txt"));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.writeObject(label);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.close();<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;&nbsp;System.out.println(label);//写入后<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;&nbsp;ObjectInputStream in = new ObjectInputStream(new<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileInputStream("Label.txt"));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LabeledPoint label1 = (LabeledPoint)in.readObject();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;in.close();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(label1);//读出并加1.0后<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (Exception e)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>}</EM></P>
<P><EM>class LabeledPoint implements Serializable<BR>{<BR>&nbsp;&nbsp;&nbsp;&nbsp;public LabeledPoint(String str, double x, double y)<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;label = str;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;point = new Point2D.Double(x, y);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;private void writeObject(ObjectOutputStream out) throws IOException<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/**<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *必须通过调用defaultWriteObject()方法来写入<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *对象的描述以及那些可以被序列化的字段<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.defaultWriteObject();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.writeDouble(point.getX());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;out.writeDouble(point.getY());<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;private void readObject(ObjectInputStream in)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throws IOException, ClassNotFoundException<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/**<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *必须调用defaultReadObject()方法<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;in.defaultReadObject();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;double x = in.readDouble() + 1.0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;double y = in.readDouble() + 1.0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;point = new Point2D.Double(x, y);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;public String toString()<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return getClass().getName()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ "[label = "+ label<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ ", point.getX() = "+ point.getX()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ ", point.getY() = "+ point.getY()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ "]";<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;&nbsp;String label;<BR>&nbsp;&nbsp;&nbsp;&nbsp;transient private Point2D.Double point;<BR>}<BR></EM></P><img src ="http://www.blogjava.net/Apple/aggbug/6694.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 20:57 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6694.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】java collection framwork</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6682.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 07:54:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6682.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6682.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6682.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6682.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6682.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Java Collection FrameworkWe have tried you to make a walk through the Collection Framework. The Collection Framework provides a well-designed set if interface and classes for sorting and manipulating ...&nbsp;&nbsp;<a href='http://www.blogjava.net/Apple/archive/2005/06/24/6682.html'>阅读全文</a><img src ="http://www.blogjava.net/Apple/aggbug/6682.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 15:54 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6682.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载 王森】程序设计师真情忏悔录</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6681.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 07:52:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6681.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6681.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6681.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6681.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6681.html</trackback:ping><description><![CDATA[<P>不久前,蔡学庸先生发表了两篇分别叫做"程序与香鸡排"，"再论香鸡排"的文章，受到的回响之大，据说连作者本人都接到该文的转寄。足以见得该文的内容道出了台湾程序设计师心里最深处的想法。所以,本文想针对台湾程序设计师来做讨论，看看身为一个程序设计师，究竟犯了什么过错，导致大家宁愿卖鸡排也不愿再继续写程序。璩美凤小姐为自己惊动台湾社会的行为写了真情忏悔录,我们也该真诚地检讨一下自己。 <BR><BR>本文: <BR>在台湾，大部分的人都有个认知，就是"搞硬件的比搞软件的要好赚"，"做SA/SD比coding地位要高"。那些做ID Design的家伙待的公司所发的股票，永远比搞Software Design的人待的公司发的还多，股价也高出好几倍。搞SA/SD的人，在公司地位好象也比单纯写程序的人高。于是我的父亲有一次问我:"是不是搞硬件的人智商比较高?"，"是不是搞分析设计的人比较有地位?" <BR><BR>事实上，在学生时期我就认识不少人，有的人因为程序写不过别人，所以转换跑道专攻硬件;也有人觉得每天面对示波器和逻辑分析仪让他们头痛，工程数学也学不过人家，所以转行搞软件。到了实际上面对许多前来接受教育训练的学员之后，发现有人是因为写了十几年BIOS和driver，觉得乏味而没有前途，所以想试看看应用程序的开发;有人则是因为受不了客户每天改需求，而且写IOS和driver的人好象比较少，价格比较高，因此想学习如何撰写系统软件。询问那些上高阶课程的朋友，有的人发现自己只会胡做SA/SD唬烂老板和客户，所以希望重新打好基础，从基本的coding做起;有的人则因为写程序写太久，觉得应该往上走向SA/SD的路子，毕竟老了程序就写不动了。这个世界上什么样的人都有，绝对不能以小羡大。就很像搞政治的老是骗我们台湾只分成外省人和本省人;搞帮派的人告诉我们警察是带着枪的流氓。实际上，每个族群大家在没有被分化之前都相处的和乐融融，而虽然有警察掳勒索，上班时间跑去聚赌,但是也有开我们罚单时酌情轻罚的警察，和保护我们身家性命的警察。 <BR><BR>即使如此，身为一个程序设计师还是要问，那为什么写程序的好象地位就是比较低? 笔者认为，这是进入门槛的问题。会做相同事情的人多了，就会削弱那样技能的价值。就很像考过MCSE/MCSD证照的人，通常比起考过SCJP/SCJD证照的人要来的没价值，前者因为考过的人实在太多，而后者考过的人少，仅是如此而已。但是绝不是代表考过MCSE的人，素质就比考过SCJP的人要差，也不代表考过SCJP的人，实力真的比较高段。相信求学的时候您一定遇过一种人，他实力也不怎样样，可是考试的时候他x的就是考的比我们高，怎么我们心里就是不服，可是大多数老师偏偏就是比较喜欢分数高的人。因此，满坑满谷的人自称会写程序，老板不愁找不到可以做project的人，你要求的薪水和价格很高? 没关系，找个学校刚毕业的小子，写的比你快，写的比你好，用的技术比你新，更重要的，价格比你这个老屁股便宜一半。所以程序设计师在雇主心中的地位日益低落，自然有迹可循。 <BR><BR>"那么，为什么写程序的进入门槛比较低?" <BR><BR>在各位信息从业人员的身边，非科班出生的比率非常高(当然没还卖鸡排的高)，科班出生的学生，搞不好在老板眼中的地位，还比不上一个半路出家的自学者。那位自学者会ASP、JSP、PHP、C++、C+、Java、Linux、Windows、Oracle...等，十八般武艺样样精通。而我们科班出生的学生，只会离散数学、数据结构和算法，Visual Basic搞不好没有用的没人家熟，网站的架设搞不好也没人家熟，更何况科班出生的学生，他的基础学问搞不好根基也不扎实(问看看您身旁的科班学生)，难怪最常见的就是很多非科班的"高手"看不起科班的学生，觉得他们无三小路用，连个Windows操作都比他们差，甚至有的还大言不惭的说:"我这辈子写程序从没用过书上的数据结构和演算法，读那些干啥?"。 长期下来，科班的学生也开始质疑自己的所学，甚至考虑转行，当然，也有曲高和寡，孤芳自赏之人。 总之，没办法受到老板垂青，薪水永远那么少，想卖鸡排的念头就开始萌生。 <BR><BR>之所以会有这种情况，笔者个人认为有两种主要原因，一是技术的流通性，二是台湾软件市场的需求。为什么技术的流通性可以产生大量半路出家的非科班生? 这都要感谢台湾大量作者，写出大量的入门书籍，大幅减低的进入程序设计领域的门槛。即使在各位眼中，繁体中文的烂书很多，而且非常。但是，大家眼中的烂书，常常是再版多次的入门书，对某些人认为是垃圾的东西，常常是另外一批人进入信息业的最佳踏脚石。不信各位到书店看看，是"24小时学C++"、"快快乐乐学Java"的书比较多，还是"轻轻松松学微分方程式"、"21天学复变"、"电子学不求人"的书多? 两者的差距是 N:0。是微分方程序和复变比++,Java难学吗?笔者两个都学过，至少我可以发誓，就一个有基本学习能力的人来说，难度是一样的，学成所需要的时间和功夫也是一样的。再看我们的公开讨论区(BBS或Forum)，我们可以看到大量的软件技术讨论区，却看不到硬件技术讨论区(喔,请别跟笔者说那些每天问哪家主机板比较好，哪台烧录器较稳定、或者说CPU时脉越高速度就越快越好，或是说x86的Out-Of-Order Executon是"故障执行" 这一类浑话的讨论区和我说的硬件技术讨论区是一样的喔!)书籍和讨论区促进知识的流通,造成了大量的软件人才,更何况,学习设计软件只要一台PC就能写出好用的软件，而学习硬件设计，需要的设备更是天价。 <BR>更重要的一点，就是学软件的人，充分发挥儒家分享的精神，只要不是在BBS上冒充小妹妹想要骗取大哥哥的同情来帮你写程序作业，只要不是很差劲的乱问一通，通常至少会有热心人士愿意留下一个URL，让我们可以找到参考文件，更具热诚的，如新竹师院BBS站Java版的TAHO先生，还擅用很多生动的比喻让初学者体会技术的真义。在这种知识充分流通的环境下，当然产生出非常多的信息从业人员，每买一台PC，上头各种软件的需求是无穷无尽的，每天都有各行各业的人需要各种软件，所以程序设计师的需求有一定的量。难怪有人说:"写程式的人饿不死,但是也不会发大财。" <BR><BR>再来就是台湾软件市场需求所引发的问题.一般我们把软件分成系统软件(System Software)和应用软件(Application Software). 编译器,组译器,除错器,操作系统,驱动程序,都是属于系统软件的一部分. <BR><BR>台湾除了硬件厂商,IC Design House之外,甚少有撰写驱动程序的需求. <BR>前几年Linux被炒的热时,也有许多高手投入操作系统,编译器以及简化标准函式库的研究.而台湾所需求的软件,极大多数是属于应用软件,这些需求的应用软件其中很大比例是和数据库有关系的(其实不只是台湾,其它地方也是约略相同的情形).所以VB,Delphi和PowerBuilder这类IDE大行其道,因为他们容易上手,可以快速开发数据库应用程序,相关入门书籍很多,会的人更多. 因此符合我们前面所说的:"会的人越多,价值就越低"的理论. <BR><BR>但是别忘了,系统软件有部分是偏向应用软件的,比方说开发软件用的JBuildr和Visual Studio;制图用的AutoCAD;一套IC Design用的Xilinx或Altera;做OOA/OOD的Rational Rose和TogetherJ,笔者习惯称这些软件叫做"软件的软件"(Meta Software). <BR><BR>在一般程序设计师眼中,这类软件"理所当然"地认为都是由国外所发展. <BR>这类软件有极高的进入门槛,所以国外这类软件公司的获利率很高,所以该公司的程序设计师可是身价非凡. 要知道,先前在媒体上有报导过,能够让Microsoft买软件来用的公司,只有Rational Rose(当然,不可能只买Rose来用). 如果做IC Design的人没有你的软件就没办法做事,身为一个程序设计师,你会觉得你的身价和地位比做IC Design的人低吗? 如果你做的软体像SoftICE或DriverWorks那样被做硬件的工程师大量地倚赖,你会觉得搞硬件的人比你厉害吗? <BR><BR>问题是,知道该如何设计一套这类软件的人,恐怕才是真正的异数. 而且需要跨领域的专才.要不是许多OpenSource的project(例如:KDeveloper)正在进行,恐怕很少人知道该如何设计一套IDE. 既然会制作这类软件的人在国内少之又少,那么他们的身价高吗? <BR><BR>很抱歉,台湾没有公司发展出足以和国外大厂竞争的开发工具. 举IDE为例,国内早期有家公司开发出一套名为DBtools的产品勉强可以算的上是这类产品, <BR>旗标也曾为它出版过入门手册,可是使用这套软件的人好象...没看过(如果该产品有不错的占有率,请原谅我是井底之蛙),没有市场,公司无法赚钱,即使你是少数能做制作某类型产品的高手,公司最后关门大吉了,您也只能算是"少数能让公司赔钱的程序设计师"罢了.所以我大胆假设, Games Golsing或Anders Hejlsberg如果学李敖一样50年不离开这个小岛,大概早饿死了. <BR><BR>以上的讨论,真正优秀的高手看了之后,或许会产生有时不我予,不得已必须远渡他乡的想法. 程序设计师的价值就如同书的价值一般,国内和国外有不同的看法.国内教科书比较便宜,应用的书比较贵.国外教科书比较贵,应用的书比较便宜. 国外重内容,薄薄一本How Debuger Work可以卖44.99美金.而国内,都是看页数来计价(侯俊杰先生的书有努力在打破这个页数的迷思,也做的蛮成功).有很多原因导致这两种文化之间的差异,但是,市场因素肯定是其中重要的一项. <BR><BR>好了,程序设计师价值低落的原因讨论过了,如果您同意笔者的论点,那么不禁疑惑,大家彼此毫不保留地让知识得以流通,技术得以扩散,本意是好的,结果反到造成自己和别人身价的低落,这样我们是不是从此不再和别人分享我们的心得? <BR><BR>王森:程序设计师真情忏悔录(下) <BR>(2002.03.28) <BR>所以从今天开始,如果你对程序设计有一股热爱,那么我们一起努力,做个真正的程序设计师,而不要做一个杂碎且破坏别人行情的程序设计师.做一个真正写的出有用软件的工程师,不要做一个只会写程序的程序设计师.当然,如"意外的计算机王国 / 联经出版社"一书所言,很多技术都是用来原本没有预料到的地方而大行其道.许多科学研究无法有立即的贡献,但是影响深远,我们也期许有意从事基础研究的科班研究生,认真的作研究,不要老是研究一些别人已经研究过的研究,不要老是冀望骗国科会等研究机构的经费,到了最后计划结案时,才匆匆忙忙交出另外一篇骗更多钱的计划书,或是拿不出台面的研究. <BR><BR>最后,我们反省自己是不是也是一个只会"写程序"的程序设计师呢? 请自行测验底下几个问题,这些问题都不可能有客观的答案,所以每个问题都附上笔者主观的答案,作为笔者自己的反省. <BR><BR>Q1: 你尊重专门技术吗? 换句话说,你认为术业有专攻吗? <BR><BR>当你接受外面的教育训练课程时,你总是崇拜看起来什么都懂的老师? <BR><BR>换句话说,你认为那些遇到课外问题就跟你说他不懂的讲师是烂老师? <BR><BR>你老觉得真正的高手应该精通各门各派的技术,如果你会XML,他不会,你就觉得你比他厉害.你觉得他的履历上写的技能太少,证照太少,所以你认为你比他优秀? <BR><BR>有人老是觉得自己蛮会用MFC开发软件,所以直觉认为那些只喜欢,或是只会用VB的人程度应该不高.问题是,有人用VB的程度是,当他觉得组件不好用,所以自己写程序处理HTTP,因为他懂HTTP协议的运作方式.组件盘里附的浏览器组件太烂,就自己用公认语法不是很顶尖的Basic语言来写parser. <BR><BR>相反的,有人号称会用MFC,但是除了靠Help找出名为Cxxxxx的类别来用,再 <BR><BR>自己补上事件处理的部分之外,其它什么事都做不出来. <BR><BR>有人认为写Java程序应该善用工具,用UltraEdit根本是重新造轮子的行为,所以一开始就学JBuilder的使用,其实他用JBuilder写了老半天GUI程序,哪天回头叫他用文字编辑器写个简单的Frame + Button, 他却写不出来,因为他从没弄懂过Java的事件处理模型. 他只会不断地: 选择组件-&gt;放在容器里头-&gt;调整位置和大小-&gt;调整属性-&gt;按两下-&gt;填写事件处理函式,成为一个名副其实的"程序女工". <BR><BR>有人觉得他精通各家厂商的数据库,所以看不起那些只会下SQL指令或是只会写store procedure的人, 因为这个人精通ODBC, JDBC, ADO, ADO.NET各种程序的写法.问题是,一个精通SQL的专家和只会写SQL指令的人,在数据库表格交互参考,资料量很大的时候,要从中取出我们需要的资料, <BR><BR>所下的指令在效率上是几秒钟和几个小时的差别. SQL也是个专门学问,要能够巧妙的操作它,必须下非常多功夫做研究,而且一研究可能就是十几年才有办法累积丰富的经验. 如果贵公司的项目老是苦于数据库存取的效能不够,你猜老板会花钱找一个有能力彻底改善所有SQL命令之中效能问题的稀有专家,还是再找一个号称他什么都会,结果一点用场也派不上的"数据库女工" ? <BR><BR>我们常常看到某人列出他的履历,好象会很多就是很厉害.但是当我们完全深入一项技术时(喔,我是说你真正下工夫的时候),通常我们会越来越感觉到自己的渺小. <BR><BR>蔡学镛先生就是一个非常尊重专业技术的例子.我们看到他在 <A href="http://www.csdn.net/expert/cxy" target=_blank>www.csdn.net/expert/cxy</A>/ 上写的,他说他只精通 lots of Java APIs.我和学镛聊过三次,有一次,我听他说:"干麻叫我搞Linux,我又不懂Linux!" 如果是你听到这句话,你会不会真的以为他玩起Linux来肯定比你逊色? <BR><BR>笔者突然想起神雕侠侣里头的独孤求败,晚年只会拿树枝和别人比武,可是你拿再厉害的刀剑就是无法打败他.所以,请尊重专业技术,不要以为人家没说他会,你就比他厉害.真正厉害的人很多都不在台面上,而是躲在后面偷偷笑我们呢! 而我们一辈子永远不知道我们被别人偷偷取笑了.中国文化数千年来都是文人相轻的历史,够了,大家尊重专业吧! <BR><BR>Q2: 你觉得算法和数据结构无三小路用,因为你从没使用过? <BR><BR>我们承认"无招胜有招"是内功心法的最高境界,但是在信手拈来之际,后面所代表的是对各家武功路数的彻底了解.由于台湾几乎只有应用软件的开发需求,没有系统软件的需求,所以大多数的程序设计师都是站在"程序女工"的角度看世界,只要有钱,只要有人贩售组件,有什么搞不定的. 但是今天如果你想设计一个XML parser,不懂数据结构和算法可以吗? <BR><BR>好吧! 你说我们不该重新造轮子,我们应该站在巨人的肩膀上看世界,如果什么都自己硬干,世界是会退步的. 那么试问,当你在使用Java提供的Collection Framework时,你了解ArrayList, LinkedList, TreeSet, HashSet之间的差别吗? 你知道他们的优缺点吗? 你知道他的特性吗? 不了解ArrayList和LinkedList的差异,用哪种去写程序执行结果都一样,可是效率差很多. <BR><BR>大多数的人连了解特性都谈不上,更别说很多每天想发展自己的语言,自己的编译器,自己的操作系统的人,没有基础学问的了解,如何去设计一个Collection Framework或STL?你说数据结构和算法没有用,你去做看看现在IDE中普遍有的code insight功能看看?以Borland C++ Builder来说,要在短时间内搜寻所有的标头文件并找出某函数的prototype,如果没有对数据结构和算法有充分了解,一样做的出来,只是产品会卖不出去罢了. <BR><BR>我在课堂上常常举一个scalability的例子给学生看: <BR><BR>我希望写一个1+2+3 ... + 100的程序,大多数的人都是写 <BR><BR>int sum = 0 ; <BR><BR>for(int i = 1 ; i &lt; 101 ; i++) <BR><BR>sum = sum + i ; <BR><BR>而真正受到数学观念熏陶的人会写成 <BR><BR>int sum = 100(100+1) / 2 <BR><BR>前者的复杂度是O(n),后者是O(1),当项数很多时,运算时间是不是差很多? <BR><BR>这些都是我们的教育所产生的问题(当然笔者也是其中一位受害者),老师只叫你写好作业,助教只叫你run出正确的结果,认真一点的还会测试你是不是抄来的. 却从来没告诉你程序中不能只有一个main函式,程序代码不能第一行写到第一千行从不切割成其它子程序. 你的程序代码看起来不堪入目,老师助教从没告诉你,你的.class檔被decompiler反编译之后,长的比你写的还漂亮. <BR><BR>但是从今天开始,我们可以开始认真思考每行程序,不要再做一个杂碎程序设计师. <BR><BR>试想发展MP3算法的人和写WinAmp的人,哪个比较厉害? 你会说都很厉害,可是没有前者就没有后者,前者搞不好还可以坐收权利金,后者只能苦哈哈的赚些小钱或等人购并. 我们停留在崇拜应用程序技巧的阶段,而真正值得崇拜的是那些难得一见的创意. <BR><BR>笔者遇过一个朋友,叫他撰写一个费式数列的小程序,比请他写一个可以浏览数据库表格内容的程序还难.(请不要与我讨论费式数列的小程序没有实用价值的问题,这里讨论的重点不是这个)前者需要稍微动点小脑筋,后者只要会拖拖组件,设定property就搞定. RAD本身不是罪,但是没学好九阳神功就妄想几小时练成乾坤大挪移.最后只会走火入魔而死,彻底变成一个"程序女工". <BR><BR>Q3: 你常常以科班和或非科班自居? <BR><BR>你是科班生,瞧不起非科班生? 因为你是正统? <BR><BR>你是非科班生,瞧不起科班生? 因为你觉得会的东西比科班生的还多. <BR><BR>烂学校会出现好学生,好学校也会有烂学生. <BR><BR>因为比例一样多,所以我们不能以偏概全. <BR><BR>如果仗着受过几年正规教育,自己又从未好好深入学习,就自己为是正统,比较学术的说法这叫做"阳具文化".有些创新的idea是一般制式脑袋的科班学生很难想出来的,因为专家是训练有素的狗. <BR><BR>如果你是学电信的朋友,你发现交换机是一个葬仪社的老板因为生意被别人抢走而发明的,那你会不会气死? <BR><BR>如果自学有成的程序设计师仗着自己会的东西比较多,你说你精通Java的各种技术,你看不起从没写过JSP的科班学生. <BR><BR>但是有人告诉你发展Java的Games Golsing博士是一个正统出生的科班生,知道了这件事情,会不会让你更加尊重幕后认真打拼的科班生? <BR><BR>Q4: 你是学计算器科学的,可是逻辑能力并没有比较好,还常常受骗? <BR><BR>你会被潮流所鼓动吗? 你常常被别人的思考牵着走? <BR><BR>人家鼓吹Linux多好多好,你的脑袋连转都没转过就发愤努力地考Linux认证 ? <BR><BR>------------------------------------------------------------------- <BR><BR>别人把公司里的server全换成Linux,客户端也都改成Linux,公司仍然正常地运作,结果你学了Linux之后,看到电视上BSA同法务部做的广告仍然吓的你冷汗直留. <BR><BR>深怕明天去住套房. <BR><BR>Sun跟你说Java跨平台,你没试过也跟人家说跨平台的优点? <BR><BR>--------------------------------------------------- <BR><BR>Borland已经可以做到一份光盘里同时附上Solaris, Linux, Windows, MacOS X的JBuilder, <BR><BR>你却为了EJB无法deploy到不同公司的Application Server忙的像无头苍蝇. <BR><BR>之前一窝峰人鼓吹XML,结果你盲目追求流行,做出来的东西tag比data还多? <BR><BR>----------------------------------------------------------------- <BR><BR>会用的人彻底了改变了公司里资料交换的流程,而你整天只会SAX来,DOM去的写XML数据库(用XML来储存资料的数据库) <BR><BR>微软的广告告诉你XP和IE将不支持Java,你都还没试过就跟别人嚷嚷Java已死? <BR><BR>-------------------------------------------------------------------- <BR><BR>套句BBS上moga先生的名言:"那我现在在Windows 2000上跑的Java程序是神迹?" <BR><BR>现在一票人每天宣传web service的好处,你连想都没想过就急着想要把公司的旧系统全部改成web service来做,结果浪费一堆钱,糟糕的速度让你每天被客户臭骂 ? <BR><BR>---------------------------------------------------------------------- <BR><BR>Web service当然是美好的前景,但是并非适用于每个角落,目前世界上并不存在完美的solution. <BR><BR>人家说不能写程序一辈子,写程序的人生命周期很短,你也跟着别人开始往SA/SD前进 ? <BR><BR>--------------------------------------------------------------------------- <BR><BR>如果世界上每个工程师都可以经由经验就成为优秀的SA/SD人员,那么理论上咱们应该有些象样的软件产品才对. 有些人写了几十年, <BR><BR>还是一个优秀的程序设计师,你问问他,如果没有遇到糟糕的老板, <BR><BR>糟糕的待遇和糟糕的制度,他愿不愿意写一辈子程序? 我愿意. <BR><BR>顾问告诉你要多用RAD,不该重新造轮子,所以你努力的问how而不问why ? <BR><BR>--------------------------------------------------------------- <BR><BR>结果真正赚到钱的都是那些像JReport做软件组件的软件公司. <BR><BR>微软说J2EE Blueprint的Pet Store,用.NET技术做比用J2EE做还要快许多,然后你就相信了,最近, IBM和Oracle重新加强Java版的Pet Store,让它比.NET版的还要快18%~22%,你又改口说Java比较好. <BR><BR>-------------------------------------------------------------------------------------------- <BR><BR>一个系统在设计的时候有很多考量,有人以扩充性为主,有的以安全性为主,有人以效能为主. <BR><BR>如果没有设计理念,大家程序里头的function全部改成inline就好了,管他编译出来的执行档有多大. <BR><BR>系统只以效能做考量,我们还需要Design Pattern做什么? <BR><BR>那些王八蛋数据和我们选举时的民调一模一样...对一个脑袋清楚的人完全没有参考的价值. <BR><BR>当然,以情感因素来看民调的人例外. <BR><BR>如果你没有经过自己的自主判断就盲目的跟随潮流,那么下次当你看到有人排队买米酒,买蛋塔,抢购卫生纸的时候,请不要投以排队的人们奇怪的眼光. <BR><BR>Q5:你尊重老前辈吗? <BR><BR>我们都相信,世界上唯一不用努力就可以获的东西就是老. <BR><BR>所以吃过的盐巴比你吃过的米还多的人,没有任何值得尊重的. <BR><BR>我们更相信,信息业永远是年轻人出头,而英雄少年也常在心里想: <BR><BR>"李杜诗篇万口传,至今已觉不新鲜,江山代有才人出,各领风骚数百年." <BR><BR>但是我们认真想想,从Apple 2的时代到现在随便一颗CPU都是1 GHz的时代, <BR><BR>计算器的本质有什么改变吗? 不就是一台不断对内存作处理和I/O动作的机器. <BR><BR>你笑那些只会用Fortran或COBOL的老前辈,那你学的Java或C#比起这些老语言又高明到哪里去? 写程序不过是 宣告,循环和函式三大要素.时间久了,产生了一堆新名词,配上一些新的发展理念但本质上没有改变. <BR><BR>如果你是推倒前浪的后浪,当你看到李维先生撰写的 "[长篇] 我的回忆和有趣的故事"或侯捷老师最近两期在Run!PC撰写的"侯捷观点"这些老前辈写的文章,你有把握写出比它们更高明,更有深度的东西吗? <BR><BR>最重要的问题, <BR><BR>Q6:你骗过老板吗? <BR><BR>你在履历上写的十八般武艺样样精通,结果是梧鼠技穷(注:比喻技能虽多，而不能专一),一录取之后什么东西都做不出来,笔者至少听过20个老板跟我讲过这件事情.你以为你的身价比较高,只因为你从事"软件研发"的工作? <BR><BR>然而现实的生活中,"获利"是真正决定成败的关键.除非你的东西帮老板赚了钱. <BR><BR>如果没有,你凭什么要求更多薪水,凭什么要求50张价值数千万元的股票? <BR><BR>如果你写的东西品质很差,bug超多,客户抱怨不断,老板赚不到很多钱, <BR><BR>你还老是在外头痛骂老板不尊重技术人员.如果把行业换成色情行业,那老板不就等于被干洗? <BR><BR>这样看来,程序设计师和詹惠华(黄显洲3P案女主角)干的事情有什么两样? <BR><BR>如果这样的杂碎程序设计师太多了,真正要去卖香鸡排的,不是写程序的人, <BR><BR>而是雇用了这些杂碎的老板才对. 不过很遗憾,真的很多软件公司的老板要改行了,鸡排太多人卖了,我建议某位感同身受老板写篇"程序员的老板与蚵仔煎", <BR><BR>请踊跃投稿. <BR><BR>............全文完 <BR>Sun计算机教育训练中心 王森 (moli@pchome.com.tw) <BR></P><img src ="http://www.blogjava.net/Apple/aggbug/6681.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 15:52 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6681.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】介绍三个集合容器库</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6680.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 07:35:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6680.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6680.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6680.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6680.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6680.html</trackback:ping><description><![CDATA[介绍三个集合容器库<BR>－－－对Java Collections Framework的补充<BR>作者：John Zukowski<BR>原文：http://www.javaworld.com/javaworld/jw-11-2002/jw-1101-collections-p1.html<BR>译者：yahh<BR>MSN: yahh2008@hotmail.com<BR>摘要<BR>自Java 2 Platform 诞生以来，Java Collections Framework 一直是Java核心类库的标准组成部分。如今，会发现起了些变化，我们有了其它的选择，譬如Jakarta Commons Collections 和 Recursion Software 公司最新的Java Generic Library (JGL)。它们打破了Java Collections Framework 的垄断。（November 1, 2002）<BR><BR>在 Java 诞生之初，Java核心类库仅支持几种数据结构，包括数组（arrays）、向量（vectors） 和 hash tables（键－值对）。而不支持另一些重要的数据结构，比如，平衡数（balanced trees）、原始对象袋（generic bags of objects）和优先队列（priority queues），但是这些你都可以自己来构建。因而，有一些开发者在类库中加入了他们自己的数据结构。<BR><BR>在这篇文章中，我们将回顾一些开发者早期的尝试，他们是如何发展Sun公司的Java Collections Framework的版本，以及该框架早期的进展和在参照了用户需求之后对一些数据结构的支持。<BR><BR><B>Java Collections Frameworks 的历史</B><BR>首先，让我们回顾一下初期的数据结构集合，服务于纽约州立大学（在美国的Oswego）的Doug Lea，他大概是创立了第一个被广泛应用的集合（collection）。该集合在1995年秋被发布。<BR><BR>那时候，一些早期的Java的开发者是由C＋＋转变而来。他们用Standard Template Library (STL) 进行开发，创立了Java 核心类库，而该类库极其缺乏对算法和数据结构的支持。 一家来自C＋＋和STL世界的公司（ObjectSpace）决定将STL接入Java。于是Java Generic Library (JGL) 在1996年发布，但是Sun并不喜欢这个名字。ObjectSpace公司因此将其改名为Generic Collection Library for Java，但仍旧使用JGL这一缩写。<BR>JGL流行一时。通过营销，ObjectSpace公司使10个IDE（集成开发环境）工具提供商集成了该类库。ObjectSpace宣称它的基本用户已超过十万人。（而那时候Java开发者还远不及七位数。）事实上JGL在Java早期成了一种标准。在几次修订之后，加了一个包并且与ObjectSpace的Voyager Object Request Broker (ORB) 集成，在1997年秋JGL 3.1版发布。<BR><BR>随着J2SE 1.2的到来，以及它对数据结构支持的变化，Doug Lea 放弃了他的 collections包，开始使用公用类的第二个类库开发。该新包提高了对数据的同步和多线程访问。在1998年7月发布的“util.concurrent”类就提供了对锁闭（locking）、池连（pooling）和同步（concurrent）的支持。<BR><BR><B>Java Collections Framework</B><BR>J2SE 1.2 是在1998年12月发布的，它包括了一组称为Java Collections Framework 的类，用来操作数据集合。对比JGL和 Collections Framework，它们有不同的目的JGL是STL的替代品，而Java Collections Framework则不是。以前的C＋＋开发者倾向于使用JGL，那是因为JGL需要进行深入地学习，而这对于已经熟悉STL的人更为自然。JGL大约有130个类和接口，而Collections Framework约是这个的百分之二十。<BR><BR>另外一个选择是Jakarta Commons Collections组件，于2001年7月发布,它用特制的数据类型和新方法扩展了J2SE 1.2 的APIS测试了集合论。在增添了一些类和接口的基础上，Collections Framework 和 J2SE 1.4版没有什么变化。对原型（如，模板），JSR14组织曾经辩论过，不过没有加入到J2SE 1.4。JSR 14 发布了一个原型，但仅在开发团体内部受到支持。<BR>JSR 166 组织于去年1月出炉，由Doug Lea 领导。该组织极力将先前提及util.concurrent 类库中的诸多高水平概念并入到Java 核心类库中。<BR>Little 从ObjectSpace公司的亲属那得知，在J2SE 1.4版发布前一天一个叫Recursion Software 的公司宣称取得了JGL版权。后来，在2002年7月，Recursion Software 公司发布了JGL 4.0版，并将JGL集合和算法与标准的Collections Framework集成。<BR>到了今天，我们可用的类库有：<BR>l Collections Framework，它是Java 核心类库的一部分，且定义了对所有数据结构实现的接口。<BR>l Jakarta Commons Collections组件，可以在Apache软件协议下自由取得。<BR>l JGL 4.0<BR>l 对Generic－types支持，作为原型对JSR 14 可用<BR>l 两个非标准的类库（来自Doug Lea），由JSR 166 正在发展成标准。<BR><BR>让我们来看看这些类库，考虑一下何时可能会使用它们。<BR><B>The Collections Framework</B><BR>Collection Framework 它提供了一套核心接口，共六个：Collection, Set, List, SortedSet, Map, 和 SortedMap.<BR><FONT color=#ff0000>Collection 是sets和lists的基本接口。它描述了一组没有特别特征的元素。对Collection没有直接的实现，仅有子接口的实现。</FONT><BR>Set是一个由一些项组成的集合，这些项不容许出现重复。HashSet 和 TreeSet 是两个Set 的标准实现；TreeSet 是经过分类的，它实现了SortedSet。<BR>List接口是一个经排序的集合，提供了索引或顺序存取。List的实现包括ArrayList和LinkedList；ArrayList替代了原来的Vector类。<BR>Map描述了‘键－值’格式的集合，类似于Hashtable。可用的maps映射有HashMap和TreeMap；TreeMap是经过分类的，它实现了SortedMap。J2SE 1.4 引入两个新的实现：LinkedHashSet和LinkedHashMap，它们内部自动维护了在增添、搜索和删除操作后的元素顺序。J2SE 1.4 中另一个实现是IdentityHashMap，它用“＝＝”代替了“equals()”来进行等比较。对于在weak reference 感兴趣的人来说，还有一个映射——WeakHashMap，它可以把WeakReference用作键（keys），因而，如果是通过键作为值（value）的唯一引用，将会丢弃该键－值对。<BR>在设计之初，基本框架是非线程安全的。多线程的同步访问安全性需要用一个线程安全的外覆器（wrapper）来达到。这样的一种外覆器在集合框架中有一个只读的类似版本。<BR>然而，这一框架比较小，不意味着对所有数据结构提供支持。相反，你仅仅可以通过它创建一些特殊的实现。<BR><BR><B>The Jakarta Commons Collections 组件</B><BR>一些特定实现已被很好地定义与解释，可他们却不是核心集合框架（core Collections Framework）的一部分。其中的一些实现可能被包含在了同步公用类库里，随后我们将更为细致的讨论。<BR>Jakarta Commons Collections 组件是这套特定实现的一个示例。被设计用来与J2SE 1.2 协作的Commons Collections 组件，它提供了两个List实现和八个Map实现。新的基本结构也是可用的而且非常有趣。<BR>让我们来看一看这一组特制实现。<BR>最易理解的是FastArrayList, FastHashMap, 和 FastTreeMap。它们分别继承了标准的ArrayList、HashMap、和TreeMap，且都提供了在多线程下安全的同步读写访问。然而，安全性是需要成本的，这样便降低了写操作的速度。当读操作是非同步时，写操作在现存结构被替代时是在一份数据备份上进行地。<BR>Bag就象是Set，但可以重复。另外它还有两个实现：HashBag和TreeBag；后者是经排序地。<BR>另一个新的接口是PriorityQueue。它支持可比较项和使用比较器（Comparator），所以这些项可以BinaryHeap（基于优先级的）来维护，而且随后可从该堆中删除。<BR>剩下的就是一组特定的Map实现。它们提供了特殊目标（special－purpose）、键－值（key－value）来查找映射（maps）。<BR>l BeanMap运用反射提供了键值对（基于JavaBean属性）；该键是属性名，该值是这一属性名的值。<BR>l ExtendedProperties继承了java.util.Properties；它为单一属性连接了多个值，而非覆写它。<BR>l LRUMap是一个可维护最大容量的Map，而且使用了至少一个运算法则来定义在这一Map已满时要移去的节点。<BR>l MultiHashMap有点象WeakHashMap，但它用的是SoftReference而非WeakReference。<BR>l DoubleOrderedMap的重点在于串。它提供了一个Map来支持快速搜索（通过值和键）。但有这样一个要求：键和值必须都是唯一的。当然你用两个TreeMap对象也可以做到，但DoubleOrderedMap对每一个键值对仅保存一次。<BR><BR>其它更多的类和接口则更多的支持角色，但仍有一些特别的方法可用。除了象isSubCollection和合并等一些公用方法外，该框架还包括了附加的Comparators和Iterators。Comparators大部分是用来连锁或替换另一个Comparator的行为。比如说，java.util.Collections.reverseOrder()可以颠倒元素的原顺序，而ReverseComparator通过一个比较器（Comparator）来颠倒顺序。Iterators是在取出元素之前将每个Collection元素通过一个函数（或方法）的作用。这是JGL和STL共享的方法。<BR><BR><B>JGL 4.0</B><BR>自1997年以来，JGL就没有任何的更新，所以它的新版本使我很惊讶。我以为JGL已没有了任何目的。显然，Recursion Software赋予了它其它的目的。该公司宣布其基本用户已超过25万人，可是我并没有听说有谁在使用它。这一新版本与先前3.1版主要有三个不同之初：<BR>l 所有的类与接口都集成到了Collections Framework中，且同该框架一样，默认非同步访问。<BR>l 包的名称由com.objectspace.jgl改成了com.recursionsw.jgl。<BR>l Voyager-related已被废止。<BR>假如你对JGL还不是很熟悉的话，请参阅JavaWorld网站的文章。然而，新版没有太大的变化，类库仅能与J2SE 1.3.1和1.4协作。你可以将Collection Framework同JGL混用。JGL还提供了独立的抽象数据类型和法则。它支持50种预运算法则，而Collection Framework有18种。我明白为什么有人要学习两个不同的框架，但是一个好处就是使用JGl的人可以将他们的应用程序部分的转移到Collections Framework中，而非所有。JGL对于原是C＋＋的java开发者比起Collections Framework要更熟悉，但是由于JGL现在是该框架的一部分，这并不要紧。<BR><BR><B>Generic types</B><BR>最早计划要对核心集合框架的修改是去支持参数化的数据类型，又称为原型（generics），或者是模板（templates）。现在所有的集合都不是型别安全的（type－safe）。你可以将任一个对象加到集合中，而且，在取出一个元素是，你必须将它适当的转型。如果从集合中取出你想要的型别只有在运行期才知道。JSR 14提议可以提供型别安全的集合访问。<BR>在最简单的情况下，你只能通过型别和类名的联系去确认正确的型别：<BR>public static void main(String args[]) {<BR>List&lt;String&gt; listOne = Arrays.asList(args);<BR>List&lt;String&gt; listTwo = new ArrayList&lt;String&gt;(listOne);<BR>}<BR>在较复杂时，你可以把型别加入类的定义中，这时类中内部这一型别是很常见的：<BR><PRE class=overflow title="pre code">class Node&lt;T&gt; {<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;T value;<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Node(T value) {<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;this.value = value;<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;T getValue() {<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return value;<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;void setValue(T value) {<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;this.value = value;<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<BR>&nbsp; &nbsp; &nbsp; }</PRE><BR>对于generic－types支持的准确语法，以及是否被融入到J2SE的未来版本（可能是J2SE 1.5）还要作出决定。但是，如今你能生成与任意J2SE 1.3运行环境协作的类文件。你也不用要等到J2SE 1.5，去试试JSR 14的原型方法。<BR>使用同步（Concurrency utilities）<BR><BR>先前我提到两个类库（来自Doug Lea）：collections和util.concurrent。后者是新类库的第一个重击，由JSR 166设计。从同步类库的里外来看，JSR 16被包含在了JSR 176的计划之内。<BR><BR>加入接受了JSR 166并加入到java的下一版本中，Java核心类库体提供了非锁闭队列（nonblocking queue）、原子变量（atomic variables）、特定的锁闭（some specialized locking）和定时机构（timing mechanisms）、同步集合（concurrent collections）（类似于Commons Collections的 FastArrayList和其相关的），还有一些有趣的机制（该机制装入每个无捕捉异常的线程控制器，而非依赖ThreadGroup）。这不是一个全包含（all－inclusive）的列表，且隶属于JSR 166。<BR><BR><B>注意</B><BR>如果要在你的程序中使用抽象数据类型，首先要做的是，你要了解核心集合框架。其它的都是以此为基础，包括JSR所建议的东西。 <BR>一旦你了解了基本的集合类，你就能延伸到其它领域。当你现在可以摆弄generic－types的同时，你必须等待它的最终版本以及J2SE下一版本的同步类库。如果发现Jakarta Commons Collections对你很有用，一定要用它。你可以避开创建一般的结构（例如multimap）。当一个一年级的计算科学系的学生能够完成这样的任务时，事实上这是有益的。Apache的许可对于大多数人来说不是什么问题。<BR><BR>JGL不是免费的，每一个开发者需要49.95美元。而且在你使用个别部分之前需要掌握这个类库是如何运作的。近可能的融和JGL和Collections Framework，这可能并不是最好的选择。一般来讲，JGL就是提供了对Java核心类库的另一选择。我们宁愿使用Java主要类的优化代码，而不在我们自己的集合上包装第三方的类库。是的，JGL支持更多的运算法则，而且有的可能还很有用。例如，你想多么频繁的将整数集合取负？JGL可能对那些从C＋＋转变来的Java开发者是有用的，但是，对于那些在用java编程的和对java感兴趣的人，只了解标准集合框架的功用就行了。一般你是不需要JGL或是Commons Collections类。<BR><BR><B>关于作者</B><BR>John Zukowski同JZ Ventures领导着关于Java的咨询，而且是jGuru组织FAQs的成员。他的新书有Learn Java with JBuilder 6 （Apress出版） and Mastering Java 2: J2SE 1.4（Sybex出版）。<BR>Resources<BR>· Original collections library from Doug Lea: <BR>http://gee.cs.oswego.edu/dl/classes/collections/<BR>· Doug Lea's util.concurrent library: <BR>http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html<BR>· Past JavaWorld article (June 1997) about JGL: <BR>http://www.javaworld.com/javaworld/jw-06-1997/jw-06-jgl.html<BR>· Past JavaWorld article (January 1999) compares JGL and the Collections Framework: <BR>http://www.javaworld.com/javaworld/jw-01-1999/jw-01-jglvscoll.html<BR>· Past JavaWorld article (November 1998) introduces the Collections Framework: <BR>http://www.javaworld.com/javaworld/jw-11-1998/jw-11-collections.html<BR>· Collections Framework Design FAQ: <BR>http://java.sun.com/j2se/1.4/docs/guide/collections/designfaq.html<BR>· Jakarta Commons Collections component: <BR>http://jakarta.apache.org/commons/collections.html<BR>· JSR 14 generic-types support: <BR>http://www.jcp.org/jsr/detail/14.jsp<BR>· JSR 14 prototype (requires free registration): <BR>http://developer.java.sun.com/developer/earlyAccess/adding_generics/<BR>· JSR 166 concurrency utilities: <BR>http://www.jcp.org/jsr/detail/166.jsp<BR>· For those interested in learning more about JSR 166: <BR>http://gee.cs.oswego.edu/dl/concurrency-interest/index.html<BR>· JSR 176 (J2SE 1.5—Tiger): <BR>http://www.jcp.org/jsr/detail/176.jsp<BR>· Java Collections book by John Zukowski (Apress, 2001; ISBN: 1893115925): <BR>http://www.apress.com/catalog/book/1893115925/<BR>· Browse the J2SE section of JavaWorld's Topical Index: <BR>http://www.javaworld.com/channel_content/jw-j2se-index.shtml?j2se<BR>· Speak out in the JavaWorld Forum: <BR>http://forums.devworld.com/webx?13@@.ee6b802<BR>· Sign up for JavaWorld's free weekly Core Java email newsletter: <BR>http://www.javaworld.com/subscribe<BR>· You'll find a wealth of IT-related articles from our sister publications at IDG.net <BR><BR><img src ="http://www.blogjava.net/Apple/aggbug/6680.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 15:35 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6680.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>垃圾收集的意义</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6678.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 07:20:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6678.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6678.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6678.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6678.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6678.html</trackback:ping><description><![CDATA[垃圾收集的意义<BR><BR>在c中，对象所占的内存在程序结束运行之前一直被占用，在明确释放之前不能分配给其它对象；而在Java中，当没有对象引用指向原先分配给某个对象的内存时，该内存便成为垃圾。Jvm的一个系统级线程会自动释放该内存块。垃圾收集意味着程序不再需要的对象是无用信息，这些信息将被丢弃。当一个对象不再被引用的时候，内存回收它占领的空间，以便空间被后来的新对象使用。事实上，除了释放没用的对象，垃圾收集也可以清除内存记录碎片。由于创建对象和垃圾收集器释放丢弃对象所占的内存空间，内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端，Jvm将整理出的内存分配给新的对象。<BR><BR><FONT color=#ff0000>垃圾收集能自动释放内存空间，减轻编程的负担。</FONT>这使Java虚拟机具有一些优点<FONT color=#800080>。首先，它能使编程效率提高。</FONT>在没有垃圾收集机制的时候，可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候，靠垃圾收集机制可大大缩短时间。<FONT color=#800080>其次是它保护程序的完整性, 垃圾收集是Java语言安全性策略的一个重要部份。<BR></FONT><BR><FONT color=#800080><FONT color=#ff0000>垃圾收集的一个潜在的缺点是它的开销影响程序性能</FONT>。</FONT><FONT color=#800080>Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象。</FONT>这一个过程需要花费处理器的时间。<FONT color=#800080>其次垃圾收集算法的不完备性</FONT>，早先采用的某些垃圾收集算法就不能保证100％收集到所有的废弃内存。当然随着垃圾收集算法的不断改进以及软硬件运行效率的不断提升，这些问题都可以迎刃而解。<BR><img src ="http://www.blogjava.net/Apple/aggbug/6678.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 15:20 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6678.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】垃圾收集趣史</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6677.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 07:16:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6677.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6677.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6677.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6677.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6677.html</trackback:ping><description><![CDATA[<TABLE class=title>
<TBODY>
<TR>
<TD vAlign=center>
<H1>垃圾收集趣史</H1></TD></TR></TBODY></TABLE>
<DIV class="frame note">
<DIV class=label>Note</DIV>
<DIV class=content>本文发表于2004年2月《CSDN开发高手》</DIV></DIV>
<P>写作本文的初衷是想和大家分享垃圾收集（ Garbage Collection ）技术简单而有趣的发展史。动笔之前，我站在窗边，望了望正在小区里装运垃圾的清洁车。和生活中环卫工人们清运垃圾的工作相似，软件开发里的垃圾收集其实就是一种自动打扫和清除内存垃圾的技术，它可以有效防范动态内存分配中可能发生的两个危险：因内存垃圾过多而引发的内存耗尽（这和生活垃圾堵塞排污管道的危险并没有什么本质的不同），以及不恰当的内存释放所造成的内存非法引用（这类似于我们在生活中买到了一瓶已经过期三年的牛奶）。 </P>
<P>据历史学家们介绍，四千多年前的古埃及人已经在城市里建设了完善的排污和垃圾清运设施，一千多年前的中国人更是修筑了当时世界上保洁能力最强的都市——长安。今天，当我们在软件开发中体验自动垃圾收集的便捷与舒适时，我们至少应当知道，这种拒绝杂乱、追求整洁的“垃圾收集”精神其实是人类自古以来就已经具备了的。 </P><A name=N10015></A><A name=S01></A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H3>拓荒时代</H3></TD>
<TD style="FONT-SIZE: 70%" vAlign=bottom align=right>
<DIV style="MARGIN-BOTTOM: 3px"><A href="http://www.contextfree.net/wangyg/b/tech_his/gc_history.html#top"></A>&nbsp;</DIV></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=8 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<DIV class=section>
<P>国内的程序员大多是在 Java 语言中第一次感受到垃圾收集技术的巨大魅力的，许多人也因此把 Java 和垃圾收集看成了密不可分的整体。但事实上，垃圾收集技术早在 Java 语言问世前 30 多年就已经发展和成熟起来了， Java 语言所做的不过是把这项神奇的技术带到了广大程序员身边而已。 </P>
<P>如果一定要为垃圾收集技术找一个孪生兄弟，那么， Lisp 语言才是当之无愧的人选。 1960 年前后诞生于 MIT 的 Lisp 语言是第一种高度依赖于动态内存分配技术的语言： Lisp 中几乎所有数据都以“表”的形式出现，而“表”所占用的空间则是在堆中动态分配得到的。 Lisp 语言先天就具有的动态内存管理特性要求 Lisp 语言的设计者必须解决堆中每一个内存块的自动释放问题（否则， Lisp 程序员就必然被程序中不计其数的 free 或 delete 语句淹没），这直接导致了垃圾收集技术的诞生和发展——说句题外话，上大学时，一位老师曾告诉我们， Lisp 是对现代软件开发技术贡献最大的语言。我当时对这一说法不以为然：布满了圆括号，看上去像迷宫一样的 Lisp 语言怎么能比 C 语言或 Pascal 语言更伟大呢？不过现在，当我知道垃圾收集技术、数据结构技术、人工智能技术、并行处理技术、虚拟机技术、元数据技术以及程序员们耳熟能详的许多技术都起源于 Lisp 语言时，我特别想向那位老师当面道歉，并收回我当时的幼稚想法。 </P>
<P>知道了 Lisp 语言与垃圾收集的密切关系，我们就不难理解，为什么垃圾收集技术的两位先驱者 J. McCarthy 和 M. L. Minsky 同时也是 Lisp 语言发展史上的重要人物了。 J. McCarthy 是 Lisp 之父，他在发明 Lisp 语言的同时也第一次完整地描述了垃圾收集的算法和实现方式； M. L. Minsky 则在发展 Lisp 语言的过程中成为了今天好几种主流垃圾收集算法的奠基人——和当时不少技术大师的经历相似， J. McCarthy 和 M. L. Minsky 在许多不同的技术领域里都取得了令人艳羡的成就。也许，在 1960 年代那个软件开发史上的拓荒时代里，思维敏捷、意志坚定的研究者更容易成为无所不能的西部硬汉吧。 </P>
<P>在了解垃圾收集算法的起源之前，有必要先回顾一下内存分配的主要方式。我们知道，大多数主流的语言或运行环境都支持三种最基本的内存分配方式，它们分别是： </P>
<P>一、静态分配（ Static Allocation ）：静态变量和全局变量的分配形式。我们可以把静态分配的内存看成是家里的耐用家具。通常，它们无需释放和回收，因为没人会天天把大衣柜当作垃圾扔到窗外。 </P>
<P>二、自动分配（ Automatic Allocation ）：在栈中为局部变量分配内存的方法。栈中的内存可以随着代码块退出时的出栈操作被自动释放。这类似于到家中串门的访客，天色一晚就要各回各家，除了个别不识时务者以外，我们一般没必要把客人捆在垃圾袋里扫地出门。 </P>
<P>三、动态分配（ Dynamic Allocation ）：在堆中动态分配内存空间以存储数据的方式。堆中的内存块好像我们日常使用的餐巾纸，用过了就得扔到垃圾箱里，否则屋内就会满地狼藉。像我这样的懒人做梦都想有一台家用机器人跟在身边打扫卫生。在软件开发中，如果你懒得释放内存，那么你也需要一台类似的机器人——这其实就是一个由特定算法实现的垃圾收集器。 </P>
<P>也就是说，下面提到的所有垃圾收集算法都是在程序运行过程中收集并清理废旧“餐巾纸”的算法，它们的操作对象既不是静态变量，也不是局部变量，而是堆中所有已分配内存块。 </P><A name=N10033></A><A name=S0101></A>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H4>引用计数（ Reference Counting ）算法</H4></TD>
<TD></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=3 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<P>1960 年以前，人们为胚胎中的 Lisp 语言设计垃圾收集机制时，第一个想到的算法是引用计数算法。拿餐巾纸的例子来说，这种算法的原理大致可以描述为： </P>
<P>午餐时，为了把脑子里突然跳出来的设计灵感记下来，我从餐巾纸袋中抽出一张餐巾纸，打算在上面画出系统架构的蓝图。按照“餐巾纸使用规约之引用计数版”的要求，画图之前，我必须先在餐巾纸的一角写上计数值 1 ，以表示我在使用这张餐巾纸。这时，如果你也想看看我画的蓝图，那你就要把餐巾纸上的计数值加 1 ，将它改为 2 ，这表明目前有 2 个人在同时使用这张餐巾纸（当然，我是不会允许你用这张餐巾纸来擦鼻涕的）。你看完后，必须把计数值减 1 ，表明你对该餐巾纸的使用已经结束。同样，当我将餐巾纸上的内容全部誊写到笔记本上之后，我也会自觉地把餐巾纸上的计数值减 1 。此时，不出意外的话，这张餐巾纸上的计数值应当是 0 ，它会被垃圾收集器——假设那是一个专门负责打扫卫生的机器人——捡起来扔到垃圾箱里，因为垃圾收集器的惟一使命就是找到所有计数值为 0 的餐巾纸并清理它们。 </P>
<P>引用计数算法的优点和缺陷同样明显。这一算法在执行垃圾收集任务时速度较快，但算法对程序中每一次内存分配和指针操作提出了额外的要求（增加或减少内存块的引用计数）。更重要的是，引用计数算法无法正确释放循环引用的内存块，对此， D. Hillis 有一段风趣而精辟的论述： </P>
<P>一天，一个学生走到 Moon 面前说：“我知道如何设计一个更好的垃圾收集器了。我们必须记录指向每个结点的指针数目。” Moon 耐心地给这位学生讲了下面这个故事：“一天，一个学生走到 Moon 面前说：‘我知道如何设计一个更好的垃圾收集器了……’” </P>
<P>D. Hillis 的故事和我们小时候常说的“从前有座山，山上有个庙，庙里有个老和尚”的故事有异曲同工之妙。这说明，单是使用引用计数算法还不足以解决垃圾收集中的所有问题。正因为如此，引用计数算法也常常被研究者们排除在狭义的垃圾收集算法之外。当然，作为一种最简单、最直观的解决方案，引用计数算法本身具有其不可替代的优越性。 1980 年代前后， D. P. Friedman ， D. S. Wise ， H. G. Baker 等人对引用计数算法进行了数次改进，这些改进使得引用计数算法及其变种（如延迟计数算法等）在简单的环境下，或是在一些综合了多种算法的现代垃圾收集系统中仍然可以一展身手。 </P><A name=N10049></A><A name=S0102></A>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H4>标记－清除（ Mark-Sweep ）算法</H4></TD>
<TD></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=3 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<P>第一种实用和完善的垃圾收集算法是 J. McCarthy 等人在 1960 年提出并成功地应用于 Lisp 语言的标记－清除算法。仍以餐巾纸为例，标记－清除算法的执行过程是这样的： </P>
<P>午餐过程中，餐厅里的所有人都根据自己的需要取用餐巾纸。当垃圾收集机器人想收集废旧餐巾纸的时候，它会让所有用餐的人先停下来，然后，依次询问餐厅里的每一个人：“你正在用餐巾纸吗？你用的是哪一张餐巾纸？”机器人根据每个人的回答将人们正在使用的餐巾纸画上记号。询问过程结束后，机器人在餐厅里寻找所有散落在餐桌上且没有记号的餐巾纸（这些显然都是用过的废旧餐巾纸），把它们统统扔到垃圾箱里。 </P>
<P>正如其名称所暗示的那样，标记－清除算法的执行过程分为“标记”和“清除”两大阶段。这种分步执行的思路奠定了现代垃圾收集算法的思想基础。与引用计数算法不同的是，标记－清除算法不需要运行环境监测每一次内存分配和指针操作，而只要在“标记”阶段中跟踪每一个指针变量的指向——用类似思路实现的垃圾收集器也常被后人统称为跟踪收集器（ Tracing Collector ） </P>
<P>伴随着 Lisp 语言的成功，标记－清除算法也在大多数早期的 Lisp 运行环境中大放异彩。尽管最初版本的标记－清除算法在今天看来还存在效率不高（标记和清除是两个相当耗时的过程）等诸多缺陷，但在后面的讨论中，我们可以看到，几乎所有现代垃圾收集算法都是标记－清除思想的延续，仅此一点， J. McCarthy 等人在垃圾收集技术方面的贡献就丝毫不亚于他们在 Lisp 语言上的成就了。 </P><A name=N1005C></A><A name=S0103></A>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H4>复制（ Copying ）算法</H4></TD>
<TD></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=3 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<P>为了解决标记－清除算法在垃圾收集效率方面的缺陷， M. L. Minsky 于 1963 年发表了著名的论文“一种使用双存储区的 Lisp 语言垃圾收集器（ A LISP Garbage Collector Algorithm Using Serial Secondary Storage ）”。 M. L. Minsky 在该论文中描述的算法被人们称为复制算法，它也被 M. L. Minsky 本人成功地引入到了 Lisp 语言的一个实现版本中。 </P>
<P>复制算法别出心裁地将堆空间一分为二，并使用简单的复制操作来完成垃圾收集工作，这个思路相当有趣。借用餐巾纸的比喻，我们可以这样理解 M. L. Minsky 的复制算法： </P>
<P>餐厅被垃圾收集机器人分成南区和北区两个大小完全相同的部分。午餐时，所有人都先在南区用餐（因为空间有限，用餐人数自然也将减少一半），用餐时可以随意使用餐巾纸。当垃圾收集机器人认为有必要回收废旧餐巾纸时，它会要求所有用餐者以最快的速度从南区转移到北区，同时随身携带自己正在使用的餐巾纸。等所有人都转移到北区之后，垃圾收集机器人只要简单地把南区中所有散落的餐巾纸扔进垃圾箱就算完成任务了。下一次垃圾收集的工作过程也大致类似，惟一的不同只是人们的转移方向变成了从北区到南区。如此循环往复，每次垃圾收集都只需简单地转移（也就是复制）一次，垃圾收集速度无与伦比——当然，对于用餐者往返奔波于南北两区之间的辛劳，垃圾收集机器人是决不会流露出丝毫怜悯的。 </P>
<P>M. L. Minsky 的发明绝对算得上一种奇思妙想。分区、复制的思路不仅大幅提高了垃圾收集的效率，而且也将原本繁纷复杂的内存分配算法变得前所未有地简明和扼要（既然每次内存回收都是对整个半区的回收，内存分配时也就不用考虑内存碎片等复杂情况，只要移动堆顶指针，按顺序分配内存就可以了），这简直是个奇迹！不过，任何奇迹的出现都有一定的代价，在垃圾收集技术中，复制算法提高效率的代价是人为地将可用内存缩小了一半。实话实说，这个代价未免也太高了一些。 </P>
<P>无论优缺点如何，复制算法在实践中都获得了可以与标记－清除算法相比拟的成功。除了 M. L. Minsky 本人在 Lisp 语言中的工作以外，从 1960 年代末到 1970 年代初， R. R. Fenichel 和 J. C. Yochelson 等人也相继在 Lisp 语言的不同实现中对复制算法进行了改进， S. Arnborg 更是成功地将复制算法应用到了 Simula 语言中。 </P>
<P>至此，垃圾收集技术的三大传统算法——引用计数算法、标记－清除算法和复制算法——都已在 1960 年前后相继问世，三种算法各有所长，也都存在致命的缺陷。从 1960 年代后期开始，研究者的主要精力逐渐转向对这三种传统算法进行改进或整合，以扬长避短，适应程序设计语言和运行环境对垃圾收集的效率和实时性所提出的更高要求。 </P></DIV><A name=N10076></A><A name=S02></A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H3>走向成熟</H3></TD>
<TD style="FONT-SIZE: 70%" vAlign=bottom align=right>
<DIV style="MARGIN-BOTTOM: 3px"><A href="http://www.contextfree.net/wangyg/b/tech_his/gc_history.html#top"></A>&nbsp;</DIV></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=8 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<DIV class=section>
<P>从 1970 年代开始，随着科学研究和应用实践的不断深入，人们逐渐意识到，一个理想的垃圾收集器不应在运行时导致应用程序的暂停，不应额外占用大量的内存空间和 CPU 资源，而三种传统的垃圾收集算法都无法满足这些要求。人们必须提出更新的算法或思路，以解决实践中碰到的诸多难题。当时，研究者的努力目标包括： </P>
<P>第一，提高垃圾收集的效率。使用标记－清除算法的垃圾收集器在工作时要消耗相当多的 CPU 资源。早期的 Lisp 运行环境收集内存垃圾的时间竟占到了系统总运行时间的 40% ！——垃圾收集效率的低下直接造就了 Lisp 语言在执行速度方面的坏名声；直到今天，许多人还条件反射似地误以为所有 Lisp 程序都奇慢无比。 </P>
<P>第二，减少垃圾收集时的内存占用。这一问题主要出现在复制算法中。尽管复制算法在效率上获得了质的突破，但牺牲一半内存空间的代价仍然是巨大的。在计算机发展的早期，在内存价格以 KB 计算的日子里，浪费客户的一半内存空间简直就是在变相敲诈或拦路打劫。 </P>
<P>第三，寻找实时的垃圾收集算法。无论执行效率如何，三种传统的垃圾收集算法在执行垃圾收集任务时都必须打断程序的当前工作。这种因垃圾收集而造成的延时是许多程序，特别是执行关键任务的程序没有办法容忍的。如何对传统算法进行改进，以便实现一种在后台悄悄执行，不影响——或至少看上去不影响——当前进程的实时垃圾收集器，这显然是一件更具挑战性的工作。 </P>
<P>研究者们探寻未知领域的决心和研究工作的进展速度同样令人惊奇：在 1970 年代到 1980 年代的短短十几年中，一大批在实用系统中表现优异的新算法和新思路脱颖而出。正是因为有了这些日趋成熟的垃圾收集算法，今天的我们才能在 Java 或 .NET 提供的运行环境中随心所欲地分配内存块，而不必担心空间释放时的风险。 </P><A name=N1008B></A><A name=S0201></A>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H4>标记－整理（ Mark-Compact ）算法</H4></TD>
<TD></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=3 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<P>标记－整理算法是标记－清除算法和复制算法的有机结合。把标记－清除算法在内存占用上的优点和复制算法在执行效率上的特长综合起来，这是所有人都希望看到的结果。不过，两种垃圾收集算法的整合并不像 1 加 1 等于 2 那样简单，我们必须引入一些全新的思路。 1970 年前后， G. L. Steele ， C. J. Cheney 和 D. S. Wise 等研究者陆续找到了正确的方向，标记－整理算法的轮廓也逐渐清晰了起来： </P>
<P>在我们熟悉的餐厅里，这一次，垃圾收集机器人不再把餐厅分成两个南北区域了。需要执行垃圾收集任务时，机器人先执行标记－清除算法的第一个步骤，为所有使用中的餐巾纸画好标记，然后，机器人命令所有就餐者带上有标记的餐巾纸向餐厅的南面集中，同时把没有标记的废旧餐巾纸扔向餐厅北面。这样一来，机器人只消站在餐厅北面，怀抱垃圾箱，迎接扑面而来的废旧餐巾纸就行了。 </P>
<P>实验表明，标记－整理算法的总体执行效率高于标记－清除算法，又不像复制算法那样需要牺牲一半的存储空间，这显然是一种非常理想的结果。在许多现代的垃圾收集器中，人们都使用了标记－整理算法或其改进版本。 </P><A name=N1009B></A><A name=S0202></A>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H4>增量收集（ Incremental Collecting ）算法</H4></TD>
<TD></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=3 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<P>对实时垃圾收集算法的研究直接导致了增量收集算法的诞生。 </P>
<P>最初，人们关于实时垃圾收集的想法是这样的：为了进行实时的垃圾收集，可以设计一个多进程的运行环境，比如用一个进程执行垃圾收集工作，另一个进程执行程序代码。这样一来，垃圾收集工作看上去就仿佛是在后台悄悄完成的，不会打断程序代码的运行。 </P>
<P>在收集餐巾纸的例子中，这一思路可以被理解为：垃圾收集机器人在人们用餐的同时寻找废弃的餐巾纸并将它们扔到垃圾箱里。这个看似简单的思路会在设计和实现时碰上进程间冲突的难题。比如说，如果垃圾收集进程包括标记和清除两个工作阶段，那么，垃圾收集器在第一阶段中辛辛苦苦标记出的结果很可能被另一个进程中的内存操作代码修改得面目全非，以至于第二阶段的工作没有办法开展。 </P>
<P>M. L. Minsky 和 D. E. Knuth 对实时垃圾收集过程中的技术难点进行了早期的研究， G. L. Steele 于 1975 年发表了题为“多进程整理的垃圾收集（ Multiprocessing compactifying garbage collection ）”的论文，描述了一种被后人称为“ Minsky-Knuth-Steele 算法”的实时垃圾收集算法。 E. W. Dijkstra ， L. Lamport ， R. R. Fenichel 和 J. C. Yochelson 等人也相继在此领域做出了各自的贡献。 1978 年， H. G. Baker 发表了“串行计算机上的实时表处理技术（ List Processing in Real Time on a Serial Computer ）”一文，系统阐述了多进程环境下用于垃圾收集的增量收集算法。 </P>
<P>增量收集算法的基础仍是传统的标记－清除和复制算法。增量收集算法通过对进程间冲突的妥善处理，允许垃圾收集进程以分阶段的方式完成标记、清理或复制工作。详细分析各种增量收集算法的内部机理是一件相当繁琐的事情，在这里，读者们需要了解的仅仅是： H. G. Baker 等人的努力已经将实时垃圾收集的梦想变成了现实，我们再也不用为垃圾收集打断程序的运行而烦恼了。 </P><A name=N100B1></A><A name=S0203></A>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H4>分代收集（ Generational Collecting ）算法</H4></TD>
<TD></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=3 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<P>和大多数软件开发技术一样，统计学原理总能在技术发展的过程中起到强力催化剂的作用。 1980 年前后，善于在研究中使用统计分析知识的技术人员发现，大多数内存块的生存周期都比较短，垃圾收集器应当把更多的精力放在检查和清理新分配的内存块上。这个发现对于垃圾收集技术的价值可以用餐巾纸的例子概括如下： </P>
<P>如果垃圾收集机器人足够聪明，事先摸清了餐厅里每个人在用餐时使用餐巾纸的习惯——比如有些人喜欢在用餐前后各用掉一张餐巾纸，有的人喜欢自始至终攥着一张餐巾纸不放，有的人则每打一个喷嚏就用去一张餐巾纸——机器人就可以制定出更完善的餐巾纸回收计划，并总是在人们刚扔掉餐巾纸没多久就把垃圾捡走。这种基于统计学原理的做法当然可以让餐厅的整洁度成倍提高。 </P>
<P>D. E. Knuth ， T. Knight ， G. Sussman 和 R. Stallman 等人对内存垃圾的分类处理做了最早的研究。 1983 年， H. Lieberman 和 C. Hewitt 发表了题为“基于对象寿命的一种实时垃圾收集器（ A real-time garbage collector based on the lifetimes of objects ）”的论文。这篇著名的论文标志着分代收集算法的正式诞生。此后，在 H. G. Baker ， R. L. Hudson ， J. E. B. Moss 等人的共同努力下，分代收集算法逐渐成为了垃圾收集领域里的主流技术。 </P>
<P>分代收集算法通常将堆中的内存块按寿命分为两类，年老的和年轻的。垃圾收集器使用不同的收集算法或收集策略，分别处理这两类内存块，并特别地把主要工作时间花在处理年轻的内存块上。分代收集算法使垃圾收集器在有限的资源条件下，可以更为有效地工作——这种效率上的提高在今天的 Java 虚拟机中得到了最好的证明。 </P></DIV><A name=N100C5></A><A name=S03></A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H3>应用浪潮</H3></TD>
<TD style="FONT-SIZE: 70%" vAlign=bottom align=right>
<DIV style="MARGIN-BOTTOM: 3px"><A href="http://www.contextfree.net/wangyg/b/tech_his/gc_history.html#top"></A>&nbsp;</DIV></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=8 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<DIV class=section>
<P>Lisp 是垃圾收集技术的第一个受益者，但显然不是最后一个。在 Lisp 语言之后，许许多多传统的、现代的、后现代的语言已经把垃圾收集技术拉入了自己的怀抱。随便举几个例子吧：诞生于 1964 年的 Simula 语言， 1969 年的 Smalltalk 语言， 1970 年的 Prolog 语言， 1973 年的 ML 语言， 1975 年的 Scheme 语言， 1983 年的 Modula-3 语言， 1986 年的 Eiffel 语言， 1987 年的 Haskell 语言……它们都先后使用了自动垃圾收集技术。当然，每一种语言使用的垃圾收集算法可能不尽相同，大多数语言和运行环境甚至同时使用了多种垃圾收集算法。但无论怎样，这些实例都说明，垃圾收集技术从诞生的那一天起就不是一种曲高和寡的“学院派”技术。 </P>
<P>对于我们熟悉的 C 和 C++ 语言，垃圾收集技术一样可以发挥巨大的功效。正如我们在学校中就已经知道的那样， C 和 C++ 语言本身并没有提供垃圾收集机制，但这并不妨碍我们在程序中使用具有垃圾收集功能的函数库或类库。例如，早在 1988 年， H. J. Boehm 和 A. J. Demers 就成功地实现了一种使用保守垃圾收集算法（ Conservative GC Algorithmic ）的函数库（参见 <A href="http://www.hpl.hp.com/personal/Hans_Boehm/gc">http://www.hpl.hp.com/personal/Hans_Boehm/gc </A>）。我们可以在 C 语言或 C++ 语言中使用该函数库完成自动垃圾收集功能，必要时，甚至还可以让传统的 C/C++ 代码与使用自动垃圾收集功能的 C/C++ 代码在一个程序里协同工作。 </P>
<P>1995 年诞生的 Java 语言在一夜之间将垃圾收集技术变成了软件开发领域里最为流行的技术之一。从某种角度说，我们很难分清究竟是 Java 从垃圾收集中受益，还是垃圾收集技术本身借 Java 的普及而扬名。值得注意的是，不同版本的 Java 虚拟机使用的垃圾收集机制并不完全相同， Java 虚拟机其实也经过了一个从简单到复杂的发展过程。在 Java 虚拟机的 1.4.1 版中，人们可以体验到的垃圾收集算法就包括分代收集、复制收集、增量收集、标记－整理、并行复制（ Parallel Copying ）、并行清除（ Parallel Scavenging ）、并发（ Concurrent ）收集等许多种， Java 程序运行速度的不断提升在很大程度上应该归功于垃圾收集技术的发展与完善。 </P>
<P>尽管历史上已经有许多包含垃圾收集技术的应用平台和操作系统出现，但 Microsoft .NET 却是第一种真正实用化的、包含了垃圾收集机制的通用语言运行环境。事实上， .NET 平台上的所有语言，包括 C# 、 Visual Basic .NET 、 Visual C++ .NET 、 J# 等等，都可以通过几乎完全相同的方式使用 .NET 平台提供的垃圾收集机制。我们似乎可以断言， .NET 是垃圾收集技术在应用领域里的一次重大变革，它使垃圾收集技术从一种单纯的技术变成了应用环境乃至操作系统中的一种内在文化。这种变革对未来软件开发技术的影响力也许要远远超过 .NET 平台本身的商业价值。 </P></DIV><A name=N100DC></A><A name=S04></A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width=9 height=10></TD>
<TD>
<H3>大势所趋</H3></TD>
<TD style="FONT-SIZE: 70%" vAlign=bottom align=right>
<DIV style="MARGIN-BOTTOM: 3px"><A href="http://www.contextfree.net/wangyg/b/tech_his/gc_history.html#top"></A>&nbsp;</DIV></TD></TR>
<TR>
<TD bgColor=#80c4ff colSpan=3><IMG class=spacer height=8 alt="" src="http://www.contextfree.net/wangyg/skin/images/spacer.gif" width=1></TD></TR></TBODY></TABLE>
<DIV class=section>
<P>今天，致力于垃圾收集技术研究的人们仍在不懈努力，他们的研究方向包括分布式系统的垃圾收集、复杂事务环境下的垃圾收集、数据库等特定系统的垃圾收集等等。 </P>
<P>但在程序员中间，仍有不少人对垃圾收集技术不屑一顾，他们宁愿相信自己逐行编写的 free 或 delete 命令，也不愿把垃圾收集的重任交给那些在他们看来既蠢又笨的垃圾收集器。 </P>
<P>我个人认为，垃圾收集技术的普及是大势所趋，这就像生活会越来越好一样毋庸置疑。今天的程序员也许会因为垃圾收集器要占用一定的 CPU 资源而对其望而却步，但二十多年前的程序员还曾因为高级语言速度太慢而坚持用机器语言写程序呢！在硬件速度日新月异的今天，我们是要吝惜那一点儿时间损耗而踟躇不前，还是该坚定不移地站在代码和运行环境的净化剂——垃圾收集的一边呢？</P></DIV><img src ="http://www.blogjava.net/Apple/aggbug/6677.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 15:16 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6677.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】攻克学习多线程时碰到的难题</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6665.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 05:48:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6665.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6665.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6665.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6665.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6665.html</trackback:ping><description><![CDATA[接触多线程已经不少时间了,也做了不少事情,但是一直觉得用起来不那么顺手,在debug的时候,往往会比较担心在同步上出什么问题,想起"程序员最怕的是自己写的代码"这句话,觉得真是不假.<BR>&nbsp;&nbsp;&nbsp; 终于有一天,我觉得是时候把这个问题弄清楚了,所以,我就在网上找相关的内容看,结果竟然是找不到在我这个阶段应该看的,不是太简单,就是一笔带过,不知所云.<BR>&nbsp;&nbsp;&nbsp; 废了九牛二虎之力,终于差不多弄清楚了,其中有不少误区,以前认为的和真理相差甚大.想起自己花费的时间,真是觉得有点多,所以把它写出来,一是防止自己以后又会忘掉,二是给像我一样的似懂非懂者留下一点可以参考的东东.<BR>&nbsp;&nbsp;&nbsp; 闲话少说,转入正题!<BR>&nbsp; ---------------------------------<BR>&nbsp;&nbsp;&nbsp; 先从线程的创建说起.线程的创建一共有两种形式:<BR>&nbsp; ---------------------------------
<P>&nbsp;&nbsp;&nbsp; 一种是继承自Thread类.Thread 类是一个具体的类，即不是抽象类，该类封装了线程的行为。要创建一个线程，程序员必须创建一个从 Thread 类导出的新类。程序员通过覆盖 Thread 的 run() 函数来完成有用的工作。用户并不直接调用此函数；而是通过调用 Thread 的 start() 函数，该函数再调用 run()。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 例如:</P>
<P>&nbsp;&nbsp;&nbsp; public class Test extends Thread{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public Test(){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static void main(String args[]){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Test t1 = new Test();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Test t2 = new Test();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t1.start();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t2.start();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void run(){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //do thread's things<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }</P>
<P>----------------------------<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 另一种是实现Runnable接口,此接口只有一个函数，run()，此函数必须由实现了此接口的类实现。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 例如: public class Test implements Runnable{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread thread1;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread thread2;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public Test(){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thread1 = new Thread(this,"1");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thread2 = new Thread(this,"2");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static void main(String args[]){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Test t = new Test();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.startThreads();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void run(){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //do thread's things<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void startThreads(){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thread1.start();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thread2.start();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }</P>
<P>&nbsp;&nbsp;&nbsp; 两种创建方式差别不大,第一种因为继承自Thread,只创建了自身对象,第二种还得创建Thread对象.但是当你想继承某一其它类时,你只能用后一种方式.大多数人偏爱后一种的原因大概也在于此吧.</P>
<P>-------------------------</P>
<P>&nbsp;&nbsp;&nbsp; 下面我们来讲synchronized的4种用法吧:</P>
<P>&nbsp;&nbsp;&nbsp; 1.方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前.这时,线程获得的是成员锁,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 例如:</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public synchronized void synMethod() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //方法体<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</P>
<P>&nbsp;&nbsp;&nbsp; 2.对某一代码块使用,synchronized后跟括号,括号里是变量,这样,一次只有一个线程进入该代码块.此时,线程获得的是成员锁.例如:</P>
<P> public int synMethod(int a1){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(a1) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //一次只能有一个线程进入<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; 3.synchronized后面括号里是一对象,此时,线程获得的是对象锁.例如:</P>
<P>&nbsp; public class MyThread implements Runnable {<BR>&nbsp; &nbsp; public static void main(String args[]) {<BR>&nbsp;&nbsp;&nbsp; MyThread mt = new MyThread();<BR>&nbsp;&nbsp;&nbsp; Thread t1 = new Thread(mt, "t1");<BR>&nbsp;&nbsp;&nbsp; Thread t2 = new Thread(mt, "t2");<BR>&nbsp;&nbsp;&nbsp; Thread t3 = new Thread(mt, "t3");<BR>&nbsp;&nbsp;&nbsp; Thread t4 = new Thread(mt, "t4");<BR>&nbsp;&nbsp;&nbsp; Thread t5 = new Thread(mt, "t5");<BR>&nbsp;&nbsp;&nbsp; Thread t6 = new Thread(mt, "t6");<BR>&nbsp;&nbsp;&nbsp; t1.start();<BR>&nbsp;&nbsp;&nbsp; t2.start();<BR>&nbsp;&nbsp;&nbsp; t3.start();<BR>&nbsp;&nbsp;&nbsp; t4.start();<BR>&nbsp;&nbsp;&nbsp; t5.start();<BR>&nbsp;&nbsp;&nbsp; t6.start();<BR>&nbsp; }</P>
<P>&nbsp; public void run() {<BR>&nbsp;&nbsp;&nbsp; synchronized (this) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName());<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp; }<BR>} <BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp; 对于3,如果线程进入,则得到当前对象锁,那么别的线程在该类所有对象上的任何操作都不能进行.在对象级使用锁通常是一种比较粗糙的方法。为什么要将整个对象都上锁，而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源？如果一个对象拥有多个资源，就不需要只为了让一个线程使用其中一部分资源，就将所有线程都锁在外面。由于每个对象都有锁，可以如下所示使用虚拟对象来上锁：class FineGrainLock {</P>
<P>&nbsp;&nbsp; MyMemberClass x, y;<BR>&nbsp;&nbsp; Object xlock = new Object(), ylock = new Object();</P>
<P>&nbsp;&nbsp; public void foo() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(xlock) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //access x here<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //do something here - but don't use shared resources</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(ylock) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //access y here<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp; }</P>
<P>&nbsp;&nbsp; public void bar() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(this) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //access both x and y here<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //do something here - but don't use shared resources<BR>&nbsp;&nbsp; }<BR>&nbsp; }</P>
<P>&nbsp;&nbsp;&nbsp; 4.synchronized后面括号里是类,此时,线程获得的是对象锁.例如:</P>
<P>&nbsp; class ArrayWithLockOrder{<BR>&nbsp; private static long num_locks = 0;<BR>&nbsp; private long lock_order;<BR>&nbsp; private int[] arr;</P>
<P>&nbsp; public ArrayWithLockOrder(int[] a)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; arr = a;<BR>&nbsp;&nbsp;&nbsp; synchronized(ArrayWithLockOrder.class) {//-----这里<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; num_locks++;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 锁数加 1。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lock_order = num_locks;&nbsp; // 为此对象实例设置唯一的 lock_order。<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp; }<BR>&nbsp; public long lockOrder()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return lock_order;<BR>&nbsp; }<BR>&nbsp; public int[] array()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return arr;<BR>&nbsp; }<BR>&nbsp; } </P>
<P></P>
<P>&nbsp; class SomeClass implements Runnable<BR>&nbsp;{<BR>&nbsp; public int sumArrays(ArrayWithLockOrder a1,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ArrayWithLockOrder a2)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; int value = 0;<BR>&nbsp;&nbsp;&nbsp; ArrayWithLockOrder first = a1;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 保留数组引用的一个<BR>&nbsp;&nbsp;&nbsp; ArrayWithLockOrder last = a2;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 本地副本。<BR>&nbsp;&nbsp;&nbsp; int size = a1.array().length;<BR>&nbsp;&nbsp;&nbsp; if (size == a2.array().length)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (a1.lockOrder() &gt; a2.lockOrder())&nbsp; // 确定并设置对象的锁定<BR>&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 顺序。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; first = a2;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; last = a1;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(first) {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 按正确的顺序锁定对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; synchronized(last) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int[] arr1 = a1.array();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int[] arr2 = a2.array();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i=0; i&lt;size; i++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value += arr1[i] + arr2[i];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; return value;<BR>}<BR>&nbsp; public void run() {<BR>&nbsp;&nbsp;&nbsp; //...<BR>&nbsp; }<BR>&nbsp; }<BR>&nbsp;&nbsp;&nbsp; 对于4,如果线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法,实际上,对于含有静态方法和静态变量的代码块的同步,我们通常用4来加锁. </P>
<P></P>
<P>&nbsp; -----------------------------</P>
<P>&nbsp; 下面谈一谈一些常用的方法:</P>
<P>&nbsp; wait(),wait(long),notify(),notifyAll()等方法是当前类的实例方法,<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wait()是使持有对象锁的线程释放锁;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wait(long)是使持有对象锁的线程释放锁时间为long(毫秒)后,再次获得锁,wait()和wait(0)等价;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notify()是唤醒一个正在等待该对象锁的线程,如果等待的线程不止一个,那么被唤醒的线程由jvm确定;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyAll是唤醒所有正在等待该对象锁的线程.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在这里我也重申一下,我们应该优先使用notifyAll()方法,因为唤醒所有线程比唤醒一个线程更容易让jvm找到最适合被唤醒的线程.</P>
<P>&nbsp;&nbsp;&nbsp; 对于上述方法,只有在当前线程中才能使用,否则报运行时错误java.lang.IllegalMonitorStateException: current thread not owner.</P>
<P>&nbsp; --------------------------</P>
<P>&nbsp;&nbsp;&nbsp; 下面,我谈一下synchronized和wait()、notify()等的关系:</P>
<P>&nbsp;&nbsp;&nbsp; 其实用生产者/消费者这个例子最好说明他们之间的关系了:</P>
<P>&nbsp;&nbsp;&nbsp; public class test {<BR>&nbsp; public static void main(String args[]) {<BR>&nbsp;&nbsp;&nbsp; Semaphore s = new Semaphore(1);<BR>&nbsp;&nbsp;&nbsp; Thread t1 = new Thread(s, "producer1");<BR>&nbsp;&nbsp;&nbsp; Thread t2 = new Thread(s, "producer2");<BR>&nbsp;&nbsp;&nbsp; Thread t3 = new Thread(s, "producer3");<BR>&nbsp;&nbsp;&nbsp; Thread t4 = new Thread(s, "consumer1");<BR>&nbsp;&nbsp;&nbsp; Thread t5 = new Thread(s, "consumer2");<BR>&nbsp;Thread t6 = new Thread(s, "consumer3");<BR>&nbsp;&nbsp;&nbsp; t1.start();<BR>&nbsp;&nbsp;&nbsp; t2.start();<BR>&nbsp;&nbsp;&nbsp; t3.start();<BR>&nbsp;&nbsp;&nbsp; t4.start();<BR>&nbsp;&nbsp;&nbsp; t5.start();<BR>&nbsp;&nbsp;&nbsp; t6.start();<BR>&nbsp; }<BR>&nbsp; } </P>
<P></P>
<P>&nbsp; class Semaphore<BR>&nbsp;&nbsp;&nbsp;&nbsp; implements Runnable {<BR>&nbsp; private int count;<BR>&nbsp; public Semaphore(int n) {<BR>&nbsp;&nbsp;&nbsp; this.count = n;<BR>&nbsp; }</P>
<P>&nbsp; public synchronized void acquire() {<BR>&nbsp;&nbsp;&nbsp; while (count == 0) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wait();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (InterruptedException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //keep trying<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; count--;<BR>&nbsp; }</P>
<P>&nbsp; public synchronized void release() {<BR>&nbsp;&nbsp;&nbsp; while (count == 10) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wait();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp; catch (InterruptedException e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //keep trying<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; count++;<BR>&nbsp;&nbsp;&nbsp; notifyAll(); //alert a thread that's blocking on this semaphore<BR>&nbsp; } </P>
<P></P>
<P>&nbsp; public void run() {<BR>&nbsp;&nbsp;&nbsp; while (true) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("consumer")) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; acquire();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("producer")) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; release();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(Thread.currentThread().getName() + " " + count);<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 生产者生产,消费者消费,一般没有冲突,但当库存为0时,消费者要消费是不行的,但当库存为上限(这里是10)时,生产者也不能生产.请好好研读上面的程序,你一定会比以前进步很多.</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 上面的代码说明了synchronized和wait,notify没有绝对的关系,在synchronized声明的方法、代码块中,你完全可以不用wait,notify等方法,但是,如果当线程对某一资源存在某种争用的情况下,你必须适时得将线程放入等待或者唤醒.</P>
<P>文章终于写完了,基本上将我学习所得全部写出来了,不过也留下些许遗憾,比如synchronized后是类时的深入的说明及讨论.而且,文章由于自己能力有限,有些地方肯定会有错误,希望看过有何建议和批评,请发帖在下面,我会修正该文.谢谢!<BR></P><img src ="http://www.blogjava.net/Apple/aggbug/6665.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 13:48 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6665.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至赛迪网】解析java的多线程机制</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6652.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 01:43:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6652.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6652.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6652.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6652.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6652.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=6 width="98%" background=http://www.21tx.com/images/data_title_bg.gif border=0>
<TBODY>
<TR>
<TD align=middle><FONT class=f18 color=#339900 size=5><B>JAVA教程：解析Java的多线程机制</B></FONT></TD></TR>
<TR>
<TD align=middle>http://dev.21tx.com 2005年05月03日 <FONT color=#a20010>赛迪网 </FONT></TD></TR></TBODY></TABLE>
<P></P>
<P align=center>
<SCRIPT src="http://www.21tx.com/images/ad/ad_dev_c_3.js"></SCRIPT>
</P>
<TABLE cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR>
<TD class=content>一、进程与应用程序的区别 <BR>　　 <BR>　　进程（Process）是最初定义在Unix等多用户、多任务<A href="http://dev.21tx.com/os/" target=_blank><FONT color=#3366cc>操作系统</FONT></A>环境下用于表示应用程序在内存环境中基本执行单元的概念。以Unix操作系统为例，进程是Unix操作系统环境中的基本成分、是系统资源分配的基本单位。Unix操作系统中完成的几乎所有用户管理和资源分配等工作都是通过操作系统对应用程序进程的控制来实现的。 <BR>　　 <BR>　　C、C++、Java等语言编写的源程序经相应的编译器编译成可执行文件后，提交给计算机处理器运行。这时，处在可执行状态中的应用程序称为进程。从用户角度来看，进程是应用程序的一个执行过程。从操作系统核心角度来看，进程代表的是操作系统分配的内存、CPU时间片等资源的基本单位，是为正在运行的程序提供的运行环境。进程与应用程序的区别在于应用程序作为一个静态文件存储在计算机系统的硬盘等存储空间中，而进程则是处于动态条件下由操作系统维护的系统资源管理实体。多任务环境下应用程序进程的主要特点包括： <BR>　　 <BR>　　●进程在执行过程中有内存单元的初始入口点，并且进程存活过程中始终拥有独立的内存地址空间； <BR>　　 <BR>　　●进程的生存期状态包括创建、就绪、运行、阻塞和死亡等类型； <BR>　　 <BR>　　●从应用程序进程在执行过程中向CPU发出的运行指令形式不同，可以将进程的状态分为用户态和核心态。处于用户态下的进程执行的是应用程序指令、处于核心态下的应用程序进程执行的是操作系统指令。 <BR>　　 <BR>　　在Unix操作系统启动过程中，系统自动创建swapper、init等系统进程，用于管理内存资源以及对用户进程进行调度等。在Unix环境下无论是由操作系统创建的进程还要由应用程序执行创建的进程，均拥有唯一的进程标识（PID）。 <BR><BR>二、进程与<A href="http://dev.21tx.com/java/adv/thread/" target=_blank><FONT color=#3366cc>Java线程</FONT></A>的区别 <BR>　　 <BR>　　 <BR>　　应用程序在执行过程中存在一个内存空间的初始入口点地址、一个程序执行过程中的代码执行序列以及用于标识进程结束的内存出口点地址，在进程执行过程中的每一时间点均有唯一的处理器指令与内存单元地址相对应。 <BR>　　 <BR>　　Java语言中定义的线程（Thread）同样包括一个内存入口点地址、一个出口点地址以及能够顺序执行的代码序列。但是进程与线程的重要区别在于线程不能够单独执行，它必须运行在处于活动状态的应用程序进程中，因此可以定义线程是程序内部的具有并发性的顺序代码流。 <BR>　　 <BR>　　Unix操作系统和Microsoft Windows操作系统支持多用户、多进程的并发执行，而Java语言支持应用程序进程内部的多个执行线程的并发执行。多线程的意义在于一个应用程序的多个逻辑单元可以并发地执行。但是多线程并不意味着多个用户进程在执行，操作系统也不把每个线程作为独立的进程来分配独立的系统资源。进程可以创建其子进程，子进程与父进程拥有不同的可执行代码和数据内存空间。而在用于代表应用程序的进程中多个线程共享数据内存空间，但保持每个线程拥有独立的执行堆栈和程序执行上下文（Context）。 <BR>　　 <BR>　　基于上述区别，线程也可以称为轻型进程 (Light Weight Process，LWP)。不同线程间允许任务协作和数据交换，使得在计算机系统资源消耗等方面非常廉价。 <BR>　　 <BR>　　线程需要操作系统的支持，不是所有类型的计算机都支持多线程应用程序。Java<A href="http://dev.21tx.com/java/j2me/code/" target=_blank><A href="http://dev.21tx.com/java/j2me/code/" target=_blank><FONT color=#3366cc>程序设计</FONT></A></A>语言将线程支持与语言运行环境结合在一起，提供了多任务并发执行的能力。这就好比一个人在处理家务的过程中，将衣服放到洗衣机中自动洗涤后将大米放在电饭锅里，然后开始做菜。等菜做好了，饭熟了同时衣服也洗好了。 <BR>　　 <BR>　　需要注意的是：在应用程序中使用多线程不会增加 CPU 的数据处理能力。只有在多CPU 的计算机或者在网络计算体系结构下，将Java程序划分为多个并发执行线程后，同时启动多个线程运行，使不同的线程运行在基于不同处理器的Java虚拟机中，才能提高应用程序的执行效率。<BR><BR>另外，如果应用程序必须等待网络连接或数据库连接等数据吞吐速度相对较慢的资源时，多线程应用程序是非常有利的。基于Internet的应用程序有必要是多线程类型的，例如，当开发要支持大量客户机的<A href="http://www.21tx.com/server/" target=_blank><FONT color=#3366cc>服务器</FONT></A>端应用程序时，可以将应用程序创建成多线程形式来响应客户端的连接请求，使每个连接用户独占一个客户端连接线程。这样，用户感觉服务器只为连接用户自己服务，从而缩短了服务器的客户端响应时间。 <BR>　　 <BR>　　 <BR>三、Java语言的多线程程序设计方法 <BR>　　 <BR>　　 <BR>　　利用Java语言实现多线程应用程序的方法很简单。根据多线程应用程序继承或实现对象的不同可以采用两种方式：一种是应用程序的并发运行对象直接继承Java的线程类Thread；另外一种方式是定义并发执行对象实现Runnable接口。 <BR>　　 <BR>　　继承Thread类的多线程程序设计方法 <BR>　　 <BR>　　Thread 类是JDK中定义的用于控制线程对象的类，在该类中封装了用于进行线程控制的方法。见下面的示例代码： <BR>　　 <BR>　　[code]//Consumer.java <BR>　　import java.util.*; <BR>　　class Consumer extends Thread <BR>　　{ <BR>　　 int nTime; <BR>　　 String strConsumer; <BR>　　 public Consumer(int nTime, String strConsumer) <BR>　　 { <BR>　　 this.nTime = nTime; <BR>　　 this.strConsumer = strConsumer; <BR>　　 } <BR>　　 public void run() <BR>　　 { <BR>　　while(true) <BR>　　{ <BR>　　 try <BR>　　{ <BR>　　 System.out.println("Consumer name:"+strConsumer+"\n"); <BR>　　 Thread.sleep(nTime); <BR>　　 } <BR>　　catch(Exception e) <BR>　　{ <BR>　　 e.printStackTrace(); <BR>　　 } <BR>　　} <BR>　　 } <BR>　　static public void main(String args[]) <BR>　　{ <BR>　　 Consumer aConsumer = new Consumer (1000, "aConsumer"); <BR>　　 aConsumer.start(); <BR>　　 Consumer bConsumer = new Consumer (2000, "bConsumer"); <BR>　　 bConsumer.start(); <BR>　　 Consumer cConsumer = new Consumer (3000, "cConsumer "); <BR>　　 cConsumer.start(); <BR>　　} <BR>　　} [/code]<BR>　　 <BR>　　 <BR>　　 <BR>　　 <BR>　　从上面的程序代码可以看出：多线程执行地下Consumer继承Java语言中的线程类Thread并且在main方法中创建了三个Consumer对象的实例。当调用对象实例的start方法时，自动调用Consumer类中定义的run方法启动对象线程运行。线程运行的结果是每间隔nTime时间打印出对象实例中的字符串成员变量strConsumer的内容。 <BR>　　 <BR>　　可以总结出继承Thread类的多线程程序设计方法是使应用程序类继承Thread类并且在该类的run方法中实现并发性处理过程。 <BR>　　 <BR>　　实现Runnable接口的多线程程序设计方法 <BR>　　 <BR>　　Java语言中提供的另外一种实现多线程应用程序的方法是多线程对象实现Runnable接口并且在该类中定义用于启动线程的run方法。这种定义方式的好处在于多线程应用对象可以继承其它对象而不是必须继承Thread类，从而能够增加类定义的逻辑性。 <BR>　　 <BR>　　实现Runnable接口的多线程应用程序框架代码如下所示： <BR>　　 <BR>　　//Consumer.java <BR>　　import java.util.*; <BR>　　class Consumer implements Runnable <BR>　　{ <BR>　　 … … <BR>　　public Consumer(int nTime, String strConsumer){… …} <BR>　　public void run(){… …} <BR>　　static public void main(String args[]) <BR>　　{ <BR>　　Thread aConsumer = new Thread(new Consumer(1000, "aConsumer")); <BR>　　aConsumer.start(); <BR>　　//其它对象实例的运行线程 <BR>　　 //… … <BR>　　 } <BR>　　} <BR>　　 <BR>　　从上述代码可以看出：该类实现了Runnable接口并且在该类中定义了run方法。<FONT color=#800080>这种多线程应用程序的实现方式与继承Thread类的多线程应用程序的重要区别在于启动多线程对象的方法设计方法不同。</FONT>在上述代码中，通过创建Thread对象实例并且将应用对象作为创建Thread类实例的参数。 <BR><BR>四、线程间的同步 <BR>　　 <BR>　　<FONT color=#006400>Java应用程序的多个线程共享同一进程的数据资源</FONT>，多个用户线程在并发运行过程中可能同时访问具有敏感性的内容。在Java中定义了线程同步的概念，实现对共享资源的一致性维护。下面以笔者最近开发的移动通信计费系统中线程间同步控制方法，说明Java语言中多线程同步方式的实现过程。 <BR>　　 <BR>　　在没有多线程同步控制策略条件下的客户账户类定义框架代码如下所示： <BR>　　 <BR>　　public class RegisterAccount <BR>　　{ <BR>　　float fBalance; <BR>　　//客户缴费方法 <BR>　　public void deposit(float fFees){ fBalance += fFees; } <BR>　　//通话计费方法 <BR>　　public void withdraw(float fFees){ fBalance -= fFees; } <BR>　　… … <BR>　　} <BR><BR>　　 <BR>　　 <BR>　　 <BR>　　 <BR>　　读者也许会认为：上述程序代码完全能够满足计费系统实际的需要。确实，在单线程环境下该程序确实是可靠的。但是，多进程并发运行的情况是怎样的呢？假设发生这种情况：客户在客户服务中心进行缴费的同时正在利用移动通信设备进行通话，客户通话结束时计费系统启动计费进程，而同时服务中心的工作人员也提交缴费进程运行。读者可以看到如果发生这种情况，对客户账户的处理是不严肃的。 <BR>　　 <BR>　　如何解决这种问题呢？很简单，在RegisterAccount类方法定义中加上用<FONT color=#008000>于标识同步方法的关键字synchronized。这样，在同步方法执行过程中该方法涉及的共享资源（在上述代码中为fBalance成员变量）将被加上共享锁</FONT><FONT color=#006400>，</FONT>以确保在方法运行期间只有该方法能够对共享资源进行访问，直到该方法的线程运行结束打开共享锁，其它线程才能够访问这些共享资源。在共享锁没有打开的时候其它访问共享资源的线程处于阻塞状态。 <BR>　　 <BR>　　进行线程同步策略控制后的RegisterAccount类定义如下面代码所示： <BR>　　 <BR>　　public class RegisterAccount <BR>　　{ <BR>　　float fBalance; <BR>　　public synchronized void deposit(float fFees){ fBalance += fFees; } <BR>　　public synchronized void withdraw(float fFees){ fBalance -= fFees; } <BR>　　… … <BR>　　} <BR><BR>　　 <BR>　　从经过线程同步机制定义后的代码形式可以看出：在对共享资源进行访问的方法访问属性关键字（public）后附加同步定义关键字synchronized，使得同步方法在对共享资源访问的时候，为这些敏感资源附加共享锁来控制方法执行期间的资源独占性，实现了应用系统数据资源的一致性管理和维护。 <BR><BR><BR>五、 Java线程的管理 <BR>　　 <BR>　　 <BR>　　线程的状态控制 <BR>　　 <BR>　　在这里需要明确的是：无论采用继承Thread类还是实现Runnable接口来实现应用程序的多线程能力，都需要在该类中定义用于完成实际功能的run方法，这个run方法称为线程体（Thread Body）。按照线程体在计算机系统内存中的状态不同，可以将线程分为创建、就绪、运行、睡眠、挂起和死亡等类型。这些线程状态类型下线程的特征为： <BR>　　 <BR>　　创建状态：当利用new关键字创建线程对象实例后，它仅仅作为一个对象实例存在，JVM没有为其分配CPU时间片等线程运行资源； <BR>　　 <BR>　　就绪状态：在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时，线程已经得到除CPU时间之外的其它系统资源，只等JVM的线程调度器按照线程的优先级对该线程进行调度，从而使该线程拥有能够获得CPU时间片的机会。 <BR>　　 <BR>　　睡眠状态：在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时，该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后，线程重新由JVM线程调度器进行调度和管理。 <BR>　　 <BR>　　挂起状态：可以通过调用suspend方法将线程的状态转换为挂起状态。这时，线程将释放占用的所有资源，由JVM调度转入临时存储空间，直至应用程序调用resume方法恢复线程运行。 <BR>　　 <BR>　　死亡状态：当线程体运行结束或者调用线程对象的stop方法后线程将终止运行，由JVM收回线程占用的资源。 <BR>　　 <BR>　　在Java线程类中分别定义了相应的方法，用于在应用程序中对线程状态进行控制和管理。 <BR>　　 <BR>　　线程的调度 <BR>　　 <BR>　　线程调用的意义在于JVM应对运行的多个线程进行系统级的协调，以避免多个线程争用有限资源而导致应用系统死机或者崩溃。 <BR>　　 <BR>　　为了线程对于操作系统和用户的重要性区分开，Java定义了线程的优先级策略。Java将线程的优先级分为10个等级，分别用1-10之间的数字表示。<FONT color=#800080>数字越大表明线程的级别越高。</FONT>相应地，在Thread类中定义了表示线程最低、最高和普通优先级的成员变量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY，代表的优先级等级分别为1、10和5。当一个线程对象被创建时，其默认的线程优先级是5。 <BR>　　 <BR>　　为了控制线程的运行策略，Java定义了线程调度器来监控系统中处于就绪状态的所有线程。线程调度器按照线程的优先级决定那个线程投入处理器运行。在多个线程处于就绪状态的条件下，具有高优先级的线程会在低优先级线程之前得到执行。线程调度器同样采用"抢占式"策略来调度线程执行，即当前线程执行过程中有较高优先级的线程进入就绪状态，则高优先级的线程立即被调度执行。具有相同优先级的所有线程采用轮转的方式来共同分配CPU时间片。 <BR>　　 <BR>　　在应用程序中设置线程优先级的方法很简单，在创建线程对象之后可以调用线程对象的setPriority方法改变该线程的运行优先级，同样可以调用getPriority方法获取当前线程的优先级。 <BR>　　 <BR>　　在Java中比较特殊的线程是被称为守护（Daemon）线程的低级别线程。这个线程具有最低的优先级，用于为系统中的其它对象和线程提供服务。将一个用户线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon方法。<FONT color=#ff0000>典型的守护线程例子是JVM中的系统资源自动回收线程，它始终在低级别的状态中运行，用于实时监控和管理系统中的可回收资源。 <BR></FONT>　　 <BR>　　线程分组管理 <BR>　　 <BR>　　Java定义了在多线程运行系统中的线程组（ThreadGroup）对象，用于实现按照特定功能对线程进行集中式分组管理。用户创建的每个线程均属于某线程组，这个线程组可以在线程创建时指定，也可以不指定线程组以使该线程处于默认的线程组之中。但是，一旦线程加入某线程组，该线程就一直存在于该线程组中直至线程死亡，不能在中途改变线程所属的线程组。 <BR>　　 <BR>　　当Java的Application应用程序运行时，JVM创建名称为main的线程组。除非单独指定，在该应用程序中创建的线程均属于main线程组。在main线程组中可以创建其它名称的线程组并将其它线程加入到该线程组中，依此类推，构成线程和线程组之间的树型管理和继承关系。 <BR>　　 <BR>　　与线程类似，可以针对线程组对象进行线程组的调度、状态管理以及优先级设置等。在对线程组进行管理过程中，加入到某线程组中的所有线程均被看作统一的对象。 <BR><BR>六、小结：<BR>本文针对Java平台中线程的性质和应用程序的多线程策略进行了分析和讲解。 <BR>　　 <BR>　　与其它操作系统环境不同，Java运行环境中的线程类似于多用户、多任务操作系统环境下的进程，但在进程和线程的运行及创建方式等方面，进程与Java线程具有明显区别。 <BR>　　 <BR>　　Unix操作系统环境下，应用程序可以利用fork函数创建子进程，但子进程与该应用程序进程拥有独立的地址空间、系统资源和代码执行单元，并且进程的调度是由操作系统来完成的，使得在应用进程之间进行通信和线程协调相对复杂。<FONT color=#ff0000>而Java应用程序中的多线程则是共享同一应用系统资源的多个并行代码执行体，线程之间的通信和协调方法相对简单</FONT>。 <BR>　　 <BR>　　可以说：Java语言对应用程序多线程能力的支持增强了Java作为网络程序设计语言的优势，为实现分布式应用系统中多客户端的并发访问以及提高服务器的响应效率奠定坚实基础。</TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/Apple/aggbug/6652.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 09:43 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6652.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至IBMdevelopworks】轻松使用线程：同步不是敌人</title><link>http://www.blogjava.net/Apple/archive/2005/06/24/6642.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Fri, 24 Jun 2005 00:57:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/24/6642.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6642.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/24/6642.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6642.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6642.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD><SPAN class=astitle>轻松使用线程: </SPAN><SPAN class=atitle>同步不是敌人</SPAN></TD>
<TD width=8><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></TD>
<TD align=right width=180><NOBR>
<TABLE cellSpacing=0 cellPadding=0>
<TBODY>
<TR vAlign=top>
<TD align=right></TD>
<TD width=46>
<FORM action=https://www-130.ibm.com/developerworks/secure/email-it.jsp>&nbsp;</FORM></TD></TR></TBODY></TABLE></NOBR></TD>
<TD width=6><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=6 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#000000 colSpan=5><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0></TD></TR>
<TR vAlign=top>
<TD bgColor=#ffffff colSpan=5><IMG height=8 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width=5><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=5 border=0></TD>
<TD width="100%">
<TABLE cellSpacing=0 cellPadding=0 width=168 align=right border=0>
<TBODY>
<TR>
<TD width=8><IMG height=21 alt="" src="http://www.ibm.com/i/c.gif" width=5></TD>
<TD width=160>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>内容：</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#1">synchronized 真正意味着什么？ </A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#2">使用一条好的运行路线</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#3">同步的代价有多大？</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#4">不要争用</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#5">什么时候需要同步？</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#6">如果情况不确定，考虑使用同步包装</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#7">结论</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR><!--Standard links for every dw-article-->
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#resources">参考资料 </A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#author1">关于作者</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#rating">对本文的评价</A></TD></TR>
<TR>
<TD><IMG height=10 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>相关内容：</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=1 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-thread/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">编写多线程的 Java 应用程序</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/praxis/pr52.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">用固定的，循环的顺序获取多个锁定以避免死锁</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">更多的 dW Java　参考资料</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>订阅:</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=1 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/newsletter/">developerWorks 时事通讯</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=150 bgColor=#000000 colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD width=150 bgColor=#ffffff colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><SPAN class=atitle2>我们什么时候需要同步，而同步的代价到底有多大？</SPAN><BR>
<P><A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#author1"><NAME>Brian Goetz</NAME></A><BR>软件顾问, Quiotix<BR>2001 年 7 月 04 日</P>
<BLOCKQUOTE><ABSTRACT-EXTENDED>
<P>与许多其它的编程语言不同，Java语言规范包括对线程和并发的明确支持。语言本身支持并发，这使得指定和管理共享数据的约束以及跨线程操作的计时变得更简单，但是这没有使得并发编程的复杂性更易于理解。这个三部分的系列文章的目的在于帮助程序员理解用Java 语言进行多线程编程的一些主要问题，特别是线程安全对 Java程序性能的影响。</P>
<P>请点击文章顶部或底部的 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">讨论</B>进入由 Brian Goetz 主持的 <A href="http://www-105.ibm.com/developerworks/java_df.nsf/AllViewTemplate?OpenForm&amp;RestrictToCategory=23" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">“Java线程：技巧、窍门和技术”讨论论坛</A>，与本文作者和其他读者交流您对本文或整个多线程的想法。注意该论坛讨论的是使用多线程时遇到的所有问题，而并不限于本文的内容。 </P></ABSTRACT-EXTENDED></BLOCKQUOTE>
<P>大多数编程语言的语言规范都不会谈到线程和并发的问题；因为一直以来，这些问题都是留给平台或操作系统去详细说明的。但是，Java 语言规范（JLS）却明确包括一个线程模型，并提供了一些语言元素供开发人员使用以保证他们程序的线程安全。</P>
<P>对线程的明确支持有利也有弊。它使得我们在写程序时更容易利用线程的功能和便利，但同时也意味着我们不得不注意所写类的线程安全，因为任何类都很有可能被用在一个多线程的环境内。</P>
<P>许多用户第一次发现他们不得不去理解线程的概念的时候，并不是因为他们在写创建和管理线程的程序，而是因为他们正在用一个本身是多线程的工具或框架。任何用过 Swing GUI 框架或写过小服务程序或 JSP 页的开发人员（不管有没有意识到）都曾经被线程的复杂性困扰过。</P>
<P>Java 设计师是想创建一种语言，使之能够很好地运行在现代的硬件，包括多处理器系统上。要达到这一目的，管理线程间协调的工作主要推给了软件开发人员；程序员必须指定线程间共享数据的位置。在 Java 程序中，用来管理线程间协调工作的主要工具是 <CODE>synchronized</CODE> 关键字。在缺少同步的情况下，JVM 可以很自由地对不同线程内执行的操作进行计时和排序。在大部分情况下，这正是我们想要的，因为这样可以提高性能，但它也给程序员带来了额外的负担，他们不得不自己识别什么时候这种性能的提高会危及程序的正确性。 </P>
<P><A name=1><SPAN class=atitle2>synchronized 真正意味着什么？ </SPAN></A><BR>大部分 Java 程序员对同步的块或方法的理解是完全根据使用互斥（互斥信号量）或定义一个临界段（一个必须原子性地执行的代码块）。虽然 <CODE>synchronized</CODE> 的语义中确实包括互斥和原子性，但在管程进入之前和在管程退出之后发生的事情要复杂得多。 </P>
<P><CODE>synchronized</CODE> 的语义确实保证了一次只有一个线程可以访问被保护的区段，但同时还包括同步线程在主存内互相作用的规则。理解 Java 内存模型（JMM）的一个好方法就是把各个线程想像成运行在相互分离的处理器上，所有的处理器存取同一块主存空间，每个处理器有自己的缓存，但这些缓存可能并不总和主存同步。在缺少同步的情况下，JMM 会允许两个线程在同一个内存地址上看到不同的值。而当用一个管程（锁）进行同步的时候，一旦申请加了锁，JMM 就会马上要求该缓存失效，然后在它被释放前对它进行刷新（把修改过的内存位置写回主存）。不难看出为什么同步会对程序的性能影响这么大；频繁地刷新缓存代价会很大。 </P>
<P><A name=2><SPAN class=atitle2>使用一条好的运行路线</SPAN></A><BR>如果同步不适当，后果是很严重的：会造成数据混乱和争用情况，导致程序崩溃，产生不正确的结果，或者是不可预计的运行。更糟的是，这些情况可能很少发生且具有偶然性（使得问题很难被监测和重现）。如果测试环境和开发环境有很大的不同，无论是配置的不同，还是负荷的不同，都有可能使得这些问题在测试环境中根本不出现，从而得出错误的结论：我们的程序是正确的，而事实上这些问题只是还没出现而已。</P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="30%" align=right border=1>
<TBODY>
<TR>
<TD background=/developerworks/cn/i/bg-gold.gif>
<P><A name=N1008C><B>争用情况定义</B></A><BR><I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">争用情况</I>是一种特定的情况：两个或更多的线程或进程读或写一些共享数据，而最终结果取决于这些线程是如何被调度计时的。争用情况可能会导致不可预见的结果和隐蔽的程序错误。 </P></TD></TR></TBODY></TABLE></P>
<P>另一方面，不当或过度地使用同步会导致其它问题，比如性能很差和死锁。当然，性能差虽然不如数据混乱那么严重，但也是一个严重的问题，因此同样不可忽视。编写优秀的多线程程序需要使用好的运行路线，足够的同步可以使您的数据不发生混乱，但不需要滥用到去承担死锁或不必要地削弱程序性能的风险。</P>
<P><A name=3><SPAN class=atitle2>同步的代价有多大？</SPAN></A><BR>由于包括缓存刷新和设置失效的过程，Java 语言中的同步块通常比许多平台提供的临界段设备代价更大，这些临界段通常是用一个原子性的“test and set bit”机器指令实现的。即使一个程序只包括一个在单一处理器上运行的单线程，一个同步的方法调用仍要比非同步的方法调用慢。如果同步时还发生锁定争用，那么性能上付出的代价会大得多，因为会需要几个线程切换和系统调用。</P>
<P>幸运的是，随着每一版的 JVM 的不断改进，既提高了 Java 程序的总体性能，同时也相对减少了同步的代价，并且将来还可能会有进一步的改进。此外，同步的性能代价经常是被夸大的。一个著名的资料来源就曾经引证说一个同步的方法调用比一个非同步的方法调用慢 50 倍。虽然这句话有可能是真的，但也会产生误导，而且已经导致了许多开发人员即使在需要的时候也避免使用同步。</P>
<P>严格依照百分比计算同步的性能损失并没有多大意义，因为一个无争用的同步给一个块或方法带来的是固定的性能损失。而这一固定的延迟带来的性能损失百分比取决于在该同步块内做了多少工作。对一个 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">空</I>方法的同步调用可能要比对一个空方法的非同步调用慢 20 倍，但我们多长时间才调用一次空方法呢？当我们用更有代表性的小方法来衡量同步损失时，百分数很快就下降到可以容忍的范围之内。 </P>
<P>表 1 把一些这种数据放在一起来看。它列举了一些不同的实例，不同的平台和不同的 JVM 下一个同步的方法调用相对于一个非同步的方法调用的损失。在每一个实例下，我运行一个简单的程序，测定循环调用一个方法 10，000，000 次所需的运行时间，我调用了同步和非同步两个版本，并比较了结果。表格中的数据是同步版本的运行时间相对于非同步版本的运行时间的比率；它显示了同步的性能损失。每次运行调用的都是清单 1 中的简单方法之一。</P>
<P>表格 1 中显示了同步方法调用相对于非同步方法调用的相对性能；为了用绝对的标准测定性能损失，必须考虑到 JVM 速度提高的因素，这并没有在数据中体现出来。在大多数测试中，每个 JVM 的更高版本都会使 JVM 的总体性能得到很大提高，很有可能 1.4 版的 Java 虚拟机发行的时候，它的性能还会有进一步的提高。</P>
<P><A name=" "><SPAN class=atitle3>表 1. 无争用同步的性能损失</SPAN></A><BR>
<TABLE cellSpacing=1 cellPadding=1 border=1>
<TBODY>
<TR>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">JDK</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">staticEmpty</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">empty</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">fetch</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">hashmapGet</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">singleton</B> </TD>
<TD><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">create</B> </TD></TR>
<TR>
<TD>Linux / JDK 1.1</TD>
<TD>9.2</TD>
<TD>2.4</TD>
<TD>2.5</TD>
<TD>n/a</TD>
<TD>2.0</TD>
<TD>1.42</TD></TR>
<TR>
<TD>Linux / IBM Java SDK 1.1</TD>
<TD>33.9</TD>
<TD>18.4</TD>
<TD>14.1</TD>
<TD>n/a</TD>
<TD>6.9</TD>
<TD>1.2</TD></TR>
<TR>
<TD>Linux / JDK 1.2</TD>
<TD>2.5</TD>
<TD>2.2</TD>
<TD>2.2</TD>
<TD>1.64</TD>
<TD>2.2</TD>
<TD>1.4</TD></TR>
<TR>
<TD>Linux / JDK 1.3 (no JIT)</TD>
<TD>2.52</TD>
<TD>2.58</TD>
<TD>2.02</TD>
<TD>1.44</TD>
<TD>1.4</TD>
<TD>1.1</TD></TR>
<TR>
<TD>Linux / JDK 1.3 -server</TD>
<TD>28.9</TD>
<TD>21.0</TD>
<TD>39.0</TD>
<TD>1.87</TD>
<TD>9.0</TD>
<TD>2.3</TD></TR>
<TR>
<TD>Linux / JDK 1.3 -client</TD>
<TD>21.2</TD>
<TD>4.2</TD>
<TD>4.3</TD>
<TD>1.7</TD>
<TD>5.2</TD>
<TD>2.1</TD></TR>
<TR>
<TD>Linux / IBM Java SDK 1.3</TD>
<TD>8.2</TD>
<TD>33.4</TD>
<TD>33.4</TD>
<TD>1.7</TD>
<TD>20.7</TD>
<TD>35.3</TD></TR>
<TR>
<TD>Linux / gcj 3.0</TD>
<TD>2.1</TD>
<TD>3.6</TD>
<TD>3.3</TD>
<TD>1.2</TD>
<TD>2.4</TD>
<TD>2.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.1</TD>
<TD>38.6</TD>
<TD>20.1</TD>
<TD>12.8</TD>
<TD>n/a</TD>
<TD>11.8</TD>
<TD>2.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.2</TD>
<TD>39.2</TD>
<TD>8.6</TD>
<TD>5.0</TD>
<TD>1.4</TD>
<TD>3.1</TD>
<TD>3.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.3 (no JIT)</TD>
<TD>2.0</TD>
<TD>1.8</TD>
<TD>1.8</TD>
<TD>1.0</TD>
<TD>1.2</TD>
<TD>1.1</TD></TR>
<TR>
<TD>Solaris / JDK 1.3 -client</TD>
<TD>19.8</TD>
<TD>1.5</TD>
<TD>1.1</TD>
<TD>1.3</TD>
<TD>2.1</TD>
<TD>1.7</TD></TR>
<TR>
<TD>Solaris / JDK 1.3 -server</TD>
<TD>1.8</TD>
<TD>2.3</TD>
<TD>53.0</TD>
<TD>1.3</TD>
<TD>4.2</TD>
<TD>3.2</TD></TR></TBODY></TABLE></P><A name=N10228><B>清单 1. 基准测试中用到的简单方法</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE> public static void staticEmpty() {  }

  public void empty() {  }

  public Object fetch() { return field; }

  public Object singleton() {
    if (singletonField == null)
      singletonField = new Object();
    return singletonField;
  }

  public Object hashmapGet() {
    return hashMap.get("this");
  }

  public Object create() { 
    return new Object();
  }
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>这些小基准测试也阐明了存在动态编译器的情况下解释性能结果所面临的挑战。对于 1.3 JDK 在有和没有 JIT 时，数字上的巨大差异需要给出一些解释。对那些非常简单的方法（ <CODE>empty</CODE> 和 <CODE>fetch</CODE> ），基准测试的本质（它只是执行一个几乎什么也不做的紧凑的循环）使得 JIT 可以动态地编译整个循环，把运行时间压缩到几乎没有的地步。但在一个实际的程序中，JIT 能否这样做就要取决于很多因素了，所以，无 JIT 的计时数据可能在做公平对比时更有用一些。在任何情况下，对于更充实的方法（ <CODE>create</CODE> 和 <CODE>hashmapGet</CODE> ），JIT 就不能象对更简单些的方法那样使非同步的情况得到巨大的改进。另外，从数据中看不出 JVM 是否能够对测试的重要部分进行优化。同样，在可比较的 IBM 和 Sun JDK 之间的差异反映了 IBM Java SDK 可以更大程度地优化非同步的循环，而不是同步版本代价更高。这在纯计时数据中可以明显地看出（这里不提供）。 </P>
<P>从这些数字中我们可以得出以下结论：对非争用同步而言，虽然存在性能损失，但在运行许多不是特别微小的方法时，损失可以降到一个合理的水平；大多数情况下损失大概在 10% 到 200% 之间（这是一个相对较小的数目）。所以，虽然同步每个方法是不明智的（这也会增加死锁的可能性），但我们也不需要这么害怕同步。这里使用的简单测试是说明一个无争用同步的代价要比创建一个对象或查找一个 <CODE>HashMap</CODE> 的代价小。 </P>
<P>由于早期的书籍和文章暗示了无争用同步要付出巨大的性能代价，许多程序员就竭尽全力避免同步。这种恐惧导致了许多有问题的技术出现，比如说 double-checked locking（DCL）。许多关于 Java 编程的书和文章都推荐 DCL，它看上去真是避免不必要的同步的一种聪明的方法，但实际上它根本没有用，应该避免使用它。DCL 无效的原因很复杂，已超出了本文讨论的范围（要深入了解，请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">参考资料</A>里的链接）。 </P>
<P><A name=4><SPAN class=atitle2>不要争用</SPAN></A><BR>假设同步使用正确，若线程真正参与争用加锁，您也能感受到同步对实际性能的影响。并且无争用同步和争用同步间的性能损失差别很大；一个简单的测试程序指出争用同步比无争用同步慢 50 倍。把这一事实和我们上面抽取的观察数据结合在一起，可以看出使用一个争用同步的代价至少相当于创建 50 个对象。</P>
<P>所以，在调试应用程序中同步的使用时，我们应该努力减少实际争用的数目，而根本不是简单地试图避免使用同步。这个系列的第 2 部分将把重点放在减少争用的技术上，包括减小锁的粒度、减小同步块的大小以及减小线程间共享数据的数量。</P>
<P><A name=5><SPAN class=atitle2>什么时候需要同步？</SPAN></A><BR>要使您的程序线程安全，首先必须确定哪些数据将在线程间共享。如果正在写的数据以后可能被另一个线程读到，或者正在读的数据可能已经被另一个线程写过了，那么这些数据就是共享数据，必须进行同步存取。有些程序员可能会惊讶地发现，这些规则在简单地检查一个共享引用是否非空的时候也用得上。</P>
<P>许多人会发现这些定义惊人地严格。有一种普遍的观点是，如果只是要读一个对象的字段，不需要请求加锁，尤其是在 JLS 保证了 32 位读操作的原子性的情况下，它更是如此。但不幸的是，这个观点是错误的。除非所指的字段被声明为 <CODE>volatile</CODE> ，否则 JMM 不会要求下面的平台提供处理器间的缓存一致性和顺序连贯性，所以很有可能，在某些平台上，没有同步就会读到陈旧的数据。有关更详细的信息，请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-threads/#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">参考资料</A>。 </P>
<P>在确定了要共享的数据之后，还要确定要如何保护那些数据。在简单情况下，只需把它们声明为 <CODE>volatile</CODE> 即可保护数据字段；在其它情况下，必须在读或写共享数据前请求加锁，一个很好的经验是明确指出使用什么锁来保护给定的字段或对象，并在你的代码里把它记录下来。 </P>
<P>还有一点值得注意的是，简单地同步存取器方法（或声明下层的字段为 <CODE>volatile</CODE> ）可能并不足以保护一个共享字段。可以考虑下面的示例： </P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE> ...
  private int foo;
  public synchronized int getFoo() { return foo; } 
  public synchronized void setFoo(int f) { foo = f; }
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>如果一个调用者想要增加 <CODE>foo</CODE> 属性值，以下完成该功能的代码就不是线程安全的： </P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE> ...
  setFoo(getFoo() + 1);
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>如果两个线程试图同时增加 <CODE>foo</CODE> 属性值，结果可能是 <CODE>foo</CODE> 的值增加了 1 或 2，这由计时决定。调用者将需要同步一个锁，才能防止这种争用情况；一个好方法是在 JavaDoc 类中指定同步哪个锁，这样类的调用者就不需要自己猜了。 </P>
<P>以上情况是一个很好的示例，说明我们应该注意多层次粒度的数据完整性；同步存取器方法确保调用者能够存取到一致的和最近版本的属性值，但如果希望属性的将来值与当前值一致，或多个属性间相互一致，我们就必须同步复合操作 ― 可能是在一个粗粒度的锁上。</P>
<P><A name=6><SPAN class=atitle2>如果情况不确定，考虑使用同步包装</SPAN></A><BR>有时，在写一个类的时候，我们并不知道它是否要用在一个共享环境里。我们希望我们的类是线程安全的，但我们又不希望给一个总是在单线程环境内使用的类加上同步的负担，而且我们可能也不知道使用这个类时合适的锁粒度是多大。幸运的是，通过提供同步包装，我们可以同时达到以上两个目的。Collections 类就是这种技术的一个很好的示例；它们是非同步的，但在框架中定义的每个接口都有一个同步包装（例如， <CODE>Collections.synchronizedMap()</CODE> ），它用一个同步的版本来包装每个方法。 </P>
<P><A name=7><SPAN class=atitle2>结论</SPAN></A><BR>虽然 JLS 给了我们可以使我们的程序线程安全的工具，但线程安全也不是天上掉下来的馅饼。使用同步会蒙受性能损失，而同步使用不当又会使我们承担数据混乱、结果不一致或死锁的风险。幸运的是，在过去的几年内 JVM 有了很大的改进，大大减少了与正确使用同步相关的性能损失。通过仔细分析在线程间如何共享数据，适当地同步对共享数据的操作，可以使得您的程序既是线程安全的，又不会承受过多的性能负担。</P>
<P><A name=resources><SPAN class=atitle2>参考资料 </SPAN></A>
<UL>
<LI>您可以参阅本文在 developerWorks 全球站点上的 <A href="http://www.ibm.com/developerworks/library/j-threads1.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">英文原文</A>. <BR><BR>
<LI>请点击文章顶部或底部的 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">讨论</B>进入由 Brian Goetz 主持的，关于“Java 线程：技巧、窍门和技术”的 <A href="http://www-105.ibm.com/developerworks/java_df.nsf/AllViewTemplate?OpenForm&amp;RestrictToCategory=23" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">讨论论坛</A>。 <BR><BR>
<LI>Jack Shirazi 编写的 <A href="http://www.amazon.com/exec/obidos/ASIN/0596000154/none0b69" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>Java Performance Tuning</I> </A>（O'Reilly &amp; Associates, 2000）可以为在 Java 平台上解决性能问题提供指导。本书引用的与本书一起提供的参考资料提供了很好的 <A href="http://www.javaperformancetuning.com/tips.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">性能调试技巧</A>。 <BR><BR>
<LI>Dov Bulka 的 <A href="http://www.amazon.com/exec/obidos/ASIN/0201704293/none0b69" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>Java Performance and Scalability，第 1 卷：Server-Side Programming Techniques</I> </A>（Addison-Wesley，2000）提供了大量的设计技巧和诀窍，可帮助您增强自己的应用程序的性能。 <BR><BR>
<LI>Steve Wilson 和 Jeff Kesselman 的 <A href="http://www.amazon.com/exec/obidos/ASIN/0201709694/none0b69" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>Java Platform Performance: Strategies and Tactics</I> </A>（Addison-Wesley，2000）为有经验的 Java 程序员提供了生成快速、有效的 Java 代码的技术。 <BR><BR>
<LI>Brian Goetz 最近的著作“ <A href="http://www.javaworld.com/jw-02-2001/jw-0209-double.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">Double-checked locking: Clever, but broken</A>”（JavaWorld，2001 年 2 月）详细探索了 JMM 并描述了特定情况下不使用同步的惊人后果。 <BR><BR>
<LI>公认的多线程权威 Allen Holub 在他的文章“ <A href="http://www.javaworld.com/jw-02-2001/jw-0209-toolbox.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">警告：多处理器世界中的线程</A>”（JavaWorld，2001 年 2 月）中揭示了为什么用于减少同步负担的大多数技巧都不起作用。 <BR><BR>
<LI>Peter Haggar 描述了怎样 <A href="http://www-128.ibm.com/developerworks/cn/java/praxis/pr52.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">用固定的，循环的顺序获取多个锁定以避免死锁</A>（developerWorks，2000 年 9 月）。 <BR><BR>
<LI>在他的文章“ <A href="http://www-128.ibm.com/developerworks/cn/java/j-thread/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">编写多线程 Java 应用</A>”（developerWorks，2001 年 9 月）中，Alex Roetter 介绍了 Java Thread API，概括了多线程涉及的问题，并为一般性的问题提供了解决方案。 <BR><BR>
<LI>Doug Lea 的 <A href="http://www.amazon.com/exec/obidos/ASIN/0201310090/none0b69" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>Concurrent Programming in Java，第 2 版</I> </A>（Addison-Wesley，1999）是关于 Java 语言中多线程编程的敏感问题的权威书籍。 <BR><BR>
<LI>“ <A href="http://gee.cs.oswego.edu/dl/cpj/jmm.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">同步和 Java 内存模型</A>”摘录自 Doug Lea 的关于 <CODE>synchronized</CODE> 的实际意义的著作。 <BR><BR>
<LI>Bill Pugh 的 <A href="http://www.cs.umd.edu/~pugh/java/memoryModel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">Java 内存模型</A>为您学习 JMM 提供了一个很好的起点。 <BR><BR>
<LI><A href="http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">“Double Checked Locking is Broken”</A>声明描述了为什么 DCL 在用 Java 语言实现时没有用。 <BR><BR>
<LI>Bill Joy、Guy Steele 和 James Gosling 的 <A href="http://www.amazon.com/exec/obidos/ASIN/0201310082/none0b69" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml"><I>The Java Language Specification，第 2 版</I> </A>（Addison-Wesley，2000）的第 17 章描述了 Java 内存模型的深层细节问题。 <BR><BR>
<LI>IBM 的这篇文章描述了如何 <A href="http://www..software.ibm.com/vad.nsf/Data/Document2351?OpenDocument&amp;p=1&amp;BCT=3&amp;Footer=1&amp;origin=j" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">优化 WebSphere 中的加锁</A>使不同的事务处理并发地读取同一个状态且仅在更新的时候检查数据完整性。 <BR><BR>
<LI>IBM T.J. Watson 研究中心有一整个项目组投入到 <A href="http://www.research.ibm.com/PM/perf_mgt.html&amp;origin=j" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">性能管理</A>中。 <BR><BR>
<LI>请在 <A href="http://www-128.ibm.com/developerworks/cn/java/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">developerWorks Java 技术专区</A>查找更多的参考资料。 <BR></LI></UL>
<P></P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1></A><SPAN class=atitle2>关于作者</SPAN><BR>Brian Goetz 是一名软件顾问，并且过去 15 年来一直是专业的软件开发人员。他是 <A href="http://www.quiotix.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">Quiotix</A>，一家坐落在 Los Altos，California 的软件开发和咨询公司的首席顾问。请通过 <A href="mailto:brian@quiotix.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/" xmlns:h="http://www.w3.org/1999/xhtml">brian@quiotix.com</A> 与 Brian 联系。 </TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/Apple/aggbug/6642.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-24 08:57 <a href="http://www.blogjava.net/Apple/archive/2005/06/24/6642.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[zz]变态级JAVA程序员面试32问</title><link>http://www.blogjava.net/Apple/archive/2005/06/23/6618.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 23 Jun 2005 13:20:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/23/6618.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6618.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/23/6618.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6618.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6618.html</trackback:ping><description><![CDATA[<P>变态级JAVA程序员面试32问<BR>&nbsp;<BR>&nbsp;<BR>文章出处：<A href="http://www.hao86.com">www.hao86.com</A>&nbsp;&nbsp; 发布时间：2005-02-28 <BR>&nbsp;<BR>　　第一，谈谈final， finally， finalize的区别。 </P>
<P>　　第二，Anonymous Inner Class （匿名内部类） 是否可以extends（继承）其它类，是否可以implements（实现）interface（接口）？ </P>
<P>　　第三，Static Nested Class 和 Inner Class的不同，说得越多越好（面试题有的很笼统）。 </P>
<P>　　第四，&amp;和&amp;&amp;的区别。 </P>
<P>　　第五，HashMap和Hashtable的区别。 </P>
<P>　　第六，Collection 和 Collections的区别。 </P>
<P>　　第七，什么时候用assert. </P>
<P>　　第八，GC是什么？ 为什么要有GC？ </P>
<P>　　第九，String s = new String（"xyz"）；创建了几个String Object？ </P>
<P>　　第十，Math.round（11.5）等於多少？ Math.round（-11.5）等於多少？ </P>
<P>　　第十一，short s1 = 1； s1 = s1 + 1；有什么错？ short s1 = 1； s1 += 1；有什么错？ </P>
<P>　　第十二，sleep（） 和 wait（） 有什么区别？ </P>
<P>　　第十三，Java有没有goto？ </P>
<P>　　第十四，数组有没有length（）这个方法？ String有没有length（）这个方法？ </P>
<P>　　第十五，Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型？ </P>
<P>　　第十六，Set里的元素是不能重复的，那么用什么方法来区分重复与否呢？ 是用==还是equals（）？ 它们有何区别？ </P>
<P>　　第十七，给我一个你最常见到的runtime exception. </P>
<P>　　第十八，error和exception有什么区别？ </P>
<P>　　第十九，List， Set， Map是否继承自Collection接口？ </P>
<P>　　第二十，abstract class和interface有什么区别？ </P>
<P>　　第二十一，abstract的method是否可同时是static，是否可同时是native，是否可同时是synchronized？ </P>
<P>　　第二十二，接口是否可继承接口？ 抽象类是否可实现（implements）接口？ 抽象类是否可继承实体类（concrete class）？ </P>
<P>　　第二十三，启动一个线程是用run（）还是start（）？ </P>
<P>　　第二十四，构造器Constructor是否可被override？ </P>
<P>　　第二十五，是否可以继承String类？ </P>
<P>　　第二十六，当一个线程进入一个对象的一个synchronized方法后，其它线程是否可进入此对象的其它方法？ </P>
<P>　　第二十七，try {}里有一个return语句，那么紧跟在这个try后的finally {}里的code会不会被执行，什么时候被执行，在return前还是后？ </P>
<P>　　第二十八，编程题： 用最有效率的方法算出2乘以8等於几？ </P>
<P>　　第二十九，两个对象值相同（x.equals（y） == true），但却可有不同的hash code，这句话对不对？ </P>
<P>　　第三十，当一个对象被当作参数传递到一个方法后，此方法可改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递？ </P>
<P>　　第三十一，swtich是否能作用在byte上，是否能作用在long上，是否能作用在String上？ </P>
<P>　　第三十二，编程题： 写一个Singleton出来。 </P>
<P>　　以下是答案 </P>
<P>　　第一，谈谈final， finally， finalize的区别。 </P>
<P>　　final—修饰符（关键字）如果一个类被声明为final，意味着它不能再派生出新的子类，不能作为父类被继承。因此一个类不能既被声明为 abstract的，又被声明为final的。将变量或方法声明为final，可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值，而在以后的引用中只能读取，不可修改。被声明为final的方法也同样只能使用，不能重载finally—再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常，那么相匹配的 catch 子句就会执行，然后控制就会进入 finally 块（如果有的话）。 </P>
<P>　　finalize—方法名。Java 技术允许使用 finalize（） 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的，因此所有的类都继承了它。子类覆盖 finalize（） 方法以整理系统资源或者执行其他清理工作。finalize（） 方法是在垃圾收集器删除对象之前对这个对象调用的。 </P>
<P>　　第二，Anonymous Inner Class （匿名内部类） 是否可以extends（继承）其它类，是否可以implements（实现）interface（接口）？ </P>
<P>　　匿名的内部类是没有名字的内部类。不能extends（继承） 其它类，但一个内部类可以作为一个接口，由另一个内部类实现。 </P>
<P>　　第三，Static Nested Class 和 Inner Class的不同，说得越多越好（面试题有的很笼统）。 </P>
<P>　　Nested Class （一般是C++的说法），Inner Class （一般是JAVA的说法）。Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用上。具体可见http： //www.frontfree.net/articles/services/view.asp？id=704&amp;page=1注： 静态内部类（Inner Class）意味着1创建一个static内部类的对象，不需要一个外部类对象，2不能从一个static内部类的一个对象访问一个外部类对象 </P>
<P>　　第四，&amp;和&amp;&amp;的区别。 </P>
<P>　　&amp;是位运算符。&amp;&amp;是布尔逻辑运算符。 </P>
<P>　　第五，HashMap和Hashtable的区别。 </P>
<P>　　都属于Map接口的类，实现了将惟一键映射到特定的值上。 </P>
<P>　　HashMap 类没有分类或者排序。它允许一个 null 键和多个 null 值。 </P>
<P>　　Hashtable 类似于 HashMap，但是不允许 null 键和 null 值。它也比 HashMap 慢，因为它是同步的。 </P>
<P>　　第六，Collection 和 Collections的区别。 </P>
<P>　　Collections是个java.util下的类，它包含有各种有关集合操作的静态方法。 </P>
<P>　　Collection是个java.util下的接口，它是各种集合结构的父接口。 </P>
<P>　　第七，什么时候用assert.断言是一个包含布尔表达式的语句，在执行这个语句时假定该表达式为 true.如果表达式计算为 false，那么系统会报告一个 AssertionError.它用于调试目的：assert（a &gt; 0）； // throws an AssertionError if a &lt;= 0断言可以有两种形式：assert Expression1 ；assert Expression1 ： Expression2 ；Expression1 应该总是产生一个布尔值。 </P>
<P>　　Expression2 可以是得出一个值的任意表达式。这个值用于生成显示更多调试信息的 String 消息。 </P>
<P>　　断言在默认情况下是禁用的。要在编译时启用断言，需要使用 source 1.4 标记：javac -source 1.4 Test.java要在运行时启用断言，可使用 -enableassertions 或者 -ea 标记。 </P>
<P>　　要在运行时选择禁用断言，可使用 -da 或者 -disableassertions 标记。 </P>
<P>　　要系统类中启用断言，可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。 </P>
<P>　　可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过，断言不应该用于验证传递给公有方法的参数，因为不管是否启用了断言，公有方法都必须检查其参数。不过，既可以在公有方法中，也可以在非公有方法中利用断言测试后置条件。另外，断言不应该以任何方式改变程序的状态。 </P>
<P>　　第八，GC是什么？ 为什么要有GC？ （基础）。 </P>
<P>　　GC是垃圾收集器。Java 程序员不用担心内存管理，因为垃圾收集器会自动进行管理。要请求垃圾收集，可以调用下面的方法之一：System.gc（） </P>
<P>　　Runtime.getRuntime（）。gc（） </P>
<P>　　第九，String s = new String（"xyz"）；创建了几个String Object？ </P>
<P>　　两个对象，一个是“xyx”，一个是指向“xyx”的引用对象s. </P>
<P>　　第十，Math.round（11.5）等於多少？ Math.round（-11.5）等於多少？ </P>
<P>　　Math.round（11.5）返回（long）12，Math.round（-11.5）返回（long）-11； </P>
<P>　　第十一，short s1 = 1； s1 = s1 + 1；有什么错？ short s1 = 1； s1 += 1；有什么错？ </P>
<P>　　short s1 = 1； s1 = s1 + 1；有错，s1是short型，s1+1是int型，不能显式转化为short型。可修改为s1 =（short）（s1 + 1） .short s1 = 1； s1 += 1正确。 </P>
<P>　　第十二，sleep（） 和 wait（） 有什么区别？ 搞线程的最爱sleep（）方法是使线程停止一段时间的方法。在sleep 时间间隔期满后，线程不一定立即恢复执行。这是因为在那个时刻，其它线程可能正在运行而且没有被调度为放弃执行，除非（a）“醒来”的线程具有更高的优先级（b）正在运行的线程因为其它原因而阻塞。 </P>
<P>　　wait（）是线程交互时，如果线程对一个同步对象x 发出一个wait（）调用，该线程会暂停执行，被调对象进入等待状态，直到被唤醒或等待时间到。 </P>
<P>　　第十三，Java有没有goto？ </P>
<P>　　Goto—java中的保留字，现在没有在java中使用。 </P>
<P>　　第十四，数组有没有length（）这个方法？ String有没有length（）这个方法？ </P>
<P>　　数组没有length（）这个方法，有length的属性。 </P>
<P>　　String有有length（）这个方法。 </P>
<P>　　第十五，Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型？ </P>
<P>　　方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现，重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数，我们说该方法被重写 （Overriding）。子类的对象使用这个方法时，将调用子类中的定义，对它而言，父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法，它们或有不同的参数个数或有不同的参数类型，则称为方法的重载（Overloading）。Overloaded的方法是可以改变返回值的类型。 </P>
<P>　　第十六，Set里的元素是不能重复的，那么用什么方法来区分重复与否呢？ 是用==还是equals（）？ 它们有何区别？ </P>
<P>　　Set里的元素是不能重复的，那么用iterator（）方法来区分重复与否。equals（）是判读两个Set是否相等。 </P>
<P>　　equals（）和==方法决定引用值是否指向同一对象equals（）在类中被覆盖，为的是当两个分离的对象的内容和类型相配的话，返回真值。 </P>
<P>　　第十七，给我一个你最常见到的runtime exception. ArithmeticException， ArrayStoreException， BufferOverflowException， BufferUnderflowException， CannotRedoException， CannotUndoException， ClassCastException， CMMException， ConcurrentModificationException， DOMException， EmptyStackException， IllegalArgumentException， IllegalMonitorStateException， IllegalPathStateException， IllegalStateException，ImagingOpException， IndexOutOfBoundsException， MissingResourceException， NegativeArraySizeException， NoSuchElementException， NullPointerException， ProfileDataException， ProviderException， RasterFormatException， SecurityException， SystemException， UndeclaredThrowableException， UnmodifiableSetException， UnsupportedOperationException </P>
<P>　　第十八，error和exception有什么区别？ </P>
<P>　　error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。 </P>
<P>　　exception 表示一种设计或实现问题。也就是说，它表示如果程序运行正常，从不会发生的情况。 </P>
<P>　　第十九，List， Set， Map是否继承自Collection接口？ </P>
<P>　　List，Set是 </P>
<P>　　Map不是 </P>
<P>　　第二十，abstract class和interface有什么区别？ </P>
<P>　　声明方法的存在而不去实现它的类被叫做抽象类（abstract class），它用于要创建一个体现某些基本行为的类，并为该类声明方法，但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量，其类型是一个抽象类，并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现，否则它们也是抽象类为。取而代之，在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。 </P>
<P>　　接口（interface）是抽象类的变体。在接口中，所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的，没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似，除了该实现类不能从接口定义中继承行为。当类实现特殊接口时，它定义（即将程序体给予）所有这种接口的方法。然后，它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类，它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换，instanceof 运算符可以用来决定某对象的类是否实现了接口。 </P>
<P>　　第二十一，abstract的method是否可同时是static，是否可同时是native，是否可同时是synchronized？ </P>
<P>　　都不能 </P>
<P>　　第二十二，接口是否可继承接口？ 抽象类是否可实现（implements）接口？ 抽象类是否可继承实体类（concrete class）？ </P>
<P>　　接口可以继承接口。抽象类可以实现（implements）接口，抽象类是否可继承实体类，但前提是实体类必须有明确的构造函数。 </P>
<P>　　第二十三，启动一个线程是用run（）还是start（）？ </P>
<P>　　启动一个线程是调用start（）方法，使线程所代表的虚拟处理机处于可运行状态，这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run（）方法可以产生必须退出的标志来停止一个线程。 </P>
<P>　　第二十四，构造器Constructor是否可被override？ </P>
<P>　　构造器Constructor不能被继承，因此不能重写Overriding，但可以被重载Overloading. </P>
<P>　　第二十五，是否可以继承String类？ </P>
<P>　　String类是final类故不可以继承。 </P>
<P>　　第二十六，当一个线程进入一个对象的一个synchronized方法后，其它线程是否可进入此对象的其它方法？ </P>
<P>　　不能，一个对象的一个synchronized方法只能由一个线程访问。 </P>
<P>　　第二十七，try {}里有一个return语句，那么紧跟在这个try后的finally {}里的code会不会被执行，什么时候被执行，在return前还是后？ </P>
<P>　　会执行，在return前执行。 </P>
<P>　　第二十八，编程题： 用最有效率的方法算出2乘以8等於几？ </P>
<P>　　有C背景的程序员特别喜欢问这种问题。 </P>
<P>　　2 &lt;&lt; 3 </P>
<P>　　第二十九，两个对象值相同（x.equals（y） == true），但却可有不同的hash code，这句话对不对？ </P>
<P>　　不对，有相同的hash code. </P>
<P>　　第三十，当一个对象被当作参数传递到一个方法后，此方法可改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递？ </P>
<P>　　是值传递。Java 编程语言只由值传递参数。当一个对象实例作为一个参数被传递到方法中时，参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变，但对象的引用是永远不会改变的。 </P>
<P>　　第三十一，swtich是否能作用在byte上，是否能作用在long上，是否能作用在String上？ </P>
<P>　　switch（expr1）中，expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte.long，string 都不能作用于swtich. </P>
<P>　　第三十二，编程题： 写一个Singleton出来。 </P>
<P>　　Singleton模式主要作用是保证在Java应用程序中，一个类Class只有一个实例存在。 </P>
<P>　　一般Singleton模式通常有几种种形式：第一种形式： 定义一个类，它的构造函数为private的，它有一个static的private的该类变量，在类初始化时实例话，通过一个public的getInstance方法获取对它的引用，继而调用其中的方法。 </P>
<P>　　public class Singleton { private Singleton（）{} //在自己内部定义自己一个实例，是不是很奇怪？ </P>
<P>　　//注意这是private 只供内部调用private static Singleton instance = new Singleton（）；//这里提供了一个供外部访问本class的静态方法，可以直接访问public static Singleton getInstance（） { return instance；}第二种形式：public class Singleton { private static Singleton instance = null；public static synchronized Singleton getInstance（） { //这个方法比上面有所改进，不用每次都进行生成对象，只是第一次//使用时生成实例，提高了效率！ </P>
<P>　　if （instance==null） </P>
<P>　　instance＝new Singleton（）；return instance； 　　} }其他形式：定义一个类，它的构造函数为private的，所有方法为static的。 </P>
<P>　　一般认为第一种形式要更加安全些第三十三 Hashtable和HashMap Hashtable继承自Dictionary类，而HashMap是Java1.2引进的Map interface的一个实现 </P>
<P>　　HashMap允许将null作为一个entry的key或者value，而Hashtable不允许 </P>
<P>　　还有就是，HashMap把Hashtable的contains方法去掉了，改成containsvalue和containsKey.因为contains方法容易让人引起误解。 </P>
<P>　　最大的不同是，Hashtable的方法是Synchronize的，而HashMap不是，在多个线程访问Hashtable时，不需要自己为它的方法实现同步，而HashMap就必须为之提供外同步。 </P>
<P>　　Hashtable和HashMap采用的hash/rehash算法都大概一样，所以性能不会有很大的差异。 <BR>&nbsp;<BR>&nbsp;<BR></P><img src ="http://www.blogjava.net/Apple/aggbug/6618.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-23 21:20 <a href="http://www.blogjava.net/Apple/archive/2005/06/23/6618.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至赛迪网】Java列表对象的性能分析和测试</title><link>http://www.blogjava.net/Apple/archive/2005/06/23/6615.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 23 Jun 2005 13:07:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/23/6615.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6615.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/23/6615.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6615.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6615.html</trackback:ping><description><![CDATA[SDK提供了有序集合接口java.util.List的几种实现，其中三种最为人们熟知的是Vector、ArrayList和LinkedList。有关这些List类的性能差别是一个经常被问及的问题。在这篇文章中，我要探讨的就是LinkedList和Vector/ArrayList之间的性能差异。 <BR><BR>为全面分析这些类之间的性能差异，我们必须知道它们的实现方法。因此，接下来我首先从性能的角度出发，简要介绍这些类的实现特点。 <BR><BR><CCID><B>一、Vector和ArrayList的实现</B></CCID> <BR><BR><FONT color=#800080>Vector和ArrayList都带有一个底层的Object[]数组</FONT>，这个Object[]数组用来保存元素。通过索引访问元素时，只需简单地通过索引访问内部数组的元素： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width=550 borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6>public Object get(int index)<BR>{ //首先检查index是否合法...此处不显示这部分代码 return<BR>elementData[index]; }</TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>内部数组可以大于Vector/ArrayList对象拥有元素的数量，两者的差值作为剩余空间，以便实现快速添加新元素。有了剩余空间，添加元素变得非常简单，只需把新的元素保存到内部数组中的一个空余的位置，然后为新的空余位置增加索引值： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width=550 borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6>public boolean add(Object o)<BR>{ ensureCapacity(size + 1); //稍后介绍 elementData[size++] = o; return true;<BR>//List.add(Object) 的返回值 }<BR></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>把元素插入集合中任意指定的位置（而不是集合的末尾）略微复杂一点：插入点之后的所有数组元素都必须向前移动一个位置，然后才能进行赋值： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width=550 borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6>public void add(int index, Object element) {<BR>//首先检查index是否合法...此处不显示这部分代码<BR>ensureCapacity(size+1);<BR>System.arraycopy(elementData, index, elementData, index + 1,<BR>size - index);<BR>elementData[index] = element;<BR>size++;<BR>}<BR></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>剩余空间被用光时，如果需要加入更多的元素，Vector/ArrayList对象必须用一个更大的新数组替换其内部Object[]数组，把所有的数组元素复制到新的数组。<FONT color=#008000>根据SDK版本的不同，新的数组要比原来的大50%或者100%</FONT>（下面显示的代码把数组扩大100%）： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width=550 borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6>public void ensureCapacity(int minCapacity) {<BR>int oldCapacity = elementData.length;<BR>if (minCapacity &gt; oldCapacity) {<BR>Object oldData[] = elementData;<BR>int newCapacity = Math.max(oldCapacity * 2, minCapacity);<BR>elementData = new Object[newCapacity];<BR>System.arraycopy(oldData, 0, elementData, 0, size);<BR>}<BR>}<BR></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR><FONT color=#800080><STRONG>Vector类和ArrayList类的主要不同之处在于同步</STRONG>。</FONT>除了两个只用于串行化的方法，没有一个ArrayList的方法具有同步执行的能力；相反，Vector的大多数方法具有同步能力，或直接或间接。因此<FONT color=#800080>，Vector是线程安全的，但ArrayList不是</FONT>。<FONT color=#800080>这使得ArrayList要比Vector快速</FONT>。对于一些最新的JVM，两个类在速度上的差异可以忽略不计：严格地说，对于这些JVM，这两个类在速度上的差异小于比较这些类性能的测试所显示的时间差异。 <BR><BR>通过索引访问和更新元素时，Vector和ArrayList的实现有着卓越的性能，因为不存在除范围检查之外的其他开销。除非内部数组空间耗尽必须进行扩展，否则，向列表的末尾添加元素或者从列表的末尾删除元素时，都同样有着优秀的性能。插入元素和删除元素总是要进行数组复制（当数组先必须进行扩展时，需要两次复制）。被复制元素的数量和[size-index]成比例，即和插入/删除点到集合中最后索引位置之间的距离成比例。对于插入操作，把元素插入到集合最前面（索引0）时性能最差，插入到集合最后面时（最后一个现有元素之后）时性能最好。随着集合规模的增大，数组复制的开销也迅速增加，因为每次插入操作必须复制的元素数量增加了。 <BR><BR><CCID><B>二、LinkedList的实现</B></CCID> <BR><BR>LinkedList通过一个双向链接的节点列表实现。要通过索引访问元素，你必须查找所有节点，直至找到目标节点： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width=550 borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6>public Object get(intindex) {<BR>//首先检查index是否合法...此处不显示这部分代码<BR>Entry e = header; //开始节点<BR>//向前或者向后查找，具体由哪一个方向距离较<BR>//近决定<BR>if (index &lt; size/2) {<BR>for (int i = 0; i &lt;= index; i++)<BR>e = e.next;<BR>} else {<BR>for (int i = size; i &gt; index; i--)<BR>e = e.previous;<BR>}<BR>return e;<BR>}<BR></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>把元素插入列表很简单：找到指定索引的节点，然后紧靠该节点之前插入一个新节点：<BR><BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width=550 borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6>public void add(int index, Object element) {<BR>//首先检查index是否合法...此处不显示这部分代码<BR>Entry e = header; //starting node<BR>//向前或者向后查找，具体由哪一个方向距离较<BR>//近决定<BR>if (index &lt; size/2) {<BR>for (int i = 0; i &lt;= index; i++)<BR>e = e.next;<BR>} else {<BR>for (int i = size; i &gt; index; i--)<BR>e = e.previous;<BR>}<BR>Entry newEntry = new Entry(element, e, e.previous);<BR>newEntry.previous.next = newEntry;<BR>newEntry.next.previous = newEntry;<BR>size++;<BR>}<BR></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>线程安全的LinkedList和其他集合 <BR><BR>如果要从Java SDK得到一个线程安全的LinkedList，你可以利用一个同步封装器从Collections.synchronizedList(List)得到一个。然而，使用同步封装器相当于加入了一个间接层，它会带来昂贵的性能代价。当封装器把调用传递给被封装的方法时，每一个方法都需要增加一次额外的方法调用，经过同步封装器封装的方法会比未经封装的方法慢二到三倍。对于象搜索之类的复杂操作，这种间接调用所带来的开销不是很突出；但对于比较简单的方法，比如访问功能或者更新功能，这种开销可能对性能造成严重的影响。 <BR><BR>这意味着，和Vector相比，经过同步封装的LinkedList在性能上处于显著的劣势，因为Vector不需要为了线程安全而进行任何额外的间接调用。如果你想要有一个线程安全的LinkedList，你可以复制LinkedList类并让几个必要的方法同步，这样你可以得到一个速度更快的实现。对于所有其它集合类，这一点都同样有效：只有List和Map具有高效的线程安全实现（分别是Vector和Hashtable类）。有趣的是，这两个高效的线程安全类的存在只是为了向后兼容，而不是出于性能上的考虑。 <BR><BR>对于通过索引访问和更新元素，LinkedList实现的性能开销略大一点，因为访问任意一个索引都要求跨越多个节点。插入元素时除了有跨越多个节点的性能开销之外，还要有另外一个开销，即创建节点对象的开销。在优势方面，LinkedList实现的插入和删除操作没有其他开销，因此，插入-删除开销几乎完全依赖于插入-删除点离集合末尾的远近。 <BR><BR><CCID><B>三、性能测试</B></CCID> <BR><BR>这些类有许多不同的功能可以进行测试。LinkedList应用比较频繁，因为人们认为它在随机插入和删除操作时具有较好的性能。所以，下面我分析的重点将是插入操作的性能，即，构造集合。我测试并比较了LinkedList和ArrayList，因为这两者都是非同步的。 <BR><BR>插入操作的速度主要由集合的大小和元素插入的位置决定。当插入点的位置在集合的两端和中间时，最差的插入性能和最好的插入性能都有机会出现。因此，我选择了三个插入位置（集合的开头、末尾和中间），三种典型的集合大小：中等（100个元素），大型（10,000个元素），超大型（1,000,000个元素）。 <BR><BR>在本文的测试中，我使用的是JAVA SDK 1.2.0和1.3.0系列的SUN JVM。此外，我还用HOTSPOT JVM 2.0进行了测试，这个版本可以在1.3.0 SDK找到。在下面的表格中，各个测量得到的时间都以其中一次SDK 1.2 VM上的测试时间（表格中显示为100%的单元）为基准显示。测试期间使用了默认的JVM配置，即启用了JIT编译，因此对于所有JVM，堆空间都必须进行扩展，以避免内存溢出错误。表格中记录的时间是多次测试的平均时间。为了避免垃圾收集的影响，在各次测试之间我强制进行了完全的内存清理（参见测试源代码了解详情）。磁盘监测确保磁盘分页不会在测试过程中出现（任何测试，如果它显示出严重的磁盘分页操作，则被丢弃）。所有显示出数秒应答时间的速度太慢的测试都重复进行，直至记录到一个明显合理的时间。 <BR><BR><CCID_NOBR>
<TABLE class=tech style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cols=4 cellPadding=0 width="90%" align=center border=1 rows="8">
<TBODY>
<TR align=middle>
<TD colSpan=4>表1：构造一个中等大小的集合（100个元素）。括号中的数字针对预先确定大小的集合。</TD></TR>
<TR align=middle>
<TD>　</TD>
<TD>1.2 JVM</TD>
<TD>1.3 JVM</TD>
<TD>HotSpot 2.0 JVM</TD></TR>
<TR>
<TD>总是插入到ArrayList的开头</TD>
<TD>100% (48.0%)</TD>
<TD>184.9% (152.0%)</TD>
<TD>108.0% (66.7%)</TD></TR>
<TR>
<TD>总是插入到LinkedList的开头</TD>
<TD>135.5%</TD>
<TD>109.1%</TD>
<TD>85.3%</TD></TR>
<TR>
<TD>总是插入到ArrayList的中间</TD>
<TD>130.0% (40.6%)</TD>
<TD>187.4% (158.0%)</TD>
<TD>84.7% (46.0%)</TD></TR>
<TR>
<TD>总是插入到LinkedList的中间</TD>
<TD>174.0%</TD>
<TD>135.0%</TD>
<TD>102.3%</TD></TR>
<TR>
<TD>总是插入到ArrayList的末尾</TD>
<TD>63.3% (20.7%)</TD>
<TD>65.9% (25.0%)</TD>
<TD>60.3% (29.3%)</TD></TR>
<TR>
<TD>总是插入到LinkedList的末尾</TD>
<TD>106.7%</TD>
<TD>86.3%</TD>
<TD>80.3%</TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>对于规模较小的集合，ArrayList和LinkedList的性能很接近。当元素插入到集合的末尾时，即追加元素时，ArrayList的性能出现了突变。然而，追加元素是ArrayList特别为其优化的一个操作：如果你只想要一个固定大小的静态集合，Java数组（例如Object[]）比任何集合对象都具有更好的性能。除了追加操作，测量得到的时间数据差别不是很大，它们反映了各个JVM的优化程度，而不是其他什么东西。 <BR><BR>例如，对于把元素插入到集合的开始位置来说（表1的前两行），HotSpot 2.0 JVM加LinkedList具有最好的性能（85.3%），处于第二位的是 1.2 JVM加ArrayList（100%）。这两个结果显示出，1.2中简单的JIT编译器在执行迭代和复制数组等简单的操作时具有很高的效率。在HotSpot中复杂的JVM加上优化的编译器能够改进复杂操作的性能，比如对象创建（创建LinkedList节点），并能够利用代码内嵌（code-inlining）的优势。1.3 JVM的结果似乎显示出，在简单操作方面它的性能有着很大的不足，这一点很可能在以后的JVM版本中得到改进。 <BR><BR>在这里我特别进行测试的是ArrayList相对于LinkedList的另一个优点，即预先确定集合大小的能力。具体地说，创建ArrayList的时候允许指定一个具体的大小（例如，在测试中ArrayList可以创建为拥有100个元素的容量），从而避免所有随着元素增多而增加集合规模的开销。表1括号中的数字显示了预先确定集合大小时性能的提高程度。LinkedList（直到 SDK 1.3）不能预先确定大小。 <BR><BR>此外，ArrayList只生成少量的需要进行垃圾收集的对象，即，用来保存元素的内部数组对象，以及每次ArrayList容量不足需要进行扩展时创建的附加内部数组对象。LinkedList不管可能出现的任何删除操作，都为每一个插入操作生成一个节点对象。因此，LinkedList会给垃圾收集器带来相当多的工作。考虑到这些因素，对于任何中小规模的集合，我会选择使用ArrayList而不是LinkedList。 <BR><BR><CCID_NOBR>
<TABLE class=tech style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cols=4 cellPadding=0 width="80%" align=center border=1 rows="8">
<TBODY>
<TR align=middle>
<TD colSpan=4>表2：构造一个大型集合（10,000个元素）</TD></TR>
<TR align=middle>
<TD>　</TD>
<TD>1.2 JVM</TD>
<TD>1.3 JVM</TD>
<TD>HotSpot 2.0 JVM</TD></TR>
<TR>
<TD>总是插入到ArrayList的开头</TD>
<TD>7773%</TD>
<TD>7537%</TD>
<TD>7500%</TD></TR>
<TR>
<TD>总是插入到LinkedList的开头</TD>
<TD>100%</TD>
<TD>90.34%</TD>
<TD>65.6%</TD></TR>
<TR>
<TD>总是插入到ArrayList的中间</TD>
<TD>3318%</TD>
<TD>3412%</TD>
<TD>3121%</TD></TR>
<TR>
<TD>总是插入到LinkedList的中间</TD>
<TD>26264%</TD>
<TD>14315%</TD>
<TD>14209%</TD></TR>
<TR>
<TD>总是插入到ArrayList的末尾</TD>
<TD>41.4%</TD>
<TD>41.2%</TD>
<TD>37.5%</TD></TR>
<TR>
<TD>总是插入到LinkedList的末尾</TD>
<TD>66.4%</TD>
<TD>73.9%</TD>
<TD>61.7%</TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>表2显示了大规模集合的测试结果。可以看到，在出现大规模插入操作的时候，我们开始遭遇严厉的性能惩罚。正如我们前面分析类的实现所得到的结果，对于LinkedList来说最差的情形出现在把元素插入到集合中间时。另外我们还可以看到，与使用ArrayList时把元素插入到集合开头的最差性能相比，使用LinkedList时把元素插入到集合中间的性能更差一些。和这两种性能最差的情况相比，把元素插入到ArrayList中间的性能显然要好得多。 <BR><BR>总地看来，ArrayList再一次在大多数情形下表现出更好的性能，包括根据索引把元素插入到随机位置的情形。如果你总是要把元素插入到集合中靠前的位置，LinkedList具有更好的性能；然而，此时你可以利用一个反向的ArrayList得到更好的性能，即，使用一个专用的实现，或者通过[size -index]映射翻转索引在集合中的位置。<BR><BR><BR><CCID_NOBR>
<TABLE class=tech style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cols=4 cellPadding=0 width="80%" align=center border=1 rows="8">
<TBODY>
<TR align=middle>
<TD colSpan=4>表3：构造一个超大集合（1,000,000个元素）</TD></TR>
<TR align=middle>
<TD>　</TD>
<TD>1.2 JVM</TD>
<TD>1.3 JVM</TD>
<TD>HotSpot 2.0 JVM</TD></TR>
<TR>
<TD>总是插入到ArrayList的开头</TD>
<TD>太长</TD>
<TD>太长</TD>
<TD>太长</TD></TR>
<TR>
<TD>总是插入到LinkedList的开头</TD>
<TD>100%</TD>
<TD>179.5%</TD>
<TD>144.1%</TD></TR>
<TR>
<TD>总是插入到ArrayList的中间</TD>
<TD>太长</TD>
<TD>太长</TD>
<TD>太长</TD></TR>
<TR>
<TD>总是插入到LinkedList的中间</TD>
<TD>太长</TD>
<TD>太长</TD>
<TD>太长</TD></TR>
<TR>
<TD>总是插入到ArrayList的末尾</TD>
<TD>38.3%</TD>
<TD>47.7%</TD>
<TD>42.9%</TD></TR>
<TR>
<TD>总是插入到LinkedList的末尾</TD>
<TD>65.1%</TD>
<TD>161.5%</TD>
<TD>139.9%</TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>表3显示了超大集合的测试结果，从该表可以得出的结论与表2非常相似。然而，表3强调的是，超大集合要求数据、集合类型、数据处理算法之间的恰到好处的配合；否则，你将得到事实上不可接受的性能表现。至于性能优化，你可以构造一个针对该问题的专用集合类。对于超大集合来说，为了获得可接受的性能，构造专用集合类往往是很有必要的。 <BR><BR><CCID><B>四、查询的性能</B></CCID> <BR><BR>在类的内部实现查询时查询的性能最高。对于查询这些列表来说，迭代所有元素所需要的时间是一个限制因素。ArrayList/Vector类中实现的查询将对类的元素进行迭代。下面的例子计算空元素的总数量： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=0 width=550 borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6>int count = 0;<BR>for (int i = 0; i &lt; size; i++)<BR>if(elementData[i] == null)<BR>count++;LinkedList类中实现的查询将搜索所有的节点。下面的例子计算所有空元素的总数量：<BR>node = header.next;<BR>count = 0;<BR>for (int i = 0; i &lt; repeat; i++, node = node.next)<BR>if (node.element == null)<BR>count++;<BR></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>表4显示出，ArrayList的性能显著地超过了LinkedList，它再一次显示出ArrayList应该是我们首选的类。表5显示了利用从List.listIterator(int)获得的ListIterator对象迭代所有元素所需要的时间，如果查询机制不能在List内部实现，这些迭代器是必需的。ArrayList再一次显示出了较高的性能，但这次性能的差异程度不象表4显示的那样不可思议。注意，表5所显示的绝对时间相当于表4显示绝对时间的10倍，即，ArrayList内部遍历大约比ArrayList利用ListIterator迭代要快10倍。<BR><BR><BR><CCID_NOBR>
<TABLE class=tech style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cols=4 width="80%" align=center border=1 rows="4">
<TBODY>
<TR align=middle>
<TD colSpan=4>表4：通过内部访问迭代集合中的所有元素</TD></TR>
<TR align=middle>
<TD>　</TD>
<TD>1.2 JVM</TD>
<TD>1.3 JVM</TD>
<TD>HotSpot 2.0 JVM</TD></TR>
<TR>
<TD>ArrayList内部搜索</TD>
<TD>100%</TD>
<TD>106%</TD>
<TD>197%</TD></TR>
<TR>
<TD>LinkedList内部搜索</TD>
<TD>470%</TD>
<TD>493%</TD>
<TD>448%</TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR><CCID_NOBR>
<TABLE class=tech style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cols=4 width="80%" align=center border=1 rows="3">
<TBODY>
<TR align=middle>
<TD colSpan=4>表5：通过ListIterator遍历集合中的所有元素</TD></TR>
<TR align=middle>
<TD>　</TD>
<TD>1.2 JVM</TD>
<TD>1.3 JVM</TD>
<TD>HotSpot 2.0 JVM</TD></TR>
<TR>
<TD>利用ListIterator迭代ArrayList</TD>
<TD>100%</TD>
<TD>118%</TD>
<TD>75.2%</TD></TR>
<TR>
<TD>利用ListIterator迭代ListedList</TD>
<TD>117%</TD>
<TD>186%</TD>
<TD>156%</TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR><CCID><B>结束语</B></CCID> <BR><BR>实际测量和我们所考虑的其他因素都清楚地显示出，ArrayList和Vector通常比LinkedList和同步封装之后的LinkedList有着更好的性能。即使在你认为LinkedList可能提供更高性能的情况下，你也可以通过修改元素加入的方式从ArrayList争取更好的性能，例如翻转集合元素的次序。 <BR><BR>有些情况下LinkedList会有更好的性能，例如，当大量元素需要同时加入到大型集合的开头和末尾时。但一般而言，我建议你优先使用ArrayList/Vector类，只有当它们存在明显的性能问题而LinkedList能够改进性能时，才使用LinkedList。<img src ="http://www.blogjava.net/Apple/aggbug/6615.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-23 21:07 <a href="http://www.blogjava.net/Apple/archive/2005/06/23/6615.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】java与C＋＋的区别</title><link>http://www.blogjava.net/Apple/archive/2005/06/23/6612.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 23 Jun 2005 12:42:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/23/6612.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6612.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/23/6612.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6612.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6612.html</trackback:ping><description><![CDATA[<SPAN class=myp111><FONT id=zoom>JAVA和C++都是面向对象语言。也就是说，它们都能够实现面向对象思想（封装，继乘，多态）。而由于c++为了照顾大量的C语言使用者，而兼容了C，使得自身仅仅成为了带类的C语言，多多少少影响了其面向对象的彻底性！<FONT color=#800080>JAVA则是完全的面向对象语言</FONT>，它句法更清晰，规模更小，更易学。它是在对多种程序设计语言进行了深入细致研究的基础上，摒弃了其他语言的不足之处，从根本上解决了c++的固有缺陷。 <BR><BR>Java和c++的相似之处多于不同之处，但两种语言有几处主要的不同使得Java更容易学习，并且编程环境更为简单。 <BR><BR>我在这里不能完全列出不同之处，仅列出比较显著的区别： <BR><BR><B>1．指针</B> <BR><BR>JAVA语言让<FONT color=#006400>编程者</FONT><FONT color=#006400>无法找到指针来直接访问内存</FONT>无指针，并且增添了<FONT color=#006400>自动的内存管理功能</FONT>，从而有效地防止了c／c++语言中指针操作失误，如野指针所造成的系统崩溃。<FONT color=#006400>但也不是说JAVA没有指针，虚拟机内部还是使用了指针，只是外人不得使用而已</FONT>。这有利于Java程序的安全。 <BR><BR><B>2．多重继承</B> <BR><BR>c++支持多重继承，这是c++的一个特征，它允许多父类派生一个类。尽管多重继承功能很强，但使用复杂，而且会引起许多麻烦，编译程序实现它也很不容易。Java不支持多重继承，但允许一个类继承多个接口(extends+implement)，实现了c++多重继承的功能，又避免了c++中的多重继承实现方式带来的诸多不便。 <BR><BR><B>3．数据类型及类</B> <BR><BR><FONT color=#006400>Java是完全面向对象的语言，所有函数和变量都必须是类的一部分</FONT>。除了基本数据类型之外，其余的都作为类对象，包括数组。对象将数据和方法结合起来，把它们封装在类中，这样每个对象都可实现自己的特点和行为。而c++允许将函数和变量定义为全局的。此外，<FONT color=#006400>Java中取消了c／c++中的结构和联合</FONT>，消除了不必要的麻烦。 <BR><BR><B>4．自动内存管理 </B><BR><BR>Java程序中所有的对象都是用new操作符建立在内存<FONT color=#ff1493>堆栈</FONT>上，这个操作符类似于c++的new操作符。下面的语句由一个建立了一个类Read的对象，然后调用该对象的work方法： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>Read r＝new Read()； 
r.work()；</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>语句Read r＝new Read()；在堆栈结构上建立了一个Read的实例。<FONT color=#ff1493>Java自动进行无用内存回收操作</FONT>，不需要程序员进行删除。而c十十中必须由程序员释放内存资源，增加了程序设计者的负担。Java中当一个对象不被再用到时，无用内存回收器将给它加上标签以示删除。<FONT color=#800080>JAVA里无用内存回收程序是以线程方式在后台运行的，利用空闲时间工作</FONT>。 <BR><BR><B>5．操作符重载 </B><BR><BR><FONT color=#800080>Java不支持操作符重载</FONT>。操作符重载被认为是c十十的突出特征，在Java中虽然类大体上可以实现这样的功能，但操作符重载的方便性仍然丢失了不少。Java语言不支持操作符重载是为了保持Java语言尽可能简单。 <BR><BR><B>6．预处理功能 </B><BR><BR>Java不支持预处理功能。c／c十十在编译过程中都有一个预编泽阶段，即众所周知的预处理器。预处理器为开发人员提供了方便，但增加了编译的复杂性。JAVA虚拟机没有预处理器，但它提供的引入语句(import)与c十十预处理器的功能类似。 <BR><BR><B>7. Java不支持缺省函数参数，而c十十支持</B> <BR><BR>在c中，代码组织在函数中，函数可以访问程序的全局变量。c十十增加了类，提供了类算法，该算法是与类相连的函数，c十十类方法与Java类方法十分相似，然而，由于c十十仍然支持c，所以不能阻止c十十开发人员使用函数，结果函数和方法混合使用使得程序比较混乱。 <BR><BR>Java没有函数，作为一个比c十十更纯的面向对象的语言，Java强迫开发人员把所有例行程序包括在类中，事实上，用方法实现例行程序可激励开发人员更好地组织编码。 <BR><BR><B>8 字符串 </B><BR><BR><FONT color=#800080>c和c十十不支持字符串变量</FONT>，在c和c十十程序中使用Null终止符代表字符串的结束，在Java中字符串是用类对象(string和stringBuffer)来实现的，这些类对象是Java语言的核心，用类对象实现字符串有以下几个优点： <BR><BR>(1)在整个系统中建立字符串和访问字符串元素的方法是一致的； <BR><BR>(2)J3阳字符串类是作为Java语言的一部分定义的，而不是作为外加的延伸部分； <BR><BR>(3)Java字符串执行运行时检空，可帮助排除一些运行时发生的错误； <BR><BR>(4)可对字符串用“十”进行连接操作。 <BR><BR><B>9“goto语句 </B><BR><BR>“可怕”的goto语句是c和c++的“遗物”，它是该语言技术上的合法部分，引用goto语句引起了程序结构的混乱，不易理解，goto语句子要用于无条件转移子程序和多结构分支技术。鉴于以广理由，Java不提供goto语句，它虽然指定goto作为关键字，但不支持它的使用，使程序简洁易读。 <BR><BR><B>l0．类型转换 </B><BR><BR>在c和c十十中有时出现数据类型的隐含转换，这就涉及了自动强制类型转换问题。例如，在c十十中可将一浮点值赋予整型变量，并去掉其尾数。Java不支持c十十中的自动强制类型转换，如果需要，必须由程序显式进行强制类型转换。 <BR><BR><B>11.异常 </B><BR><BR>JAVA中的异常机制用于捕获例外事件，增强系统容错能力 <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>try{／／可能产生例外的代码 
}catch(exceptionType name){ 
//处理 
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>其中exceptionType表示异常类型。而C++则没有如此方便的机制。 <BR></FONT></SPAN><img src ="http://www.blogjava.net/Apple/aggbug/6612.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-23 20:42 <a href="http://www.blogjava.net/Apple/archive/2005/06/23/6612.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【6.22日总结】GEF项目体会</title><link>http://www.blogjava.net/Apple/archive/2005/06/22/6570.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Wed, 22 Jun 2005 13:28:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/22/6570.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6570.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/22/6570.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6570.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6570.html</trackback:ping><description><![CDATA[&nbsp;&nbsp; 今天只写了一小部分，先贴出来，以后继续。<BR>&nbsp;&nbsp; 
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="mso-spacerun: yes">&nbsp;</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">离开</SPAN><SPAN lang=EN-US>GEF</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">项目组已经一个多月了，但是那段日子给我很深的印象，因为那仿佛是我真正下定决心，学习成为一个好的</SPAN><SPAN lang=EN-US>IT</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">人员之路的开始。而且开始积累编程经验。</SPAN><SPAN lang=EN-US>GEF</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">并不简单，面对这个拦路虎，我很是兴奋，想把那段时间的学习体会以及我们的项目来个大致的介绍。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">&nbsp;&nbsp; </SPAN></SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">我们的项目是一个</SPAN><SPAN lang=EN-US>Eclipse</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">平台上的用于本体建模的图形编辑器，大家决定采用</SPAN><SPAN lang=EN-US>GEF</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">框架，因为它非常适合开发带有调色板的以编辑器为主要部件的应用程序。刚开始一个星期，非常迷茫，不知从何下手。这里要非常感谢八进制的</SPAN><SPAN lang=EN-US>blog</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，他写的关于</SPAN><SPAN lang=EN-US>GEF</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的系列给了我很大的帮助，至今我还有很多没有完全消化的地方。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p>&nbsp;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 18pt; TEXT-INDENT: -18pt; mso-list: l0 level1 lfo1; tab-stops: list 18.0pt"><SPAN lang=EN-US style="mso-bidi-font-family: 宋体"><SPAN style="mso-list: Ignore">一.<SPAN style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </SPAN></SPAN></SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">入门</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 17.95pt; TEXT-INDENT: 15.75pt; mso-para-margin-left: 1.71gd; mso-char-indent-count: 1.5"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">我们选取了一个</SPAN><SPAN lang=EN-US>GefPractice</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的例子作为学习的对象。它的结构很清楚，包划分得很容易理解<SPAN style="COLOR: green">（现在发现包的划分基本是按照</SPAN></SPAN><SPAN lang=EN-US style="COLOR: green">gef api</SPAN><SPAN style="COLOR: green; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中包的划分来的）</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。它实现了一个简单得编辑器的功能，可以创建节点、编辑节点和删除节点，还可以创建节点之间的连接。它一共划分了</SPAN><SPAN lang=EN-US>10</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">个包：</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 57pt; TEXT-INDENT: -36pt; mso-list: l0 level2 lfo1; tab-stops: list 57.0pt"><SPAN lang=EN-US style="mso-fareast-font-family: 'Times New Roman'"><SPAN style="mso-list: Ignore">1．<SPAN style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></SPAN><SPAN lang=EN-US>ui</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">包：这里是用户界面，主要是编辑器</SPAN><SPAN lang=EN-US>editor,</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">编辑器一般是继承</SPAN><SPAN lang=EN-US>GraphicalEditorWithPalette</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，这样我们就可以得到一个</SPAN><SPAN lang=EN-US>GraphicalViewer</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">和一个</SPAN><SPAN lang=EN-US>PaletteViewer</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。我认为学习从</SPAN><SPAN lang=EN-US>ui</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">入手比较简单，因为我们一般和</SPAN><SPAN lang=EN-US>editor</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">接触比较多，对于</SPAN><SPAN lang=EN-US>GEF</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">内部工作机制的了解也应从</SPAN><SPAN lang=EN-US>editor</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">开始，这样更直观。</SPAN><SPAN lang=EN-US>Editor</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中有几个重要的概念：</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 60pt; TEXT-INDENT: -18pt; mso-list: l0 level3 lfo1; tab-stops: list 60.0pt"><SPAN lang=EN-US style="mso-fareast-font-family: 'Times New Roman'"><SPAN style="mso-list: Ignore">①<SPAN style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在</SPAN><SPAN lang=EN-US>editor</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的构造函数中，调用</SPAN><SPAN lang=EN-US>setEditDomain()</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">方法，而使用的一般就是</SPAN><SPAN lang=EN-US>DefaultEditDomain(),</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">它是</SPAN><SPAN lang=EN-US>EditDomain</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的一个默认实现。它是一个</SPAN><SPAN lang=EN-US>GEF</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">程序的状态集合，包括命令栈，一个或多个</SPAN><SPAN lang=EN-US>EditpartViewer</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，和当前的活动工具。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 60pt; TEXT-INDENT: -18pt; mso-list: l0 level3 lfo1; tab-stops: list 60.0pt"><SPAN lang=EN-US style="mso-fareast-font-family: 'Times New Roman'"><SPAN style="mso-list: Ignore">②<SPAN style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">两个方法：</SPAN><SPAN lang=EN-US>configureGraphicalViewer()</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">和</SPAN><SPAN lang=EN-US>initialGraphicalViewer(). configureCraphicalViewer()</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">方法中替</SPAN><SPAN lang=EN-US>graphicalViewer</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">设置</SPAN><SPAN lang=EN-US>rootEditPart</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，设置</SPAN><SPAN lang=EN-US>EditPartFactory</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。</SPAN><SPAN lang=EN-US>InitalGraphicalViewer()</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">方法中，替</SPAN><SPAN lang=EN-US>graphicalViewer</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">设置</SPAN><SPAN lang=EN-US>contentProvider</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，一般是画布</SPAN><SPAN lang=EN-US>(model</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">包中</SPAN><SPAN lang=EN-US>)</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，设置拖放监听器</SPAN><SPAN lang=EN-US>(</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">这个以后还要重点说明</SPAN><SPAN lang=EN-US>)</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 60pt; TEXT-INDENT: -18pt; mso-list: l0 level3 lfo1; tab-stops: list 60.0pt"><SPAN lang=EN-US style="mso-fareast-font-family: 'Times New Roman'"><SPAN style="mso-list: Ignore">③<SPAN style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></SPAN><SPAN lang=EN-US>GetAdapt()</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">方法，应该是视图之间的切换</SPAN><SPAN lang=EN-US>(</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">这个现在还不太清楚</SPAN><SPAN lang=EN-US>)</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 60pt; TEXT-INDENT: -18pt; mso-list: l0 level3 lfo1; tab-stops: list 60.0pt"><SPAN lang=EN-US style="mso-fareast-font-family: 'Times New Roman'"><SPAN style="mso-list: Ignore">④<SPAN style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">内部类继承</SPAN><SPAN lang=EN-US>ContentOutlinePage</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">实现大纲视图。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><o:p>&nbsp;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>2.model</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">包：</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><STRONG><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">模型</SPAN></STRONG><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">：<SPAN lang=EN-US>GEF</SPAN>的模型只与控制器打交道，而不知道任何与视图有关的东西。为了能让控制器知道模型的变化，应该把控制器作为事件监听者注册在模型中，当模型发生变化时，就触发相应的事件给控制器，后者负责通知各个视图进行更新。</SPAN><SPAN lang=EN-US style="FONT-FAMILY: 宋体"><o:p></o:p></SPAN></P>
<H3 style="MARGIN: 13pt 0cm"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><FONT size=5>类之间的层次关系</FONT></SPAN></H3>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在</SPAN><SPAN lang=EN-US>org.sklse.oed</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">本题编辑器项目中，创建了</SPAN><SPAN lang=EN-US>org.sklse.oed.model</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">包来装载模型，在设计中，我们结合</SPAN><SPAN lang=EN-US>java</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">程序面向对象的特点，将创建的类抽象成三个层次，如下图：</SPAN></P><SPAN lang=EN-US><?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><v:shapetype id=_x0000_t75 stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype>
<P align=center><SPAN lang=EN-US style="FONT-SIZE: 10.5pt; FONT-FAMILY: 'Times New Roman'; mso-bidi-font-size: 12.0pt; mso-fareast-font-family: 宋体; mso-font-kerning: 1.0pt; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA"><v:shapetype id=_x0000_t75 stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><IMG style="WIDTH: 530px; HEIGHT: 500px" height=500 alt=o_tmp.JPG src="http://www.blogjava.net/images/blogjava_net/apple/1475/o_tmp.JPG" width=640 border=0>&nbsp;</P><SPAN lang=EN-US><v:shapetype id=_x0000_t75 stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype>
<P align=center>
<P></P></SPAN><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype></SPAN></SPAN><B><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">根类：</SPAN><SPAN lang=EN-US>AbstractModel.class<o:p></o:p></SPAN></B> 
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>AbstractModel</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">作为整个模块的根类，实现模型层接点和连线公用的监听接口，属性变化监听事件。主要是通过</SPAN><SPAN lang=EN-US>implements IpropertySource</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">来实现的，</SPAN><SPAN lang=EN-US>IpropertySource</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">是</SPAN><SPAN lang=EN-US>eclipse</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中关于属性视图开发方面的接口。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><o:p>&nbsp;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><B><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">结点父类：</SPAN><SPAN lang=EN-US>AbstractNodeModel.class<o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">该类抽象出结点公用的一些属性和方法，比如：定义了</SPAN><SPAN lang=EN-US>P_CONSTRAINT</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">、</SPAN><SPAN lang=EN-US>P_TEXT</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">、</SPAN><SPAN lang=EN-US>P_SOURCE_CONNECTION</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">、</SPAN><SPAN lang=EN-US>P_TARGET_CONNECTION</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">等属性，其中</SPAN><SPAN lang=EN-US>P_CONSTRAINT</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">用来布局的，</SPAN><SPAN lang=EN-US>P_TEXT</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">是模型的名字，而</SPAN><SPAN lang=EN-US>P_SOURCE_CONNECTION</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">、</SPAN><SPAN lang=EN-US>P_TARGET_CONNECTION</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">用来存放模型的源连接和目的连接的链表。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">然后根据这些属性定义一系列相关的操作，比如：</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>public void addSourceConnection(Object connx) {</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>sourceConnections.add(connx);</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>firePropertyChange(P_SOURCE_CONNECTION, null, null);</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">上面就是一个很典型的方法，目的是给模型对象添加一个源连接，参数</SPAN><SPAN lang=EN-US>connx</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">表示源连接，</SPAN><SPAN lang=EN-US>sourceConnections.add(connx);</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">是源链表的添加方法，</SPAN><SPAN lang=EN-US>firePropertyChange(P_SOURCE_CONNECTION, null, null)</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">方法来激活属性变化事件。正如</SPAN><SPAN lang=EN-US>mvc</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">模式的思想一样，在模型被修改的同时，通过消息事件通知控制层（</SPAN><SPAN lang=EN-US>firePropertyChange</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">），后者根据模型变化来修改视图。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><o:p>&nbsp;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><B><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">连线父类：</SPAN><SPAN lang=EN-US>AbstractConnectionModel.class<o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">该类抽象出连线公用的一些属性和方法，连线最重要的属性就是</SPAN><SPAN lang=EN-US>P_BEND_POINT </SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，我把它理解成拐点，即可以通过拖动这个拐点来折变连线。一条连线就必须有源接点和目的接点，即</SPAN><SPAN lang=EN-US>source, target</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，在</SPAN><SPAN lang=EN-US>model</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">层增加一个连线，也就等于在</SPAN><SPAN lang=EN-US>source</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">和</SPAN><SPAN lang=EN-US>target</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的源接点连表和目的接点链表上增加这条记录。有这类方法：</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>public void attachSource() {<SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>if (!source.getModelSourceConnections().contains(this))</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 3">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>source.addSourceConnection(this);</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">对于</SPAN><SPAN lang=EN-US>P_BEND_POINT</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">属性的变化也必须有一套方法：</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>public void addBendpoint(int index, Point point) {</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>bendpoints.add(index, point);</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>firePropertyChange(P_BEND_POINT, null, null);</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><o:p>&nbsp;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><B><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">画板模型：</SPAN><SPAN lang=EN-US>ContentModel.class<o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">编辑区的画板模型</SPAN><SPAN lang=EN-US>,</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">相当于母版，其他图形都是添加到它上面的。</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>//</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">定义一个属性</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>public static final String P_CHILDREN = "_children";</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>//</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">定义一个存放孩子接点的链表</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>private List children = new ArrayList(); </SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US>public void addChild(Object child) {//</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">添加孩子结点</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>children.add(child); </SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>firePropertyChange(P_CHILDREN, null, null);//</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">激活属性变化事件</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}</SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><o:p>&nbsp;</o:p></SPAN></P><img src ="http://www.blogjava.net/Apple/aggbug/6570.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-22 21:28 <a href="http://www.blogjava.net/Apple/archive/2005/06/22/6570.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至八进制】GEF入门系列（四、其他功能）</title><link>http://www.blogjava.net/Apple/archive/2005/06/22/6534.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Wed, 22 Jun 2005 06:35:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/22/6534.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6534.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/22/6534.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6534.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6534.html</trackback:ping><description><![CDATA[<U><FONT color=#800080>GEF入门系列（四、其他功能） </FONT></U>
<DIV class=postText>
<P>最近由于实验室任务繁重，一直没有继续研究GEF，本来已经掌握的一些东西好象又丢掉了不少，真是无奈啊，看来还是要经常碰碰。刚刚接触GEF的朋友大都会有这样的印象：GEF里概念太多，比较绕，一些能直接实现的功能非要拐几个弯到另一个类里做，而且很多类的名字十分相似，加上不知道他们的作用，感觉就好象一团乱麻。我觉得这种情况是由图形用户界面（GUI）的复杂性所决定的，GUI看似简单，实际上包含了相当多的逻辑，特别是GEF处理的这种图形编辑方式，可以说是最复杂的一种。GEF里每一个类，应该说都有它存在的理由，我们要尽可能了解作者的意图，这就需要多看文档和好的例子。</P>
<P>在Eclipse里查看文档和代码相当便利，比如我们对某个类的用法不清楚，一般首先找它的注释（选中类或方法按F2），其次可以查看它在其他地方用法（选中类或方法按Ctrl+Shift+G），还可以找它的源代码（Ctrl+鼠标左键或F3）来看，另外Ctrl+Shift+T可以按名称查找一个类等等。学GEF是少不了看代码的，当然还需要时间和耐心。</P>
<P>好，闲话少说，下面进入正题。这篇帖子将继续上一篇内容，主要讨论如何实现DirectEdit、属性页和大纲视图，这些都是一个完整GEF应用程序需要提供的基本功能。</P>
<P><STRONG>实现DirectEdit</STRONG></P>
<P>所谓DirectEdit（也称In-Place-Edit），就是允许用户在原本显示内容的地方直接对内容进行修改，例如在Windows资源管理器里选中一个文件，然后按F2键就可以开始修改文件名。实现DirectEdit的原理很直接：当用户发出修改请求（REQ_DIRECT_EDIT）时，就在文字内容所在位置覆盖一个文本框（也可以是下拉框，这里我们只讨论文本的情况）作为编辑器，编辑结束后，再将编辑器中的内容应用到模型里即可。（作为类似的功能请参考：<A href="http://www.cnblogs.com/bjzhanghao/archive/2005/01/25/97336.html">给表格的单元格增加编辑功能</A>）</P>
<P align=center><IMG height=112 alt=directedit.gif src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/directedit.gif" width=278 border=0><BR>图1 Direct Edit</P>
<P>在GEF里，这个弹出的编辑器由DirectEditManager类负责管理，在我们的NodePart类里，通过覆盖performRequest()方法响应用户的DirectEdit请求，在这个方法里一般要构造一个DirectEditManager类的实例（例子中的NodeDirectEditManager），并传入必要的参数，包括接受请求的EditPart（就是自己，this）、编辑器类型（使用TextCellEditor）以及用来定位编辑器的CellEditorLocator（NodeCellEditorLocator），然后用show()方法使编辑器显示出来，而编辑器中显示的内容已经在构造方法里得到。简单看一下NodeCellEditorLocator类，它的关键方法在relocate()里，当编辑器里的内容改变时，这个方法被调用从而让编辑器始终处于正确的坐标位置。DirectEditManager有一个重要的initCellEditor()方法，它的主要作用是设置编辑器的初始值。在我们的例子里，初始值设置为被编辑NodePart对应模型 （Node）的name属性值；这里还另外完成了设置编辑器字体和选中全部文字（selectAll）的功能，因为这样更符合一般使用习惯。</P>
<P>在NodePart里还要增加一个角色为DIRECT_EDIT_ROLE的EditPolicy，它应该继承自DirectEditPolicy，有两个方法需要实现：getDirectEditCommand()和showCurrentEditValue()，虽然还未遇到过，但前者的作用你不应该感到陌生--在编辑结束时生成一个Command对象将修改结果作用到模型；后者的目的是更新Figure中的显示，虽然我们的编辑器覆盖了Figure中的文本，似乎并不需要管Figure的显示，但在编辑中时刻保持这两个文本的一致才不会出现"盖不住"的情况，例如当编辑器里的文本较短时。</P>
<P><STRONG>实现属性页</STRONG></P>
<P>在GEF里实现属性页和普通应用程序基本一样，例如我们希望可以通过属性视图（PropertyView）显示和编辑每个节点的属性，则可以让Node类实现IPropertySource接口，并通过一个IPropertyDescriptor[]类型的成员变量描述要在属性视图里显示的那些属性。有朋友问，要在属性页里增加一个属性都该改哪些地方，主要是三个地方：首先要在你的IPropertyDescriptor[]变量里增加对应的描述，包括属性名和属性编辑方式（比如文本或是下拉框，如果是后者还要指定选项列表），其次是getPropertyValue()和setPropertyValue()里增加读取属性值和将结果写入的代码，这两个方法里一般都是像下面的结构（以前者为例）：</P>
<P></P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">public&nbsp;Object&nbsp;getPropertyValue(Object&nbsp;id)&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">if</SPAN><SPAN style="COLOR: #000000">&nbsp;(PROP_NAME.equals(id))<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">return</SPAN><SPAN style="COLOR: #000000">&nbsp;getName();<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">if</SPAN><SPAN style="COLOR: #000000">&nbsp;(PROP_VISIBLE.equals(id))<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">return</SPAN><SPAN style="COLOR: #000000">&nbsp;isVisible()&nbsp;</SPAN><SPAN style="COLOR: #000000">?</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;Integer(</SPAN><SPAN style="COLOR: #000000">0</SPAN><SPAN style="COLOR: #000000">)&nbsp;:&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;Integer(</SPAN><SPAN style="COLOR: #000000">1</SPAN><SPAN style="COLOR: #000000">);<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">return</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">null</SPAN><SPAN style="COLOR: #000000">;<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>}</SPAN></DIV></DIV>
<P>也就是根据要处理的属性名做不同操作。要注意的是，下拉框类型的编辑器是以Integer类型数据代表选中项序号的，而不是int或String，例如上面的代码根据visible属性返回第零项或第一项，否则会出现ClassCastException。</P>
<P align=center><IMG style="WIDTH: 475px; HEIGHT: 153px" height=153 alt=properties.gif src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/properties.gif" width=521 border=0><BR>图2 属性页</P>
<P><STRONG>实现大纲视图</STRONG></P>
<P>在Eclipse里，当编辑器（Editor）被激活时，大纲视图自动通过这个编辑器的getAdapter()方法寻找它提供的大纲（大纲实现IcontentOutlinePage接口）。GEF提供了ContentOutlinePage类用来实现大纲视图，我们要做的就是实现一个它的子类，并重点实现createControl()方法。ContentOutlinePage是org.eclipse.ui.part.Page的一个子类，大纲视图则是PageBookView的子类，在大纲视图中有一个PageBook，包含了很多Page并可以在它们之间切换，切换的依据就是当前活动的Editor。因此，我们在createControl()方法里要做的就是构造这个Page，简化后的代码如下所示：</P>
<P></P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">private&nbsp;Control&nbsp;outline;<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>public&nbsp;OutlinePage()&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;super(</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;TreeViewer());<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>}<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>public&nbsp;</SPAN><SPAN style="COLOR: #0000ff">void</SPAN><SPAN style="COLOR: #000000">&nbsp;createControl(Composite&nbsp;parent)&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;outline&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;getViewer().createControl(parent);<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;getSelectionSynchronizer().addViewer(getViewer());<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;getViewer().setEditDomain(getEditDomain());<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;getViewer().setEditPartFactory(</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;TreePartFactory());<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;getViewer().setContents(getDiagram());<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>}</SPAN></DIV></DIV>
<P>由于我们在构造方法里指定了使用树结构显示大纲，所以createControl()里的第一句就会使outline变量得到一个Tree（见org.eclipse.gef.ui.parts.TreeViewer的代码），第二句把TreeViewer加到选择同步器中，从而让用户不论在大纲或编辑区域里选择EditPart时，另一方都能自动做出同样的选择；最后三行的作用在以前的帖子里都有介绍，总体目的是把大纲视图的模型与编辑区域的模型联系在一起，这样，对于同一个模型我们就有了两个视图，体会到MVC的好处了吧。</P>
<P>实现大纲视图最重要的工作基本就是这些，但还没有完，我们要在init()方法里绑定UNDO/REDO/DELETE等命令到Eclipse主窗口，否则当大纲视图处于活动状态时，主工具条上的这些命令就会变为不可用状态；在 getControl()方法里要返回我们的outline成员变量，也就是指定让这个控件出现在大纲视图中；在dispose()方法里应该把这个TreeViewer从选择同步器中移除；最后，必须在PracticeEditor里覆盖getAdapter()方法，前面说过，这个方法是在Editor激活时被大纲视图调用的，所以在这里必须把我们实现好的OutlinePage返回给大纲视图使用，代码如下：</P>
<P></P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top><SPAN style="COLOR: #000000">public&nbsp;Object&nbsp;getAdapter(Class&nbsp;type)&nbsp;{<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">if</SPAN><SPAN style="COLOR: #000000">&nbsp;(type&nbsp;</SPAN><SPAN style="COLOR: #000000">==</SPAN><SPAN style="COLOR: #000000">&nbsp;IContentOutlinePage.class)<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">return</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;OutlinePage();<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">return</SPAN><SPAN style="COLOR: #000000">&nbsp;super.getAdapter(type);<BR><IMG src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align=top>}</SPAN></DIV></DIV>
<P>这样，树型大纲视图就完成了，见下图。很多GEF应用程序同时具有树型和缩略图两种大纲，实现的基本思路是一样的，但代码会稍微复杂一些，因为这两种大纲一般要通过一个PageBook进行切换，缩略图一般由org.eclipse.draw2d.parts.ScrollableThumbnail负责实现，这里暂时不讲了（也许以后会详细说），你也可以通过看logic例子的LogicEditor这个类的代码来了解。</P>
<P align=center><IMG height=124 alt=outline.gif src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/outline.gif" width=294 border=0><BR>图3 大纲视图</P>
<P>P.S.写这篇帖子的时候，我对例子又做了一些修改，都是和这篇帖子所说的内容相关的，所以如果你以前下载过，会发现那时的代码与现在稍有不同（功能还是完全一样的，<A href="http://www.cnblogs.com/Files/bjzhanghao/gefpractice.zip">下载</A>）。另外要说一下，这个例子并不完善，比如删除一个节点的时候，它的连接就没同时删除，一些键盘快捷键不起作用，还存在很多被注释掉的代码等等。如果有兴趣你可以来修改它们，也是不错的学习途径。</P></DIV><img src ="http://www.blogjava.net/Apple/aggbug/6534.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-22 14:35 <a href="http://www.blogjava.net/Apple/archive/2005/06/22/6534.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至八进制】GEF入门系列（三、应用实例）</title><link>http://www.blogjava.net/Apple/archive/2005/06/22/6528.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Wed, 22 Jun 2005 05:07:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/22/6528.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6528.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/22/6528.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6528.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6528.html</trackback:ping><description><![CDATA[<DIV class=postText>
<P>原文地址：<A href="http://www.cnblogs.com/bjzhanghao/archive/2005/02/19/106000.html">http://www.cnblogs.com/bjzhanghao/archive/2005/02/19/106000.html</A><BR><FONT color=#008000>构造一个GEF应用程序通常分为这么几个步骤：设计模型、设计EditPart和Figure、设计EditPolicy和Command</FONT>，其中EditPart是最主要的一部分，因为在实现它的时候不可避免的要使用到EditPolicy，而后者又涉及到Command。</P>
<P></P>
<P>现在我们来看个例子，它的功能非常简单，用户可以在画布上增加节点（Node）和节点间的连接，可以直接编辑节点的名称以及改变节点的位置，用户可以撤消/重做任何操作，有一个树状的大纲视图和一个属性页。<A href="http://www.cnblogs.com/Files/bjzhanghao/gefpractice.zip">点此下载</A>，这是一个Eclipse的项目打包文件，在Eclipse里导入后运行Run-time Workbench，新建一个扩展名为"gefpractice"的文件就会打开这个编辑器。 </P>
<P align=center><IMG style="WIDTH: 484px; HEIGHT: 577px" height=577 hspace=5 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/practice-editor.gif" width=628 align=baseline> </P>
<P align=center>图1 Practice Editor的使用界面</P>
<P></P>
<P>你可以参考着代码来看接下来的内容了，让我们从模型开始说起。模型是根据应用需求来设计的，所以我们的模型包括代表整个图的Diagram、代表节点的Node和代表连接的Connection这些对象。我们知道，模型是要负责把自己的改变通知给EditPart的，<FONT color=#800080>为了把这个功能分离出来，我们使用名为Element的抽象类专门来实现通知机制，</FONT>然后让其他模型类继承它。Element类里包括一个PropertyChangeSupport类型的成员变量，并提供了addPropertyChangeListener()、removePropertyChangeListener()和fireXXX()方法分别用来注册监听器和通知监听器模型改变事件。在GEF里，模型的监听器就是EditPart，在EditPart的active()方法里我们会把它作为监听器注册到模型中。所以，总共有<STRONG>四</STRONG>个类组成了我们的模型部分。（Element,Node,Diagram,Connection)</P>
<P></P>
<P>在前面的贴子里说过，大部分GEF应用程序都是实现为Editor的，这个例子也不例外，对应的Editor名为PracticeEditor。这个Editor继承了GraphicalEditorWithPalette类，表示它是一个具有调色板的图形编辑器。最重要的两个方法是configureGraphicalViewer()和initializeGraphicalViewer()，分别用来定制和初始化EditPartViewer（关于EditPartViewer的作用请查看前面的帖子），简单查看一下GEF的代码你会发现，在GraphicalEditor类里会先后调用这两个方法，只是中间插了一个hookGraphicalViewer()方法，其作用是同步选择和把EditPartViewer作为SelectionProvider注册到所在的site（Site是Workbench的概念，请查Eclipse帮助）。所以，与选择无关的初始化操作应该在前者中完成，否则放在后者完成。例子中，在这两个方法里我们配置了RootEditPart、用于创建EditPart的EditPartFactory、Contents即Diagram对象和增加了拖放支持，拖动目标是当前EditPartViewer，后面会看到拖动源就是调色板。</P>
<P></P>
<P>这个Editor是带有调色板的，所以要告诉GEF我们的调色板里都有哪些工具，这是通过覆盖getPaletteRoot()方法来实现的。在这个方法里，我们利用自己写的一个工具类PaletteFactory构造一个PaletteRoot对象并返回，我们的调色板里需要有三种工具：选择工具、节点工具和连接工具。在GEF里，调色板里可以有抽屉（PaletteDrawer）把各种工具归类放置，每个工具都是一个ToolEntry，选择工具（SelectionToolEntry）和连接工具（ConnectionCreationToolEntry）是预先定义好的几种工具中的两个，所以可以直接使用。对于节点工具，要使用CombinedTemplateCreationEntry，并把节点类型作为参数之一传给它，创建节点工具的代码如下所示。</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top><SPAN style="COLOR: #000000">ToolEntry&nbsp;tool&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;CombinedTemplateCreationEntry(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">Node</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">Create&nbsp;a&nbsp;new&nbsp;Node</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,&nbsp;Node.class,&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;SimpleFactory(Node.class),&nbsp;</SPAN><SPAN style="COLOR: #0000ff">null</SPAN><SPAN style="COLOR: #000000">,&nbsp;</SPAN><SPAN style="COLOR: #0000ff">null</SPAN><SPAN style="COLOR: #000000">);</SPAN> </DIV></DIV>
<P></P>
<P></P>
<P>在新的3.0版本GEF里还提供了一种可以自动隐藏调色板的编辑器GraphicalEditorWithFlyoutPalette，对调色板的外观有更多选项可以选择，以后的帖子里可能会提到如何使用。</P>
<P></P>
<P>调色板的初始化操作应该放在initializePaletteViewer()里完成，最主要的任务是为调色板所在的EditPartViewer添加拖动源事件支持，前面我们已经为画布所在EditPartViewer添加了拖动目标事件，所以现在就可以实现完整的拖放操作了。这里稍微讲解一下拖放的实现原理，以用来创建节点对象的节点工具为例，它在调色板里是一个CombinedTemplateCreationEntry，在创建这个PaletteEntry时（见上面的代码）我们指定该对象对应一个Node.class，所以在用户从调色板里拖动这个工具时，内存里有一个TemplateTransfer<FONT color=#ff1493>单例</FONT>对象会记录下Node.class（称作template），当用户在画布上松开鼠标时，拖放结束的事件被触发，将由画布注册的DiagramTemplateTransferDropTargetListener对象来处理template对象（现在是Node.class），在例子中我们的处理方法是用一个名为ElementFactory的对象负责根据这个template创建一个对应类型的实例。</P>
<P></P>
<P>以上我们建立了模型和用于实现视图的Editor，因为模型的改变都是由Command对象直接修改的，所以下面我们先来看都有哪些Command。由需求可知，我们对模型的操作有增加/删除节点、修改节点名称、改变节点位置和增加/删除连接等，所以对应就有CreateNodeCommand、DeleteNodeCommand、RenameNodeCommand、MoveNodeCommand、CreateConnectionCommand和DeleteConnectionCommand这些对象，它们都放归类在commands包里。一个Command对象里最重要的当然是execute()方法了，也就是执行命令的方法。除此以外，因为要实现撤消/重做功能，所以在Command对象里都有Undo()和Redo()方法，同时在Command对象里要有成员变量负责保留执行该命令时的相关状态，例如RenameNodeCommand里要有oldName和newName两个变量，这样才能正确的执行Undo()和Redo()方法，要记住，每个被执行过的Command对象实例都是被保存在EditDomain的CommandStack中的。</P>
<P></P>
<P>例子里的EditPolicy都放在policies包里，与图形有关的（GraphicalEditPart的子类）有DiagramLayoutEditPolicy、NodeDirectEditPolicy和NodeGraphicalNodeEditPolicy，另外两个则是与图形无关的编辑策略。可以看到，在后一种类型的两个类（ConnectionEditPolicy和NodeEditPolicy）中我们只覆盖了createDeleteCommand()方法，该方法用于创建一个负责"删除"操作的Command对象并返回，要搞清这个方法看似矛盾的名字里create和delete是对不同对象而言的。</P>
<P></P>
<P>有了Command和EditPolicy，现在可以来看看EditPart部分了。每一个模型对象都对应一个EditPart，所以我们的三个模型对象（Element不算）分别对应DiagramPart、ConnectionPart和NodePart。对于含有子元素的EditPart，必须覆盖getModelChildren()方法返回子对象列表，例如DiagramPart里这个方法返回的是Diagram对象包含的Node对象列表。</P>
<P></P>
<P>每个EditPart都有<STRONG>active</STRONG>()和<STRONG>deactive</STRONG>()两个方法，一般我们在前者里注册监听器（因为实现了PropertyChangeListener接口，所以EditPart本身就是监听器）到模型对象，在后者里将监听器从列表里移除。在触发监听器事件的propertyChange()方法里，一般是根据"事件名"称决定使用何种方式刷新视图，例如对于NodePart，如果是节点本身的属性发生变化，则调用refreshVisuals()方法，若是与它相关的连接发生变化，则调用refreshTargetConnections()或refreshSourceConnections()。这里用到的事件名称都是我们自己来规定的，在例子中比如Node.PROP_NAME表示节点的名称属性，Node.PROP_LOCATION表示节点的位置属性，等等。</P>
<P></P>
<P>EditPart（确切的说是AbstractGraphicalEditpart）另外一个需要实现的重要方法是<STRONG>createFigure</STRONG>()，这个方法应该返回模型在视图中的图形表示，是一个IFigure类型对象。一般都把这些图形放在figures包里，例子里只有NodeFigure一个自定义图形，Diagram对象对应的是GEF自带的名为FreeformLayer的图形，它是一个可以在东南西北四个方向任意扩展的层图形；而Connection对应的也是GEF自带的图形，名为PolylineConnection，这个图形缺省是一条用来连接另外两个图形的直线，在例子里我们通过setTargetDecoration()方法让连接的目标端显示一个箭头。</P>
<P></P>
<P>最后，要为EditPart增加适当的EditPolicy，这是通过覆盖EditPart的createEditPolicies()方法来实现的，每一个被"安装"到EditPart中的EditPolicy都对应一个用来表示角色（Role）的字符串。对于在模型中有子元素的EditPart，一般都会安装一个EditPolicy.LAYOUT_ROLE角色的EditPolicy（见下面的代码），后者多为LayoutEditPolicy的子类；对于连接类型的EditPart，一般要安装EditPolicy.CONNECTION_ENDPOINTS_ROLE角色的EditPolicy，后者则多为ConnectionEndpointEditPolicy或其子类，等等。</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top><SPAN style="COLOR: #000000">installEditPolicy(EditPolicy.LAYOUT_ROLE,&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;DiagramLayoutEditPolicy());</SPAN> </DIV></DIV>
<P></P>
<P></P>
<P>用户的操作会被当前工具（缺省为选择工具SelectionTool）转换为请求（Request），请求根据类型被分发到目标EditPart所安装的EditPolicy，后者根据请求对应的角色来判断是否应该创建命令并执行。</P>
<P></P>
<P>在以前的帖子里说过，<FONT color=#ff1493>Role-EditPolicy-Command</FONT>这样的设计主要是为了尽量重用代码，例如同一个EditPolicy可以被安装在不同EditPart中，而同一个Command可以被不同的EditPolicy所使用，等等。当然，凡事有利必有弊，我认为这种的设计也有缺点，首先在代码上看来不够直观，你必须对众多Role、EditPolicy有所了解，增加了学习周期；另外大部分不需要重用的代码也要按照这个相对复杂的方式来写，带来了额外工作量。</P>
<P></P>
<P>以上就是一个GEF应用程序里最基本的几个组成部分，例子中还有如Direct Edit、属性表和大纲视图等一些功能没有讲解，下面的帖子里将介绍这些常用功能的实现。</P></DIV><img src ="http://www.blogjava.net/Apple/aggbug/6528.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-22 13:07 <a href="http://www.blogjava.net/Apple/archive/2005/06/22/6528.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至八进制】GEF入门系列（二、GEF概述）</title><link>http://www.blogjava.net/Apple/archive/2005/06/21/6500.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Tue, 21 Jun 2005 13:10:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/21/6500.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6500.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/21/6500.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6500.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6500.html</trackback:ping><description><![CDATA[<DIV class=postText>
<P>原文地址：<A href="http://www.cnblogs.com/bjzhanghao/archive/2005/02/13/104045.html">http://www.cnblogs.com/bjzhanghao/archive/2005/02/13/104045.html</A><BR>在前面的帖子已经提到，GEF（Graphical Editor Framework）是一个图形化编辑框架，它允许开发人员以图形化的方式展示和编辑模型，从而提升用户体验。这样的应用程序有很多，例如：UML类图编辑器、图形化XML编辑器、界面设计工具以及图形化数据库结构设计工具等等。归结一下，可以发现它们在图形化编辑方面具有以下共同之处：</P>
<UL>
<LI>提供一个编辑区域和一个工具条，用户在工具条里选择需要的工具，以拖动或单击的方式将节点或连接放置在编辑区域； 
<LI>节点可以包含子节点； 
<LI>用户能够查看和修改某个节点或连接的大部分属性； 
<LI>连接端点锚定在节点上； 
<LI>提供上下文菜单和键盘命令； 
<LI>提供图形的缩放功能； 
<LI>提供一个大纲视图，显示编辑区域的缩略图，或是树状模型结构； 
<LI>支持撤消/重做功能； 
<LI>等等。 </LI></UL>
<P align=center>&nbsp;<IMG height=472 hspace=5 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef//ve.gif" width=465 align=baseline> <BR>图1 基于GEF的界面设计工具（Visual Editor，VE）的工作界面 </P>
<P>GEF最早是Eclipse的一个内部项目，后来逐渐转变为Eclipse的一个开源工具项目，Eclipse的不少其他子项目都需要它的支持。Eclipse 3.0版本花了很大功夫在从Platform中剥离各种功能部件上，包括GEF和IDE在内的很多曾经只能在Eclipse内部使用的工具成为可以独立使用的软件/插件包了。理论上我们是可以脱离Eclipse用GEF包构造自己的应用程序的，但由于它们之间天然的联系，而且Eclipse确实是一个很值得支持的开发平台，所以我还是推荐你在Eclipse中使用它。</P>
<P><FONT color=#800080>GEF的优势是提供了标准的MVC（Model-View-Control）结构</FONT>，开发人员可以利用GEF来完成以上这些功能，而不需要自己重新设计。与其他一些MVC编辑框架相比，<FONT color=#800080>GEF的一个主要设计目标是尽量减少模型和视图之间的依赖，好处是可以根据需要选择任意模型和视图的组合，而不必受开发框架的局限</FONT>（不过实际上还是很少有脱离Draw2D的实现）。</P>
<P>现在来看看GEF是如何实现MVC框架的吧，在这个帖子里我们先概括介绍一下它的各个组成部分，以后将结合例子进行更详细的说明。</P>
<P align=center><IMG style="WIDTH: 403px; HEIGHT: 361px" height=524 src="file:///C:/eclipse/plugins/org.eclipse.gef.doc.isv_3.0.1/reference/api/org/eclipse/gef/doc-files/gefoverview.gif" width=757 border=1>&nbsp;<BR>图2 GEF结构图 </P>
<P><STRONG>模型</STRONG>：<FONT color=#800080>GEF的模型只与控制器打交道，而不知道任何与视图有关的东西。</FONT>为了能让控制器知道模型的变化，应该把控制器作为事件监听者注册在模型中，当模型发生变化时，就触发相应的事件给控制器，后者负责通知各个视图进行更新。 </P>
<P>典型的模型对象会包含PropertyChangeSupport类型的成员变量，用来<STRONG>维护</STRONG>监听器成员即控制器；对于与其他对象具有连接关系的模型，要维护连入/连出的连接列表；如果模型对应的节点具有大小和位置信息，还要维护它们。<FONT color=#800080>这些变量并不是模型本身必须的信息，维护它们使模型变得不够清晰，但你可以通过构造一些抽象模型类（例如让所有具有连接的模型对象继承Node类）来维持它们的可读性。</FONT></P>
<P>相对来讲GEF中模型是MVC中最简单的一部分。</P>
<P><STRONG>控制器</STRONG>：我们知道，在MVC结构里控制器是模型与视图之间的桥梁，也是整个GEF的核心。它不仅要监听模型的变化，当用户编辑视图时，还要把编辑结果反映到模型上。举个例子来说，用户在数据库结构图上删除一个表时，控制器应该从模型中删除这个表对象、表中的字段对象、以及与这些对象有关的所有连接。当然在GEF中这些操作不是由直接控制器完成的，这个稍后就会说到。 </P>
<P><FONT color=#800080>GEF中的控制器是所谓的EditPart对象，更确切的说应该是一组EditPart对象共同组成了GEF的控制器这部分，</FONT>每一个模型对象都对应一个EditPart对象。你的应用程序中需要有一个EditPartFactory对象负责根据给定模型对象创建对应的EditPart对象，<FONT color=#008000><FONT color=#008000>这个工厂类将被视图利用</FONT>。</FONT></P>
<P><FONT color=#800080>RootEditPart是一种特殊的EditPart，它和你的模型没有任何关系，它的作用是把EditPartViewer和contents</FONT>（应用程序的最上层EditPart，一般代表一块画布）<FONT color=#800080>联系起来</FONT>，可以把它想成是contents的容器。EditPartViewer有一个方法setRootEditPart()专门用来指定视图对应的RooEditPart。</P>
<P align=center><IMG height=231 hspace=5 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/editpart.gif" width=347 align=baseline> <BR>图3 EditPart对象 </P>
<P><FONT color=#006400>用户的编辑操作被转换为一系列请求（Request），有很多种类的请求，这些种类在GEF里被称为角色（Role），GEF里有图形化和非图形化这两大类角色</FONT>，前者比如Layout Role对应和布局有关的的操作，后者比如Connection Role对应和连接有关的操作等等。<FONT color=#ff0000>角色这个概念是通过编辑策略（EditPolicy）来实现的，EditPolicy的主要功能是根据请求创建相应的命令（Command），而后者会直接操作模型对象。</FONT>对每一个EditPart，你都可以"安装"一些EditPolicy，用户对这个EditPart的特定操作会被交给已安装的对应EditPolicy处理。这样做的直接好处是可以在不同EditPart之间共享一些重复操作。</P>
<P>在GEF SDK提供的帮助文档（GEF开发指南）里有一份详细的EditPolicy、Role和Request类型列表，这里就不赘述了。</P>
<P><STRONG>视图</STRONG>：前面说过，GEF的视图可以有很多种，GEF目前提供了图形（GraphicalViewer）和树状（TreeViewer）这两种，前者利用Draw2D图形（IFigure）作为表现方式，多用于编辑区域，后者则多用于实现大纲展示。视图的任务同样繁重，除了模型的显示功能以外，还要提供编辑功能、回显（Feedback）、工具提示（ToolTip）等等。 </P>
<P><FONT color=#800080>GEF使用EditPartViewer作为视图，它的作用和JFace中的Viewer十分类似，而EditPart就相当于是它的ContentProvider和LabelProvider，通过setContents()方法来指定。</FONT>我们经常使用的Editor是一个GraphicalEditorWithPalette（GEF提供的Editor，是EditorPart的子类，具有图形化编辑区域和一个工具条），这个Editor使用GraphicalEditViewer和PaletteViewer这两个视图类，PaletteViewer也是GraphicalEditViewer的子类。开发人员要在configureGraphicalViewer()和initializeGraphicalViewer()这两个方法里对EditPartViewer进行定制，包括指定它的contents和EditPartFactory等等。</P>
<P>EditPartViewer同时也是ISelectionProvider，这样当用户在编辑区域做选择操作时，注册的SelectionChangeListener就可以收到选择事件。EditPartViewer会维护各个EditPart的选中状态，如果没有被选中的EditPart，则缺省选中的是作为contents的EditPart。</P>
<P>初步了解了GEF的MVC实现方式，让我们看看典型的GEF应用程序是什么样子的。大部分GEF应用程序都实现为Eclipse的Editor，也就是说整个编辑区域是放置在一个Editor里的。<FONT color=#800080>所以典型的GEF应用程序具有一个图形编辑区域包含在一个Editor（例如GraphicalEditorWithPalette）里，可能有一个大纲视图和一个属性页，一个用于创建EditPart实例的EditPartFactory，一些表示业务的模型对象，与模型对象对应的一些EditPart，每个EditPart对应一个IFigure的子类对象显示给用户，一些EditPolicy对象，以及一些Command对象。</FONT></P>
<P><EM><FONT color=#006400>GEF应用程序的工作方式如下： EditPartViewer接受用户的操作，例如节点的选择、新增或删除等等，每个节点都对应一个EditPart对象，这个对象有一组按操作Role分开的EditPolicy，每个EditPolicy会对应一些Command对象，Command最终对模型进行直接修改。用户的操作转换为Request分配给适当的EditPolicy，由后者创建适当的Command来修改模型，这些Command会保留在EditDomain（专门用于维护EditPartViewer、Command等信息的对象，一般每个Editor对应唯一一个该对象）的命令堆栈里，用于实现撤消/重做功能。</FONT></EM></P>
<P>以上介绍了GEF中一些比较重要的概念，不知道看过之后你是否对它有了一个大概的印象。如果没有也没关系，因为在后面的帖子里将会有结合例子的讲解，我们使用的实例就是序言里提到的第六个项目。</P>
<P>参考资料：</P>
<UL>
<LI>GEF开发指南 
<LI>Eclipse Development - Using the Graphical Editing Framework and the Eclipse Modeling Framework </LI></UL></DIV><img src ="http://www.blogjava.net/Apple/aggbug/6500.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-21 21:10 <a href="http://www.blogjava.net/Apple/archive/2005/06/21/6500.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至八进制】GEF入门系列（一、Draw2D）</title><link>http://www.blogjava.net/Apple/archive/2005/06/21/6498.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Tue, 21 Jun 2005 11:59:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/21/6498.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6498.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/21/6498.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6498.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6498.html</trackback:ping><description><![CDATA[<DIV class=postText>
<P>原文地址：<A href="http://www.cnblogs.com/bjzhanghao/archive/2005/02/09/103595.html">http://www.cnblogs.com/bjzhanghao/archive/2005/02/09/103595.html</A><BR><BR>鸡年第一天，首先向大家拜个年，恭祝新春快乐，万事如意。一年之计在于春，你对新的一年有什么安排呢？好的，下面还是进入正题吧。</P>
<P>关于Java2D相信大家都不会陌生，它是基于AWT/Swing的二维图形处理包， JDK附带的示例程序向我们展示了Java2D十分强大的图形处理能力。在Draw2D出现以前，SWT应用程序在这方面一直处于下风，而Draw2D这个SWT世界里的Java2D改变了这种形势。</P>
<P>可能很多人还不十分了解GEF和Draw2D的关系：一些应用程序是只使用Draw2D，看起来却和GEF应用程序具有相似的外观。原因是什么，下面先简单解释一下：</P>
<P><FONT color=#800080>GEF是具有标准MVC（Model-View-Control）结构的图形编辑框架，</FONT>其中Model由我们自己根据业务来设计，它要能够提供某种模型改变通知的机制，用来把Model的变化告诉Control层；Control层由一些EditPart实现，EditPart是整个GEF的核心部件，关于EditPart的机制和功能将在以后的帖子里介绍；而View层（大多数情况下）就是我们这里要说的Draw2D了，其作用是把Model以图形化的方式表现给使用者。</P>
<P>虽然GEF可以使用任何图形包作为View层，但实际上GEF对Draw2D的依赖是很强的。举例来说：虽然EditPart（org.eclipse.gef.EditPart）接口并不要求引入任何Draw2D的类，但我们最常使用的AbstractGraphicalEditPart类的createFigure()方法就需要返回IFigure类型。由于这个原因，在GEF的SDK中索性包含了Draw2D包就不奇怪了，同样道理，只有先了解Draw2D才可能掌握GEF。</P>
<P>这样，对于一开始提出的问题可以总结如下：Draw2D是基于SWT的图形处理包，它适合用作GEF的View层。如果一个应用仅需要显示图形，只用Draw2D就够了；若该应用的模型要求以图形化的方式被编辑，那么最好使用GEF框架。</P>
<P>现在让我们来看看Draw2D里都有些什么，请看下图。</P>
<P align=center><IMG height=484 hspace=5 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/draw2d-1.gif" width=535 align=baseline> <BR>图1 Draw2D的结构 </P>
<P>Draw2D通过被称为LightweightSystem（以下简称LWS）的部件与SWT中的某一个Canvas实例相连，这个Canvas在Draw2D应用程序里一般是应用程序的Shell，在GEF应用程序里更多是某个Editor的Control（createPartControl()方法中的参数），在界面上我们虽然看不到LWS的存在，但其他所有能看到的图形都是放在它里面的，这些图形按父子包含关系形成一个树状的层次结构。</P>
<P>LWS是Draw2D的核心部件，它包含三个主要组成部分：RootFigure是LWS中所有图形的根，也就是说其他图形都是直接或间接放在RootFigure里的；EventDispatcher把Canvas上的各种事件分派给RootFigure，这些事件最终会被分派给适当的图形，请注意这个RootFigure和你应用程序中最顶层的IFigure不是同一个对象，前者是看不见的被LWS内部使用的，而后者通常会是一个可见的画布，它是直接放在前者中的；UpdateManager用来重绘图形，当Canvas被要求重绘时，LWS会调用它的performUpdate()方法。</P>
<P>LWS是连接SWT和Draw2D的桥梁，利用它，我们不仅可以轻松创建任意形状的图形（不仅仅限于矩形），同时能够节省系统资源（因为是轻量级组件）。一个典型的纯Draw2D应用程序代码具有类似下面的结构：</P>
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">创建SWT的Canvas（Shell是Canvas的子类）</SPAN><SPAN style="COLOR: #008000"> <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #000000">Shell&nbsp;shell&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;Shell(); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top>shell.open(); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top>shell.setText(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">A&nbsp;Draw2d&nbsp;application</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">创建LightweightSystem，放在shell上</SPAN><SPAN style="COLOR: #008000"> <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #000000">LightweightSystem&nbsp;lws&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;LightweightSystem(shell); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">创建应用程序中的最顶层图形</SPAN><SPAN style="COLOR: #008000"> <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #000000">IFigure&nbsp;panel&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;Figure(); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top>panel.setLayoutManager(</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><SPAN style="COLOR: #000000">&nbsp;FlowLayout()); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">把这个图形放置于LightweightSystem的RootFigure里</SPAN><SPAN style="COLOR: #008000"> <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #000000">lws.setContents(panel);&nbsp; <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top><IMG height=20 src="http://www.cnblogs.com/Images/dot.gif" width=15> <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">创建应用程序中的其他图形，并放置于应用程序的顶层图形中</SPAN><SPAN style="COLOR: #008000"> <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #000000">panel.add(<IMG height=20 src="http://www.cnblogs.com/Images/dot.gif" width=15>); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #0000ff">while</SPAN><SPAN style="COLOR: #000000">&nbsp;(</SPAN><SPAN style="COLOR: #000000">!</SPAN><SPAN style="COLOR: #000000">shell.isDisposed&nbsp;())&nbsp;{ <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top></SPAN><SPAN style="COLOR: #0000ff">if</SPAN><SPAN style="COLOR: #000000">&nbsp;(</SPAN><SPAN style="COLOR: #000000">!</SPAN><SPAN style="COLOR: #000000">display.readAndDispatch&nbsp;()) <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top>&nbsp;&nbsp;&nbsp;display.sleep&nbsp;(); <BR><IMG height=16 src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" width=11 align=top>}</SPAN> </DIV></DIV>
<P>接下来说说图形，Draw2D中的图形全部都实现IFigure（org.eclipse.draw2d.IFigure）接口，这些图形不仅仅是你看到的屏幕上的一块形状而已，除了控制图形的尺寸位置以外，你还可以监听图形上的事件（鼠标事件、图形结构改变等等，来自LWS的EventDispatcher）、设置鼠标指针形状、让图形变透明、聚焦等等，每个图形甚至还拥有自己的Tooltip，十分的灵活。</P>
<P>Draw2D提供了很多缺省图形，最常见的有三类：1、形状（Shape），如矩形、三角形、椭圆形等等；2、控件（Widget），如标签、按钮、滚动条等等；3、层（Layer），它们用来为放置于其中的图形提供缩放、滚动等功能，在3.0版本的GEF中，还新增了GridLayer和GuideLayer用来实现"吸附到网格"功能。在以IFigure为根节点的类树下有相当多的类，不过我个人感觉组织得有些混乱，幸好大部分情况下我们只用到其中常用的那一部分。 </P>
<P align=center><IMG height=395 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/draw2d-2.gif" width=466> <BR>图2 一个Draw2D应用程序 </P>
<P>每个图形都可以拥有一个边框（Border），Draw2D所提供的边框类型有GroupBoxBorder、TitleBarBorder、ImageBorder、ButtonBorder，以及可以组合两种边框的CompoundBorder等等，在Draw2D里还专门有一个Insets类用来表示边框在图形中所占的位置，它包含上下左右四个整型数值。</P>
<P>我们知道，一个图形可以包含很多个子图形，这些被包含的图形在显示的时候必须以某种方式被排列起来，负责这个任务的就是父图形的LayoutManager。同样的，Draw2D已经为我们提供了一系列可以直接使用的LayoutManager，如FlowLayout适合用于表格式的排列，XYLayout适合让用户在画布上用鼠标随意改变图形的位置，等等。如果没有适合我们应用的LayoutManager，可以自己定制。每个LayoutManager都包含某种算法，该算法将考虑与每个子图形关联的Constraint对象，计算得出子图形最终的位置和大小。</P>
<P>图形化应用程序的一个常见任务就是在两个图形之间做连接，想象一下UML类图中的各种连接线，或者程序流程图中表示数据流的线条，它们有着不同的外观，有些连接线还要显示名称，而且最好能不交叉。<FONT color=#0000ff>利用Draw2D中的Router、Anchor和Locator，可以实现多种连接样式，其中Router负责连接线的外观和操作方式，</FONT>最简单的是设置Router为null（无Router），这样会使用直线连接，其他连接方式包括折线、具有控制点的折线等等（见图3），若想控制连接线不互相交叉也需要在Router中作文章。Anchor控制连接线端点在图形上的位置，即"锚点"的位置，最易于使用的是ChopBoxAnchor，它先假设图形中心为连接点，然后计算这条假想连线与图形边缘的交汇点作为实际的锚点，其他Anchor还有EllipseAnchor、LabelAnchor和XYAnchor等等；最后，Locator的作用是定位图形，例如希望在连接线中点处以一个标签显示此连线的名称/作用，就可以使用MidpointLocator来帮助定位这个标签，其他Locator还有ArrowLocator用于定位可旋转的修饰（Decoration，例如PolygonDecoration）、BendpointerLocator用于定位连接控制点、ConnectionEndpointLocator用于定位连接端点（通过指定uDistance和vDistance属性的值可以设置以端点为原点的坐标）。 </P>
<P align=center><IMG height=369 hspace=5 src="http://www.cnblogs.com/images/cnblogs_com/bjzhanghao/gef/draw2d-3.gif" width=525 align=baseline> <BR>图3 三种Router的外观 </P>
<P>此外，Draw2D在org.eclipse.draw2d.geometry包里提供了几个很方便的类型，如Dimension、Rectangle、Insets、Point和PointList等等，这些类型既在Draw2D内部广泛使用，也可以被开发人员用来简化计算。例如Rectangle表示的是一个矩形区域，它提供getIntersection()方法能够方便的计算该区域与另一矩形区域的重叠区域、getTransposed()方法可以得到长宽值交换后的矩形区域、scale()方法进行矩形的拉伸等等。在自己实现LayoutManager的时候，由于会涉及到比较复杂的几何计算，所以更推荐使用这些类。</P>
<P>以上介绍了Draw2D提供的大部分功能，利用这些我们已经能够画出十分漂亮的图形了。但对大多数实际应用来说这样还远远不够，我们还要能编辑它，并把对图形的修改反映到模型里去。为了漂亮的完成这个艰巨任务，GEF绝对是不二之选。从下一次开始，我们将正式进入GEF的世界。</P>
<P>参考资料：</P>
<UL>
<LI>GEF Developer Guide 
<LI>Eclipse Development - Using the Graphical Editing Framework and the Eclipse Modeling Framework 
<LI><A href="http://www.eclipse.org/articles/Article-GEF-Draw2d/GEF-Draw2d.html">Displaying a UML Diagram with Draw2D</A> </LI></UL></DIV><img src ="http://www.blogjava.net/Apple/aggbug/6498.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-21 19:59 <a href="http://www.blogjava.net/Apple/archive/2005/06/21/6498.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至doodoofish blog】java陷阱一箩筐－－－－面试题集及解答</title><link>http://www.blogjava.net/Apple/archive/2005/06/16/6202.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 16 Jun 2005 03:16:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/16/6202.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6202.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/16/6202.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6202.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6202.html</trackback:ping><description><![CDATA[找工作要面试，有面试就有对付面试的办法。以下一些题目来自我和我朋友痛苦的面试经历，提这些问题的公司包括IBM, E*Trade, Siebel, Motorola, SUN, 以及其它大小公司。
<P>
<SCRIPT id=ad_text_pcjob src="http://www.pconline.com.cn/script/ad_text_pcjob.js" defer></SCRIPT>
　　面试是没什么道理可讲的，它的题目有的不合情理、脱离实际。有在纸上写的，有当面考你的，也有在电话里问的，给你IDE的估计很少(否则你赶快去买彩票， 说不定中)。所以如果你看完此文后，请不要抱怨说这些问题都能用IDE来解决。你必须在任何情况下准确回答这些问题，在面试中如果出现一两题回答不准确很 有可能你就被拒之门外了。</P>
<P>　　当然这些都是Java的基本题，那些面试的人大多数不会问你Hibernate有多先进，Eclipse的三个组成部分，或command design pattern，他们都是老一辈了，最喜欢问的就是基础知识。别小看了这些基础，我朋友水平一流，结果就栽在一到基础知识的问题下，和高薪无缘。</P>
<P>　　好了废话少说，开始正题。</P>
<P><STRONG><FONT color=#ff0000>问：</FONT></STRONG></P>
<P>第一，谈谈final, finally, finalize的区别。</P>
<P>最常被问到。</P>
<P>第二，Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类，是否可以implements(实现)interface(接口)?</P>
<P>第三，Static Nested Class 和 Inner Class的不同，说得越多越好(面试题有的很笼统)。</P>
<P>第四，&amp;和&amp;&amp;的区别。</P>
<P>这个问得很少。</P>
<P>第五，HashMap和Hashtable的区别。</P>
<P>常问。</P>
<P>第六，Collection 和 Collections的区别。</P>
<P>你千万别说一个是单数一个是复数。</P>
<P>第七，什么时候用assert。</P>
<P>API级的技术人员有可能会问这个。</P>
<P>第八，GC是什么? 为什么要有GC? </P>
<P>基础。</P>
<P>第九，String s = new String("xyz");创建了几个String Object?</P>
<P>第十，Math.round(11.5)等於多少? Math.round(-11.5)等於多少?</P>
<P>第十一，short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?</P>
<P>面试题都是很变态的，要做好受虐的准备。</P>
<P>第十二，sleep() 和 wait() 有什么区别?</P>
<P>搞线程的最爱。</P>
<P>第十三，Java有没有goto?</P>
<P>很十三的问题，如果哪个面试的问到这个问题，我劝你还是别进这家公司。</P>
<P>第十四，数组有没有length()这个方法? String有没有length()这个方法?</P>
<P>第十五，Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?</P>
<P>常问。</P>
<P>第十六，Set里的元素是不能重复的，那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?</P>
<P>第十七，给我一个你最常见到的runtime exception。</P>
<P>如果你这个答不出来，面试的人会认为你没有实际编程经验。</P>
<P>第十八，error和exception有什么区别?</P>
<P>第十九，List, Set, Map是否继承自Collection接口?</P>
<P>第二十，abstract class和interface有什么区别?</P>
<P>常问。</P>
<P>第二十一，abstract的method是否可同时是static,是否可同时是native，是否可同时是synchronized?</P>
<P>第二十二，接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?</P>
<P>第二十三，启动一个线程是用run()还是start()?</P>
<P>第二十四，构造器Constructor是否可被override?</P>
<P>第二十五，是否可以继承String类?</P>
<P>第二十六，当一个线程进入一个对象的一个synchronized方法后，其它线程是否可进入此对象的其它方法?</P>
<P>第二十七，try {}里有一个return语句，那么紧跟在这个try后的finally {}里的code会不会被执行，什么时候被执行，在return前还是后?</P>
<P>第二十八，编程题: 用最有效率的方法算出2乘以8等於几?</P>
<P>有C背景的程序员特别喜欢问这种问题。</P>
<P>第二十九，两个对象值相同(x.equals(y) == true)，但却可有不同的hash code，这句话对不对?</P>
<P>第三十，当一个对象被当作参数传递到一个方法后，此方法可改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递?</P>
<P>第三十一，swtich是否能作用在byte上，是否能作用在long上，是否能作用在String上?</P>
<P>第三十二，编程题: 写一个Singleton出来<BR></P>
<P>好先说这么一些。</P>
<P><STRONG><FONT color=#ff0000>答：</FONT></STRONG></P>
<P>第一，谈谈final, finally, finalize的区别。 </P>
<P>　　<FONT style="BACKGROUND-COLOR: #66ffff">final</FONT>—修饰符（关键字）如果一个类被声明为final，意味着它不能再派生出新的子类，不能作为父类被继承。因此一个类不能既被声明为 abstract的，又被声明为final的。将变量或方法声明为final，可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值，而在以后的引用中只能读取，不可修改。被声明为final的方法也同样只能使用，不能重载。 </P>
<P>　　<FONT style="BACKGROUND-COLOR: #66ffff">finally</FONT>—再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常，那么相匹配的 catch 子句就会执行，然后控制就会进入 finally 块（如果有的话）。</P>
<P>　　<FONT style="BACKGROUND-COLOR: #66ffff">finalize</FONT>—方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的，因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。 </P>
<P>第二，Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类，是否可以implements(实现)interface(接口)? </P>
<P>　　匿名的内部类是没有名字的内部类。不能extends(继承) 其它类，但一个内部类可以作为一个接口，由另一个内部类实现。 </P>
<P>第三，Static Nested Class 和 Inner Class的不同，说得越多越好(面试题有的很笼统)。</P>
<P>　　Nested Class （一般是C++的说法），Inner Class (一般是JAVA的说法)。Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用上。具体可见http: //www.frontfree.net/articles/services/view.asp?id=704&amp;page=1 </P>
<P>　　注： 静态内部类（Inner Class）意味着1创建一个static内部类的对象，不需要一个外部类对象，2不能从一个static内部类的一个对象访问一个外部类对象 </P>
<P>第四，&amp;和&amp;&amp;的区别。</P>
<P>　　&amp;是位运算符。&amp;&amp;是布尔逻辑运算符。 </P>
<P>第五，HashMap和Hashtable的区别。</P>
<P>　　都属于Map接口的类，实现了将惟一键映射到特定的值上。</P>
<P>　　HashMap 类没有分类或者排序。它允许一个 null 键和多个 null 值。</P>
<P>　　Hashtable 类似于 HashMap，但是不允许 null 键和 null 值。它也比 HashMap 慢，因为它是同步的。 </P>
<P>第六，Collection 和 Collections的区别。</P>
<P>　　Collections是个java.util下的类，它包含有各种有关集合操作的静态方法。</P>
<P>　　Collection是个java.util下的接口，它是各种集合结构的父接口。 </P>
<P><BR>第七，什么时候用assert。</P>
<P>　　断言是一个包含布尔表达式的语句，在执行这个语句时假定该表达式为 true。如果表达式计算为 false，那么系统会报告一个 Assertionerror。它用于调试目的：</P>
<P>assert(a &gt; 0); // throws an Assertionerror if a &lt;= 0 </P>
<P>断言可以有两种形式：</P>
<P>assert Expression1 ; <BR>assert Expression1 : Expression2 ; </P>
<P>　　Expression1 应该总是产生一个布尔值。</P>
<P>　　Expression2 可以是得出一个值的任意表达式。这个值用于生成显示更多调试信息的 String 消息。</P>
<P>　　断言在默认情况下是禁用的。要在编译时启用断言，需要使用 source 1.4 标记：</P>
<P>　　javac -source 1.4 Test.java </P>
<P>　　要在运行时启用断言，可使用 -enableassertions 或者 -ea 标记。</P>
<P>　　要在运行时选择禁用断言，可使用 -da 或者 -disableassertions 标记。</P>
<P>　　要系统类中启用断言，可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。 </P>
<P>　　可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过，断言不应该用于验证传递给公有方法的参数，因为不管是否启用了断言，公有方法都必须检查其参数。不过，既可以在公有方法中，也可以在非公有方法中利用断言测试后置条件。另外，断言不应该以任何方式改变程序的状态。 </P>
<P>第八，GC是什么? 为什么要有GC? (基础)。</P>
<P>　　GC是垃圾收集器。Java 程序员不用担心内存管理，因为垃圾收集器会自动进行管理。要请求垃圾收集，可以调用下面的方法之一：</P>
<P>System.gc() <BR>Runtime.getRuntime().gc() <BR></P>
<P>第九，String s = new String("xyz");创建了几个String Object? </P>
<P>　　两个对象，一个是“xyx”,一个是指向“xyx”的引用对象s。 </P>
<P>第十，Math.round(11.5)等於多少? Math.round(-11.5)等於多少? </P>
<P>　　Math.round(11.5)返回（long）12，Math.round(-11.5)返回（long）-11; </P>
<P>第十一，short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错? </P>
<P>　　short s1 = 1; s1 = s1 + 1;有错，s1是short型，s1+1是int型,不能显式转化为short型。可修改为s1 =(short)(s1 + 1) 。short s1 = 1; s1 += 1正确。 </P>
<P>第十二，sleep() 和 wait() 有什么区别? 搞线程的最爱</P>
<P>　　sleep()方法是使线程停止一段时间的方法。在sleep 时间间隔期满后，线程不一定立即恢复执行。这是因为在那个时刻，其它线程可能正在运行而且没有被调度为放弃执行，除非(a)“醒来”的线程具有更高的优先级 (b)正在运行的线程因为其它原因而阻塞。</P>
<P>　　wait()是线程交互时，如果线程对一个同步对象x 发出一个wait()调用，该线程会暂停执行，被调对象进入等待状态，直到被唤醒或等待时间到。 </P>
<P>第十三，Java有没有goto? </P>
<P>　　Goto—java中的保留字，现在没有在java中使用。 </P>
<P>第十四，数组有没有length()这个方法? String有没有length()这个方法？</P>
<P>　　数组没有length()这个方法，有length的属性。</P>
<P>　　String有有length()这个方法。 </P>
<P>第十五，Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型? </P>
<P>　　方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现，重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数，我们说该方法被重写 (Overriding)。子类的对象使用这个方法时，将调用子类中的定义，对它而言，父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法，它们或有不同的参数个数或有不同的参数类型，则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。 </P>
<P>第十六，Set里的元素是不能重复的，那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别? </P>
<P>　　Set里的元素是不能重复的，那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。</P>
<P>　　equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖，为的是当两个分离的对象的内容和类型相配的话，返回真值。 </P>
<P>第十七，给我一个你最常见到的runtime exception。</P>
<P>ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException, <BR>ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFORMatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException </P>
<P>第十八，error和exception有什么区别? </P>
<P>　　error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。</P>
<P>　　exception 表示一种设计或实现问题。也就是说，它表示如果程序运行正常，从不会发生的情况。 </P>
<P>第十九，List, Set, Map是否继承自Collection接口? </P>
<P>List，Set是 </P>
<P>Map不是 </P>
<P>第二十，abstract class和interface有什么区别? </P>
<P>　　声明方法的存在而不去实现它的类被叫做抽象类（abstract class），它用于要创建一个体现某些基本行为的类，并为该类声明方法，但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量，其类型是一个抽象类，并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现，否则它们也是抽象类为。取而代之，在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。</P>
<P>　　接口（interface）是抽象类的变体。在接口中，所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的，没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似，除了该实现类不能从接口定义中继承行为。当类实现特殊接口时，它定义（即将程序体给予）所有这种接口的方法。然后，它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类，它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换，instanceof 运算符可以用来决定某对象的类是否实现了接口。 </P>
<P>第二十一，abstract的method是否可同时是static,是否可同时是native，是否可同时是synchronized? </P>
<P>　　都不能 </P>
<P>第二十二，接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? </P>
<P>　　接口可以继承接口。抽象类可以实现(implements)接口，抽象类是否可继承实体类，但前提是实体类必须有明确的构造函数。 </P>
<P>第二十三，启动一个线程是用run()还是start()? </P>
<P>　　启动一个线程是调用start()方法，使线程所代表的虚拟处理机处于可运行状态，这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。 </P>
<P>第二十四，构造器Constructor是否可被override? </P>
<P>　　构造器Constructor不能被继承，因此不能重写Overriding，但可以被重载Overloading。 </P>
<P>第二十五，是否可以继承String类? </P>
<P>　　String类是final类故不可以继承。 </P>
<P>第二十六，当一个线程进入一个对象的一个synchronized方法后，其它线程是否可进入此对象的其它方法? </P>
<P>　　不能，一个对象的一个synchronized方法只能由一个线程访问。 </P>
<P>第二十七，try {}里有一个return语句，那么紧跟在这个try后的finally {}里的code会不会被执行，什么时候被执行，在return前还是后? </P>
<P>　　会执行，在return前执行。 </P>
<P>第二十八，编程题: 用最有效率的方法算出2乘以8等於几? </P>
<P>　　有C背景的程序员特别喜欢问这种问题。 </P>
<P>　　2 &lt;&lt; 3 </P>
<P>第二十九，两个对象值相同(x.equals(y) == true)，但却可有不同的hash code，这句话对不对? </P>
<P>　　不对，有相同的hash code。 </P>
<P>第三十，当一个对象被当作参数传递到一个方法后，此方法可改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递? </P>
<P>　　是值传递。Java 编程语言只由值传递参数。当一个对象实例作为一个参数被传递到方法中时，参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变，但对象的引用是永远不会改变的。 </P>
<P>第三十一，swtich是否能作用在byte上，是否能作用在long上，是否能作用在String上?</P>
<P>　　switch（expr1）中，expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。 </P>
<P>第三十二，编程题: 写一个Singleton出来。</P>
<P>　　Singleton模式主要作用是保证在Java应用程序中，一个类Class只有一个实例存在。</P>
<P>　　一般Singleton模式通常有几种种形式：</P>
<P>　　第一种形式：定义一个类，它的构造函数为private的，它有一个static的private的该类变量，在类初始化时实例话，通过一个public的getInstance方法获取对它的引用,继而调用其中的方法。</P>
<P>public class Singleton { <BR>　　private Singleton(){} <BR>　　//在自己内部定义自己一个实例，是不是很奇怪？ <BR>　　//注意这是private 只供内部调用 <BR>　　private static Singleton instance = new Singleton(); <BR>　　//这里提供了一个供外部访问本class的静态方法，可以直接访问　　 <BR>　　public static Singleton getInstance() { <BR>　　　　return instance; 　　 <BR>　　 } <BR>} </P>
<P>　　第二种形式：</P>
<P>public class Singleton { <BR>　　private static Singleton instance = null; <BR>　　public static synchronized Singleton getInstance() { <BR>　　//这个方法比上面有所改进，不用每次都进行生成对象，只是第一次　　　 　 <BR>　　//使用时生成实例，提高了效率！ <BR>　　if (instance==null) <BR>　　　　instance＝new Singleton(); <BR>return instance; 　　} <BR>} </P>
<P>其他形式：</P>
<P>　　定义一个类，它的构造函数为private的，所有方法为static的。</P>
<P>　　一般认为第一种形式要更加安全些 </P>
<P>　　Hashtable和HashMap </P>
<P>　　Hashtable继承自Dictionary类，而HashMap是Java1.2引进的Map interface的一个实现 </P>
<P>　　HashMap允许将null作为一个entry的key或者value，而Hashtable不允许 </P>
<P>　　还有就是，HashMap把Hashtable的contains方法去掉了，改成containsvalue和containsKey。因为contains方法容易让人引起误解。 </P>
<P>　　最大的不同是，Hashtable的方法是Synchronize的，而HashMap不是，在 <BR>多个线程访问Hashtable时，不需要自己为它的方法实现同步，而HashMap <BR>就必须为之提供外同步。 </P>
<P>　　Hashtable和HashMap采用的hash/rehash算法都大概一样，所以性能不会有很大的差异<BR><BR clear=all></P><img src ="http://www.blogjava.net/Apple/aggbug/6202.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-16 11:16 <a href="http://www.blogjava.net/Apple/archive/2005/06/16/6202.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至piggy blog】java内存泄露</title><link>http://www.blogjava.net/Apple/archive/2005/06/16/6201.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 16 Jun 2005 03:10:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/16/6201.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6201.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/16/6201.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6201.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6201.html</trackback:ping><description><![CDATA[<P><A name=1><SPAN class=atitle2>一 问题的提出</SPAN></A></P>
<P>Java的一个重要优点就是通过垃圾收集器(Garbage Collection，GC)自动管理内存的回收，程序员不需要通过调用函数来释放内存。因此，很多程序员认为Java不存在内存泄漏问题，或者认为即使有内存泄漏也不是程序的责任，而是GC或JVM的问题。其实，这种想法是不正确的，因为Java也存在内存泄露，但它的表现与C++不同。</P>
<P>随着越来越多的服务器程序采用Java技术，例如JSP，Servlet， EJB等，服务器程序往往长期运行。另外，在很多嵌入式系统中，内存的总量非常有限。内存泄露问题也就变得十分关键，即使每次运行少量泄漏，长期运行之后，系统也是面临崩溃的危险。</P>
<P><A name=2><SPAN class=atitle2>二 Java是如何管理内存</SPAN></A></P>
<P>为了判断Java中是否有内存泄露，我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中，程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外)，所有的对象都在堆 (Heap)中分配空间。另外，对象的释放是由GC决定和执行的。在Java中，内存的分配是由程序完成的，而内存的释放是有GC完成的，这种收支两条线的方法确实简化了程序员的工作。但同时，它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为，GC为了能够正确释放对象，GC必须监控每一个对象的运行状态，包括对象的申请、引用、被引用、赋值等，GC都需要进行监控。</P>
<P>监视对象状态是为了更加准确地、及时地释放对象，而释放对象的根本原则就是该对象不再被引用。</P>
<P>为了更好理解GC的工作原理，我们可以将对象考虑为有向图的顶点，将引用关系考虑为图的有向边，有向边从引用者指向被引对象。另外，每个线程对象可以作为一个图的起始顶点，例如大多程序从main进程开始执行，那么该图就是以main进程顶点开始的一棵根树。在这个有向图中，根顶点可达的对象都是有效对象，GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意，该图为有向图)，那么我们认为这个(这些)对象不再被引用，可以被GC回收。</P>
<P>以下，我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻，我们都有一个有向图表示JVM的内存分配情况。以下右图，就是左边程序运行到第6行的示意图。</P>
<P>
<CENTER><IMG height=201 alt=图1 src="http://www-900.ibm.com/developerWorks/cn/java/l-JavaMemoryLeak/1.gif" width=582 border=0> </CENTER>
<P></P>
<P>Java使用有向图的方式进行内存管理，可以消除引用循环的问题，例如有三个对象，相互引用，只要它们和根进程不可达的，那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高，但是效率较低。另外一种常用的内存管理技术是使用计数器，例如COM模型采用计数器方式管理构件，它与有向图相比，精度行低(很难处理循环引用的问题)，但执行效率很高。</P>
<P><A name=3><SPAN class=atitle2>三 什么是Java中的内存泄露</SPAN></A></P>
<P>下面，我们就可以描述什么是内存泄漏。在Java中，内存泄漏就是存在一些被分配的对象，这些对象有下面两个特点，首先，这些对象是可达的，即在有向图中，存在通路可以与其相连；其次，这些对象是无用的，即程序以后不会再使用这些对象。如果对象满足这两个条件，这些对象就可以判定为Java中的内存泄漏，这些对象不会被GC所回收，然而它却占用内存。</P>
<P>在C++中，内存泄漏的范围更大一些。有些对象被分配了内存空间，然后却不可达，由于C++中没有GC，这些内存将永远收不回来。在Java中，这些不可达的对象都由GC负责回收，因此程序员不需要考虑这部分的内存泄露。</P>
<P>通过分析，我们得知，对于C++，程序员需要自己管理边和顶点，而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式，Java提高了编程的效率。</P>
<P>
<CENTER><IMG height=231 alt=图2 src="http://www-900.ibm.com/developerWorks/cn/java/l-JavaMemoryLeak/2.gif" width=507 border=0> </CENTER>
<P></P>
<P>因此，通过以上分析，我们知道在Java中也有内存泄漏，但范围比C++要小一些。因为Java从语言上保证，任何对象都是可达的，所有的不可达对象都由GC管理。</P>
<P>对于程序员来说，GC基本是透明的，不可见的。虽然，我们只有几个函数可以访问GC，例如运行GC的函数System.gc()，但是根据Java语言规范定义， 该函数不保证JVM的垃圾收集器一定会执行。因为，不同的JVM实现者可能使用不同的算法管理GC。通常，GC的线程的优先级别较低。JVM调用GC的策略也有很多种，有的是内存使用到达一定程度时，GC才开始工作，也有定时执行的，有的是平缓执行GC，有的是中断式执行GC。但通常来说，我们不需要关心这些。除非在一些特定的场合，GC的执行影响应用程序的性能，例如对于基于Web的实时系统，如网络游戏等，用户不希望GC突然中断应用程序执行而进行垃圾回收，那么我们需要调整GC的参数，让GC能够通过平缓的方式释放内存，例如将垃圾回收分解为一系列的小步骤执行，Sun提供的HotSpot JVM就支持这一特性。</P>
<P>下面给出了一个简单的内存泄露的例子。在这个例子中，我们循环申请Object对象，并将所申请的对象放入一个Vector中，如果我们仅仅释放引用本身，那么Vector仍然引用该对象，所以这个对象对GC来说是不可回收的。因此，如果对象加入到Vector后，还必须从Vector中删除，最简单的方法就是将Vector对象设置为null。</P>
<P>Vector v=new Vector(10);<BR>for (int i=1;i&lt;100; i++)<BR>{<BR>Object o=new Object();<BR>v.add(o);<BR>o=null;<BR>}<BR>//此时，所有的Object对象都没有被释放，因为变量v引用这些对象。</P>
<P><A name=4><SPAN class=atitle2>四 如何检测内存泄漏</SPAN></A></P>
<P>最后一个重要的问题，就是如何检测Java的内存泄漏。目前，我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具，它们的基本工作原理大同小异，都是通过监测Java程序运行时，所有对象的申请、释放等动作，将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler，JProbe Profiler，JinSight , Rational 公司的Purify等。</P>
<P>下面，我们将简单介绍Optimizeit的基本功能和工作原理。</P>
<P>Optimizeit Profiler版本4.11支持Application，Applet，Servlet和Romote Application四类应用，并且可以支持大多数类型的JVM，包括SUN JDK系列，IBM的JDK系列，和Jbuilder的JVM等。并且，该软件是由Java编写，因此它支持多种操作系统。Optimizeit系列还包括Thread Debugger和Code Coverage两个工具，分别用于监测运行时的线程状态和代码覆盖面。</P>
<P>当设置好所有的参数了，我们就可以在OptimizeIt环境下运行被测程序，在程序运行过程中，Optimizeit可以监视内存的使用曲线(如下图)，包括JVM申请的堆(heap)的大小，和实际使用的内存大小。另外，在运行过程中，我们可以随时暂停程序的运行，甚至强行调用GC，让GC进行内存回收。通过内存使用曲线，我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要，也很容易发现内存泄露。</P>
<P>
<CENTER><IMG height=354 alt=图3 src="http://www-900.ibm.com/developerWorks/cn/java/l-JavaMemoryLeak/3.gif" width=521 border=0> </CENTER>
<P></P>
<P>在运行过程中，我们还可以从不同视角观查内存的使用情况，Optimizeit提供了四种方式：</P>
<UL>
<LI>堆视角。 这是一个全面的视角，我们可以了解堆中的所有的对象信息(数量和种类)，并进行统计、排序，过滤。了解相关对象的变化情况。 
<LI>方法视角。通过方法视角，我们可以得知每一种类的对象，都分配在哪些方法中，以及它们的数量。 
<LI>对象视角。给定一个对象，通过对象视角，我们可以显示它的所有出引用和入引用对象，我们可以了解这个对象的所有引用关系。 
<LI>引用图。 给定一个根，通过引用图，我们可以显示从该顶点出发的所有出引用。 </LI></UL>
<P>在运行过程中，我们可以随时观察内存的使用情况，通过这种方式，我们可以很快找到那些长期不被释放，并且不再使用的对象。我们通过检查这些对象的生存周期，确认其是否为内存泄露。在实践当中，寻找内存泄露是一件非常麻烦的事情，它需要程序员对整个程序的代码比较清楚，并且需要丰富的调试经验，但是这个过程对于很多关键的Java程序都是十分重要的。</P>
<P>综上所述，Java也存在内存泄露问题，其原因主要是一些对象虽然不再被使用，但它们仍然被引用。为了解决这些问题，我们可以通过软件工具来检查内存泄露，检查的主要原理就是暴露出所有堆中的对象，让程序员寻找那些无用但仍被引用的对象。</P><img src ="http://www.blogjava.net/Apple/aggbug/6201.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-16 11:10 <a href="http://www.blogjava.net/Apple/archive/2005/06/16/6201.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载至piggy blog】【java面试题系列三】</title><link>http://www.blogjava.net/Apple/archive/2005/06/16/6200.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 16 Jun 2005 03:08:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/16/6200.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6200.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/16/6200.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6200.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6200.html</trackback:ping><description><![CDATA[<DIV class=postTitle><A href="http://blog.csdn.net/jery_lee/archive/2004/10/25/149926.aspx">&nbsp;</A></DIV>
<DIV class=postText><FONT color=#000000><FONT size=2>一些著名的大公司面试题目往往很基础.</FONT> </FONT>
<P><FONT color=#000000 size=2>一、Java基础知识<BR>1. Java有那些基本数据类型，String是不是基本数据类型，他们有何区别。<BR></FONT><FONT color=#000000 size=2>&nbsp;Integer literals,Floating-point literals,character literals,Boolean literal,String iteral.<BR>&nbsp;String 不是基本数据类型<BR>2. 字符串的操作：<BR>写一个方法，实现字符串的反转，如：输入abc，输出cba<BR></FONT><FONT color=#000000 size=2>&nbsp;&nbsp;&nbsp; public static String reverse(String s){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int length=s.length();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StringBuffer result=new StringBuffer(length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=length-1;i&gt;=0;i--)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result.append(s.charAt(i));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return result.toString();<BR>&nbsp;&nbsp;&nbsp; }<BR>写一个方法，实现字符串的替换，如：输入bbbwlirbbb，输出bbbhhtccc。<BR>3. 数据类型之间的转换<BR>如何将数值型字符转换为数字（Integer，Double）<BR>如何将数字转换为字符<BR>如何去小数点前两位，并四舍五入。<BR>4. 日期和时间<BR>如何取得年月日，小时分秒<BR></FONT><FONT color=#000000 size=2>Date dat=new Date();<BR>dat.getYear();dat.getMonth();dat.getDay();dat.getHours();...<BR>如何取得从1970年到现在的毫秒数<BR>long now=dat.getTime();<BR>如何获取某个日期是当月的最后一天<BR>如何格式化日期<BR></FONT><FONT color=#000000 size=2>DateFormate df=DateFormate.getInstance();<BR>df.Format(dat);<BR>5. 数组和集合<BR>6. 文件和目录（I/O）操作<BR>如何列出某个目录下的所有文件<BR>如何列出某个目录下的所有子目录<BR>判断一个文件或目录是否存在<BR>如何读写文件<BR>7. Java多态的实现（继承、重载、覆盖）<BR>8. 编码转换，怎样实现将GB2312编码的字符串转换为ISO-8859-1编码的字符串。<BR>9. Java中访问数据库的步骤，Statement和PreparedStatement之间的区别。<BR>10. 找出下列代码可能存在的错误，并说明原因：<BR>二、JSP&amp;Servlet技术<BR>1. 描述JSP和Servlet的区别、共同点、各自应用的范围<BR>2. 在Web开发中需要处理HTML标记时，应做什么样的处理，要筛选那些字符（&lt; &gt; &amp; “”）<BR>3. 在JSP中如何读取客户端的请求，如何访问CGI变量，如何确定某个Jsp文件的真实路径。<BR>4. 描述Cookie和Session的作用，区别和各自的应用范围，Session工作原理。<BR>5. 列出Jsp中包含外部文件的方式，两者有何区别。<BR>6. 说明Jsp中errorPage的作用，应用范围。<BR>7. 介绍在Jsp中如何使用JavaBeans。<BR>8. 简单介绍JSP的标记库<BR>9. Jsp和Servlet中的请求转发分别如何实现。<BR>三、J2EE相关知识<BR>1. 介绍J2EE、J2SE、J2SE的区别。<BR>2. J2EE是一种技术还是一种平台，他提供了那些技术。<BR>3. 什么是Application Server，它有什么功能和优点。<BR>4. 简单介绍连接池的优点和原理。<BR>5. Web.xml的作用<BR>四、其他<BR>1. Web安全性的考虑（表单验证、浏览器Basic方式的验证，应用程序的安全性，SSL，代码考虑）<BR>2. 简单介绍您所了解的MVC。<BR>3. 简单介绍所了解的XML。<BR>4. 文档和编码规范<BR>5. Java中的分页、效率考虑。<BR>6. 简单介绍您所了解的structs。</FONT></P>
<P><BR><FONT color=#000000 size=2>找出以下程序错误。<BR>Class Test{<BR>&nbsp;&nbsp;&nbsp; private String par1;<BR>&nbsp;&nbsp;&nbsp; private String par2;<BR>&nbsp;&nbsp;&nbsp; Test(){<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; public static void main(String[] arg){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int a ;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(a){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("par1="+par1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("par2=" + par2);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }<BR>}</FONT></P></DIV><img src ="http://www.blogjava.net/Apple/aggbug/6200.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-16 11:08 <a href="http://www.blogjava.net/Apple/archive/2005/06/16/6200.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【java面试题系列二】【转载至piggy blog】</title><link>http://www.blogjava.net/Apple/archive/2005/06/16/6199.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 16 Jun 2005 03:06:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/16/6199.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6199.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/16/6199.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6199.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6199.html</trackback:ping><description><![CDATA[注：可能和前面的题有重复<BR>
<SCRIPT>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</SCRIPT>
&nbsp;
<DIV class=post>
<DIV class=postTitle><A href="http://blog.csdn.net/jery_lee/archive/2004/10/24/149609.aspx">SCJP一些笔记&amp;&amp;局部类和嵌套类&amp;&amp;JAVA面试题&amp;&amp;XML区别</A> </DIV>
<DIV class=postText><SPAN id=ArticleContent1_ArticleContent1_lblContent>&nbsp; 
<P>1，初始化 <BR>类变量自动初始化为0或者null，局部变量使用前一定要初始化</P>
<P>2，主类型过载 <BR>类型匹配顺序：按照byte-&gt;short-&gt;char-&gt;int-&gt;long-&gt;float-&gt;double 顺序匹配。<BR>一种主类型只能匹配参数范围不小于其本身和其本身范围最接近的函数 <BR>即有int i;1.f(short s);2.f(long l);i只能适用于第2个f函数 <BR>如果是常数，则编译器作为int处理<BR>&nbsp;<BR>3，主类型计算结果的类型 和最大范围计算子的类型一致，但是最小也要是int，即byte+byte结果为int型。</P>
<P>4，short型和char型不能互相赋值（因为short有符号，char无符号） </P>
<P>5，package一定要放在java文件第一行（除注释空行外） </P>
<P>6，import只能放在第一行或者package下面 </P>
<P>7，多个import的package里面如果有同名class，使用时必须指定package </P>
<P>8，interface中只能有方法宣言和定数 </P>
<P>9，class实现interface必须实现全部方法 </P>
<P>10，同一java文件中class和interface只能有一个public </P>
<P>11，为了宣言定数，在变量前加final修饰子，变量不可改变，但是object的属性可以改变（有点忘记了，请确认，：）） </P>
<P>12，配列不能在宣言的时候用char arr[12]；形式指定，必须要用new指定大小。指定完毕后自动初期化；也可以new的时候用{0,0,0}形式指定 </P>
<P>13，如果一个object为null，使用其属性或者方法，产生NullPointerException </P>
<P>14，如果使用一个越界index访问配列，产生ArrayOfBoundsException</P>
<P>15，配列长用配列名.length </P>
<P>16，constructor如果有返回值，编译器作为一般方法看待 </P>
<P>17，constructor里面如果要用super(..)和this(..)，必须放在第一行，而且只能用一次 </P>
<P>18，如果没有显式调用super和this，编译器自动在第一行插入super();要求超类必须存在无参数的constructor </P>
<P>19，如果没有显式声明constructor，编译器自动生成默认无参数constructor。一旦声明，则无默认constructor </P>
<P>20，修饰子（访问范围大小顺序） <BR>public 完全公开 <BR>protected 同一package或者子类中无同一package <BR>private 本class </P>
<P>21，override的方法不能使访问范围缩小即父类中public方法不能被protected override<BR>&nbsp;<BR>22，static方法不能被override？确认 <BR>23，constructor不能被继承 </P>
<P>24，抽象class不能同时被static修饰子修饰<BR>&nbsp;<BR>25，native：方法被java以外其它语言实现 </P>
<P>26，interface里面： <BR>方法默认为public abstract修饰 <BR>变量默认为public static final修饰 </P>
<P>27，abstract和final，abstract和static，abstract和private不能同时指定 </P>
<P>28，&amp;&amp;，||为短路条件运算符 <BR>&amp;，|为不短路条件运算符 </P>
<P>29，switch()只能判断byte，short，int，char（long，float，double不行）<BR>&nbsp;<BR>30，Exception包括可check Exception和Runtime Exception可check Exception可以被编译器检查，Runtime Exception不被编译器检查，但是可以被catch捕捉 </P>
<P>31，try{}catch{}catch{}多个catch语句按照声明顺序检查，一旦被捕捉，以后catch就直接跳过，超类Exception可以捕捉子类Exception </P>
<P>32，try中即使有return，finally段也会被执行 </P>
<P>33，method声明用throws，抛出Exception用throw </P>
<P>34，强制垃圾回收，system.gc()；促使垃圾回收，但不是马上回收 </P>
<P>35，finalize()；在垃圾回收的时候自动调用；建议显式调用super.finalize()；<BR>&nbsp;<BR>36，main方法的args[0]是第一个参数，例如：java Test a b c，args[0]是"a"<BR>&nbsp;<BR>37，类中static段中不能throw异常，constructor里面不能throw异常 </P>
<P>38，对于除零：整数运算抛出ArithmeticException实数运算不抛出异常；返回Infinity或者-Infinity </P>
<P>39，instanceof运算子，is的意思，例如：betty instanceof girl，返回true，：）<BR>&nbsp;<BR>40，override和overload的区别 <BR>很简单，不多写了 </P>
<P>41，inner class（内部类：重点注意）<BR>nest class：class内部定义的class包括<BR>（member class：作为class的member定义的class <BR>local class：作为method局部变量定义的class <BR>anonymous class：用new生成的没有名字的class） </P>
<P>42，inner class：除了static member class以外的所有nest class总称 <BR>inner class中不能定义static段 <BR>inner class里面不能定义没有final修饰子修饰的static变量 <BR>inner class不能定义interface？（记不清了，自己确认一下） </P>
<P>43，member class的使用方法：TopClass.memberClass </P>
<P>44，static宣言的member class的生成方法： <BR>TopClass的instance.new memberClass()或者new TopClass.memberClass(); </P>
<P>45，local class不能显式被修饰子修饰 </P>
<P>46，local class只能存取被final修饰子修饰的上级变量（注意） </P>
<P>47，anonymous class instance生成方法： <BR>new SuperClass名(){.......}或者 <BR>new Interface名(){......} </P>
<P>48，anonymous class <BR>不能定义sub class（因为自己没有名字） <BR>不能定义为abstract class <BR>不能定义constructor（注意） </P>
<P>49，Thread生成方法 <BR>继承java.lang.Thread <BR>实现java.lang.Runnable interface，然后用new Thread(实现Runnable接口的class)来生成Thread instance </P>
<P>50，Thread的join方法，等待Thread结束 </P>
<P>51，Object的wait()方法和notify()，notifyAll()方法（线程同期化，自己好好看看） </P>
<P>52，同期化保留字:synchronized（有可能要求拼写） </P>
<P>53，如果用synchronized修饰static方法，static写在前头 </P>
<P>54，String和StringBuffer区别：String初始化后不可改变，StringBuffer可以改变 <BR>String s1="hello";<BR>String s2="hello"; <BR>if (s1==s2){System.out.println("OK");} <BR>执行结果是打印出OK </P>
<P>55, &gt;&gt;&gt;运算符高位填0，是为逻辑移位 <BR><BR><BR></P>
<DIV class=postbody>
<P><STRONG><FONT class=title color=#ff0000>局部类和嵌套类</FONT></STRONG></P>
<P>局部类<BR><BR>　　在一个函数体内定义的类称为局部类。局部类中只能使用它的外围作用域中的对象和函数进行联系，因为外围作用域中的变量与该局部类的对象无关。在定义局部类时需要注意：局部类中不能说明静态成员函数，并且所有成员函数都必须定义在类体内。在实践中，局部类是很少使用的。下面是一个局部类的例子。<BR><BR>int a;<BR>void fun()<BR>{<BR>　　static int s;<BR>　　class A<BR>　　　{<BR>　　　　public:<BR>　　　　　void init(int i) { s = i; }<BR>　　　　};<BR>　　A m;<BR>　　m.init(10);<BR>}<BR><BR><BR>　　嵌套类<BR><BR>　　在一个类中定义的类称为嵌套类，定义嵌套类的类称为外围类。<BR><BR>　　定义嵌套类的目的在于隐藏类名，减少全局的标识符，从而限制用户能否使用该类建立对象。这样可以提高类的抽象能力，并且强调了两个类(外围类和嵌套类)之间的主从关系。下面是一个嵌套类的例子：<BR><BR>class A<BR>{<BR>　public:<BR>　class B<BR>　　{　<BR>　　　public:<BR>　　　　…<BR>　　　private:<BR>　　　　…<BR>　　};<BR>　void f();<BR>　private:<BR>　int a;<BR>}<BR><BR><BR>　　其中，类B是一个嵌套类，类A是外围类，类B定义在类A的类体内。<BR><BR>　　对嵌套类的若干说明：<BR><BR>　　1、从作用域的角度看，嵌套类被隐藏在外围类之中，该类名只能在外围类中使用。如果在外围类的作用域内使用该类名时，需要加名字限定。<BR><BR>　　2、从访问权限的角度来看，嵌套类名与它的外围类的对象成员名具有相同的访问权限规则。不能访问嵌套类的对象中的私有成员函数，也不能对外围类的私有部分中的嵌套类建立对象。<BR><BR>　　3、嵌套类中的成员函数可以在它的类体外定义。<BR><BR>　　4、嵌套类中说明的成员不是外围类中对象的成员，反之亦然。嵌套类的成员函数对外围类的成员没有访问权，反之亦然。国此，在分析嵌套类与外围类的成员访问关系时，往往把嵌套类看作非嵌套类来处理。这样，上述的嵌套类可写成如下格式：<BR><BR>class A<BR>{<BR>　public:<BR>　void f();<BR>　private:<BR>　int a;<BR>　};<BR><BR>class B<BR>{<BR>　public:<BR>　　…<BR>　private:<BR>　　…<BR>};<BR><BR><BR>　　由引可见，嵌套类仅仅是语法上的嵌入。<BR><BR>　　5、在嵌套类中说明的友元对外围类的成员没有访问权。<BR><BR>　　6、如果嵌套类比较复杂，可以只在外围类中对嵌套类进行说明，关于嵌套的详细的内容可在外围类体外的文件域中进行定义。<BR></P></DIV></SPAN></DIV>
<DIV class=postFoot>&nbsp;</DIV></DIV><LINK href="http://blog.csdn.net/jery_lee/Services/Pingback.aspx" rel=pingback><!--
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
<rdf:Description
rdf:about="http://blog.csdn.net/jery_lee/archive/2004/10/24/149609.aspx"
dc:identifier="http://blog.csdn.net/jery_lee/archive/2004/10/24/149609.aspx"
dc:title="SCJP一些笔记&&局部类和嵌套类&&JAVA面试题&&XML区别"
trackback:ping="http://blog.csdn.net/jery_lee/services/trackbacks/149609.aspx" />
</rdf:RDF>
--><BR>
<DIV id=comments>
<H3></H3>
<DIV class=post>
<DIV class=postTitle>&nbsp;&nbsp;</DIV>
<DIV class=postText>个人在国内做IT已经有五年了，我从事JAVA也有三年了，有一些个人在面试后的对面试问题总结，在些写出来： <BR>1.J2EE是什么？ <BR><BR>2.介绍JAVA中的Collection FrameWork(包括如何写自己的数据结构)? <BR><BR>如COLLECTION中遗留类(HASHTABLE、VECTOR)和现有类的区别？(同步) <BR><BR>3.Java中异常处理机制，事件机制？ <BR><BR>　 <BR><BR>4.EJB与JAVA BEAN的区别？ <BR><BR>EJB与JAVA BEAN是SUN的不同组件规范，EJB是在容器中运行的，分步式的，而JAVA BEAN主要是一种可利用的组件，主要在客户端UI表现上。 <BR><BR>5.JAVA中的多形与继承？ <BR><BR>可出编程，选择题。 <BR><BR>6.抽象类与接口？ <BR><BR>接口的好处是将实现与表现分离。 <BR><BR>抽象类与接口都用于抽象，但是抽象类(JAVA中)可以有自己的部分实现，而接口则完全是一个标识(同时有多重继承的功能)。 <BR><BR>　 <BR><BR>7.Java 的通信编程，编程题(或问答)，用JAVA SOCKET编程，读服务器几个字符，再写入本地显示？ <BR><BR>　 <BR><BR>8.JAVA SERVLET API中forward() 与redirect()的区别？ <BR><BR>forward()方法可以使用原来的对象，而且速度较快。 <BR><BR>9.JAVA解析XML的方式？ <BR><BR>SAX、DOM <BR><BR><BR><BR>10.用JAVA实现数据库查询，这是一道编程题。 <BR><BR>基本操作，不用讲了。 <BR><BR>11.用JAVA实现一种排序 ，比较，JAVA类实现序列化的方法(二种)？ <BR><BR>如在COLLECTION框架中，实现比较要实现什么样的接口。 <BR><BR>12.JSP中动态INCLUDE与静态INCLUDE的区别？ <BR><BR>老一套，动态可以带参数，静态相当于一段静态HTML文件。 <BR><BR>　 <BR><BR>13.应用服务器与WEB SERVER的区别？ <BR><BR>　 <BR><BR>14.设计模式与UML方面。 <BR><BR>如工厂模式，解决多数据库支持问题。 <BR><BR>15。 编程：编写一个截取字符串的函数，输入为一个字符串和字节数，输出为按字节截取的字符串。 <BR><BR>但是要保证汉字不被截半个，如“我ABC”4，应该截为“我AB”，输入“我ABC汉DEF”，6，应该输出为“我ABC”而不是“我ABC+汉的半个”。 <BR><BR>16。在ORACLE大数据量下的分页解决方法。 一般用截取ID方法，还有是三层嵌套方法。 <BR><BR>17。WEB SERVICE名词解释。JSWDL开发包的介绍。JAXP、JAXM的解释。SOAP、UDDI解释。 <BR><BR>18。BS与CS的联系与区别。 <BR><BR>19。LINUX下线程，GDI类的解释。 <BR><BR>20。JAVA多线程编程。 <BR><BR>用JAVA写一个多线程程序，如写四个线程，二个加1，二个对一个变量减一，输出。 <BR><BR>21。JAVA的基本功： STRING与STRINGBUFFER的区别。 <BR><BR>22。STRUTS的应用(如STRUTS架构) <BR></DIV></DIV><BR>
<DIV class=post>
<DIV class=postTitle>&nbsp;</DIV>
<DIV class=postText>一、DOM （文档对象模型） <BR>为 XML 文档的已解析版本定义了一组接口。解析器读入整个文档，然后构建一个驻留内存的树结构，然后代码就可以使用 DOM 接口来操作这个树结构。 <BR>优点：整个文档树在内存中，便于操作；支持删除、修改、重新排列等多种功能； <BR>缺点：将整个文档调入内存（包括无用的节点），浪费时间和空间； <BR>使用场合：一旦解析了文档还需多次访问这些数据； <BR>硬件资源充足（内存、CPU） <BR><BR>二、SAX <BR>为解决DOM的问题，出现了SAX。 <BR>SAX ，事件驱动。当解析器发现元素开始、元素结束、文本、文档的开始或结束等时，发送事件，程序员编写响应这些事件的代码，保存数据。 <BR>优点：不用事先调入整个文档，占用资源少； <BR>SAX解析器代码比DOM解析器代码小，适于Applet，下载 <BR>缺点：不是持久的；事件过后，若没保存数据，那么数据就丢了； <BR>无状态性；从事件中只能得到文本，但不知该文本属于哪个元素； <BR>使用场合：Applet; <BR>只需XML文档的少量内容，很少回头访问； <BR>机器内存少； <BR><BR>三、JDOM <BR>为减少DOM、SAX的编码量，出现了JDOM； <BR>优点：20-80原则，极大减少了代码量 <BR>使用场合：要实现的功能简单，如解析、创建等 <BR>Java程序 <BR><BR>但在底层，JDOM还是使用SAX（最常用）、DOM、Xanan <BR><BR>四、JAPX <BR>为多个XML解析器提供了统一编程接口 <BR>更换解析器，不用更改代码 <BR>使用场合：若不用Jdom，一般建议使用JAPX，将代码与各种解析器的实现细节隔离。 <BR></DIV></DIV>
<DIV class=post>
<DIV class=postTitle>&nbsp;</DIV>
<DIV class=postText>16。在ORACLE大数据量下的分页解决方法。 一般用截取ID方法，还有是三层嵌套方法。 <BR><BR>select * from ( select row_.*, rownum rownum_ from (sql) row_ where rownum &lt;= ?) where rownum_ &gt; ? <BR></DIV></DIV><BR></DIV><img src ="http://www.blogjava.net/Apple/aggbug/6199.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-16 11:06 <a href="http://www.blogjava.net/Apple/archive/2005/06/16/6199.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【Java面试题集锦系列一】</title><link>http://www.blogjava.net/Apple/archive/2005/06/16/6198.html</link><dc:creator>苹果</dc:creator><author>苹果</author><pubDate>Thu, 16 Jun 2005 03:03:00 GMT</pubDate><guid>http://www.blogjava.net/Apple/archive/2005/06/16/6198.html</guid><wfw:comment>http://www.blogjava.net/Apple/comments/6198.html</wfw:comment><comments>http://www.blogjava.net/Apple/archive/2005/06/16/6198.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.blogjava.net/Apple/comments/commentRss/6198.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Apple/services/trackbacks/6198.html</trackback:ping><description><![CDATA[&nbsp;&nbsp; 这个还是早点准备好！<BR>&nbsp;<SPAN class=javascript id=text99548>回答不一定完全正确，但题目还是有些参考意义。故转贴<BR>原文： <A class=ilink href="http://www.java-cn.com/bbs-jsp/show.jsp?id=1055&amp;forum=tian_cookie" target=_blank>http://www.java-cn.com/bbs-jsp/show.jsp?id=1055&amp;forum=tian_cookie</A><BR>Regards<BR>Zerol <BR><BR>=======<BR><BR>前段时间因为要参加一个笔试，在准备期间在网上找到了两条关于笔试题目的文章，其中一篇为&lt;&lt;有感:应聘Java笔试时可能出现问题&gt;&gt;，还有一篇忘了名字，读后深受启发。 <BR>在寻找这些答案的过程中，我将相关答案记录下来，就形成了以下这些东西。需要说明的是以下答案肯定有很多不完整甚至错误的地方，需要各位来更正与完善它，千万不要扔我的鸡蛋啊。 <BR>希望本文能够给即将奔赴笔试考场的同仁些许帮助，更希望更多的人加入到收集整理笔试题与完善答案的这些工作中来，为大家更好的获得工作机会做一点贡献。 <BR>在此感谢前面两文的作者的对笔试题目的收集与整理。 <BR>如有任何意见与建议请通过QQ:6045306,Mail:huijunzi@21cn.com与我联系。 <BR>Java基础方面: <BR><BR>1、作用域public,private,protected,以及不写时的区别 <BR>答：区别如下： <BR>作用域 当前类 同一package 子孙类 其他package <BR>public √ √ √ √ <BR>protected √ √ √ × <BR>friendly √ √ × × <BR>private √ × × × <BR>不写时默认为friendly <BR><BR>2、ArrayList和Vector的区别,HashMap和Hashtable的区别 <BR>答：就ArrayList与Vector主要从二方面来说. <BR>一.同步性:Vector是线程安全的，也就是说是同步的，而ArrayList是线程序不安全的，不是同步的 <BR>二.数据增长:当需要增长时,Vector默认增长为原来一培，而ArrayList却是原来的一半 <BR>就HashMap与HashTable主要从三方面来说。 <BR>一.历史原因:Hashtable是基于陈旧的Dictionary类的，HashMap是Java 1.2引进的Map接口的一个实现 <BR>二.同步性:Hashtable是线程安全的，也就是说是同步的，而HashMap是线程序不安全的，不是同步的 <BR>三.值：只有HashMap可以让你将空值作为一个表的条目的key或value <BR><BR>3、char型变量中能不能存贮一个中文汉字?为什么? <BR>答：是能够定义成为一个中文的，因为java中以unicode编码，一个char占16个字节，所以放一个中文是没问题的 <BR><BR>4、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? <BR>答：多线程有两种实现方法，分别是继承Thread类与实现Runnable接口 <BR>同步的实现方面有两种，分别是synchronized,wait与notify <BR><BR>5、继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么? <BR>答:父类： <BR>package test; <BR>public class FatherClass <BR>{ <BR>public FatherClass() <BR>{ <BR>System.out.println("FatherClass Create"); <BR>} <BR>} <BR>子类: <BR>package test; <BR>import test.FatherClass; <BR>public class ChildClass extends FatherClass <BR>{ <BR>public ChildClass() <BR>{ <BR>System.out.println("ChildClass Create"); <BR>} <BR>public static void main(String[] args) <BR>{ <BR>FatherClass fc = new FatherClass(); <BR>ChildClass cc = new ChildClass(); <BR>} <BR>} <BR>输出结果： <BR>C:\&gt;java test.ChildClass <BR>FatherClass Create <BR>FatherClass Create <BR>ChildClass Create <BR><BR>6、内部类的实现方式? <BR>答：示例代码如下： <BR>package test; <BR>public class OuterClass <BR>{ <BR>private class InterClass <BR>{ <BR>public InterClass() <BR>{ <BR>System.out.println("InterClass Create"); <BR>} <BR>} <BR>public OuterClass() <BR>{ <BR>InterClass ic = new InterClass(); <BR>System.out.println("OuterClass Create"); <BR>} <BR>public static void main(String[] args) <BR>{ <BR>OuterClass oc = new OuterClass(); <BR>} <BR>} <BR>输出结果: <BR>C:\&gt;java test/OuterClass <BR>InterClass Create <BR>OuterClass Create <BR>再一个例题： <BR>public class OuterClass { <BR>private double d1 = 1.0; <BR>//insert code here <BR>} <BR>You need to insert an inner class declaration at line 3. Which two inner class declarations are <BR><BR>valid?(Choose two.) <BR>A. class InnerOne{ <BR>public static double methoda() {return d1;} <BR>} <BR>B. public class InnerOne{ <BR>static double methoda() {return d1;} <BR>} <BR>C. private class InnerOne{ <BR>double methoda() {return d1;} <BR>} <BR>D. static class InnerOne{ <BR>protected double methoda() {return d1;} <BR>} <BR>E. abstract class InnerOne{ <BR>public abstract double methoda(); <BR>} <BR>说明如下： <BR>一.静态内部类可以有静态成员，而非静态内部类则不能有静态成员。 故 A、B 错 <BR>二.静态内部类的非静态成员可以访问外部类的静态变量，而不可访问外部类的非静态变量；return d1 出错。 <BR><BR>故 D 错 <BR>三.非静态内部类的非静态成员可以访问外部类的非静态变量。 故 C 正确 <BR>四.答案为C、E <BR><BR>7、垃圾回收机制,如何优化程序? <BR>希望大家补上，谢谢 <BR><BR>8、float型float f=3.4是否正确? <BR>答:不正确。精度不准确,应该用强制类型转换，如下所示：float f=(float)3.4 <BR><BR>9、介绍JAVA中的Collection FrameWork(包括如何写自己的数据结构)? <BR>答：Collection FrameWork如下： <BR>Collection <BR>├List <BR>│├LinkedList <BR>│├ArrayList <BR>│└Vector <BR>│　└Stack <BR>└Set <BR>Map <BR>├Hashtable <BR>├HashMap <BR>└WeakHashMap <BR>Collection是最基本的集合接口，一个Collection代表一组Object，即Collection的元素（Elements） <BR>Map提供key到value的映射 <BR><BR>10、Java中异常处理机制，事件机制？ <BR><BR>11、JAVA中的多形与继承？ <BR>希望大家补上，谢谢 <BR><BR>12、抽象类与接口？ <BR>答：抽象类与接口都用于抽象，但是抽象类(JAVA中)可以有自己的部分实现，而接口则完全是一个标识(同时有多重继承的功能)。 <BR><BR>13、Java 的通信编程，编程题(或问答)，用JAVA SOCKET编程，读服务器几个字符，再写入本地显示？ <BR>答:Server端程序: <BR>package test; <BR>import java.net.*; <BR>import java.io.*; <BR><BR>public class Server <BR>{ <BR>private ServerSocket ss; <BR>private Socket socket; <BR>private BufferedReader in; <BR>private PrintWriter out; <BR>public Server() <BR>{ <BR>try <BR>{ <BR>ss=new ServerSocket(10000); <BR>while(true) <BR>{ <BR>socket = ss.accept(); <BR>String RemoteIP = socket.getInetAddress().getHostAddress(); <BR>String RemotePort = ":"+socket.getLocalPort(); <BR>System.out.println("A client come in!IP:"+RemoteIP+RemotePort); <BR>in = new BufferedReader(new <BR><BR>InputStreamReader(socket.getInputStream())); <BR>String line = in.readLine(); <BR>System.out.println("Cleint send is :" + line); <BR>out = new PrintWriter(socket.getOutputStream(),true); <BR>out.println("Your Message Received!"); <BR>out.close(); <BR>in.close(); <BR>socket.close(); <BR>} <BR>}catch (IOException e) <BR>{ <BR>out.println("wrong"); <BR>} <BR>} <BR>public static void main(String[] args) <BR>{ <BR>new Server(); <BR>} <BR>}; <BR>Client端程序: <BR>package test; <BR>import java.io.*; <BR>import java.net.*; <BR><BR>public class Client <BR>{ <BR>Socket socket; <BR>BufferedReader in; <BR>PrintWriter out; <BR>public Client() <BR>{ <BR>try <BR>{ <BR>System.out.println("Try to Connect to 127.0.0.1:10000"); <BR>socket = new Socket("127.0.0.1",10000); <BR>System.out.println("The Server Connected!"); <BR>System.out.println("Please enter some Character:"); <BR>BufferedReader line = new BufferedReader(new <BR><BR>InputStreamReader(System.in)); <BR>out = new PrintWriter(socket.getOutputStream(),true); <BR>out.println(line.readLine()); <BR>in = new BufferedReader(new InputStreamReader(socket.getInputStream())); <BR>System.out.println(in.readLine()); <BR>out.close(); <BR>in.close(); <BR>socket.close(); <BR>}catch(IOException e) <BR>{ <BR>out.println("Wrong"); <BR>} <BR>} <BR>public static void main(String[] args) <BR>{ <BR>new Client(); <BR>} <BR>}; <BR><BR>14、用JAVA实现一种排序，JAVA类实现序列化的方法(二种)？ 如在COLLECTION框架中，实现比较要实现什么样的接口？ <BR>答:用插入法进行排序代码如下 <BR>package test; <BR>import java.util.*; <BR>class InsertSort <BR>{ <BR>ArrayList al; <BR>public InsertSort(int num,int mod) <BR>{ <BR>al = new ArrayList(num); <BR>Random rand = new Random(); <BR>System.out.println("The ArrayList Sort Before:"); <BR>for (int i=0;i&lt;num ;i++ ) <BR>{ <BR>al.add(new Integer(Math.abs(rand.nextInt()) % mod + 1)); <BR>System.out.println("al["+i+"]="+al.get(i)); <BR>} <BR>} <BR>public void SortIt() <BR>{ <BR>Integer tempInt; <BR>int MaxSize=1; <BR>for(int i=1;i&lt;al.size();i++) <BR>{ <BR>tempInt = (Integer)al.remove(i); <BR>if(tempInt.intValue()&gt;=((Integer)al.get(MaxSize-1)).intValue()) <BR>{ <BR>al.add(MaxSize,tempInt); <BR>MaxSize++; <BR>System.out.println(al.toString()); <BR>} else { <BR>for (int j=0;j&lt;MaxSize ;j++ ) <BR>{ <BR>if <BR><BR>(((Integer)al.get(j)).intValue()&gt;=tempInt.intValue()) <BR>{ <BR>al.add(j,tempInt); <BR>MaxSize++; <BR>System.out.println(al.toString()); <BR>break; <BR>} <BR>} <BR>} <BR>} <BR>System.out.println("The ArrayList Sort After:"); <BR>for(int i=0;i&lt;al.size();i++) <BR>{ <BR>System.out.println("al["+i+"]="+al.get(i)); <BR>} <BR>} <BR>public static void main(String[] args) <BR>{ <BR>InsertSort is = new InsertSort(10,100); <BR>is.SortIt(); <BR>} <BR>} <BR>JAVA类实现序例化的方法是实现java.io.Serializable接口 <BR>Collection框架中实现比较要实现Comparable 接口和 Comparator 接口 <BR><BR>15、编程：编写一个截取字符串的函数，输入为一个字符串和字节数，输出为按字节截取的字符串。 但是要保证汉字不被截半个，如“我ABC”4，应该截为“我AB”，输入“我ABC汉DEF”，6，应该输出为“我ABC”而不是“我ABC+汉的半个”。 <BR>答：代码如下： <BR>package test; <BR><BR>class SplitString <BR>{ <BR>String SplitStr; <BR>int SplitByte; <BR>public SplitString(String str,int bytes) <BR>{ <BR>SplitStr=str; <BR>SplitByte=bytes; <BR>System.out.println("The String is:´"+SplitStr+"´;SplitBytes="+SplitByte); <BR>} <BR>public void SplitIt() <BR>{ <BR>int loopCount; <BR><BR>loopCount=(SplitStr.length()%SplitByte==0)?(SplitStr.length()/SplitByte):(SplitStr.length()/Split <BR><BR>Byte+1); <BR>System.out.println("Will Split into "+loopCount); <BR>for (int i=1;i&lt;=loopCount ;i++ ) <BR>{ <BR>if (i==loopCount){ <BR><BR>System.out.println(SplitStr.substring((i-1)*SplitByte,SplitStr.length())); <BR>} else { <BR><BR>System.out.println(SplitStr.substring((i-1)*SplitByte,(i*SplitByte))); <BR>} <BR>} <BR>} <BR>public static void main(String[] args) <BR>{ <BR>SplitString ss = new SplitString("test中dd文dsaf中男大3443n中国43中国人 <BR><BR>0ewldfls=103",4); <BR>ss.SplitIt(); <BR>} <BR>} <BR><BR>16、JAVA多线程编程。 用JAVA写一个多线程程序，如写四个线程，二个加1，二个对一个变量减一，输出。 <BR>希望大家补上，谢谢 <BR><BR>17、STRING与STRINGBUFFER的区别。 <BR>答：STRING的长度是不可变的，STRINGBUFFER的长度是可变的。如果你对字符串中的内容经常进行操作，特别是内容要修改时，那么使用StringBuffer，如果最后需要String，那么使用StringBuffer的toString()方法 <BR><BR>Jsp方面 <BR><BR>1、jsp有哪些内置对象?作用分别是什么? <BR>答:JSP共有以下9种基本内置组件（可与ASP的6种内部组件相对应）： <BR>　request 用户端请求，此请求会包含来自GET/POST请求的参数 <BR>response 网页传回用户端的回应 <BR>pageContext 网页的属性是在这里管理 <BR>session 与请求有关的会话期 <BR>application servlet 正在执行的内容 <BR>out 用来传送回应的输出 <BR>config servlet的构架部件 <BR>page JSP网页本身 <BR>exception 针对错误网页，未捕捉的例外 <BR><BR>2、jsp有哪些动作?作用分别是什么? <BR>答:JSP共有以下6种基本动作 <BR>jsp:include：在页面被请求的时候引入一个文件。 <BR>jsp:useBean：寻找或者实例化一个JavaBean。 <BR>jsp:setProperty：设置JavaBean的属性。 <BR>jsp:getProperty：输出某个JavaBean的属性。 <BR>jsp:forward：把请求转到一个新的页面。 <BR>jsp:plugin：根据浏览器类型为Java插件生成OBJECT或EMBED标记 <BR><BR>3、JSP中动态INCLUDE与静态INCLUDE的区别？ <BR>答：动态INCLUDE用jsp:include动作实现 <BR>&lt;jsp:include page="included.jsp" flush="true" /&gt;它总是会检查所含文件中的变化，适合用于包含动态页面，并且可以带参数 <BR>静态INCLUDE用include伪码实现,定不会检查所含文件的变化，适用于包含静态页面 <BR>&lt;%@ include file="included.htm" %&gt; <BR><BR>4、两种跳转方式分别是什么?有什么区别? <BR>答：有两种，分别为： <BR>&lt;jsp:include page="included.jsp" flush="true"&gt; <BR>&lt;jsp:forward page= "nextpage.jsp"/&gt; <BR>前者页面不会转向include所指的页面，只是显示该页的结果，主页面还是原来的页面。执行完后还会回来，相当于函数调用。并且可以带参数.后者完全转向新页面，不会再回来。相当于go to 语句。 <BR><BR>Servlet方面 <BR><BR>1、说一说Servlet的生命周期? <BR>答:servlet有良好的生存期的定义，包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。 <BR><BR>2、Servlet版本间(忘了问的是哪两个版本了)的不同? <BR>希望大家补上，谢谢 <BR><BR>3、JAVA SERVLET API中forward() 与redirect()的区别？ <BR>答:前者仅是容器中控制权的转向，在客户端浏览器地址栏中不会显示出转向后的地址；后者则是完全的跳转，浏览器将会得到跳转的地址，并重新发送请求链接。这样，从浏览器的地址栏中可以看到跳转后的链接地址。所以，前者更加高效，在前者可以满足需要时，尽量使用forward()方法，并且，这样也有助于隐藏实际的链接。在有些情况下，比如，需要跳转到一个其它服务器上的资源，则必须使用sendRedirect()方法。 <BR><BR>4、Servlet的基本架构 <BR>public class ServletName extends HttpServlet { <BR>public void doPost(HttpServletRequest request, HttpServletResponse response) throws <BR>ServletException, IOException { <BR>} <BR>public void doGet(HttpServletRequest request, HttpServletResponse response) throws <BR>ServletException, IOException { <BR>} <BR>} <BR><BR>Jdbc、Jdo方面 <BR><BR>1、可能会让你写一段Jdbc连Oracle的程序,并实现数据查询. <BR>答:程序如下： <BR>package hello.ant; <BR>import java.sql.*; <BR>public class jdbc <BR>{ <BR>String dbUrl="jdbc:oracle:thin:@127.0.0.1:1521:orcl"; <BR>String theUser="admin"; <BR>String thePw="manager"; <BR>Connection c=null; <BR>Statement conn; <BR>ResultSet rs=null; <BR>public jdbc() <BR>{ <BR>try{ <BR>Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); <BR>c = DriverManager.getConnection(dbUrl,theUser,thePw); <BR>conn=c.createStatement(); <BR>}catch(Exception e){ <BR>e.printStackTrace(); <BR>} <BR>} <BR>public boolean executeUpdate(String sql) <BR>{ <BR>try <BR>{ <BR>conn.executeUpdate(sql); <BR>return true; <BR>} <BR>catch (SQLException e) <BR>{ <BR>e.printStackTrace(); <BR>return false; <BR>} <BR>} <BR>public ResultSet executeQuery(String sql) <BR>{ <BR>rs=null; <BR>try <BR>{ <BR>rs=conn.executeQuery(sql); <BR>} <BR>catch (SQLException e) <BR>{ <BR>e.printStackTrace(); <BR>} <BR>return rs; <BR>} <BR>public void close() <BR>{ <BR>try <BR>{ <BR>conn.close(); <BR>c.close(); <BR>} <BR>catch (Exception e) <BR>{ <BR>e.printStackTrace(); <BR>} <BR>} <BR>public static void main(String[] args) <BR>{ <BR>ResultSet rs; <BR>jdbc conn = new jdbc(); <BR>rs=conn.executeQuery("select * from test"); <BR>try{ <BR>while (rs.next()) <BR>{ <BR>System.out.println(rs.getString("id")); <BR>System.out.println(rs.getString("name")); <BR>} <BR>}catch(Exception e) <BR>{ <BR>e.printStackTrace(); <BR>} <BR>} <BR>} <BR><BR>2、Class.forName的作用?为什么要用? <BR>答：调用该访问返回一个以字符串指定类名的类的对象。 <BR><BR>3、Jdo是什么? <BR>答:JDO是Java对象持久化的新的规范，为java data object的简称,也是一个用于存取某种数据仓库中的对象的标准化API。JDO提供了透明的对象存储，因此对开发人员来说，存储数据对象完全不需要额外的代码（如JDBC API的使用）。这些繁琐的例行工作已经转移到JDO产品提供商身上，使开发人员解脱出来，从而集中时间和精力在业务逻辑上。另外，JDO很灵活，因为它可以在任何数据底层上运行。JDBC只是面向关系数据库（RDBMS)JDO更通用，提供到任何数据底层的存储功能，比如关系数据库、文件、XML以及对象数据库（ODBMS）等等，使得应用可移植性更强。 <BR><BR>4、在ORACLE大数据量下的分页解决方法。一般用截取ID方法，还有是三层嵌套方法。 <BR>答:一种分页方法 <BR>&lt;% <BR>int i=1; <BR>int numPages=14; <BR>String pages = request.getParameter("page") ; <BR>int currentPage = 1; <BR>currentPage=(pages==null)?(1):{Integer.parseInt(pages)} <BR>sql = "select count(*) from tables"; <BR>ResultSet rs = DBLink.executeQuery(sql) ; <BR>while(rs.next()) i = rs.getInt(1) ; <BR>int intPageCount=1; <BR>intPageCount=(i%numPages==0)?(i/numPages):(i/numPages+1); <BR>int nextPage ; <BR>int upPage; <BR>nextPage = currentPage+1; <BR>if (nextPage&gt;=intPageCount) nextPage=intPageCount; <BR>upPage = currentPage-1; <BR>if (upPage&lt;=1) upPage=1; <BR>rs.close(); <BR>sql="select * from tables"; <BR>rs=DBLink.executeQuery(sql); <BR>i=0; <BR>while((i&lt;numPages*(currentPage-1))&amp;&amp;rs.next()){i++;} <BR>%&gt; <BR>//输出内容 <BR>//输出翻页连接 <BR>合计:&lt;%=currentPage%&gt;/&lt;%=intPageCount%&gt;&lt;a href="List.jsp?page=1"&gt;第一页&lt;/a&gt;&lt;a <BR><BR>href="List.jsp?page=&lt;%=upPage%&gt;"&gt;上一页&lt;/a&gt; <BR>&lt;% <BR>for(int j=1;j&lt;=intPageCount;j++){ <BR>if(currentPage!=j){ <BR>%&gt; <BR>&lt;a href="list.jsp?page=&lt;%=j%&gt;"&gt;[&lt;%=j%&gt;]&lt;/a&gt; <BR>&lt;% <BR>}else{ <BR>out.println(j); <BR>} <BR>} <BR>%&gt; <BR>&lt;a href="List.jsp?page=&lt;%=nextPage%&gt;"&gt;下一页&lt;/a&gt;&lt;a href="List.jsp?page=&lt;%=intPageCount%&gt;"&gt;最后页 <BR><BR>&lt;/a&gt; <BR><BR>Xml方面 <BR><BR>1、xml有哪些解析技术?区别是什么? <BR>答:有DOM,SAX,STAX等 <BR>DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的，这种结构占用的内存较多，而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件，不需要一次全部装载整个文件。当遇到像文件开头，文档结束，或者标签开头与标签结束时，它会触发一个事件，用户通过在其回调事件中写入处理代码来处理XML文件，适合对XML的顺序访问 <BR>STAX:Streaming API for XML (StAX) <BR><BR>2、你在项目中用到了xml技术的哪些方面?如何实现的? <BR>答:用到了数据存贮，信息配置两方面。在做数据交换平台时，将不能数据源的数据组装成XML文件，然后将XML文件压缩打包加密后通过网络传送给接收者，接收解密与解压缩后再同XML文件中还原相关信息进行处理。在做软件配置时，利用XML可以很方便的进行，软件的各种配置参数都存贮在XML文件中。 <BR><BR>3、用jdom解析xml文件时如何解决中文问题?如何解析? <BR>答:看如下代码,用编码方式加以解决 <BR>package test; <BR>import java.io.*; <BR>public class DOMTest <BR>{ <BR>private String inFile = "c:\\people.xml"; <BR>private String outFile = "c:\\people.xml"; <BR>public static void main(String args[]) <BR>{ <BR>new DOMTest(); <BR>} <BR>public DOMTest() <BR>{ <BR>try <BR>{ <BR>javax.xml.parsers.DocumentBuilder builder = <BR><BR>javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder(); <BR>org.w3c.dom.Document doc = builder.newDocument(); <BR>org.w3c.dom.Element root = doc.createElement("老师"); <BR>org.w3c.dom.Element wang = doc.createElement("王"); <BR>org.w3c.dom.Element liu = doc.createElement("刘"); <BR>wang.appendChild(doc.createTextNode("我是王老师")); <BR>root.appendChild(wang); <BR>doc.appendChild(root); <BR>javax.xml.transform.Transformer transformer = <BR>javax.xml.transform.TransformerFactory.newInstance().newTransformer(); <BR>transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "gb2312"); <BR>transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes"); <BR><BR>transformer.transform(new javax.xml.transform.dom.DOMSource(doc), <BR>new <BR><BR>javax.xml.transform.stream.StreamResult(outFile)); <BR>} <BR>catch (Exception e) <BR>{ <BR>System.out.println (e.getMessage()); <BR>} <BR>} <BR>} <BR><BR>4、编程用JAVA解析XML的方式. <BR>答:用SAX方式解析XML，XML文件如下： <BR>&lt;?xml version="1.0" encoding="gb2312"?&gt; <BR>&lt;person&gt; <BR>&lt;name&gt;王小明&lt;/name&gt; <BR>&lt;college&gt;信息学院&lt;/college&gt; <BR>&lt;telephone&gt;6258113&lt;/telephone&gt; <BR>&lt;notes&gt;男,1955年生,博士，95年调入海南大学&lt;/notes&gt; <BR>&lt;/person&gt; <BR>事件回调类SAXHandler.java <BR>import java.io.*; <BR>import java.util.Hashtable; <BR>import org.xml.sax.*; <BR>public class SAXHandler extends HandlerBase <BR>{ <BR>private Hashtable table = new Hashtable(); <BR>private String currentElement = null; <BR>private String currentValue = null; <BR>public void setTable(Hashtable table) <BR>{ <BR>this.table = table; <BR>} <BR>public Hashtable getTable() <BR>{ <BR>return table; <BR>} <BR>public void startElement(String tag, AttributeList attrs) <BR>throws SAXException <BR>{ <BR>currentElement = tag; <BR>} <BR>public void characters(char[] ch, int start, int length) <BR>throws SAXException <BR>{ <BR>currentValue = new String(ch, start, length); <BR>} <BR>public void endElement(String name) throws SAXException <BR>{ <BR>if (currentElement.equals(name)) <BR>table.put(currentElement, currentValue); <BR>} <BR>} <BR>JSP内容显示源码,SaxXml.jsp: <BR>&lt;HTML&gt; <BR>&lt;HEAD&gt; <BR>&lt;TITLE&gt;剖析XML文件people.xml&lt;/TITLE&gt; <BR>&lt;/HEAD&gt; <BR>&lt;BODY&gt; <BR>&lt;%@ page errorPage="ErrPage.jsp" <BR>contentType="text/html;charset=GB2312" %&gt; <BR>&lt;%@ page import="java.io.*" %&gt; <BR>&lt;%@ page import="java.util.Hashtable" %&gt; <BR>&lt;%@ page import="org.w3c.dom.*" %&gt; <BR>&lt;%@ page import="org.xml.sax.*" %&gt; <BR>&lt;%@ page import="javax.xml.parsers.SAXParserFactory" %&gt; <BR>&lt;%@ page import="javax.xml.parsers.SAXParser" %&gt; <BR>&lt;%@ page import="SAXHandler" %&gt; <BR>&lt;% <BR>File file = new File("c:\\people.xml"); <BR>FileReader reader = new FileReader(file); <BR>Parser parser; <BR>SAXParserFactory spf = SAXParserFactory.newInstance(); <BR>SAXParser sp = spf.newSAXParser(); <BR>SAXHandler handler = new SAXHandler(); <BR>sp.parse(new InputSource(reader), handler); <BR>Hashtable hashTable = handler.getTable(); <BR>out.println("&lt;TABLE BORDER=2&gt;&lt;CAPTION&gt;教师信息表&lt;/CAPTION&gt;"); <BR>out.println("&lt;TR&gt;&lt;TD&gt;姓名&lt;/TD&gt;" + "&lt;TD&gt;" + <BR>(String)hashTable.get(new String("name")) + "&lt;/TD&gt;&lt;/TR&gt;"); <BR>out.println("&lt;TR&gt;&lt;TD&gt;学院&lt;/TD&gt;" + "&lt;TD&gt;" + <BR>(String)hashTable.get(new String("college"))+"&lt;/TD&gt;&lt;/TR&gt;"); <BR>out.println("&lt;TR&gt;&lt;TD&gt;电话&lt;/TD&gt;" + "&lt;TD&gt;" + <BR>(String)hashTable.get(new String("telephone")) + "&lt;/TD&gt;&lt;/TR&gt;"); <BR>out.println("&lt;TR&gt;&lt;TD&gt;备注&lt;/TD&gt;" + "&lt;TD&gt;" + <BR>(String)hashTable.get(new String("notes")) + "&lt;/TD&gt;&lt;/TR&gt;"); <BR>out.println("&lt;/TABLE&gt;"); <BR>%&gt; <BR>&lt;/BODY&gt; <BR>&lt;/HTML&gt; <BR><BR>EJB方面 <BR><BR>1、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别? <BR>答：规范内容包括Bean提供者，应用程序装配者，EJB容器，EJB配置工具，EJB服务提供者，系统管理员。这里面，EJB容器是EJB之所以能够运行的核心。EJB容器管理着EJB的创建，撤消，激活，去活，与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC,JMS..... <BR><BR>2、EJB与JAVA BEAN的区别？ <BR>答:Java Bean 是可复用的组件，对Java Bean并没有严格的规范，理论上讲，任何一个Java类都可以是一个Bean。但通常情况下，由于Java Bean是被容器所创建（如Tomcat)的，所以Java Bean应具有一个无参的构造器，另外，通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件，它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM，即分布式组件。它是基于Java的远程方法调用（RMI）技术的，所以EJB可以被远程访问（跨进程、跨计算机）。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中，EJB客户从不直接访问真正的EJB组件，而是通过其容器访问。EJB容器是EJB组件的代理，EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。 <BR><BR>3、EJB的基本架构 <BR>答:一个EJB包括三个部分: <BR>Remote Interface 接口的代码 <BR>package Beans; <BR>import javax.ejb.EJBObject; <BR>import java.rmi.RemoteException; <BR>public interface Add extends EJBObject <BR>{ <BR>//some method declare <BR>} <BR>Home Interface 接口的代码 <BR>package Beans; <BR>import java.rmi.RemoteException; <BR>import jaax.ejb.CreateException; <BR>import javax.ejb.EJBHome; <BR>public interface AddHome extends EJBHome <BR>{ <BR>//some method declare <BR>} <BR>EJB类的代码 <BR>package Beans; <BR>import java.rmi.RemoteException; <BR>import javax.ejb.SessionBean; <BR>import javx.ejb.SessionContext; <BR>public class AddBean Implements SessionBean <BR>{ <BR>//some method declare <BR>} <BR><BR>J2EE,MVC方面 <BR><BR>1、MVC的各个部分都有那些技术来实现?如何实现? <BR>答:MVC是Model－View－Controller的简写。"Model" 代表的是应用的业务逻辑（通过JavaBean，EJB组件实现）， "View" 是应用的表示面（由JSP页面产生），"Controller" 是提供应用的处理过程控制（一般是一个Servlet），通过这种设计模型把应用逻辑，处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。 <BR><BR>2、应用服务器与WEB SERVER的区别？ <BR>希望大家补上，谢谢 <BR><BR>3、J2EE是什么？ <BR>答:Je22是Sun公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企业级应用模型(enterpriese application model).在这样的一个应用系统中，可按照功能划分为不同的组件，这些组件又可在不同计算机上，并且处于相应的层次(tier)中。所属层次包括客户层(clietn tier)组件,web层和组件,Business层和组件,企业信息系统(EIS)层。 <BR><BR>4、WEB SERVICE名词解释。JSWDL开发包的介绍。JAXP、JAXM的解释。SOAP、UDDI,WSDL解释。 <BR>答：Web Service描述语言WSDL <BR>SOAP即简单对象访问协议(Simple Object Access Protocol)，它是用于交换XML编码信息的轻量级协议。 <BR>UDDI 的目的是为电子商务建立标准；UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范，同时也包含一组使企业能将自身提供的Web Service注册，以使别的企业能够发现的访问协议的实现标准。 <BR><BR>5、BS与CS的联系与区别。 <BR>希望大家补上，谢谢 <BR><BR>6、STRUTS的应用(如STRUTS架构) <BR>答：Struts是采用Java Servlet/JavaServer Pages技术，开发Web应用程序的开放源码的framework。 采用Struts能开发出基于MVC(Model-View-Controller)设计模式的应用构架。 Struts有如下的主要功能： <BR>一.包含一个controller servlet，能将用户的请求发送到相应的Action对象。 <BR>二.JSP自由tag库，并且在controller servlet中提供关联支持，帮助开发员创建交互式表单应用。 <BR>三.提供了一系列实用对象：XML处理、通过Java reflection APIs自动处理JavaBeans属性、国际化的提示和消息。 <BR><BR>设计模式方面 <BR><BR>1、开发中都用到了那些设计模式?用在什么场合? <BR>答：每个模式都描述了一个在我们的环境中不断出现的问题，然后描述了该问题的解决方案的核心。通过这种方式，你可以无数次地使用那些已有的解决方案，无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。 <BR><BR>2、UML方面 <BR>答：标准建模语言UML。用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图,合作图),实现图, <BR><BR>JavaScript方面 <BR><BR>1、如何校验数字型? <BR>var re=/^\d{1,8}$|\.\d{1,2}$/; <BR>var str=document.form1.all(i).value; <BR>var r=str.match(re); <BR>if (r==null) <BR>{ <BR>sign=-4; <BR>break; <BR>} <BR>else{ <BR>document.form1.all(i).value=parseFloat(str); <BR>} <BR><BR>CORBA方面 <BR><BR>1、CORBA是什么?用途是什么? <BR>答：CORBA 标准是公共对象请求代理结构(Common Object Request Broker Architecture)，由对象管理组织 (Object Management Group，缩写为 OMG)标准化。它的组成是接口定义语言(IDL), 语言绑定(binding:也译为联编)和允许应用程序间互操作的协议。 其目的为： <BR>用不同的程序设计语言书写 <BR>在不同的进程中运行 <BR>为不同的操作系统开发 <BR><BR>LINUX方面 <BR><BR>1、LINUX下线程，GDI类的解释。 <BR>答：LINUX实现的就是基于核心轻量级进程的"一对一"线程模型，一个线程实体对应一个核心轻量级进程，而线程之间的管理在核外函数库中实现。 <BR>GDI类为图像设备编程接口类库。</SPAN><img src ="http://www.blogjava.net/Apple/aggbug/6198.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Apple/" target="_blank">苹果</a> 2005-06-16 11:03 <a href="http://www.blogjava.net/Apple/archive/2005/06/16/6198.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>