﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-大道至简-文章分类-Java</title><link>http://www.blogjava.net/hellotony/category/4793.html</link><description>道生一，一生二，二生三，三生万物。万物负阴而抱阳，冲气以为和。</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 20:39:45 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 20:39:45 GMT</pubDate><ttl>60</ttl><item><title>HttpSession常见问题</title><link>http://www.blogjava.net/hellotony/articles/43139.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Tue, 25 Apr 2006 14:18:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/43139.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/43139.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/43139.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/43139.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/43139.html</trackback:ping><description><![CDATA[
		<p>
				<font size="2">1、session在何时被创建<br />一个常见的误解是以为session在有客户端访问时就被创建，然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建，注意如果JSP没有显示的使用 &lt;</font>
				<a href="mailto:%@page">
						<font size="2">%@page</font>
				</a>
				<font size="2"> session="false"%&gt; 关闭session，则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。</font>
		</p>
		<p>
				<font size="2">由于session会消耗内存资源，因此，如果不打算使用session，应该在所有的JSP中关闭它。</font>
		</p>
		<p>
				<font size="2">2、session何时被删除<br />综合前面的讨论，session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止（非持久session）</font>
		</p>
		<p>
				<font size="2">3、如何做到在浏览器关闭时删除session<br />严格的讲，做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作，然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。</font>
		</p>
		<p>
				<font size="2">4、有个HttpSessionListener是怎么回事<br />你可以创建这样的listener去监控session的创建和销毁事件，使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener，而不是相反。类似的与HttpSession有关的listener还有HttpSessionBindingListener，HttpSessionActivationListener和HttpSessionAttributeListener。</font>
		</p>
		<p>
				<font size="2">5、存放在session中的对象必须是可序列化的吗<br />不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象，在session销毁时会有一个Exception，很奇怪。</font>
		</p>
		<p>
				<font size="2">6、如何才能正确的应付客户端禁止cookie的可能性<br />对所有的URL使用URL重写，包括超链接，form的action，和重定向的URL，具体做法参见[6]<br /></font>
				<a href="http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770">
						<font size="2">http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770</font>
				</a>
		</p>
		<p>
				<font size="2">7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session<br />参见第三小节对cookie的讨论，对session来说是只认id不认人，因此不同的浏览器，不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。</font>
		</p>
		<p>
				<font size="2">8、如何防止用户打开两个浏览器窗口操作导致的session混乱<br />这个问题与防止表单多次提交是类似的，可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端，同时保存在session里，客户端提交表单时必须把这个id也返回服务器，程序首先比较返回的id与保存在session里的值是否一致，如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口，一般不设置这个id，或者使用单独的id，以防主窗口无法操作，建议不要再window.open打开的窗口里做修改操作，这样就可以不用设置。</font>
		</p>
		<p>
				<font size="2">9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue<br />做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变，需要向其他服务器进程复制新的session值。</font>
		</p>
		<p>
				<font size="2">10、为什么session不见了<br />排除session正常失效的因素之外，服务器本身的可能性应该是微乎其微的，虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过；浏览器插件的可能性次之，笔者也遇到过3721插件造成的问题；理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。<br />出现这一问题的大部分原因都是程序的错误，最常见的就是在一个应用程序中去访问另外一个应用程序。</font>
		</p>
<img src ="http://www.blogjava.net/hellotony/aggbug/43139.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2006-04-25 22:18 <a href="http://www.blogjava.net/hellotony/articles/43139.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java高级日期概念</title><link>http://www.blogjava.net/hellotony/articles/27883.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Fri, 13 Jan 2006 03:33:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/27883.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/27883.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/27883.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/27883.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/27883.html</trackback:ping><description><![CDATA[<font id="zoom" size="2"> 如果你的Java
程序向处在不同时区或者不同国家的用户显示时间和日期，那么你需要了解Java日期类的一些更加高级的方面。在“使用Java
Date和Calendar类计算，定制和解析日期”的这篇文章里我们提供了对日期，日期数据的格式化，日期数据的解析和日期计算的一个概览。对于这些概
念的深入的理解对于讨论更高级的诸如时区，国际化标准格式和SQL日期数据等这些有关日期的问题是关键的。
<br>
				</font><div align="center">
<font id="zoom" size="2">				<script type="text/javascript"><!--
				google_ad_client = "pub-5535238706151070";
				google_ad_width = 468;
				google_ad_height = 60;
				google_ad_format = "468x60_as";
				google_ad_type = "text_image";
				google_ad_channel ="";
				google_color_border = "ffffff";
				google_color_bg = "ffffff";
				google_color_link = "990000";
				google_color_url = "008000";
				google_color_text = "000000";
				//--></script>
				<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
				</font></div>
<font id="zoom" size="2">				我们在本文中讨论的类将包含java.text.DateFormat，以及java.util.TimeZone和java.util.Locate。我们还将讨论如何使用一个java.util.Date的子类java.sql.Date来从Oracle<a href="http://www.gamvan.com/database/" target="_blank">数据库</a>里提取和保存Java日期数据。 <br><br>地区的问题 <br>在
我们国际化我们的日期数据以前，我们需要进一步的学习Locale类，也就是java.util.Locale。Locale类的一个实例通常包含国家和
语言信息。其中的每一个部分都是由基于国际标准化组织（ISO）制定的国家代码ISO－3166和语言代码ISO－639的两字符的字符串构成的。 <br><br>让我们来创建两个Locale实例，其中一个对应的是美国英语而另一个对应的是法国法语。见表A。 <br><br><strong>表A</strong> <br><br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>import java.util.Locale; <br><br>public class DateExample6 { <br><br>public static void main(String[] args) { <br>// Create a locale for the English language in the US. <br>Locale localeEN = new Locale("en", "US"); <br><br>System.out.println("Display Name: " + <br>localeEN.getDisplayName()); <br>System.out.println("Country: " + localeEN.getCountry()); <br>System.out.println("Language: " + localeEN.getLanguage()); <br><br>// Create a locale for the French language in France. <br>Locale localeFR = new Locale("fr", "FR"); <br>System.out.println(" Display Name: " + <br>localeFR.getDisplayName()); <br>System.out.println("Country: " + localeFR.getCountry()); <br>System.out.println("Language: " + localeFR.getLanguage()); <br><br>// Display the English-US locale in French <br>System.out.println(" en Display Name in French: " + <br>localeEN.getDisplayName(localeFR)); <br>} <br>} </td></tr></tbody></table><br>在
这个例子中，我们用getDisplayName方法来显示Locale的一个更易读的文本。你还应该注意到我们在最后一次调用
getDisplayName的时候，我们在对English Locale对象调用getDisplayName的时候同时传递了French
Locale对象。这允许我们选择显示Locale对象所用的语言，让我们用英语显示法语Locale对象的内容。下面是这个例子的输出： <br><br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>Display Name: English (United States) <br>Country: US <br>Language: en <br>Display Name: French (France) <br>Country: FR <br>Language: fr <br>en Display Name in French: anglais (états-Unis) </td></tr></tbody></table><br><br>多个地域的日期格式化 <br>使用java.util.Locale和java.text.DateFormat类我们就能够格式化日期数据把它显示给在另一个地域的用户，比方法国。表B中的例子为英语和法语各创建了一个完整的日期格式化器。 <br><br><strong>表 B</strong> <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>import java.util.Locale; <br>import java.util.Date; <br>import java.text.DateFormat; <br><br>public class DateExample7 { <br><br>public static void main(String[] args) { <br>// Get the current system date and time. <br>Date date = new Date(); <br><br>// Get a France locale using a Locale constant. <br>Locale localeFR = Locale.FRANCE; <br><br>// Create an English/US locale using the constructor. <br>Locale localeEN = new Locale("en", "US" ); <br><br>// Get a date time formatter for display in France. <br>DateFormat fullDateFormatFR = <br>DateFormat.getDateTimeInstance( <br>DateFormat.FULL, <br>DateFormat.FULL, <br>localeFR); <br><br>// Get a date time formatter for display in the U.S. <br>DateFormat fullDateFormatEN = <br>DateFormat.getDateTimeInstance( <br>DateFormat.FULL, <br>DateFormat.FULL, <br>localeEN); <br><br>System.out.println("Locale: " + localeFR.getDisplayName()); <br>System.out.println(fullDateFormatFR.format(date)); <br>System.out.println("Locale: " + localeEN.getDisplayName()); <br>System.out.println(fullDateFormatEN.format(date)); <br>} <br>} </td></tr></tbody></table>这个例子的输出是： <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>Locale: French (France) <br>vendredi 5 octobre 2001 21 h 05 GMT-04:00 <br>Locale: English (United States) <br>Friday, October 5, 2001 9:05:54 PM EDT </td></tr></tbody></table>
</font><p><font id="zoom" size="2"><br><br>注意这个输出包括了时区信息：GMT-04:00 和 PM EDT。这个时区是人系统的时区设置里捕获的。你可以看见，日期是以那个地区的用户期望的格式显示的。让我们等一下来看看时区的概念</font></p>
<p><font id="zoom" size="2">时区 <br>TimeZone类，即java.util.TimeZone类的实例包含了一个与格林威治标准时间（GMT）相比较得出的以微秒为单位的时区偏移量，而且它还处理夏令时 <br>。要获得一个所有支持的进区的列表，你可以使用方法TimeZone.getAvailableIDs，它将返回一个包含了所有进区ID的字符串数组。要知道关于TimeZone类的更多细节，可以参看Sun公司的Web站点。 <br><br>为了演示这个概念，我们将创建三个时区对象。第一个对象将使用getDefault从系统时钟返回时区数据；第二个和第三个对象将传入一个时区字符串ID。见表C中的代码。 <br><br><strong>表 C</strong> <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>import java.util.TimeZone; <br>import java.util.Date; <br>import java.text.DateFormat; <br>import java.util.Locale; <br>public class DateExample8 { <br>public static void main(String[] args) { <br>// Get the system time zone. <br>TimeZone timeZoneFL = TimeZone.getDefault(); <br>System.out.println(" " + timeZoneFL.getDisplayName()); <br>System.out.println("RawOffset: " + timeZoneFL.getRawOffset()); <br>System.out.println("Uses daylight saving: " + timeZoneFL.useDaylightTime()); <br><br>TimeZone timeZoneLondon = TimeZone.getTimeZone("Europe/London"); <br>System.out.println(" " + timeZoneLondon.getDisplayName()); <br>System.out.println("RawOffset: " + timeZoneLondon.getRawOffset()); <br>System.out.println("Uses daylight saving: " + timeZoneLondon.useDaylightTime()); <br><br>TimeZone timeZoneParis = TimeZone.getTimeZone("Europe/Paris"); <br>System.out.println(" " + timeZoneParis.getDisplayName()); <br>System.out.println("RawOffset: " + timeZoneParis.getRawOffset()); <br>System.out.println("Uses daylight saving: " + timeZoneParis.useDaylightTime()); <br>} <br>} </td></tr></tbody></table><br>其输出如下： <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>Eastern Standard Time <br>RawOffset: -18000000 <br>Uses daylight saving: true <br>GMT+00:00 <br>RawOffset: 0 <br>Uses daylight saving: true <br><br>Central European Standard Time <br>RawOffset: 3600000 <br>Uses daylight saving: true </td></tr></tbody></table><br><br>正如你所看见的，TimeZone对象给我们的是原始的偏移量，也就是与GMT相差的微秒数，而且还会告诉我们这个时区是否使用夏令时。有个这个信息，我们就能够继续将时区对象和日期格式化器结合在一起在其它的时区和其它的语言显示时间了。 <br><br>国际化的时期显示了时区转换 <br>让我们来看一个结合了国际化显示，时区和日期格式化的例子。表D为一个在迈阿密和巴黎拥有办公室的公司显示了当前的完整日期和时间。对于迈阿密的办公室，我们将在每个办公室里用英语显示完整的日期和时间。对于巴黎的办公室，我们将用法语显示完整的当前日期和时间。 <br><br><strong>表 D</strong> <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>import java.util.TimeZone; <br>import java.util.Date; <br>import java.util.Locale; <br>import java.text.DateFormat; <br><br>public class DateExample9 { <br><br>public static void main(String[] args) { <br>Locale localeEN = Locale.US; <br>Locale localeFrance = Locale.FRANCE; <br><br>TimeZone timeZoneMiami = TimeZone.getDefault(); <br>TimeZone timeZoneParis = TimeZone.getTimeZone("Europe/Paris"); <br><br>DateFormat dateFormatter = DateFormat.getDateTimeInstance( <br>DateFormat.FULL, <br>DateFormat.FULL, <br>localeEN); <br>DateFormat dateFormatterParis = DateFormat.getDateTimeInstance( <br>DateFormat.FULL, <br>DateFormat.FULL, <br>localeFrance); <br><br>Date curDate = new Date(); <br><br>System.out.println("Display for Miami office."); <br>// Print the Miami time zone display name in English <br>System.out.println(timeZoneMiami.getDisplayName(localeEN)); <br>// Set the time zone of the dateFormatter to Miami time zone. <br>dateFormatter.setTimeZone(timeZoneMiami); <br>// Print the formatted date. <br>System.out.println(dateFormatter.format(curDate)); <br><br>// Set the time zone of the date formatter to Paris time zone. <br>dateFormatter.setTimeZone(timeZoneParis); <br>// Print the Paris time zone display name in English. <br>System.out.println(timeZoneParis.getDisplayName(localeEN)); <br>// Print the Paris time in english. <br>System.out.println(dateFormatter.format(curDate)); <br><br>System.out.println(" Display for Paris office."); <br>// Print the Miami time zone display name in French <br>System.out.println(timeZoneMiami.getDisplayName(localeFrance)); <br>// Set the timezone of the <br>// dateFormatterParis to Miami time zone. <br>dateFormatterParis.setTimeZone(timeZoneMiami); <br>// Print the formatted date in French. <br>燬ystem.out.println(dateFormatterParis.format(curDate)); <br><br>// Set the timezone of the date formatter to Paris time zone. <br>dateFormatterParis.setTimeZone(timeZoneParis); <br>// Print the Paris time zone display name in French. <br>System.out.println(timeZoneParis.getDisplayName(localeFrance)); <br>// Print the Paris time in French. <br>System.out.println(dateFormatterParis.format(curDate)); <br>} <br>} </td></tr></tbody></table><br>这个例子的输出是： <br><br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>Display for Miami office. <br>Eastern Standard Time <br>Friday, October 5, 2001 10:28:02 PM EDT <br>Central European Standard Time <br>Saturday, October 6, 2001 4:28:02 AM CEST <br>Display for Paris office. <br>GMT-05:00 <br>vendredi 5 octobre 2001 22 h 28 GMT-04:00 <br>GMT+01:00 <br>samedi 6 octobre 2001 04 h 28 GMT+02:00 </td></tr></tbody></table><br>在一个SQL<a href="http://www.gamvan.com/database/" target="_blank">数据库</a>中保存和提取日期数据我们将要使用的下一个类是java.sql.Date，它是java.util.Date的子类但它使用了Java<a href="http://www.gamvan.com/database/" target="_blank">数据库</a>连接（JDBC）方法 <br>。让我们来看一个简单的只有一个表单－－LAST_ACCESS的ORACLE<a href="http://www.gamvan.com/database/" target="_blank">数据库</a>，它是用下面的SQL创建的： <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>create table LAST_ACCESS ( <br>LAST_HIT date <br>); </td></tr></tbody></table><br><br>这个表单只有一个记录，用下面的插入语句创建： <br>insert into LAST_ACCESS values (Sysdate); <br><br>表E演示了如何修改和提取LAST_HIT<a href="http://www.gamvan.com/database/" target="_blank">数据库</a>域。 <br><br><strong>表 E</strong> <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top">
<p><font style="color: rgb(176, 176, 176);">代码内容</font><br>import java.sql.*; <br>import java.text.DateFormat; <br>import java.util.Date; <br><br>public class DateExample10 { <br><br>public static void main(String[] args) { <br>// Get a full date formatter. <br>DateFormat dateFormatter = DateFormat.getDateTimeInstance( <br>DateFormat.FULL, <br>DateFormat.FULL); <br>// Get the system date and time. <br>java.util.Date utilDate = new Date(); <br>// Convert it to java.sql.Date <br>java.sql.Date date = new java.sql.Date(utilDate.getTime()); <br>// Display the date before storing. <br>System.out.println(dateFormatter.format(date)); <br>// Save the date to the database. <br>setLastHit(date); <br>// Get the date from the database. <br>Date dateFromDB = getLastHit(); <br>// Display the date from the database. <br>System.out.println(dateFormatter.format(dateFromDB)); <br>} <br><br>public static void setLastHit(java.sql.Date date) { <br><br>try { <br>// Load the class. <br>Class.forName("oracle.jdbc.driver.OracleDriver"); <br>// Get a connection. <br>Connection connection = DriverManager.getConnection( <br>// Database URL <br>"jdbc:oracle:thin:@localhost:1521:buzz2", <br>"web_site", // Username <br>"web_site"); // Password <br>try { <br>/ Get a prepared statement fromthe connection <br>// specifying the update SQL. <br>PreparedStatement ps = connection.prepareStatement( <br>"update LAST_ACCESS set LAST_HIT="); <br>try { <br>/ set the date letting JDBC to the work of <br>// formatting the SQL appropriately. <br>ps.setDate(1, date); <br>// Execute the update statement. <br>int iRowsUpdated = ps.executeUpdate(); <br>System.out.println("Rows updated: " + iRowsUpdated); <br>} finally { <br>ps.close(); <br>} <br>} finally { <br>connection.close(); <br>} <br>} catch (Exception ex) { <br>System.out.println("Error: " + ex.getMessage()); <br>} <br>} <br><br>public static java.sql.Date getLastHit() { <br>java.sql.Date returnDate = null; <br><br>try { <br>// Load the driver class. <br>Class.forName("oracle.jdbc.driver.OracleDriver"); <br>// Get the connection. <br>Connection connection = DriverManager.getConnection( <br>"jdbc:oracle:thin:@localhost:1521:buzz2", <br>"web_site", "web_site"); <br>try { <br>/ Get the prepared statement specifying the <br>// select SQL. <br>PreparedStatement ps = connection.prepareStatement( <br>"select LAST_HIT from LAST_ACCESS"); <br>try { <br>// Execute the SQL and get the ResultSet object. <br>ResultSet rs = ps.executeQuery(); <br>try { <br>// Retreive the record. <br>if (rs else { <br>System.out.println("Did not get last hit."); <br>} <br>} <br>finally { <br>rs.close(); <br>} <br><br>} finally { <br>ps.close();&nbsp;<br><br>} finally { <br>connection.close(); <br>} <br>} catch (Exception ex) { <br>System.out.println("Error: " + ex.getMessage()); <br>} <br>return returnDate; <br>} <br><br>} </p></td></tr></tbody></table><br>这个例子的输出如下： <br>
<table style="border: 1px none ;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
<tbody>
<tr>
<td style="height: 25px;" bgcolor="#f6f6f6" valign="top"><font style="color: rgb(176, 176, 176);">代码内容</font><br>Friday, October 5, 2001 10:42:34 PM EDT <br>Rows updated: 1 <br>Successfully retrieved last hit. <br>Friday, October 5, 2001 12:00:00 AM EDT </td></tr></tbody></table><br>虽然这个例子没有为保存和提取日期数据提供性能上优良的方法，但它确实示范了如何为一条更新和删除语句将Java日期数据转换成SQL日期数据。从一个java.util.Date对象设置Oracle date数据域的过程是由以下的语句处理的： <br>ps.setDate(1, date); <br><br>它是我们预定义语句接口java.sql.PreparedStatement.setDate 的一个方法。 <br><br>这行代码出现在我们的setLastHit方法里。它将Java以微秒为单位的长整型日期值转换成ORACLE的SQL日期格式。当我们能够在getLastHit方法里用java.sql.PreparedStatement.getDate从<a href="http://www.gamvan.com/database/" target="_blank">数据库</a>取得日期数据的时候这种转换就能够完成。 <br><br>你还应该注意到只有日期被设置了。小时，分钟，秒，和微秒都没有包括在从Java日期数据到SQL日期数据的转换过程中。 <br><br>结论 <br>一旦你掌握了这些概念，你就应该能够基于系统时间或者一个输入的时间创建日期对象了。另外，你还应该能够使用标准和定制的格式化过程格式化日期数据，将文本的日期数据解析成日期对象，并以多种语言和多种时区显示一个日期数据。最后，你将能够在一个SQL<a href="http://www.gamvan.com/database/" target="_blank">数据库</a>里保存和提取日期值</font></p><img src ="http://www.blogjava.net/hellotony/aggbug/27883.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2006-01-13 11:33 <a href="http://www.blogjava.net/hellotony/articles/27883.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>几种分页算法</title><link>http://www.blogjava.net/hellotony/articles/24516.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Sun, 18 Dec 2005 11:04:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/24516.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/24516.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/24516.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/24516.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/24516.html</trackback:ping><description><![CDATA[<p><font size="2"><strong>1.“俄罗斯存储过程”的改良版</strong></font></p><div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;"><div><font size="2"><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><span style="color: rgb(0, 0, 255);">CREATE</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">procedure</span><span style="color: rgb(0, 0, 0);">&nbsp;pagination1<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">(@pagesize&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">,&nbsp;&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">页面大小，如每页存储20条记录</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">@pageindex&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">&nbsp;&nbsp;&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">当前页码</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">)<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">as</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;nocount&nbsp;</span><span style="color: rgb(0, 0, 255);">on</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">begin</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">declare</span><span style="color: rgb(0, 0, 0);">&nbsp;@indextable&nbsp;</span><span style="color: rgb(0, 0, 255);">table</span><span style="color: rgb(0, 0, 0);">(id&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(255, 0, 255);">identity</span><span style="color: rgb(0, 0, 0);">(</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);">,</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);">),nid&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">)&nbsp;&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">定义表变量</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">declare</span><span style="color: rgb(0, 0, 0);">&nbsp;@PageLowerBound&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">&nbsp;&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">定义此页的底码</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">declare</span><span style="color: rgb(0, 0, 0);">&nbsp;@PageUpperBound&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">&nbsp;&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">定义此页的顶码</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;@PageLowerBound</span><span style="color: rgb(128, 128, 128);">=</span><span style="color: rgb(0, 0, 0);">(@pageindex</span><span style="color: rgb(128, 128, 128);">-</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);">)</span><span style="color: rgb(128, 128, 128);">*</span><span style="color: rgb(0, 0, 0);">@pagesize<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;@PageUpperBound</span><span style="color: rgb(128, 128, 128);">=</span><span style="color: rgb(0, 0, 0);">@PageLowerBound</span><span style="color: rgb(128, 128, 128);">+</span><span style="color: rgb(0, 0, 0);">@pagesize<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">rowcount</span><span style="color: rgb(0, 0, 0);">&nbsp;@PageUpperBound<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">insert</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">into</span><span style="color: rgb(0, 0, 0);">&nbsp;@indextable(nid)&nbsp;</span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;gid&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;TGongwen&nbsp;</span><span style="color: rgb(0, 0, 255);">where</span><span style="color: rgb(0, 0, 0);">&nbsp;fariqi&nbsp;</span><span style="color: rgb(128, 128, 128);">&gt;</span><span style="color: rgb(255, 0, 255);">dateadd</span><span style="color: rgb(0, 0, 0);">(</span><span style="color: rgb(255, 0, 255);">day</span><span style="color: rgb(0, 0, 0);">,</span><span style="color: rgb(128, 128, 128);">-</span><span style="font-weight: bold; color: rgb(128, 0, 0);">365</span><span style="color: rgb(0, 0, 0);">,</span><span style="color: rgb(255, 0, 255);">getdate</span><span style="color: rgb(0, 0, 0);">())&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;fariqi&nbsp;</span><span style="color: rgb(0, 0, 255);">desc</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;O.gid,O.mid,O.title,O.fadanwei,O.fariqi&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;TGongwen&nbsp;O,@indextable&nbsp;t&nbsp;</span><span style="color: rgb(0, 0, 255);">where</span><span style="color: rgb(0, 0, 0);">&nbsp;O.gid</span><span style="color: rgb(128, 128, 128);">=</span><span style="color: rgb(0, 0, 0);">t.nid<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(128, 128, 128);">and</span><span style="color: rgb(0, 0, 0);">&nbsp;t.id</span><span style="color: rgb(128, 128, 128);">&gt;</span><span style="color: rgb(0, 0, 0);">@PageLowerBound&nbsp;</span><span style="color: rgb(128, 128, 128);">and</span><span style="color: rgb(0, 0, 0);">&nbsp;t.id</span><span style="color: rgb(128, 128, 128);">&lt;=</span><span style="color: rgb(0, 0, 0);">@PageUpperBound&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;t.id<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">end</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;nocount&nbsp;</span><span style="color: rgb(0, 0, 255);">off</span></font></div></div><p><font size="2"><strong>文章中的点评：</strong></font> </p><p><font size="2">以
上存储过程运用了SQL
SERVER的最新技术――表变量。应该说这个存储过程也是一个非常优秀的分页存储过程。当然，在这个过程中，您也可以把其中的表变量写成临时表：
CREATE TABLE #Temp。但很明显，在SQL
SERVER中，用临时表是没有用表变量快的。所以笔者刚开始使用这个存储过程时，感觉非常的不错，速度也比原来的ADO的好。但后来，又发现了比此方
法更好的方法。 </font></p><p><font size="2"><b>感觉：</b></font> </p><p><font size="2">没有做过测试，从感觉上讲，效率不是太高。 </font></p><hr id="null"><font size="2"><br><b>2. not in 的方法：</b></font> <div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;"><div><font size="2"><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><span style="color: rgb(0, 0, 0);">从publish&nbsp;表中取出第&nbsp;n&nbsp;条到第&nbsp;m&nbsp;条的记录：<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">SELECT</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">TOP</span><span style="color: rgb(0, 0, 0);">&nbsp;m</span><span style="color: rgb(128, 128, 128);">-</span><span style="color: rgb(0, 0, 0);">n</span><span style="color: rgb(128, 128, 128);">+</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(128, 128, 128);">*</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">FROM</span><span style="color: rgb(0, 0, 0);">&nbsp;publish<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">WHERE</span><span style="color: rgb(0, 0, 0);">&nbsp;(id&nbsp;</span><span style="color: rgb(128, 128, 128);">NOT</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(128, 128, 128);">IN</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">　　　　(</span><span style="color: rgb(0, 0, 255);">SELECT</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">TOP</span><span style="color: rgb(0, 0, 0);">&nbsp;n</span><span style="color: rgb(128, 128, 128);">-</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);">&nbsp;id<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">　　　　&nbsp;</span><span style="color: rgb(0, 0, 255);">FROM</span><span style="color: rgb(0, 0, 0);">&nbsp;publish))<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">id&nbsp;为publish&nbsp;表的关键字</span></font></div></div><p><font size="2"><strong>文章中的点评：</strong></font> </p><p><font dragover="true" size="2">&nbsp;当时看到这篇文章的时候，真的是精神为之一振，觉得思路非常得好。等到后来，在作办公自动化系统（ASP.NET+ C#＋SQL
SERVER）的时候，忽然想起了这篇文章，想如果把这个语句改造一下，这就可能是一个非常好的分页存储过程。于是就满网上找这篇文章，没想到，文章
还没找到，却找到了一篇根据此语句写的一个分页存储过程，这个存储过程也是目前较为流行的一种分页存储过程，很后悔没有争先把这段文字改造成存储过程：<br>（更多的内容，请查看原文。） </font></p><p><font size="2"><b>感觉：</b></font> </p><p><font size="2">使用了 not in &nbsp;而 not in &nbsp;是无法使用索引的，所以从效率上讲还是差了一点。 </font></p><hr id="null"><p><font size="2"><b>2. max 的方法：</b></font></p><p><font size="2">&nbsp;</font></p><div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;"><div><font size="2"><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">top</span><span style="color: rgb(0, 0, 0);">&nbsp;页大小&nbsp;</span><span style="color: rgb(128, 128, 128);">*</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;table1<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">where</span><span style="color: rgb(0, 0, 0);">&nbsp;id</span><span style="color: rgb(128, 128, 128);">&gt;</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(</span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(255, 0, 255);">max</span><span style="color: rgb(0, 0, 0);">&nbsp;(id)&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(</span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">top</span><span style="color: rgb(0, 0, 0);">&nbsp;((页码</span><span style="color: rgb(128, 128, 128);">-</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);">)</span><span style="color: rgb(128, 128, 128);">*</span><span style="color: rgb(0, 0, 0);">页大小)&nbsp;id&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;table1&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;id)&nbsp;</span><span style="color: rgb(0, 0, 255);">as</span><span style="color: rgb(0, 0, 0);">&nbsp;T<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;&nbsp;&nbsp;&nbsp;<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;id</span></font></div></div><p><font size="2"><strong>文章中的点评：</strong></font> </p><p><font size="2">几乎任何字段，都可以通过max(字段)或min(字段)来提取某个字段中的最大或最小值，所以如果这个字段不重复，那么就可以利用这些不重
复的字段的max或min作为分水岭，使其成为分页算法中分开每页的参照物。在这里，可以用操作符“&gt;”或“&lt;”号来完成这个使命，使查
询语句符合SARG形式。如： </font></p><p><font size="2">Select top 10 * from table1 where id&gt;200 </font></p><p><font size="2"><b>感觉：</b></font> </p><p><font size="2">这个就高高效了一点。但是不清楚 max的工作原理，不知道它的性能如何。 </font></p><hr id="null"><p align="center"><font size="2"><strong><font color="#0000ff">下面的才是重点</font></strong></font></p><p><font size="2"><strong>1、追求高效的翻页算法 —— 定位法。</strong></font></p><p><font size="2">&nbsp;</font></p><div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;"><div><font size="2"><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><span style="color: rgb(0, 0, 255);">declare</span><span style="color: rgb(0, 0, 0);">&nbsp;@pageSize&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">&nbsp;&nbsp;&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">返回一页的记录数</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">declare</span><span style="color: rgb(0, 0, 0);">&nbsp;@CurPage&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);">&nbsp;&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">页号（第几页）0：第一页；-1最后一页。</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">declare</span><span style="color: rgb(0, 0, 0);">&nbsp;@Count&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">declare</span><span style="color: rgb(0, 0, 0);">&nbsp;@id&nbsp;</span><span style="font-weight: bold; color: rgb(0, 0, 0);">int</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;@pageSize</span><span style="color: rgb(128, 128, 128);">=</span><span style="font-weight: bold; color: rgb(128, 0, 0);">10</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;@CurPage&nbsp;</span><span style="color: rgb(128, 128, 128);">=</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">定位</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">if</span><span style="color: rgb(0, 0, 0);">&nbsp;@CurPage&nbsp;</span><span style="color: rgb(128, 128, 128);">=</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(128, 128, 128);">-</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">begin</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;</span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">最后一页</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">rowcount</span><span style="color: rgb(0, 0, 0);">&nbsp;@pageSize<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;</span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;@id</span><span style="color: rgb(128, 128, 128);">=</span><span style="color: rgb(0, 0, 0);">newsID&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;newsTemp&nbsp;&nbsp;&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;newsID<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">end</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">if</span><span style="color: rgb(0, 0, 0);">&nbsp;@CurPage&nbsp;</span><span style="color: rgb(128, 128, 128);">&gt;</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="font-weight: bold; color: rgb(128, 0, 0);">0</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">begin</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;</span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;@Count&nbsp;</span><span style="color: rgb(128, 128, 128);">=</span><span style="color: rgb(0, 0, 0);">&nbsp;@pageSize&nbsp;</span><span style="color: rgb(128, 128, 128);">*</span><span style="color: rgb(0, 0, 0);">&nbsp;(@CurPage&nbsp;</span><span style="color: rgb(128, 128, 128);">-</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);">)&nbsp;</span><span style="color: rgb(128, 128, 128);">+</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="font-weight: bold; color: rgb(128, 0, 0);">1</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;</span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">rowcount</span><span style="color: rgb(0, 0, 0);">&nbsp;@Count<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;</span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;@id</span><span style="color: rgb(128, 128, 128);">=</span><span style="color: rgb(0, 0, 0);">newsID&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;newsTemp&nbsp;&nbsp;&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;newsID&nbsp;</span><span style="color: rgb(0, 0, 255);">desc</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">end</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 128, 128);">--</span><span style="color: rgb(0, 128, 128);">返回记录</span><span style="color: rgb(0, 128, 128);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">rowcount</span><span style="color: rgb(0, 0, 0);">&nbsp;@pageSize<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(128, 128, 128);">*</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;newsTemp&nbsp;</span><span style="color: rgb(0, 0, 255);">where</span><span style="color: rgb(0, 0, 0);">&nbsp;newsID&nbsp;</span><span style="color: rgb(128, 128, 128);">&lt;=</span><span style="color: rgb(0, 0, 0);">@id&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;newsID&nbsp;</span><span style="color: rgb(0, 0, 255);">desc</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">set</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">rowcount</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="font-weight: bold; color: rgb(128, 0, 0);">0</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span></font></div></div><p><font size="2"><strong>思路：</strong>就是上面的算法的延续，就是说呢避免使用&nbsp;&nbsp;not in 和&nbsp;max 的方法。</font></p><p><font size="2">也就是这个思路：Select top 10 * from table1 where id&gt;200</font></p><p><font size="2">定位 —— 就是说要找到“临界点”，分页的临界点。找到了之后剩下的事情就好办了。</font></p><p><font size="2"><strong>缺点：</strong>单字段排序、排序字段的值不能重复（不是绝对不能重复，可以有少量的重复）。</font></p><hr id="null"><p><font size="2"><strong>2、通用法 —— 颠颠倒倒法</strong></font></p><p><font size="2">有的时候“定位法”的缺点是不可以接受的，但是没有关系，可以用这个的。</font></p><p><font size="2">&nbsp;</font></p><div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;"><div><font size="2"><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(128, 128, 128);">*</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">table</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">where</span><span style="color: rgb(0, 0, 0);">&nbsp;id&nbsp;</span><span style="color: rgb(128, 128, 128);">in</span><span style="color: rgb(0, 0, 0);">&nbsp;<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">(<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;</span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">top</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="font-weight: bold; color: rgb(128, 0, 0);">10</span><span style="color: rgb(0, 0, 0);">&nbsp;ID&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;(<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;&nbsp;</span><span style="color: rgb(0, 0, 255);">select</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">top</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="font-weight: bold; color: rgb(128, 0, 0);">20</span><span style="color: rgb(0, 0, 0);">&nbsp;ID,addedDate&nbsp;</span><span style="color: rgb(0, 0, 255);">from</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">table</span><span style="color: rgb(0, 0, 0);">&nbsp;<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;addedDate&nbsp;</span><span style="color: rgb(0, 0, 255);">desc</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">&nbsp;)&nbsp;</span><span style="color: rgb(0, 0, 255);">as</span><span style="color: rgb(0, 0, 0);">&nbsp;aa&nbsp;</span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;addedDate<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top">)<br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span><span style="color: rgb(0, 0, 255);">order</span><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">by</span><span style="color: rgb(0, 0, 0);">&nbsp;addedDate&nbsp;</span><span style="color: rgb(0, 0, 255);">desc</span><span style="color: rgb(0, 0, 0);"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"><br><img src="http://blog.52forum.com/Images/OutliningIndicators/None.gif" alt="" align="top"></span></font></div></div><p><font size="2">ID 是主键，addedDate 是排序字段。</font></p><p><font size="2"><strong>缺点：</strong>必须有主键。</font></p><img src ="http://www.blogjava.net/hellotony/aggbug/24516.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-12-18 19:04 <a href="http://www.blogjava.net/hellotony/articles/24516.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>六种异常处理的陋习</title><link>http://www.blogjava.net/hellotony/articles/24515.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Sun, 18 Dec 2005 11:00:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/24515.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/24515.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/24515.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/24515.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/24515.html</trackback:ping><description><![CDATA[<p><font size="2">你觉得自己是一个Java专家吗？是否肯定自己已经全面掌握了Java的异常处理机制？在下面这段代码中，你能够迅速找出异常处理的六个问题吗？ <br><br></font></p><table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%"><tbody><tr><td><font size="2">1 OutputStreamWriter out = ... <br>2 java.sql.Connection conn = ... <br>3 try { // ⑸ <br>4 　Statement stat = conn.createStatement(); <br>5 　ResultSet rs = stat.executeQuery( <br>6 　　"select uid, name from user"); <br>7 　while (rs.next()) <br>8 　{ <br>9 　　out.println("ID：" + rs.getString("uid") // ⑹ <br>10 　　　"，姓名：" + rs.getString("name")); <br>11 　} <br>12 　conn.close(); // ⑶ <br>13 　out.close(); <br>14 } <br>15 catch(Exception ex) // ⑵ <br>16 { <br>17 　ex.printStackTrace(); //⑴，⑷ <br>18 }</font></td></tr></tbody></table><p><font size="2"><br></font><font size="2">　　作为一个Java程序员，你至少应该能够找出两个问题。但是，如果你不能找出全部六个问题，请继续阅读本文。 <br><br>　　本文讨论的不是Java异常处理的一般性原则，因为这些原则已经被大多数人熟知。我们要做的是分析各种可称为“反例”（anti-pattern）的违背优秀编码规范的常见坏习惯，帮助读者熟悉这些典型的反面例子，从而能够在实际工作中敏锐地察觉和避免这些问题。 <br><br>　　<b>反例之一：丢弃异常 </b><br><br>　　代码：15行-18行。 <br><br>　
　这段代码捕获了异常却不作任何处理，可以算得上Java编程中的杀手。从问题出现的频繁程度和祸害程度来看，它也许可以和C/C++程序的一个恶名远播
的问题相提并论??不检查缓冲区是否已满。如果你看到了这种丢弃（而不是抛出）异常的情况，可以百分之九十九地肯定代码存在问题（在极少数情况下，这段代
码有存在的理由，但最好加上完整的注释，以免引起别人误解）。 <br><br>　　这段代码的错误在于，异常（几乎）总是意味着某些事情不对劲了，或
者说至少发生了某些不寻常的事情，我们不应该对程序发出的求救信号保持沉默和无动于衷。调用一下printStackTrace算不上“处理异常”。不
错，调用printStackTrace对调试程序有帮助，但程序调试阶段结束之后，printStackTrace就不应再在异常处理模块中担负主要责
任了。 <br><br>　　丢弃异常的情形非常普遍。打开JDK的ThreadDeath类的文档，可以看到下面这段说明：“特别地，虽然出现
ThreadDeath是一种‘正常的情形’，但ThreadDeath类是Error而不是Exception的子类，因为许多应用会捕获所有的
Exception然后丢弃它不再理睬。”这段话的意思是，虽然ThreadDeath代表的是一种普通的问题，但鉴于许多应用会试图捕获所有异常然后不
予以适当的处理，所以JDK把ThreadDeath定义成了Error的子类，因为Error类代表的是一般的应用不应该去捕获的严重问题。可见，丢弃
异常这一坏习惯是如此常见，它甚至已经影响到了Java本身的设计。 <br><br>　　那么，应该怎样改正呢？主要有四个选择： <br><br>　　1、处理异常。针对该异常采取一些行动，例如修正问题、提醒某个人或进行其他一些处理，要根据具体的情形确定应该采取的动作。再次说明，调用printStackTrace算不上已经“处理好了异常”。 <br><br>　　2、重新抛出异常。处理异常的代码在分析异常之后，认为自己不能处理它，重新抛出异常也不失为一种选择。 <br><br>　　3、把该异常转换成另一种异常。大多数情况下，这是指把一个低级的异常转换成应用级的异常（其含义更容易被用户了解的异常）。 <br><br>　　4、不要捕获异常。 <br><br>　　结论一：既然捕获了异常，就要对它进行适当的处理。不要捕获异常之后又把它丢弃，不予理睬。 <br><br>　　<b>反例之二：不指定具体的异常 </b><br><br>　　代码：15行。 <br><br>　　许多时候人们会被这样一种“美妙的”想法吸引：用一个catch语句捕获所有的异常。最常见的情形就是使用catch(Exception ex)语句。但实际上，在绝大多数情况下，这种做法不值得提倡。为什么呢？ <br><br>　
　要理解其原因，我们必须回顾一下catch语句的用途。catch语句表示我们预期会出现某种异常，而且希望能够处理该异常。异常类的作用就是告诉
Java编译器我们想要处理的是哪一种异常。由于绝大多数异常都直接或间接从java.lang.Exception派生，catch
(Exception ex)就相当于说我们想要处理几乎所有的异常。 <br><br>　　再来看看前面的代码例子。我们真正想要捕获的异常是什么
呢？最明显的一个是SQLException，这是JDBC操作中常见的异常。另一个可能的异常是IOException，因为它要操作
OutputStreamWriter。显然，在同一个catch块中处理这两种截然不同的异常是不合适的。如果用两个catch块分别捕获
SQLException和IOException就要好多了。这就是说，catch语句应当尽量指定具体的异常类型，而不应该指定涵盖范围太广的
Exception类。 <br><br>　　另一方面，除了这两个特定的异常，还有其他许多异常也可能出现。例如，如果由于某种原因，
executeQuery返回了null，该怎么办？答案是让它们继续抛出，即不必捕获也不必处理。实际上，我们不能也不应该去捕获可能出现的所有异常，
程序的其他地方还有捕获异常的机会??直至最后由JVM处理。 <br><br>　　结论二：在catch语句中尽可能指定具体的异常类型，必要时使用多个catch。不要试图处理所有可能出现的异常。 <br><br>　　<b>反例之三：占用资源不释放 </b><br><br>　　代码：3行-14行。 <br><br>　　异常改变了程序正常的执行流程。这个道理虽然简单，却常常被人们忽视。如果程序用到了文件、Socket、JDBC连接之类的资源，即使遇到了异常，也要正确释放占用的资源。为此，Java提供了一个简化这类操作的关键词finally。 <br><br>　　finally是样好东西：不管是否出现了异常，Finally保证在try/catch/finally块结束之前，执行清理任务的代码总是有机会执行。遗憾的是有些人却不习惯使用finally。 <br><br>　　当然，编写finally块应当多加小心，特别是要注意在finally块之内抛出的异常??这是执行清理任务的最后机会，尽量不要再有难以处理的错误。 <br><br>　　结论三：保证所有资源都被正确释放。充分运用finally关键词。</font></p><font size="2"><strong>反例之四：不说明异常的详细信息 <br><br></strong>　　代码：3行-18行。 <br><br>　　仔细观察这段代码：如果循环内部出现了异常，会发生什么事情？我们可以得到足够的信息判断循环内部出错的原因吗？不能。我们只能知道当前正在处理的类发生了某种错误，但却不能获得任何信息判断导致当前错误的原因。 <br><br>　　printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程，但只提供了一些最基本的信息，未能说明实际导致错误的原因，同时也不易解读。 <br><br>　　因此，在出现异常时，最好能够提供一些文字信息，例如当前正在执行的类、方法和其他状态信息，包括以一种更适合阅读的方式整理和组织printStackTrace提供的信息。 <br><br>　　结论四：在异常处理模块中提供适量的错误原因信息，组织错误信息使其易于理解和阅读。 <br><br>　　<b>反例之五：过于庞大的try块 </b><br><br>　　代码：3行-14行。 <br><br>　
　经常可以看到有人把大量的代码放入单个try块，实际上这不是好习惯。这种现象之所以常见，原因就在于有些人图省事，不愿花时间分析一大块代码中哪几行
代码会抛出异常、异常的具体类型是什么。把大量的语句装入单个巨大的try块就象是出门旅游时把所有日常用品塞入一个大箱子，虽然东西是带上了，但要找出
来可不容易。 <br><br>　　一些新手常常把大量的代码放入单个try块，然后再在catch语句中声明Exception，而不是分离各个可能出现异常的段落并分别捕获其异常。这种做法为分析程序抛出异常的原因带来了困难，因为一大段代码中有太多的地方可能抛出Exception。 <br><br>　　结论五：尽量减小try块的体积。 <br><br>　　<b>反例之六：输出数据不完整</b> <br><br>　　代码：7行-11行。 <br><br>　
　不完整的数据是Java程序的隐形杀手。仔细观察这段代码，考虑一下如果循环的中间抛出了异常，会发生什么事情。循环的执行当然是要被打断的，其次，
catch块会执行??就这些，再也没有其他动作了。已经输出的数据怎么办？使用这些数据的人或设备将收到一份不完整的（因而也是错误的）数据，却得不到
任何有关这份数据是否完整的提示。对于有些系统来说，数据不完整可能比系统停止运行带来更大的损失。 <br><br>　　较为理想的处置办法是向输出设备写一些信息，声明数据的不完整性；另一种可能有效的办法是，先缓冲要输出的数据，准备好全部数据之后再一次性输出。 <br><br>　　结论六：全面考虑可能出现的异常以及这些异常对执行流程的影响。 <br><br>　　<b>改写后的代码</b> <br><br>　　根据上面的讨论，下面给出改写后的代码。也许有人会说它稍微有点?嗦，但是它有了比较完备的异常处理机制。 <br><br></font><table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%"><tbody><tr><td><font size="2">OutputStreamWriter out = ... <br>java.sql.Connection conn = ... <br>try { <br>　Statement stat = conn.createStatement(); <br>　ResultSet rs = stat.executeQuery( <br>　　"select uid, name from user"); <br>　while (rs.next()) <br>　{ <br>　　out.println("ID：" + rs.getString("uid") + "，姓名: " + rs.getString("name")); <br>　} <br>} <br>catch(SQLException sqlex) <br>{ <br>　out.println("警告：数据不完整"); <br>　throw new ApplicationException("读取数据时出现SQL错误", sqlex); <br>} <br>catch(IOException ioex) <br>{ <br>　throw new ApplicationException("写入数据时出现IO错误", ioex); <br>} <br>finally <br>{ <br>　if (conn != null) { <br>　　try { <br>　　　conn.close(); <br>　　} <br>　　catch(SQLException sqlex2) <br>　　{ <br>　　　System.err(this.getClass().getName() + ".mymethod - 不能关闭数据库连接: " + sqlex2.toString()); <br>　　} <br>　} <br><br>　if (out != null) { <br>　　try { <br>　　　out.close(); <br>　　} <br>　　catch(IOException ioex2) <br>　　{ <br>　　　System.err(this.getClass().getName() + ".mymethod - 不能关闭输出文件" + ioex2.toString()); <br>　　} <br>　} <br>} </font></td></tr></tbody></table><font size="2"><br></font><font size="2">　　本文的结论不是放之四海皆准的教条，有时常识和经验才是最好的老师。如果你对自己的做法没有百分之百的信心，务必加上详细、全面的注释。 <br><br>　
　另一方面，不要笑话这些错误，不妨问问你自己是否真地彻底摆脱了这些坏习惯。即使最有经验的程序员偶尔也会误入歧途，原因很简单，因为它们确确实实带来
了“方便”。所有这些反例都可以看作Java编程世界的恶魔，它们美丽动人，无孔不入，时刻诱惑着你。也许有人会认为这些都属于鸡皮蒜毛的小事，不足挂
齿，但请记住：勿以恶小而为之，勿以善小而不为。</font><img src ="http://www.blogjava.net/hellotony/aggbug/24515.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-12-18 19:00 <a href="http://www.blogjava.net/hellotony/articles/24515.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2EE 面试题综合</title><link>http://www.blogjava.net/hellotony/articles/20885.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Tue, 22 Nov 2005 02:12:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/20885.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/20885.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/20885.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/20885.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/20885.html</trackback:ping><description><![CDATA[<p><font size="2"><br>[基础问答]<br>1.下面哪些类可以被继承?<br>java.lang.Thread&nbsp;(T)<br>java.lang.Number&nbsp;(T)<br>java.lang.Double&nbsp;(F)<br>java.lang.Math&nbsp;&nbsp;(F)<br>java.lang.Void&nbsp;&nbsp;(F)<br>java.lang.Class&nbsp;&nbsp;(F)<br>java.lang.ClassLoader&nbsp;(T)</font></p><p><font size="2">2.抽象类和接口的区别<br>(1)接口可以被多重implements,抽象类只能被单一extends<br>(2)接口只有定义,抽象类可以有定义和实现<br>(3)接口的字段定义默认为:public static final, 抽象类字段默认是"friendly"(本包可见)</font></p><p><font size="2">3.Hashtable的原理,并说出HashMap与Hashtable的区别<br>HashTable的原理:通过节点的关键码确定节点的存储位置,即给定节点的关键码k,通过一定的函数关系H(散列函数),得到函数值H(k),将此值解释为该节点的存储地址.<br>HashMap 与Hashtable很相似,但HashMap 是非同步(unsynchronizded)和可以以null为关键码的.</font></p><p><font size="2">4.forward和redirect的区别<br>forward: an internal transfer in servlet<br>redirect: 重定向,有2次request,第2次request将丢失第一次的attributs/parameters等</font></p><p><font size="2">5.什么是Web容器?<br>实现J2EE规范中web协议的应用.该协议定义了web程序的运行时环境,包括:并发性,安全性,生命周期管理等等.</font></p><p><font size="2">6.解释下面关于J2EE的名词<br>(1)JNDI:Java Naming &amp; Directory Interface,JAVA命名目录服务.主要提供的功能是：提供一个目录系统，让其它各地的应用程序在其上面留下自己的索引，从而满足快速查找和定位分布式应用程序的功能.<br>(2)JMS：Java Message Service,JAVA消息服务.主要实现各个应用程序之间的通讯.包括点对点和广播.<br>(3)JTA：Java Transaction API,JAVA事务服务.提供各种分布式事务服务.应用程序只需调用其提供的接口即可.<br>(4)JAF: Java Action FrameWork,JAVA安全认证框架.提供一些安全控制方面的框架.让开发者通过各种部署和自定义实现自己的个性安全控制策略.<br>(5)RMI:Remote Method Interface,远程方法调用</font></p><p><font size="2">7.EJB是基于哪些技术实现的？并说&nbsp;出SessionBean和EntityBean的区别，StatefulBean和StatelessBean的区别.<br>EJB包括Session Bean、Entity Bean、Message Driven Bean，基于JNDI、RMI、JAT等技术实现.<br>SessionBean
在J2EE应用程序中被用来完成一些服务器端的业务操作，例如访问数据库、调用其他EJB组件.EntityBean被用来代表应用系统中用到的数据.对
于客户机，SessionBean是一种非持久性对象，它实现某些在服务器上运行的业务逻辑;EntityBean是一种持久性对象，它代表一个存储在持
久性存储器中的实体的对象视图，或是一个由现有企业应用程序实现的实体.<br>Session Bean 还可以再细分为 Stateful
Session Bean 与 Stateless Session Bean .这两种的 Session Bean都可以将系统逻辑放在
method之中执行，不同的是 Stateful Session Bean 可以记录呼叫者的状态，因此通常来说，一个使用者会有一个相对应的
Stateful Session Bean 的实体.Stateless Session Bean
虽然也是逻辑组件，但是他却不负责记录使用者状态，也就是说当使用者呼叫 Stateless Session Bean 的时候，EJB
Container 并不会找寻特定的 Stateless Session Bean 的实体来执行这个
method.换言之，很可能数个使用者在执行某个 Stateless Session Bean 的 methods 时，会是同一个 Bean
的 Instance 在执行.从内存方面来看， Stateful Session Bean 与 Stateless Session Bean
比较， Stateful Session Bean 会消耗 J2EE Server 较多的内存，然而 Stateful Session
Bean 的优势却在于他可以维持使用者的状态.</font></p><p><font size="2">8.XML的解析方法<br>Sax,DOM,JDOM</font></p><p><font size="2">9.什么是Web Service?<br>Web Service就是为了使原来各孤立的站点之间的信息能够相互通信、共享而提出的一种接口。<br>Web Service所使用的是Internet上统一、开放的标准，如HTTP、XML、SOAP（简单对象访问协议）、WSDL等，所以Web Service可以在任何支持这些标准的环境（Windows,Linux）中使用。<br>注：
SOAP协议（Simple Object Access
Protocal,简单对象访问协议）,它是一个用于分散和分布式环境下网络信息交换的基于XML的通讯协议。在此协议下，软件组件或应用程序能够通过标
准的HTTP协议进行通讯。它的设计目标就是简单性和扩展性，这有助于大量异构程序和平台之间的互操作性，从而使存在的应用程序能够被广泛的用户访问。</font></p><p><font size="2">优势：<br>(1).跨平台；<br>(2).SOAP协议是基于XML和HTTP这些业界的标准的，得到了所有的重要公司的支持。<br>(3).由于使用了SOAP，数据是以ASCII文本的方式而非二进制传输，调试很方便；并且由于这样，它的数据容易通过防火墙，不需要防火墙为了程序而单独开一个“漏洞”。<br>(4).此外，WebService实现的技术难度要比CORBA和DCOM小得多。<br>(5).要实现B2B集成，EDI比较完善与比较复杂；而用WebService则可以低成本的实现，小公司也可以用上。<br>(6).在C/S的程序中，WebService可以实现网页无整体刷新的与服务器打交道并取数。<br>缺点：<br>(1).WebService使用了XML对数据封装，会造成大量的数据要在网络中传输。<br>(2).WebService规范没有规定任何与实现相关的细节，包括对象模型、编程语言，这一点，它不如CORBA。</font></p><p><font size="2">10.多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?<br>答：多线程有两种实现方法，分别是继承Thread类与实现Runnable接口<br>同步的实现方面有两种，分别是synchronized,wait与notify</font></p><p><font size="2">11.JSP中动态INCLUDE与静态INCLUDE的区别？ <br>动态INCLUDE用jsp:include动作实现<br>&lt;jsp:include page="included.jsp" flush="true"/&gt;<br>它总是会检查所含文件中的变化，适合用于包含动态页面，并且可以带参数<br>静态INCLUDE用include伪码实现,定不会检查所含文件的变化，适用于包含静态页面<br>&lt;%@ include file="included.htm" %&gt;<br>&nbsp;</font></p><p><font size="2"><br></font><font size="2">[Java编程与程序运行结果]<br>1.Java编程,打印昨天的当前时刻<br>public class YesterdayCurrent{<br>&nbsp; public void main(String[] args){<br>&nbsp;&nbsp;&nbsp; Calendar cal = Calendar.getInstance();<br>&nbsp;&nbsp;&nbsp; cal.add(Calendar.DATE, -1);<br>&nbsp;&nbsp;&nbsp; System.out.println(cal.getTime());<br>&nbsp; }<br>}</font></p><p><font size="2">2.文件读写,实现一个计数器<br>&nbsp; public int getNum(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int i = -1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String stri="";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BufferedReader in = new BufferedReader(new FileReader(f));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while((stri=in.readLine())!=null){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i = Integer.parseInt(stri.trim());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; in.close();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }catch(Exception e){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return i;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; public void setNum(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int i = getNum();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i++;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PrintWriter out=new PrintWriter(new BufferedWriter(new FileWriter(f,false)));&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.write(String.valueOf(i));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //可能是编码的原因，如果直接写入int的话，将出现java编码和windows编码的混乱，因此此处写入的是String<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.close() ;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }catch(Exception e){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>3. 指出下面程序的运行结果:<br>class A{<br>&nbsp;&nbsp;&nbsp; static{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print("1");<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; public A(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print("2");<br>&nbsp;&nbsp;&nbsp; }<br>}<br>class B extends A{<br>&nbsp;&nbsp;&nbsp; static{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print("a");<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; public B(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.print("b");<br>&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; <br>}<br>public class Hello{<br>&nbsp;&nbsp;&nbsp; public static void main(String[] ars){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A ab = new B(); //执行到此处,结果: 1a2b<br>&nbsp;ab = new B();&nbsp;//执行到此处,结果: 1a2bab<br>&nbsp;&nbsp;&nbsp; }<br>}<br>注:类的static 代码段,可以看作是类首次加载(被虚拟机加载)执行的代码,而对于类的加载,首先要执行其基类的构造,再执行其本身的构造<br>4.写一个Singleton模式的例子<br>public class Singleton{<br>&nbsp;private static Singleton single = new Singleton();<br>&nbsp;private Singleton(){}<br>&nbsp;public Singleton getInstance(){<br>&nbsp;&nbsp;return single;<br>&nbsp;}<br>}</font></p><p><font size="2">[数据库]<br>1.删除表的重复记录<br>如果记录完全相同才算重复记录,那么:&nbsp; (sql server2000下测试通过)<br>select distinct * into #tmpp from tid<br>delete from tid&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>insert into tid select * from #tmpp<br>drop table #tmpp<br>如果有id主键(数字,自增1的那种),那么:(sql server2000下测试通过)<br>delete from tableA where id not in<br>(select id = min(id) from tableA group by name)</font></p><font size="2">2.delete from tablea ＆ truncate table tablea的区别<br>truncate 语句执行速度快,占资源少,并且只记录页删除的日志；<br>delete 对每条记录的删除均需要记录日志</font><img src ="http://www.blogjava.net/hellotony/aggbug/20885.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-22 10:12 <a href="http://www.blogjava.net/hellotony/articles/20885.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java理论与实践：再谈Urban性能之传言</title><link>http://www.blogjava.net/hellotony/articles/20574.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Sat, 19 Nov 2005 08:09:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/20574.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/20574.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/20574.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/20574.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/20574.html</trackback:ping><description><![CDATA[<font size="2">流行问题：哪种语言的原始分配性能更快，Java 语言还是 C/C++？答案可能令人惊讶 —— 现代 JVM 中的分配比执行得最好的
malloc 实现还要快得多。HotSpot 1.4.2 之后虚拟机中的 new Object() 常见代码路径最多 10 条机器指令，而用
C 语言实现的执行得最好的 malloc 实现，每个调用平均要求的指令在 60 到 100 条之间。
<br><br>而且分配性能在整体性能中不是一个微不足道的部分，测评显示：对于许多实际的 C 和 C++ 程序（例如 Perl 和
Ghostscript），整体执行时间中的 20% 到 30% 都花在 malloc 和 free 上，远远多于健康的 Java
应用程序在分配和垃圾收集上的开销。 <br><br>继续，弄得一团糟
<br><br>没有必要搜索众多的 blog 或 Slashdot 贴子，去寻找像“垃圾收集永远不会像直接内存管理一样有效”这样能够说服人的陈述。而且，从某个方面来说，这些话说的是对的 —— 动态内存管理并不一样快 —— 而是快得多。
<br><br>malloc/free 技术一次处理一个内存块，而垃圾收集机制则采用大批量方式处理内存管理，从而形成更多的优化机会（以一些可以预见到的损失为代价）。 
<br><br>这条“听起来有理的意见” （以大批量清理垃圾要比一天到晚一点点儿清理垃圾更容易）得到了数据的证实。一项研究测量了在许多常见
C++ 应用程序中，用保守的 Boehm-Demers-Weiser（BDW）替换 malloc
的效果，结果是：许多程序在采用垃圾收集而不是传统的分配器运行时，表现出了速度提升。（BDW
是个保守的、不移动的垃圾收集器，严重地限制了对分配和回收进行优化的能力，也限制了改善内存位置的能力；像 JVM
中使用的那些精确的浮动收集器可以做得更好。） <br><br>在 JVM 中的分配并不总是这么快，早期 JVM 的分配和垃圾收集性能实际上很差，这当然就是 JVM 分配慢这一说法的起源。
<br><br>在非常早的时候，我们看到过许多“分配慢”的意见 —— 因为就像早期 JVM 中的一切一样，它确实慢 ——
而性能顾问提供了许多避免分配的技巧，例如对象池。（公共服务声明：除了对最重量的对象之外，对象池现在对于所有对象都是严重的性能损失，而且要在不造成
并发瓶颈的情况下使用对象池也很需要技巧。）但是，从 JDK 1.0 开始已经发生了许多变化；JDK 1.2
中引入的分代收集器（generational collector）支持简单得多的分配方式，可以极大地提高性能。 <br><br>分代垃圾收集
<br><br>分代垃圾收集器把堆分成多代；多数JVM使用两代，“年轻代”和“年老代”。对象在年轻代中分配；如果它们在一定数量的垃圾收集之后仍然存在，就被当作是”长寿的“，并晋升到年老代。 
<br><br>HotSpot 提供了使用三个年轻代收集器的选择（串行拷贝、并行拷贝和并行清理），它们都采用“拷贝”收集器的形式，有几个重要的公共特征。拷贝收集器把内存空间从中间分成两半，每次只使用一半。
<br><br>开始时，使用中的一半构成了可用内存的一个大块；分配器满足分配请求时，返回它没有使用的空间的前 N 个字节，并把指针（分隔“使用”部分）从“自由”部分移动过来，如清单 1的伪代码所示。
<br><br>当使用的那一半用满时，垃圾收集器把所有活动对象（不是垃圾的那些对象）拷贝到另一半的底部（把堆压缩成连续的），然后从另一半开始分配。 清单 1. 在存在拷贝收集器的情况下，分配器的行为
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>void *malloc(int n)<br>{<br>    if (heapTop - heapStart &lt; n)<br>        doGarbageCollection();<br><br>    void *wasStart = heapStart;<br>    heapStart += n;<br>    return wasStart;<br>}</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>从这个伪代码可以看出为什么拷贝收集器可以实现这么快的分配 —— 分配新对象只是检查在堆中是否还有足够的剩余空间，如果还有，就移动指针。不需要搜索自由列表、最佳匹配、第一匹配、lookaside 列表 ，只要从堆中取出前 N 个字节，就成功了。<br><br>
              如何回收？
<br><br>但是分配仅仅是内存管理的一半，回收是另一半。对于多数对象来说，直接垃圾收集的成本为零。这是因为，拷贝收集器不需要访问或拷贝死对象，只处理活动对象。所以在分配之后很快就变成垃圾的对象，不会造成收集周期的工作量。 
<br><br>在典型的面向对象程序中，绝大多数对象（根据不同的研究，在 92% 到 98%
之间）“死于年轻”，这意味着它们在分配之后，通常在下一次垃圾收集之前，很快就变成垃圾。（这个属性叫作
分代假设，对于许多面向对象语言已经得到实际测试，证明为真。）所以，不仅分配要快，对于多数对象来说，回收也要自由。 <br><br>线程本地分配
<br><br>如果分配器完全像 清单 1 所示的那样实现，那么共享的 heapStart
字段会迅速变成显著的并发瓶颈，因为每个分配都要取得保护这个字段的锁。为了避免这个问题，多数 JVM 采用了
线程本地分配块，这时每个线程都从堆中分配一个更大的内存块，然后顺序地用这个线程本地块为小的分配请求提供服务。
<br><br>所以，线程花在获得共享堆锁的大量时间被大大减少，从而提高了并发性。（在传统的 malloc 实现的情况下要解决这个问题更困难，成本更高；把线程支持和垃圾收集都构建进平台促进了这类协作。） 
<br><br>堆栈分配
<br><br>C++ 向程序员提供了在堆或堆栈中分配对象的选择。基于堆栈的分配更有效：分配更便宜，回收成本真正为零，而且语言提供了隔离对象生命周期的帮助，减少了忘记释放对象的风险。
<br><br>另一方面，在 C++ 中，在发布或共享基于堆栈的对象的引用时，必须非常小心，因为在堆栈帧整理时，基于堆栈的对象会被自动释放，从而造成孤悬的指针。 
<br><br>基于堆栈的分配的另一个优势是它对高速缓存更加友好。在现代的处理器上，缓存遗漏的成本非常显著，所以如果语言和运行时能够帮助程序实现
更好的数据位置，就会提高性能。堆栈的顶部通常在高速缓存中是“热”的，而堆的顶部通常是“冷”的（因为从这部分内存使用之后可能过了很长时间）。所以，
在堆上分配对象，比起在堆栈上分配对象，会带来更多缓存遗漏。 <br><br>更糟的是，在堆上分配对象时，缓存遗漏还有一个特别讨厌的内存交互。在从堆中分配内存时，不管上次使用内存之后留下了什么内容，内存中的内容都被当作垃圾。如果在堆的顶部分配的内存块不在缓存中，执行会在内存内容装入缓存的过程中出现延迟。
<br><br>然后，还要用 0 或其他初始值覆盖掉刚刚费时费力装入缓存的那些值，从而造成大量内存活动的浪费。（有些处理器，例如 Azul 的 Vega，包含加速堆分配的硬件支持。） 
<br><br>escape 分析
<br><br>Java 语句没有提供任何明确地在堆栈上分配对象的方式，但是这个事实并不影响 JVM 仍然可以在适当的地方使用堆栈分配。JVM
可以使用叫作 escape 分析 的技术，通过这项技术，JVM
可以发现某些对象在它们的整个生命周期中都限制在单一线程内，还会发现这个生命周期绑定到指定堆栈帧的生命周期上。这样的对象可以安全地在堆栈上而不是在
堆上分配。更好的是，对于小型对象，JVM 可以把分配工作完全优化掉，只把对象的字段放入寄存器。 <br><br>清单 2 显示了一个可以用 escape 分析把堆分配优化掉的示例。Component.getLocation() 方法对组件的位置做了一个保护性的拷贝，这样调用者就无法在不经意间改变组件的实际位置。
<br><br>先调用 getDistanceFrom() 得到另一个组件的位置，其中包括对象的分配，然后用 getLocation() 返回的 Point 的 x 和 y 字段计算两个组件之间的距离。清单 2. 返回复合值的典型的保护性拷贝方式
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>public class Point <br>{<br>  private int x, y;<br>  public Point(int x, int y) <br>  {<br>    this.x = x; this.y = y;<br>  }<br>  public Point(Point p) <br>  {<br>  this(p.x, p.y); }<br>  public int getX() <br>  {<br>  return x; }<br>  public int getY() <br>  { <br>  return y; <br>  }<br>}<br><br>public class Component <br>{<br>  private Point location;<br>  public Point getLocation() <br>  { <br>  return new Point(location);<br>  }<br><br><br>  public double <br>  getDistanceFrom(Component other) <br>  {<br>    Point otherLocation =<br>	other.getLocation();<br>    int deltaX = <br>	otherLocation.getX() - location.getX();<br>    int deltaY =<br>	otherLocation.getY() - location.getY();<br>    return Math.sqrt<br>	(deltaX*deltaX + deltaY*deltaY);<br>  }<br>}</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>              
              <p align="center"><font size="2"><br><span class="content01"></span><span class="content01"></span></font>
              
              
		
		</p><font size="2">getLocation()方法不知道它的调用者要如何处理它返回的Point；有可能得到一个指向
Point的引用，比如把它放在集合中，所以getLocation()采用了保护性的编码方式。但是，在这个示例中，getDistanceFrom
() 并不会这么做，它只会使用 Point 很短的时间，然后释放它，这看起来像是对完美对象的浪费。 <br><br>聪明的 JVM 会看出将要进行的工作，并把保护性拷贝的分配优化掉。首先，对 getLocation() 的调用会变成内联的，对 getX() 和 getY() 的调用也同样处理，从而导致 getDistanceFrom() 的表现会像清单 3 一样有效。 
<br><br>清单 3. 伪代码描述了把内联优化应用到 getDistanceFrom() 的结果
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>public double getDistanceFrom<br> (Component other)<br> {<br>    Point otherLocation = <br>	new Point(other.x, other.y);<br>    int deltaX = <br>	otherLocation.x - location.x;<br>    int deltaY = <br>	otherLocation.y - location.y;<br>    return Math.sqrt<br>	(deltaX*deltaX + deltaY*deltaY);<br>  }</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>在这一点上，escape 分析可以显示在第一行分配的对象永远不会脱离它的基本块，而 getDistanceFrom() 也永远不会修改 other 组件的状态。（escape 指的是对象引用没有保存到堆中，或者传递给可能保留一份拷贝的未知代码。）
<br><br>如果 Point 真的是线程本地的，而且也清楚它的生命周期限制在分配它的基本块内，那么它既可以进行堆栈分配，也可以完全优化掉，如清单 4 所示。 
<br><br>清单 4. 伪代码描述了从 getDistanceFrom() 优化掉分配后的结果
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>public double getDistanceFrom<br> (Component other)<br> {<br>    int tempX = other.x, <br>	tempY = other.y;<br>    int deltaX = <br>	tempX - location.x;<br>    int deltaY = <br>	tempY - location.y;<br>    return Math.sqrt<br>	(deltaX*deltaX + deltaY*deltaY);<br>  }</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>结果就是得到了与所有字段都是 public 时能够得到的相同的性能，同时保持了封装和保护性拷贝（在其他安全编码技术之中）提供的安全性。 
<br><br>Mustang 中的 escape 分析
<br><br>escape 分析是一项被议论了很久的优化，它最后终于出现了：Mustang（Java SE 6）的当前构建中可以做
escape 分析，并在适当的地方把堆分配转换成堆栈分析（或者不分配）。用 escape
分析清除一些分配，会带来更快的平均分配时间，简化的内存工作，更少的缓存遗漏。而且，优化掉一些分配，可以降低垃圾收集器的压力，从而让收集运行得更
少。 <br><br>即使在源代码中进行堆栈分配不太现实的地方，即使语言提供了分配的选项，escape 分析也能找到堆栈分配的机会，因为特定的分配是否会被优化掉，是根据特定代码路径中实际上如何使用对象返回方法的结果而决定的。
<br><br>getLocation() 返回的 Point 可能不是在所有情况下都适合进行堆栈分配，但是一旦 JVM 内联了
getLocation()，它就可以自由而且独立地优化每个调用，从而在两方面都提供了最好的结果：最优的性能，最少的时间花在进行低级的性能调整决策
上。 <br><br>结束语
<br><br>JVM 擅长发现我们一直以为只有开发人员才能知道的事情，这令人震惊。让 JVM 根据具体情况在堆栈分配和堆分配之间进行选择，我们就能得到堆栈分配的性能好处，却不必让程序员在进行堆栈分配还是进行堆分配上费脑筋。<br><br></font> 
<img src ="http://www.blogjava.net/hellotony/aggbug/20574.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-19 16:09 <a href="http://www.blogjava.net/hellotony/articles/20574.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>专家讲述J2EE中的多字节字符的处理</title><link>http://www.blogjava.net/hellotony/articles/20573.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Sat, 19 Nov 2005 08:06:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/20573.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/20573.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/20573.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/20573.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/20573.html</trackback:ping><description><![CDATA[<font size="2">摘要
<br><br>大多数的J2EE服务器都能很好地支持多字节语言（如中文和日文），但这些J2EE服务器和浏览器在支持的方式上是有区别的。当开发者将
一些中文（或日文）本地化应用从一种服务器迁移到另一种服务器上时，通常会遇到多字节问题。本文分析了有关多字节字符问题的根源，并给出了一些解决方案和
指导原则。
<br><br>中文是世界上最复杂、最完备的语言之一。有时我会为自己是个中国人而感到幸运，特别是当我看到我的一些外国朋友为学习这门语言（尤其是写汉字）而绞尽脑汁的时候。可当我用J2EE开发本地化Web应用时，又会感到很不幸。下面我就说说为什么。
<br><br>尽管Java平台和大多数J2EE服务器都能很好地支持国际化，我在开发中文或日文应用时仍会遇到许多有关多字节字符方面的问题：
<br><br>·编码和字符集之间有什么区别？
<br><br>·为什么多字节字符应用从一种操作系统迁移到另一种上时显示会有差异？
<br><br>·为什么多字节字符应用从一种应用服务器迁移到另一种上时显示会有差异？
<br><br>·为什么我的多字节字符应用在IE浏览器里显示正常，可到了Mozila浏览器里却又不行？
<br><br>·为什么以UTF-16（通用转换格式）编码的应用在大多数J2EE服务器上都不能很好地显示？
<br><br>如果你也有同样的问题，本文将有助于你找到答案。
<br><br>字符的基础知识
<br><br>字符在计算机出现之前就早已存在。大约3,000年前，古代中国就出现了一些特殊的文字符号（即甲骨文）。这些文字符号有特定的形状和含
义，它们中的大部分都有名字和发音。所有这些文字符号汇集成了字码表，一套从属于特定语言的独特字符集合，它们与计算机没有任何关系。几千年过去了，许多
语言都在发展，数以千计的字符被创造出来。如今我们要将所有这些字符都数字化为0和1，这样计算机才能理解它们。
<br><br>用键盘输入单词的时候要用到字符输入法。对于简单的字符，键盘和字符间存在着一对一的映射关系；而对于较复杂的语言，需要多次敲击键盘才能输入一个字符。
<br><br>在你能从屏幕上看到字符之前，操作系统必须先将字符存放在内存里。实际上操作系统在字码表的字符与一系列非负整数之间定义了一一对应的关系，它们被存放在内存里并被操作系统调用，这些整数被称为字符代码。
<br><br>字符可以用文件存储或通过网络传输。软件用字符编码来定义每个字符的字符代码与八进制序列数之间的对应方法（算法）。有些字符代码对应一个字节，如ASCII码；还有一些字符代码需要对应两个或更多的字节（如中文和日文），这种对应关系依赖于不同的字符编码方式。<br><br>不同的语言使用不同的字码表，每个字码表都有一些特定的编码方式。在某些情况下,当你选用某种语言时就已经不自觉地选择了某种字符编码方式。例如当你选用
中文的时候，在默认情况下你用的可能就是GBK中文字码表及称作GBK的特定字符编码方式。
<br><br>为了不致混淆，我避免使用字符集这个词。显然，字符集与字码表是同义词。字符集在HTTPMime（多用途网际邮件扩充协议）页头里被误用，其实这里的“charset（字符集）”是指“encoding（编码）”。
<br><br>Java的特征之一是字符是16位的，这样就能支持Unicode（一种表示各种语言中许多不同种类的字符的标准方式）。不幸的是，这个特征在开发多字节J2EE应用中也引发了许多问题，本文将就此进行讨论。
<br><br>开发阶段引起的显示问题
<br><br>J2EE应用开发包括若干个阶段（如图1所示），每个阶段都可能导致多字节字符显示问题。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537135.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图1 J2EE应用开发生命周期</font></center>
<font size="2"><br><br>编码阶段
<br><br>当你开始J2EE应用编码时，大多数情况下你会用JBuilder、NetBean之类的IDE，或者是UltraEdit、Vi之类的
编辑器。无论你选择了哪一个，只要在JSP（JavaServer
Pages）、Java或HTML文件中有文字字符串，而且这些字符串是像中文或日文这样的多字节字符，那么你要是不小心的话就很可能会遇到显示问题。
<br><br>文字字符串是存储于文件中的静态信息，不同语言的字符采用不同的编码方式。大多数IDE的默认编码方式是ISO-8859-1，这种编
码方式适合ASCII字符，但会使多字节字符丢失信息。例如，中文版的NetBean在对文件编码时其默认编码方式就不幸为ISO-8859-1。<br><br>
              在我编写带有中文字符的JSP文件时（如图2所示），看上去一切正常。我前面提到，屏幕上显示的所有字符都在内存中，与编码方式没有直接的关系。
<br><br>在保存文件后，如果关闭IDE再重新打开，这些字符就显示为乱码（如图3所示），这是因为ISO-8859-1编码方式在存储中文字符时会丢失一些信息。<br><br><br><br><br><br><br><br></font>
<center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537137.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图2 NetBeans里的中文字符</font></center>
<font size="2"><br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537139.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图3 中文字符成了乱码</font></center>
<font size="2"><br><br>字符编码API
<br><br>在servlet和JSP规范里有几个API用来控制J2EE应用的字符编码过程。对于servlet请求，
setCharacterEncoding()方法对当前HTTP请求设定编码方式；对于servlet响应，setContentType()方法和
setLocale()方法对HTTP响应输出设置Mime头的编码方式。
<br><br>这些API本身不会引发问题，但如果你忘了用它们就有问题了。例如在有些服务器上你可以正确无误地显示多字节字符而不必在代码中使用上述的任何一个API，但在其它的服务器上运行应用时字符却变成了乱码。
<br><br>多字节字符显示问题的成因在于服务器在处理HTTP请求和响应期间如何对字符进行编码。以下是服务器确定请求和响应的编码方式的规则，对大多数服务器都适用：
<br><br>在处理servlet请求时，服务器按以下次序（自上而下）来确定请求的字符编码方式：
<br><br>·代码中指定的设置
<br><br>（如setCharacterEncoding()方法中指定的编码方式）
<br><br>·厂商的初始设置
<br><br>·默认的设置
<br><br>在处理servlet响应时，服务器按以下次序（自上而下）来确定响应的字符编码方式：
<br><br>·代码中指定的设置
<br><br>（如setContentType() 方法和setLocale()方法中指定的编码方式）
<br><br>·厂商的初始设置
<br><br>·默认的设置
<br><br>按照上述规则，如果在代码中用API进行了指定，所有的服务器都会按指定的字符编码方式编码，否则服务器就会各行其道。有些厂商用HTTP表单的隐藏字段（hidden fields）来确定请求的编码方式，还有些厂商则采用它们自己配置文件中的特定设置。
<br><br>即使是默认设置也不尽相同，大多数厂商采用ISO-8859-1作为默认设置，还有少数厂商采用操作系统的本地设置值。因此，一些带有多字节字符的应用在迁移到另一个厂商的J2EE服务器上时就会出现显示问题。<br><br>
              编译阶段
<br><br>如果设置正确，在编辑的时候就能在源文件中存储多字节的文字字符串，但这些源文件不能直接执行。如果编写的是servlet代码，这些Java文件在部署到应用服务器之前必须先被编译成类文件。
<br><br>对于JSP文件，应用服务器在执行前会自动将其编译成类文件。在编译阶段，字符编码问题仍有可能存在。为了运行下面这个简单示例，请下载本文的源代码。
<br><br>程序清单1 EncodingTest.java
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>1  import java.io.ByteArrayOutputStream;<br>2  import java.io.OutputStreamWriter;<br>3<br>4  public class EncodingTest {  <br>5  public static void main(String[] args) {<br>6  OutputStreamWriter out = <br>new OutputStreamWriter<br>(new ByteArrayOutputStream());<br>7  System.out.println("Current Encoding: <br>"+out.getEncoding());<br>8  System.out.println("Literal output:  <br>&amp;Auml;&amp;atilde;&amp;ordm;&amp;Atilde;&amp;pound;&amp;iexcl;");<br>// You may not see this Chinese String<br>9  }<br>10 }</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>有关这段源代码的说明如下：
<br><br>·        
<br><br>我们用下面的代码确定系统当前的编码方式：
<br><br>6 OutputStreamWriter out = 
<br><br>new OutputStreamWriter
<br><br>(new ByteArrayOutputStream());
<br><br>7 System.out.println("Current Encoding: 
<br><br>"+out.getEncoding());
<br><br>·第8行包含直接打印输出中文文字字符串（由于操作系统语言设置的原因可能造成该字符串不能正常显示）的代码。
<br><br>·用GBK编码方式保存这个Java源文件。
<br><br>执行结果如图4所示。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537141.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图4 示例程序的输出</font></center>
<font size="2"><br><br>从图4的执行结果中我们可以归纳出：
<br><br>·  Java编译器（javac）将系统的语言环境作为默认的编码设置，Java运行时（Java Runtime Environment.）也如此。
<br><br>·  只有第一次的运行结果是正确的，其它的字符串显示都有问题。
<br><br>·   仅当运行时的编码设置与源文件保存时的编码方式相一致时才能正确显示多字节文字字符串（否则就必须进行转码，参见“运行时阶段”部分）。              
              <br><br>
              服务器配置阶段
<br><br>在运行J2EE应用之前，一般会根据特定的需要对应用进行配置。在上一节中，我们发现不同的语言设置会导致文字字符串显示出问题。实际上配置存在于不同的层面，它们都可能会引发多字节字符问题。
<br><br>操作系统层
<br><br>操作系统对语言的支持非常重要。前面提到服务器端对语言的支持会影响JVM默认的编码设置，而在客户端的语言支持（如字体）也能直接影响字符的显示，但这不是本文要讨论的重点。
<br><br>J2EE应用服务器层
<br><br>大多数服务器都有一个基本服务器设置，可用来配置默认的字符编码处理方式。清单2就是Tomcat配置文件的一部分（位于$TOMCAT_HOME/conf/web.xml）。
<br><br>清单2 web.xml
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>&lt;servlet&gt;        <br>&lt;servlet-name&gt;jsp&lt;/servlet-name&gt;        &lt;servlet-class&gt;org.apache.jasper.<br>servlet.JspServlet&lt;/servlet-class&gt;        <br>&lt;init-param&gt;            <br>&lt;param-name&gt;fork&lt;/param-name&gt;            &lt;param-value&gt;false&lt;/param-value&gt;        <br>&lt;/init-param&gt;        <br>&lt;init-param&gt;            <br>&lt;strong&gt;           <br>&lt;param-name&gt;javaEncoding&lt;/param-name&gt;&gt;            &lt;param-value&gt;&gt;UTF8&lt;/param-value&gt;            <br>&lt;/strong&gt;        <br>&lt;/init-param&gt;        <br>&lt;load-on-startup&gt;3&lt;/load-on-startup&gt;  <br>&lt;/servlet&gt;</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>Tomcat以参数javaEncoding来确定从JSP文件生成Java源文件的Java文件编码方式。这里的默认值是UTF-8，这意味着如果JSP文件中的中文字符以GBK编码保存，将会以UTF-8编码（浏览器端设置）显示，在这种情况下就可能会出问题。
<br><br>JVM层
<br><br>大多数服务器都允许同时运行多个实例，且每个服务器实例都能有自己的JVM实例。此外，还可对每一个JVM实例分别设置。大多数服务器用本地设置来为每个实例定义默认的语言支持。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537143.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图5 Sun ONE 应用服务器设置</font></center>
<font size="2"><br><br>图5显示的是Sun ONE（开放网络环境）应用服务器的一个本地单个实例设置。该设置给出了登录系统和标准输出的默认字符编码方式。
<br><br>此外，不同的服务器使用的JVM版本可能会不同，而不同的JDK版本支持的编码标准各异，所有这些都会导致迁移问题。例如Sun
ONE应用服务器与Tomcat都支持J2SE 1.4，而有些服务器只支持到J2SE 1.3。J2SE 1.4支持Unicode
3.1，它具有许多早期版本所没有的新特性。
<br><br>单个应用层 
<br><br>每个部署在服务器上的应用在运行前都可以为其配置独立的编码设置，这就使得在同一个服务器实例上能够运行多个采用不同语言的应用。一些服务器用以下的字符编码设置为每个部署的应用指定其应使用的编码方式：
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>&lt;locale-charset-info default-locale="en_US"&gt;      <br>&lt;/locale-charset-map locale="zh_CN" <br>agent="Mozilla/4.77 <br>[en] (Windows NT 5.0; U)"  <br>charset="GBK"&gt;&lt;/locale-charset-info&gt;</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center> 
<font size="2"><br><br>这种分层配置的目的是为了灵活性和可维护性。但不幸的是，当在服务器间迁移时这种做法就可能导致出问题，因为并非所有的服务器配置都遵循
标准。比如说，如果在一个支持本地字符集设置的服务器上开发了应用，那么当把该应用迁移到另一个不支持这种编码设置的服务器上时就可能会遇到问题。<br><br>
              运行时阶段
<br><br>在运行过程中J2EE应用很可能会与其它外部系统通信。应用也许会读写文件，或者用数据库管理数据，有时候还可能用LDAP（轻量目录访
问协议）服务器存储标识信息。在这些情况下，J2EE应用和外部系统之间需要进行数据交换。如果数据中带有象中文这样的多字节字符，就可能会遇到问题。
<br><br>大部分的外部系统都有他们自己的编码设置。例如LDAP服务器很可能使用UTF-8对字符编码；Oracle数据库系统用环境变量
NLS_LANG来指定编码方式。如果Oracle是安装在中文操作系统上，该变量的默认设置为ZHS16GBK，也就是用GBK编码方式来存储中文字
符。因此当J2EE应用的编码设置与外部系统不同时需要进行转码，通常用以下代码来完成这一工作：
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>byte[] defaultBytes = <br>original.getBytes(current_encoding);<br>String newEncodingStr = <br>new String(defaultBytes, <br>old_encoding);</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>以上代码给出了如何将字符串从一种编码方式转换为另一种。例如你在LDAP服务器中用UTF-8编码存储了一个用户名（多字节字符），而在J2EE应用中用的却是GBK编码，因此当应用从LDAP服务器中取用户名时就可能被错误地编码。
<br><br>要解决这个问题，可以用original.getBytes("GBK")得到原始的字节，然后用new String(defaultBytes, "UTF-8")构造一个新字符串，这样就可以正确显示了。
<br><br>客户端显示阶段
<br><br>现在大多数J2EE应用都采用浏览器/服务器架构，以浏览器作为客户端。要在浏览器里正确显示多字节字符，需要注意以下几个方面：
<br><br>浏览器语言支持：
<br><br>为能正确地显示多字节字符，浏览器及其所运行的操作系统应提供对特定语言的支持，比如字体和字码表。
<br><br>浏览器编码设置
<br><br>服务器返回的HTML头
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>&lt;meta http-equiv="content-type" <br>content="text/html;charset=gb2312"&gt;</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>向浏览器声明了该页面使用的编码方式，否则浏览器将使用默认编码设置或自动进行匹配。当然，用户也可以对页面的编码进行设定，如图6所示。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537145.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图6 Netscape的编码设置页</font></center>
<font size="2"><br><br>因此如果页面没有声明，多字节字符就可能显示不正确，在这种情况下用户必须手工设定当前页面的编码方式。
<br><br>HTTP POST编码 
<br><br>用HTML页面的Form标签向服务器提交数据会使情况变得更为复杂。浏览器的编码方式取决于当前页面的编码设定，对Form标签也照此处理。这意味着如果ASCII格式的HTML页面用ISO-8859-1编码，那么用户在此页面中将不能提交中文字符。
<br><br>这是因为所有提交的数据都用ISO-8859-1编码，这将使中文字符丢失字节。所有的浏览器都遵守这个HTML标准。              
              <br><br>
              HTTP GET编码
<br><br>URL链接中带有多字节字符会使事情复杂化，这种情况很常见，例如在链接里加入用户名或其它信息以便传给下一页。
<br><br>但RFC （因特网标准草案） 2396中并未明确规定URL中有非US-ASCII字符时的格式，不同的浏览器会采用它们自己的方式来编码URL中的多字节字符。
<br><br>以Mozila为例（如图7/8/9/10），通常是在HTTP请求发送前对URL编码。我们知道在URL编码过程中，首先根据某种编码
方式（如UTF-8或GBK）将一个多字节字符转换成两个或更多的字节，然后每个字节用3个字符组成的字符串%xy来表示，其中xy是表示该字节的两个十
六进制数。这方面的更多信息可参考HTML规范。不管怎样，URL编码所采用的编码方式取决于当前页面的编码方式。
<br><br>我用下面这个gbk_test.jsp页面做演示：
<br><br>清单3 
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>gbk_test.jsp<br>&lt;%@page contentType="text/html;charset=GBK"%&gt;<br>&lt;HTML&gt;   <br>&lt;BODY&gt;      <br>&lt;a href='/chartest/servlet/httpGetTest?name=王'&gt;<br>&lt;h1&gt;Test for GBK encoded URL&lt;/h1&gt;&lt;/a&gt;   <br>&lt;/BODY&gt;<br>&lt;/HTML&gt;</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>x738b是一个中文字符的转义值，这个中文字符就是我的姓。该页面如图7所示。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537147.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图7 Mozilla的URL</font></center>
<font size="2"><br><br>当鼠标移动到该链接上时，链接的地址就会在状态栏中显示出来，可以看到URL中嵌入了一个中文字符。点击页面中的链接，可以从地址栏中清楚地看到该字符已被URL编码。字符x738b被编码为%CD%F5，这是URL编码与GBK编码共同作用的结果。
<br><br>在服务器端，用request.getQueryString()方法取出查询字符串；为与查询字符串相比较，接下来的一行用另一种方法getParameter(String)来显示字符，如图8所示。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537149.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图8 Mozilla中的URL编码</font></center>
<font size="2"><br><br>把当前页面的编码方式由GBK改为UTF-8，再次点击页面中的链接，出现的结果为：x738b被编码为%E7%8E%8B，如图9所示，这是URL编码与UTF-8共同作用的结果。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537151.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图9 Mozilla中的URL编码</font></center>
<font size="2"><br><br>Microsoft的IE浏览器却以不同的方式处理多字节的URL编码。IE在HTTP请求发送前不对URL编码，URL编码方式取决于当前页面的编码方式，如图10所示。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537153.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图10 IE不对URL编码</font></center>
<font size="2"><br><br>IE还有一个高级选项设置，可以强制浏览器总是以UTF-8编码方式发送URL请求，如图11所示。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537155.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图11 IE中的高级选项设置</font></center>
<font size="2"><br><br>根据以上说明我们会面临一个问题：如果应用页面的URL链接中带有多字节字符，只要用GBK编码就能在Mozilla里正常使用；但如果用户的客户端是IE，且在设置中强制浏览器以UTF-8编码发送URL请求，那么在使用中就会遇到问题。<br><br>
              多字节字符问题的解决方案 
<br><br>编写能运行于任何服务器、在任何浏览器中都能正常显示的J2EE应用是个挑战，下面是一些针对J2EE应用多字节字符问题的解决方案：
<br><br>通用原则：从不假定客户端（浏览器）和服务器端有任何默认设置。
<br><br>在编辑阶段，不要假定IDE的默认编码设置是你想要的，要手工设置它们。
<br><br>如果IDE不支持特定语言，就在Java代码中用\uXXXX转义序列，在HTML页面中用XXX转义序列，或用随JDK分发的native2ascii工具将本地文字字符串转换成Unicode转义序列，这样就能避免绝大部分问题。
<br><br>在编码阶段，从不假定服务器默认的编码处理设置是正确的，而用下面的方法显式指定：
<br><br>· 请求：
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>setCharacterEncoding()</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>· 响应：
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>setContentType(), <br>setLocale(),<br>&lt;%@ page contentType="text/html; <br>charset=encoding" <br>%&gt;</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center> 
<font size="2"><br><br>在为多种语言开发应用时，采用UTF-8编码方式或将所有语言的字符都用\uXXXX转义序列表示。
<br><br>· 在编译Java类时，确保当前语言环境变量与编码方式正确匹配。
<br><br>· 在配置阶段，尽可能地使用标准设置。例如在Servlet 2.4规范中有一个配置每个应用的字符编码方式的标准：
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>&lt;locale-encoding-mapping-list&gt;<br>    &lt;locale-encoding-mapping&gt;<br>        &lt;locale&gt;ja&lt;/locale&gt;<br>        &lt;encoding&gt;Shift_JIS&lt;/encoding&gt;<br>    &lt;/locale-encoding-mapping&gt;<br>&lt;/locale-encoding-mapping-list&gt;</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>当与外部系统通信时，尽可能地找出这些系统的编码方式，如果编码不同就进行转码。可以用UnicodeFormatter.java作为调试器打印所有的字节：
<br><br>清单4 
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>UnicodeFormatter.java<br>import java.io.*;<br>public class UnicodeFormatter  <br>{   <br>static public<br>String byteToHex(byte b) <br>{      <br>// Returns hex String <br>representation of byte b      <br>char hexDigit[] = <br>{         <br>'0', '1', '2', '3', '4', '5', '6', '7',         <br>'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'      <br>};      <br>char[] array = <br>{ <br>hexDigit[(b &gt;&gt; 4) &amp; 0x0f], <br>hexDigit[b &amp; 0x0f] };      <br>return new String(array);   <br>}   <br>static public String charToHex(char c) <br>{      <br>// Returns hex String <br>representation of char c      <br>byte hi = (byte) (c &gt;&gt;&gt; 8);      <br>byte lo = (byte) (c &amp; 0xff);      <br>return byteToHex(hi) + byteToHex(lo);   <br>}<br>}</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>总在HTML页面中对编码方式做显式声明，例如<br><br>content="text/html;charset=gb2312"&gt;，不要假定浏览器的默认设置是正确的。
<br><br>· 不要在链接里加入多字节字符，例如查询字符串里不要用用户名，改用用户ID。
<br><br>· 如果必须在链接里加入多字节字符，那就要对URL进行手工编码，可以在服务器端处理（用Java），也可以在客户端处理（用JavaScript或VBscript）。<br><br>
              难题之一：UTF-16
<br><br>有了前面的知识，现在我们来分析一个真正的难题，这是我负责的一个ISV（独立软件开发商）在项目中遇到的：J2EE中的UTF-16。
<br><br>现行的中文字符标准（GB18030）定义并支持27,484个中文字符。尽管这个数字看起来很大，实际上对中国人来说还不够。目前中文
拥有60,000多个字符，每年还在快速增长，这个状况对中国政府在信息化方面的工作会产生严重影响。例如我姐姐的名在标准字符集中就没有，因此银行或邮
电系统的计算机就打不出她的名。
<br><br>我的ISV希望能建立一套完整的、能让所有人满意的中文字符系统。它定义了自己的字码表，有两个现成的字符字码表可供选择：采用
GB18030标准，可扩充到1600万个字符；或采用Unicode
3.1标准，可支持1,112,064个字符。GB18030标准定义了编码规则，也叫做GB18030，它用起来很简便，目前已被JDK支持。然而如果
采用Unicode 3.1标准，我们就可以从三种编码方式中进行选择：UTF-8、UTF-16或UTF-32。
<br><br>我的ISV希望用UTF-16编码来处理它对中文字符的Unicode扩展。UTF-16编码最主要的特点就是所有的ASCII字符都
被编码为16位的单元，这会在各个阶段引发问题。在几个服务器上做过测试后，ISV发现J2EE应用根本不支持UTF-16编码。果真如此吗？我们一起来
分析开发的各个阶段以找出问题所在。
<br><br>编辑阶段 
<br><br>如果在Java、JSP或HTML源文件中有多字节文字字符串，就需要用支持它们的IDE。我用的是NetBeans，只要将文本编码属
性设置为UTF-16就能轻松支持UTF-16编码。图12所示的是一个用UTF-16编码的JSP页面，它里面只有一个静态文字字符串“hello
world!”。该页面在Tomcat上运行，在Mozilla中显示。
<br><br></font><center><font size="2"><img src="http://tech.ccidnet.com/col/attachment/2005/11/537157.gif"></font></center>
<font size="2"><br><br></font><center><font size="2">图12 Mozilla中用UTF-16编码的页面</font></center>
<font size="2"><br><br>编译阶段
<br><br>由于在Java或JSP源文件中带有用UTF-16编码的字符，因此需要编译器的支持。可以用javac -encoding
UTF-16命令来编译Java源文件，而在NetBeans里则可通过GUI方便地设置编译器的属性。通过一些简单的代码测试可以发现：如果
servlet文件中的字符是用UTF-16编码的，那么运行时就不会有问题。
<br><br>运行时动态编译的JSP文件值得我们注意。幸运的是，大多数服务器可以对JSP页面的编码方式进行配置；而不幸的是，在Tomcat和
Sun ONE应用服务器上做测试时，我发现用来将JSP文件转换为servlet
Java源文件的Jasper不能识别被UTF-16编码过的JSP标签（比如&lt;%page..%&gt;），所有这些标签都被当作文字字符串处理
了！我认为问题的根源可能在于Jasper（大多数应用服务器都用它做JSP编译器），因为它以字节为单位来识别JSP的特定记号和标签。<br><br>
              浏览器测试
<br><br>现在我们看到由于识别被UTF-16编码过的JSP标签失败，JSP不能支持UTF-16编码的文字字符，而servlets却没有这个问题。
<br><br>且慢！为使测试更能说明问题，我们在测试代码中加入POST功能，让用户通过HTML的Form标签提交UTF-16编码的字符。从本文
的资源一节中下载下面的示例程序：servlet PostForm.java和servlet ByteTest.java。Servlet
PostForm.java用来输出一个用UTF-16编码的页面，它有一个用来向服务器提交数据的表单。
<br><br>在ByteTest.java里，由于不能确定服务器是否配置为UTF-16编码方式，我没有用
request.getParameter()方法显示浏览器提交的数据，而改用request.getInputStream()方法从请求中提取原始
数据，然后打印从浏览器得到的每一个字节。
<br><br>清单5
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>PostForm.java<br>public class PostForm extends HttpServlet<br>{    <br>....        <br>protected void processRequest<br>(HttpServletRequest request, <br>HttpServletResponse response)    <br>throws ServletException, IOException <br>{        <br>response.setContentType("text/html;<br>charset=UTF-16");        <br>PrintWriter out = response.getWriter();        <br>out.println("&lt;html&gt;&lt;head&gt;");        <br>out.println("&lt;meta content="text/html; <br>charset=UTF-16\" http-equiv="content-type\"&gt;");        out.println("&lt;/head&gt;&lt;body&gt;");        <br>out.println("&lt;form action=\"servlet/ByteTest\"<br>method=\"POST\"&gt;");       <br>out.println("&lt;input type=\"text\"<br>name=\"name\"&gt;&lt;input type=\"submit\"&gt;");        out.println("&lt;/form&gt;&lt;/body&gt;&lt;/html&gt;");       <br>out.close();    <br>}       <br>....    <br>}</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>清单6
<br><br></font><center><font size="2"><ccid_nobr>
</ccid_nobr></font><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><font size="2"><ccid_code>ByteTest.java<br>public class ByteTest extends HttpServlet <br>{      <br>...     <br>protected void processRequest<br>(HttpServletRequest request,<br>HttpServletResponse response)    <br>throws ServletException, <br>IOException <br>{        <br>ServletInputStream in = <br>request.getInputStream();        <br>response.setContentType("text/html");        <br>PrintWriter out = response.getWriter();        <br>byte[] postdata = new byte[50];        <br>int size = in.read(postdata,0,50);        <br>in.close();       <br>out.println("&lt;html&gt;");        <br>out.println("&lt;head&gt;");        <br>out.println("&lt;title&gt;Servlet&lt;/title&gt;");       <br>out.println("&lt;/head&gt;");        <br>out.println("&lt;body&gt;");        <br>printBytes(out,postdata, size,<br>"postdata");        <br>out.println("&lt;/body&gt;");        <br>out.println("&lt;/html&gt;");        <br>out.close();    }...}</ccid_code></font></pre> 
   </td>
  </tr>
</tbody></table>
</center>
<font size="2"><br><br>在运行过程中PostForm页面显然会用UTF-16编码，而ByteTest的输出结果又会是什么呢？
<br><br>· IE：尽管页面是用UTF-16编码的，浏览器对所有输入的字符都采用UTF-8编码。
<br><br>· Mozilla：无论在这个UTF-16编码的页面里输入什么字符，只有“＝”这个字符能显示出来，这个运行结果显然是错误的。
<br><br>结论
<br><br>J2EE应用只能在以下条件下使用UTF-16编码：
<br><br>· 只用于servlet编程
<br><br>· 浏览器只限制于用IE
<br><br>· 虽然浏览器端的页面用UTF-16编码，在服务器端要用UTF-8解码
<br><br>实际上，在J2EE应用中使用UTF-8编码并不困难。在Unicode3.1标准中，UTF-8编码与UTF-16编码能处理的字符数目是一样的，只是在存储和处理效率方面有差异。
<br><br>结束语
<br><br>由此可见，如果J2EE应用遇到了多字节字符问题，你一定要深入到开发生命周期的各个阶段，检查服务器和客户端的配置情况，并借助调试工具，这样才能找出问题的根源所在。
<br><br></font><img src ="http://www.blogjava.net/hellotony/aggbug/20573.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-19 16:06 <a href="http://www.blogjava.net/hellotony/articles/20573.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2ME平台体系结构</title><link>http://www.blogjava.net/hellotony/articles/20305.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Thu, 17 Nov 2005 11:33:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/20305.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/20305.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/20305.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/20305.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/20305.html</trackback:ping><description><![CDATA[<font size="2">做J2ME应用开发的程序员也许经常会被一些名词或者概念所迷惑，比如Personal Basic Profile和Personal
Profile有什么关系？基于CLDC的应用程序能够无修改的移植到基于CDC的设备上嘛？要回答这些问题并不容易，因为你必须首先揭开J2ME平台的
神秘面纱。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
2000年当J2ME平台的CLDC发布的时候，所有的java开发者都为之兴奋不已。SUN也不负众望在两个月后发布了基于CLDC的MIDP，给开发
者提供了开发交互式应用程序的平台。我想现在国内还主要是基于CLDC/MIDP在开发应用吧。J2ME平台的另一个规范CDC在大概10个月后才发布，
同时发布的还有Foundation
Profile。但是由于他们都没有给开发者提供开发可交互应用程序的API，因此他们发布的影响远远小于CLDC/MIDP的发布。大概在2003年
SUN发布了基于CDC的Personal Basic Profile和Personal
Profile。他们的组合替代了以前的PersonalJava平台。</font>
<p><font size="2">&nbsp;&nbsp;&nbsp;
是不是已经被这些名字弄糊涂了，我们现在来看J2ME平台的结构。J2ME平台是本着Configuration和Profile结合来设计的。
Configuration是提供支持最大范围设备的最小的平台。Profile是针对特定的设备提供相应的开发包集合。在J2ME的两个基本配置CDC
和CLDC是按照如下的标准进行区分的。<br>CLDC：</font></p>
<p><font size="2">512 KB 以下内存 <br>有限能源供应（通常使用电池） <br>有限或非持续网络连接 <br>简单的用户界面<br>16位或者32位的处理器<br>CDC：</font></p>
<p><font size="2">2M以上内存<br>具有网络连接能力，通常为无线网络 <br>需要实现java虚拟机规范的全部功能<br>32位或者64位的处理器</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 从上述的要求中我们不难看出CLDC主要针对那些资源非常受限的设备比如手机、PDA、双工寻呼机等。而CDC主要面对那些家电产品，比如机顶盒、汽车导航系统等。<br>&nbsp;&nbsp;&nbsp; 下面我们来看看J2ME平台的结构图：</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://images.csdn.net/20050719/j1.gif" align="bottom" border="0" hspace="0"><br>&nbsp;&nbsp;&nbsp;
我们可以看出J2ME的核心是Configuration，在它里面定义了java的虚拟机，通过它来和底层的Host
OS打交道。Profile提供了访问设备的IO或者图形界面的能力，这样Configuration和Profile共同构成了J2ME的运行环境。比
如CLCD/MIDP可以提供给你开发手机程序的环境。在Profile之上针对不同的设备还可以提供不同的可选开发包。<br>&nbsp;&nbsp;&nbsp; 由于CLDC/MIDP大家都比较熟悉了，我们下面主要介绍一下基于CDC的J2ME系统构架，同样还是看它的系统图：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://images.csdn.net/20050719/j2.gif" align="bottom" border="0" hspace="0"></font></p><font size="2">
&nbsp;&nbsp;&nbsp;
基于CDC的设备可以支持全部的Java2语言规范和Java虚拟机规范，它是CLDC的超集并且远大于CLDC,因此基于CLDC的程序是可以无修改的
移植到基于CDC的设备的。Foundation
Profile是对CDC进行扩展的Profile，但是他并没有提供GUI的开发包，GUI是在Personal Basic
Profile中进行定义的，它提供了AWT的一个子集给开发人员。它还提供了Xlet应用程序模式——针对java TV。Personal
Profile是Personal Basci
Profile的超集。他提供了对Applet的支持，丰富了AWT的组件，添加了java.awt.datatransfer包。<br>&nbsp;&nbsp;&nbsp;&nbsp; 总结：上面是我对J2ME平台进行的阐述，我想最重要的就是Configuration/Profile的设计。理解了它就掌握了一半。如果大家有不同的意见，欢迎一起交流！</font><img src ="http://www.blogjava.net/hellotony/aggbug/20305.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-17 19:33 <a href="http://www.blogjava.net/hellotony/articles/20305.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2ME概念解析</title><link>http://www.blogjava.net/hellotony/articles/20304.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Thu, 17 Nov 2005 11:31:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/20304.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/20304.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/20304.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/20304.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/20304.html</trackback:ping><description><![CDATA[<p><font size="2"><strong><a href="http://java.sun.com/j2me/index.jsp">J2ME</a></strong>，即Java 2 Micro Edition，是SUN公司推出的在移动设备上运行的微型版Java平台，常见的移动设备有手机，PDA，电子词典，以及各式各样的信息终端如机顶盒等等。</font></p>
<p><font size="2">由于移动终端的类型成千上万，而且计算能力差异非常大，不可能像桌面系统那样仅仅两三个版本的JVM即可满足Windows，Linux和Unix
系统，因此，J2ME不是一个简单的微型版的JVM。为了满足千差万别的移动设备的需求，SUN定义了一系列的针对不同类型设备的规范，因此，J2ME平
台便是由许多的规范组成的集合。</font></p>
<p><font size="2">最重要的移动终端当然是手机了，因此，我们主要讨论手机相关的J2ME规范。</font></p>
<p><font color="#ff0000" size="2"><strong>Configuration</strong></font></p>
<p><font size="2">SUN把不同的设备按照计算能力分为<strong><a href="http://java.sun.com/products/cldc/index.jsp">CLDC</a></strong>（Connected Limited Device Configuration）和<strong><a href="http://java.sun.com/products/cdc/index.jsp">CDC</a></strong>（Connected
Device
Configuration）两大类，这两个Configuration是针对设备软硬件环境严格定义的，比如CLDC1.0定义了内存大小为64-
512k，任何设备如果支持CLDC1.0，就必须严格满足定义，不能有可选的或者含糊的功能。</font></p>
<p><font size="2">CLDC1.0是针对计算能力非常有限的设备定义的，只支持整数运算，不支持浮点运算，早期的Java手机大部分都支持CLDC1.0，如Nokia 3650，Siemens 6688i。</font></p>
<p><font size="2">CLDC1.1则增加了浮点运算，因此，在支持CLDC1.1的设备上，可以使用float和double类型的变量。现在的Java手机很多都能支持CLDC1.1，如Nokia 9500，Siemens S65。</font></p>
<p><font size="2">CDC则是针对计算能力比较强的设备定义的，如PPC等，CDC平台的JVM基本上和桌面的JVM很接近了，只是可以使用的Package大大少于J2SE的包。支持CDC的非常高端的Java手机也会很快上市。</font></p>
<p><font color="#ff0000" size="2"><strong>Profile</strong></font></p>
<p><font size="2">和Configuration相比，Profile更多是针对软件接口的定义，Profile有必须实现的，也有可选的功能，因此，Profile更灵活。</font></p>
<p><font size="2">最重要的Profile当然是</font><font color="#0000ff" size="2"><strong><a href="http://java.sun.com/products/midp/index.jsp">MIDP</a></strong></font><font size="2">（Micro
Information Device
Profile），MIDP定义了能在Java手机上运行的Java程序的规范，包括应用程序生命周期，各种UI界面组件，支持Record存储和
Http连接等等，符合MIDP规范的Java小程序被称为MIDlet，可以直接通过无线网络下载到手机并运行。</font></p>
<p><font size="2">早期的MIDP1.0规范使我们能在手机上运行有UI界面的Java程序，但是MIDP1.0对游戏的支持不够，必须自己实现许多代码，因此，MIDP2.0规范大大加强了对游戏开发的支持，使开发者能编写更少的代码来创建游戏。</font></p>
<p><font size="2">MIDP规范的图形界面基本上都是独立于J2SE的AWT和Swing组件，因为目前手机的计算能力还比较有限，但是，随着手机的CPU越来越快，使得AWT和Swing移植到手机上也将成为可能，因此，基于CDC规范的最新的<strong><a href="http://java.sun.com/products/personalbasis/index.jsp">PBP 1.0</a></strong>（Personal Basic Profile）和<strong><a href="http://java.sun.com/products/personalprofile/index.jsp">PP 1.0</a></strong>（Personal Profile）提供了部分AWT和Swing的支持，目前，部分高端PDA已经可以运行PBP和PP的Java程序了。可以预见，将来大部分的AWT和Swing组件都能移植到手机上。</font></p>
<p><font size="2">前面已经说过，和Configuration相比，Profile有许多可选包，比较实用的Profile还有在<strong><a href="http://jcp.org/en/jsr/detail?id=135">JSR135</a></strong>定义的</font><font color="#ff0000" size="2"><strong><a href="http://java.sun.com/products/mmapi/index.jsp">MMAPI</a></strong></font><font size="2">（Mobile Media API），实现多媒体播放功能；在<strong><a href="http://jcp.org/en/jsr/detail?id=184">JSR184</a></strong>定义的</font><font color="#ff0000" size="2"><strong><a href="http://jcp.org/jsr/detail/184.jsp">M3G API</a></strong></font><font size="2">（Mobile 3D Graphics API），实现3D功能；在<strong><a href="http://jcp.org/en/jsr/detail?id=120">JSR120</a></strong>定义的</font><font color="#ff0000" size="2"><strong><a href="http://java.sun.com/products/wma/">WMA</a></strong></font><font size="2">（Wireless Messaging API），实现短消息收发。如果你的手机支持某一Profile，如M3G，那么便可以在MIDlet中使用M3G的3D API实现3D游戏。<br><br>如果你准备在手机上开发J2ME应用，选择手机时就需要注意厂商支持的CLDC规范，支持MIDP1.0还是2.0，是否支持MMAPI，M3G，WMA等可选包。</font></p>
<p><font size="2">Profile虽然定义了Java API接口，但是底层如何实现是由各厂商自己决定的，如M3G定义了3D接口，但是底层实现既可以使用硬件加速，也可以由C程序模拟，或者部分由硬件实现，部分由软件实现。</font></p>
<p><font size="2">比J2ME更精简的Java平台被SUN称为</font><font color="#0000ff" size="2"><strong><a href="http://java.sun.com/products/javacard/index.jsp">JavaCard</a></strong></font><font size="2">，运行在信用卡等芯片中，实现电子支付等功能，目前SUN还没有把JavaCard并入J2ME平台。</font></p><img src ="http://www.blogjava.net/hellotony/aggbug/20304.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-17 19:31 <a href="http://www.blogjava.net/hellotony/articles/20304.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAVA面试题集2</title><link>http://www.blogjava.net/hellotony/articles/19742.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Mon, 14 Nov 2005 09:15:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/19742.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/19742.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/19742.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/19742.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/19742.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 基础知识：1.C++或Java中的异常处理机制的简单原理和应用。当JAVA程序违反了JAVA的语义规则时，JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程序员...&nbsp;&nbsp;<a href='http://www.blogjava.net/hellotony/articles/19742.html'>阅读全文</a><img src ="http://www.blogjava.net/hellotony/aggbug/19742.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-14 17:15 <a href="http://www.blogjava.net/hellotony/articles/19742.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAVA面试题集</title><link>http://www.blogjava.net/hellotony/articles/19741.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Mon, 14 Nov 2005 09:13:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/19741.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/19741.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/19741.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/19741.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/19741.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 基础知识： 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JAVA的语义规则时，JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程...&nbsp;&nbsp;<a href='http://www.blogjava.net/hellotony/articles/19741.html'>阅读全文</a><img src ="http://www.blogjava.net/hellotony/aggbug/19741.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-14 17:13 <a href="http://www.blogjava.net/hellotony/articles/19741.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java源码分析：深入探讨Iterator模式</title><link>http://www.blogjava.net/hellotony/articles/19713.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Mon, 14 Nov 2005 08:42:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/19713.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/19713.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/19713.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/19713.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/19713.html</trackback:ping><description><![CDATA[<font size="2"><span class="style1"></span><span class="oblog_text"><p dragover="true">java.util包中包含了一系列重要的集合类。本文将从分析源码入手，深入研究一个集合类的内部结构，以及遍历集合的迭代模式的源码实现内幕。<br><br>下面我们先简单讨论一个根接口Collection，然后分析一个抽象类AbstractList和它的对应Iterator接口，并仔细研究迭代子模式的实现原理。<br><br>本文讨论的源代码版本是JDK&nbsp;1.4.2，因为JDK&nbsp;1.5在java.util中使用了很多泛型代码，为了简化问题，所以我们还是讨论1.4版本的代码。<br><br>集合类的根接口Collection<br><br><br>Collection接口是所有集合类的根类型。它的一个主要的接口方法是：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;boolean&nbsp;add(Object&nbsp;c)<br><br>add
()方法将添加一个新元素。注意这个方法会返回一个boolean，但是返回值不是表示添加成功与否。仔细阅读doc可以看到，Collection规
定：如果一个集合拒绝添加这个元素，无论任何原因，都必须抛出异常。这个返回值表示的意义是add()方法执行后，集合的内容是否改变了（就是元素有无数
量，位置等变化），这是由具体类实现的。即：如果方法出错，总会抛出异常；返回值仅仅表示该方法执行后这个Collection的内容有无变化。<br><br>类似的还有：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;boolean&nbsp;addAll(Collection&nbsp;c);<br>&nbsp;&nbsp;&nbsp;&nbsp;boolean&nbsp;remove(Object&nbsp;o);<br>&nbsp;&nbsp;&nbsp;&nbsp;boolean&nbsp;removeAll(Collection&nbsp;c);<br>&nbsp;&nbsp;&nbsp;&nbsp;boolean&nbsp;remainAll(Collection&nbsp;c);<br><br>Object
[]&nbsp;toArray()方法很简单，把集合转换成数组返回。Object[]&nbsp;toArray(Object[]&nbsp;a)
方法就有点复杂了，首先，返回的Object[]仍然是把集合的所有元素变成的数组，但是类型和参数a的类型是相同的，比如执行：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;String[]&nbsp;o&nbsp;=&nbsp;(String[])c.toArray(new&nbsp;String[0]);<br><br>得到的o实际类型是String[]。<br><br>其次，如果参数a的大小装不下集合的所有元素，返回的将是一个新的数组。如果参数a的大小能装下集合的所有元素，则返回的还是a，但a的内容用集合的元素来填充。尤其要注意的是，如果a的大小比集合元素的个数还多，a后面的部分全部被置为null。<br><br>最后一个最重要的方法是iterator()，返回一个Iterator（迭代子），用于遍历集合的所有元素。<br><br>用Iterator模式实现遍历集合<br><br><br>Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来，从而避免向客户端暴露集合的内部结构。<br><br>例如，如果没有使用Iterator，遍历一个数组的方法是使用索引：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;for(int&nbsp;i=0;&nbsp;i&lt;array.size();&nbsp;i++)&nbsp;{&nbsp;...&nbsp;get(i)&nbsp;...&nbsp;}<br><br>而访问一个链表（LinkedList）又必须使用while循环：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;while((e=e.next())!=null)&nbsp;{&nbsp;...&nbsp;e.data()&nbsp;...&nbsp;}<br><br>以上两种方法客户端都必须事先知道集合的内部结构，访问代码和集合本身是紧耦合，无法将访问逻辑从集合类和客户端代码中分离出来，每一种集合对应一种遍历方法，客户端代码无法复用。<br><br>更恐怖的是，如果以后需要把ArrayList更换为LinkedList，则原来的客户端代码必须全部重写。<br><br>为解决以上问题，Iterator模式总是用同一种逻辑来遍历集合：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;for(Iterator&nbsp;it&nbsp;=&nbsp;c.iterater();&nbsp;it.hasNext();&nbsp;)&nbsp;{&nbsp;...&nbsp;}<br><br>奥秘在于客户端自身不维护遍历集合的"指针"，所有的内部状态（如当前元素位置，是否有下一个元素）都由Iterator来维护，而这个Iterator由集合类通过工厂方法生成，因此，它知道如何遍历整个集合。<br><br>客户端从不直接和集合类打交道，它总是控制Iterator，向它发送"向前"，"向后"，"取当前元素"的命令，就可以间接遍历整个集合。<br><br>首先看看java.util.Iterator接口的定义：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;interface&nbsp;Iterator&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;boolean&nbsp;hasNext();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;next();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;remove();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>依赖前两个方法就能完成遍历，典型的代码如下：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;for(Iterator&nbsp;it&nbsp;=&nbsp;c.iterator();&nbsp;it.hasNext();&nbsp;)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;o&nbsp;=&nbsp;it.next();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;对o的操作...<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>在JDK1.5中，还对上面的代码在语法上作了简化：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Type是具体的类型，如String。<br>&nbsp;&nbsp;&nbsp;&nbsp;for(Type&nbsp;t&nbsp;:&nbsp;c)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;对t的操作...<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>每
一种集合类返回的Iterator具体类型可能不同，Array可能返回ArrayIterator，Set可能返回SetIterator，Tree可
能返回TreeIterator，但是它们都实现了Iterator接口，因此，客户端不关心到底是哪种Iterator，它只需要获得这个
Iterator接口即可，这就是面向对象的威力。<br><br>Iterator源码剖析<br><br>让我们来看看AbstracyList如何创建Iterator。首先AbstractList定义了一个内部类（inner&nbsp;class）：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;class&nbsp;Itr&nbsp;implements&nbsp;Iterator&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>而iterator()方法的定义是：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;Iterator&nbsp;iterator()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;new&nbsp;Itr();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>因此客户端不知道它通过Iterator&nbsp;it&nbsp;=&nbsp;a.iterator();所获得的Iterator的真正类型。<br><br>现在我们关心的是这个申明为private的Itr类是如何实现遍历AbstractList的：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;class&nbsp;Itr&nbsp;implements&nbsp;Iterator&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;cursor&nbsp;=&nbsp;0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;lastRet&nbsp;=&nbsp;-1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;expectedModCount&nbsp;=&nbsp;modCount;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>Itr类依靠3个int变量（还有一个隐含的AbstractList的引用）来实现遍历，cursor是下一次next()调用时元素的位置，第一次调用next()将返回索引为0的元素。lastRet记录上一次游标所在位置，因此它总是比cursor少1。<br><br>变量cursor和集合的元素个数决定hasNext()：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;boolean&nbsp;hasNext()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;cursor&nbsp;!=&nbsp;size();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>方法next()返回的是索引为cursor的元素，然后修改cursor和lastRet的值：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;Object&nbsp;next()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;checkForComodification();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;next&nbsp;=&nbsp;get(cursor);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastRet&nbsp;=&nbsp;cursor++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;catch(IndexOutOfBoundsException&nbsp;e)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;checkForComodification();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;NoSuchElementException();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br><br>expectedModCount
表示期待的modCount值，用来判断在遍历过程中集合是否被修改过。AbstractList包含一个modCount变量，它的初始值是0，当集合
每被修改一次时（调用add，remove等方法），modCount加1。因此，modCount如果不变，表示集合内容未被修改。<br><br>Itr初始化时用expectedModCount记录集合的modCount变量，此后在必要的地方它会检测modCount的值：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;final&nbsp;void&nbsp;checkForComodification()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(modCount&nbsp;!=&nbsp;expectedModCount)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;ConcurrentModificationException();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;<br>如果modCount与一开始记录在expectedModeCount中的值不等，说明集合内容被修改过，此时会抛出ConcurrentModificationException。<br><br>这个ConcurrentModificationException是RuntimeException，不要在客户端捕获它。如果发生此异常，说明程序代码的编写有问题，应该仔细检查代码而不是在catch中忽略它。<br><br>但是调用Iterator自身的remove()方法删除当前元素是完全没有问题的，因为在这个方法中会自动同步expectedModCount和modCount的值：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;remove()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AbstractList.this.remove(lastRet);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;在调用了集合的remove()方法之后重新设置了expectedModCount：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;expectedModCount&nbsp;=&nbsp;modCount;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;<br>要确保遍历过程顺利完成，必须保证遍历过程中不更改集合的内容（Iterator的remove()方法除外），因此，确保遍历可靠的原则是只在一个线程中使用这个集合，或者在多线程中对遍历代码进行同步。<br><br>最后给个完整的示例：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Collection&nbsp;c&nbsp;=&nbsp;new&nbsp;ArrayList();<br>&nbsp;&nbsp;&nbsp;&nbsp;c.add("abc");<br>&nbsp;&nbsp;&nbsp;&nbsp;c.add("xyz");<br>&nbsp;&nbsp;&nbsp;&nbsp;for(Iterator&nbsp;it&nbsp;=&nbsp;c.iterator();&nbsp;it.hasNext();&nbsp;)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;s&nbsp;=&nbsp;(String)it.next();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(s);<br>&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p><br>如果你把第一行代码的ArrayList换成LinkedList或Vector，剩下的代码不用改动一行就能编译，而且功能不变，这就是针对抽象编程的原则：对具体类的依赖性最小。</p></span></font><img src ="http://www.blogjava.net/hellotony/aggbug/19713.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-14 16:42 <a href="http://www.blogjava.net/hellotony/articles/19713.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基础才是最重要的</title><link>http://www.blogjava.net/hellotony/articles/19607.html</link><dc:creator>Tony</dc:creator><author>Tony</author><pubDate>Sun, 13 Nov 2005 13:29:00 GMT</pubDate><guid>http://www.blogjava.net/hellotony/articles/19607.html</guid><wfw:comment>http://www.blogjava.net/hellotony/comments/19607.html</wfw:comment><comments>http://www.blogjava.net/hellotony/articles/19607.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/hellotony/comments/commentRss/19607.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/hellotony/services/trackbacks/19607.html</trackback:ping><description><![CDATA[<p><font size="2">基础才是最重要的</font></p>
<p><font size="2">对于这个系列里的问题，每个学Java的人都应该搞懂。当然，如果只是学Java玩玩就无所谓了。如果你认为自己已经超越初学者了，却不很懂这些问题，请将你自己重归初学者行列。内容均来自于CSDN的经典老贴。</font></p>
<p><font size="2">问题一：我声明了什么！</font></p>
<p><font size="2">String s = "Hello world!";</font></p>
<p><font size="2">许多人都做过这样的事情，但是，我们到底声明了什么？回答通常是：一个String，内容是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答，一半的人大概会回答错误。<br>这个语句声明的是一个指向对象的引用，名为“s”，可以指向类型为String的任何对象，目前指向"Hello world!"这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象，我们只是声明了一个只能指向String对象的引用变量。所以，如果在刚才那句语句后面，如果再运行一句：</font></p>
<p><font size="2">String string = s;</font></p>
<p><font size="2">我们是声明了另外一个只能指向String对象的引用，名为string，并没有第二个对象产生，string还是指向原来那个对象，也就是，和s指向同一个对象。</font></p>
<p><font size="2">问题二："=="和equals方法究竟有什么区别？</font></p>
<p><font size="2">==操作符专门用来比较变量的值是否相等。比较好理解的一点是：<br>int a=10;<br>int b=10;<br>则a==b将是true。<br>但不好理解的地方是：<br>String a=new String("foo");<br>String b=new String("foo");<br>则a==b将返回false。</font></p>
<p><font size="2">根据前一帖说过，对象变量其实是一个引用，它们的值是指向对象所在的内存地址，而不是对象本身。a和b都使用了new操作符，意味着将在内存中产生两个内容为"foo"的字符串，既然是“两个”，它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值，所以使用"=="操作符，结果会是 false。诚然，a和b所指的对象，它们的内容都是"foo"，应该是“相等”，但是==操作符并不涉及到对象内容的比较。<br>对象内容的比较，正是equals方法做的事。</font></p>
<p><font size="2">看一下Object对象的equals方法是如何实现的：<br>boolean equals(Object o){</font></p>
<p><font size="2">return this==o;</font></p>
<p><font size="2">}<br>Object 对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法，那你的类使用equals和使用==会得到同样的结果。同样也可以看出， Object的equals方法没有达到equals方法应该达到的目标：比较两个对象内容是否相等。因为答案应该由类的创建者决定，所以Object把这个任务留给了类的创建者。</font></p>
<p><font size="2">看一下一个极端的类：<br>Class Monster{<br>private String content;<br>...<br>boolean equals(Object another){ return true;}</font></p>
<p><font size="2">}<br>我覆盖了equals方法。这个实现会导致无论Monster实例内容如何，它们之间的比较永远返回true。</font></p>
<p><font size="2">所以当你是用equals方法判断对象的内容是否相等，请不要想当然。因为可能你认为相等，而这个类的作者不这样认为，而类的equals方法的实现是由他掌握的。如果你需要使用equals方法，或者使用任何基于散列码的集合（HashSet,HashMap,HashTable），请察看一下java doc以确认这个类的equals逻辑是如何实现的。</font></p>
<p><font size="2">问题三：String到底变了没有？</font></p>
<p><font size="2">没有。因为String被设计成不可变(immutable)类，所以它的所有对象都是不可变对象。请看下列代码：</font></p>
<p><font size="2">String s = "Hello";<br>s = s + " world!";</font></p>
<p><font size="2">s 所指向的对象是否改变了呢？从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中，s原先指向一个String对象，内容是 "Hello"，然后我们对s进行了+操作，那么s所指向的那个对象是否发生了改变呢？答案是没有。这时，s不指向原来那个对象了，而指向了另一个 String对象，内容为"Hello world!"，原来那个对象还存在于内存之中，只是s这个引用变量不再指向它了。<br>通过上面的说明，我们很容易导出另一个结论，如果经常对字符串进行各种各样的修改，或者说，不可预见的修改，那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变，所以对于每一个不同的字符串，都需要一个String对象来表示。这时，应该考虑使用StringBuffer 类，它允许修改，而不是每个不同的字符串都要生成一个新的对象。并且，这两种类的对象转换十分容易。<br>同时，我们还可以知道，如果要使用内容相同的字符串，不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化，把它设置为初始值，应当这样做：<br>public class Demo {<br>private String s;<br>...<br>public Demo {<br>s = "Initial Value";<br>}<br>...<br>}<br>而非<br>s = new String("Initial Value");<br>后者每次都会调用构造器，生成新对象，性能低下且内存开销大，并且没有意义，因为String对象不可改变，所以对于内容相同的字符串，只要一个 String对象来表示就可以了。也就说，多次调用上面的构造器创建多个对象，他们的String类型属性s都指向同一个对象。<br>上面的结论还基于这样一个事实：对于字符串常量，如果内容相同，Java认为它们代表同一个String对象。而用关键字new调用构造器，总是会创建一个新的对象，无论内容是否相同。<br>至于为什么要把String类设计成不可变类，是它的用途决定的。其实不只String，很多Java标准类库中的类都是不可变的。在开发一个系统的时候，我们有时候也需要设计不可变类，来传递一组相关的值，这也是面向对象思想的体现。不可变类有一些优点，比如因为它的对象是只读的，所以多线程并发访问也不会有任何问题。当然也有一些缺点，比如每个不同的状态都要一个对象来代表，可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本，即 StringBuffer。</font></p>
<p><font size="2">问题四：final关键字到底修饰了什么？</font></p>
<p><font size="2">final使得被修饰的变量"不变"，但是由于对象型变量的本质是“引用”，使得“不变”也有了两种含义：引用本身的不变，和引用指向的对象不变。</font></p>
<p><font size="2">引用本身的不变：<br>final StringBuffer a=new StringBuffer("immutable");<br>final StringBuffer b=new StringBuffer("not immutable");<br>a=b;//编译期错误</font></p>
<p><font size="2">引用指向的对象不变：<br>final StringBuffer a=new StringBuffer("immutable");<br>a.append(" broken!"); //编译通过</font></p>
<p><font size="2">可见，final只对引用的“值”(也即它所指向的那个对象的内存地址)有效，它迫使引用只能指向初始指向的那个对象，改变它的指向会导致编译期错误。至于它所指向的对象的变化，final是不负责的。这很类似==操作符：==操作符只负责引用的“值”相等，至于这个地址所指向的对象内容是否相等，==操作符是不管的。</font></p>
<p><font size="2">理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象，不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改，一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final，意图使得它“永远不变”。其实那是徒劳的。</font></p>
<p><font size="2">问题五：到底要怎么样初始化！</font></p>
<p><font size="2">本问题讨论变量的初始化，所以先来看一下Java中有哪些种类的变量。<br>1. 类的属性，或者叫值域<br>2. 方法里的局部变量<br>3. 方法的参数</font></p>
<p><font size="2">对于第一种变量，Java虚拟机会自动进行初始化。如果给出了初始值，则初始化为该初始值。如果没有给出，则把它初始化为该类型变量的默认初始值。</font></p>
<p><font size="2">int类型变量默认初始值为0<br>float类型变量默认初始值为0.0f<br>double类型变量默认初始值为0.0<br>boolean类型变量默认初始值为false<br>char类型变量默认初始值为0(ASCII码)<br>long类型变量默认初始值为0<br>所有对象引用类型变量默认初始值为null，即不指向任何对象。注意数组本身也是对象，所以没有初始化的数组引用在自动初始化后其值也是null。</font></p>
<p><font size="2">对于两种不同的类属性，static属性与instance属性，初始化的时机是不同的。instance属性在创建实例的时候初始化，static属性在类加载，也就是第一次用到这个类的时候初始化，对于后来的实例的创建，不再次进行初始化。这个问题会在以后的系列中进行详细讨论。</font></p>
<p><font size="2">对于第二种变量，必须明确地进行初始化。如果再没有初始化之前就试图使用它，编译器会抗议。如果初始化的语句在try块中或if块中，也必须要让它在第一次使用前一定能够得到赋值。也就是说，把初始化语句放在只有if块的条件判断语句中编译器也会抗议，因为执行的时候可能不符合if后面的判断条件，如此一来初始化语句就不会被执行了，这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句，就可以通过编译，因为无论如何，总有至少一条初始化语句会被执行，不会发生使用前未被初始化的事情。对于try-catch也是一样，如果只有在try块里才有初始化语句，编译部通过。如果在 catch或finally里也有，则可以通过编译。总之，要保证局部变量在使用之前一定被初始化了。所以，一个好的做法是在声明他们的时候就初始化他们，如果不知道要出事化成什么值好，就用上面的默认值吧！</font></p>
<p><font size="2">其实第三种变量和第二种本质上是一样的，都是方法中的局部变量。只不过作为参数，肯定是被初始化过的，传入的值就是初始值，所以不需要初始化。</font></p>
<p><font size="2">问题六：instance of是什么东东？</font></p>
<p><font size="2">instance of是Java的一个二元操作符，和==，&gt;，&lt;是同一类东东。由于它是由字母组成的，所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例，返回boolean类型的数据。举个例子：</font></p>
<p><font size="2">String s = "I AM an Object!";<br>boolean is Object = s instance of Object;</font></p>
<p><font size="2">我们声明了一个String对象引用，指向一个String对象，然后用instanc of来测试它所指向的对象是否是Object类的一个实例，显然，这是真的，所以返回true，也就是isObject的值为True。<br>instance of有一些用处。比如我们写了一个处理账单的系统，其中有这样三个类：</font></p>
<p><font size="2">public class Bill {//省略细节}<br>public class PhoneBill extends Bill {//省略细节}<br>public class GasBill extends Bill {//省略细节}</font></p>
<p><font size="2">在处理程序里有一个方法，接受一个Bill类型的对象，计算金额。假设两种账单计算方法不同，而传入的Bill对象可能是两种中的任何一种，所以要用instanceof来判断：</font></p>
<p><font size="2">public double calculate(Bill bill) {<br>if (bill instanceof PhoneBill) {<br>//计算电话账单<br>}<br>if (bill instanceof GasBill) {<br>//计算燃气账单<br>}<br>...<br>}<br>这样就可以用一个方法处理两种子类。</font></p>
<p><font size="2">然而，这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现，这是面向对象变成应有的做法，避免回到结构化编程模式。只要提供两个名字和返回值都相同，接受参数类型不同的方法就可以了：</font></p>
<p><font size="2">public double calculate(PhoneBill bill) {<br>//计算电话账单<br>}</font></p>
<p><font size="2">public double calculate(GasBill bill) {<br>//计算燃气账单<br>}</font></p>
<p><font size="2">所以，使用instanceof在绝大多数情况下并不是推荐的做法，应当好好利用多态。<br></font></p><img src ="http://www.blogjava.net/hellotony/aggbug/19607.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/hellotony/" target="_blank">Tony</a> 2005-11-13 21:29 <a href="http://www.blogjava.net/hellotony/articles/19607.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>