2008年7月30日
1、在一般情況下,actionForm是被存儲(chǔ)在一定的scope中(request或session,通過action的scope屬性來(lái)配置),當(dāng)我們?cè)谂渲脮r(shí),指定name而不指定attribute,那么指定的name值就作為actionForm存儲(chǔ)在scope中的key值,我們可以在action中通過httpServletRequest.getAttribute("指定的name屬性值")來(lái)獲得這個(gè)actionForm; 當(dāng)我們既配置了name又配置了attribute,那么actionForm存儲(chǔ)在scope中的key值就采用attribute屬性指定的值了,這時(shí)要通過httpServletRequest.getAttribute("指定的attribute屬性值")來(lái)獲得actionForm,此時(shí)通過httpServletRequest.getAttribute("指定的name屬性值")是不能獲得actionForm的。
所以,是否配置attribute屬性就決定了actionForm存儲(chǔ)在scope中的key值是采用name,還是采用attribute
2、 在《Programming Jakarta Struts》這本書中的第四章“Configuring the Struts Application”中這樣一段說(shuō)明來(lái)分別闡述這兩
個(gè)屬性:(102頁(yè))
++++++++
atribute:
++++++++
The name of the request or session scope attribute under which the form bean for this action can be accessed.
A value is only allowed here if there is a form bean specified in the name attribute. This attribute is
optional and has no default value.
++++++++
name:
++++++++
The name of the form bean, if any, that is associated with this action. This value must be the name attribute
from one of the form-bean elements defined earlier. This attribute is optional and has no default value.
最初看這些真的還是不好區(qū)分這兩者。不過在仔細(xì)看過struts的源代碼以后,豁然開朗。。。
下面主要對(duì)attribute進(jìn)行解釋,應(yīng)為沒有人會(huì)對(duì)name屬性不了解的(呵呵。。。)
解釋:在struts實(shí)例化actionform的時(shí)候,有兩種情況:如果已經(jīng)存在,那么從內(nèi)存中取回;如果第一次實(shí)例化,那么創(chuàng)建,并放入內(nèi)存。
這樣就有一個(gè)問題了,struts是根據(jù)什么來(lái)取回并創(chuàng)建actionform的呢,答案就是attribute的值。讓我們進(jìn)入struts的源代碼:
/**
*創(chuàng)建或者取回formbean方法
*該方法在:org.apache.struts.util.RequestUtils中
*/
public static Actionform createActionform(
HttpServletRequest request,
ActionMapping mapping,
ModuleConfig moduleConfig,
ActionServlet servlet) {
。。。。
。。。
// Is there a form bean associated with this mapping?
//得到action mapping中attribute的值
String attribute = mapping.getAttribute();
。。。。
。。。。
Actionform instance = null;
HttpSession session = null;
//yes!!就在這里了,把創(chuàng)建以后的actionform放在request或者session里,看到放入的名字了么,就是mapping.getAttribute();
if ("request".equals(mapping.getScope())) {
instance = (Actionform) request.getAttribute(attribute);
} else {
session = request.getSession();
instance = (Actionform) session.getAttribute(attribute);
}
。。。
。。。
}
下面又有一個(gè)問題浮出水面:如果我沒有在action mapping中指定attribute呢,那struts 是如何解決的?
答案很簡(jiǎn)單,如果單從結(jié)果上看,此時(shí)struts使用的name的值,為什么呢,看struts源代碼:
/**
* The request-scope or session-scope attribute name under which our
* form bean is accessed, if it is different from the form bean's
* specified <code>name</code>.
*該代碼在:org.apache.struts.config.ActionConfig中
*/
protected String attribute = null;
public String getAttribute() {
//yes!!!!就在這里,看到了吧,如果你沒有設(shè)定attribute,那么struts 會(huì)把name的值拿過來(lái)用。呵呵。。。
if (this.attribute == null) {
return (this.name);
} else {
return (this.attribute);
}
}
public void setAttribute(String attribute) {
if (configured) {
throw new IllegalStateException("Configuration is frozen");
}
this.attribute = attribute;
}
當(dāng)兩個(gè)Web組件之間為轉(zhuǎn)發(fā)關(guān)系時(shí),轉(zhuǎn)發(fā)源會(huì)將要共享 request范圍內(nèi)的數(shù)據(jù)先用setAttribute將數(shù)據(jù)放入到HttpServletRequest對(duì)象中,然后轉(zhuǎn)發(fā)目標(biāo)通過 getAttribute方法來(lái)取得要共享的數(shù)據(jù)。而MVC中用的就是Web組件之間的轉(zhuǎn)發(fā)啊!真是笨,怎么當(dāng)時(shí)沒有想到呢?
下面整理一下getParameter和getAttribute的區(qū)別和各自的使用范圍。
(1)HttpServletRequest類有setAttribute()方法,而沒有setParameter()方法
(2)當(dāng)兩個(gè)Web組件之間為鏈接關(guān)系時(shí),被鏈接的組件通過getParameter()方法來(lái)獲得請(qǐng)求參數(shù),例如假定welcome.jsp和authenticate.jsp之間為鏈接關(guān)系,welcome.jsp中有以下代碼:
<a href="authenticate.jsp?username=wolf">authenticate.jsp </a>
或者:
<form name="form1" method="post" action="authenticate.jsp">
請(qǐng)輸入用戶姓名:<input type="text" name="username">
<input type="submit" name="Submit" value="提交">
</form>
在authenticate.jsp中通過request.getParameter("username")方法來(lái)獲得請(qǐng)求參數(shù)username:
<% String username=request.getParameter("username"); %>
(3)當(dāng)兩個(gè)Web組件之間為轉(zhuǎn)發(fā)關(guān)系時(shí),轉(zhuǎn)發(fā)目標(biāo)組件通過getAttribute()方法來(lái)和轉(zhuǎn)發(fā)源組件共享request范圍內(nèi)的數(shù)據(jù)。
假定 authenticate.jsp和hello.jsp之間為轉(zhuǎn)發(fā)關(guān)系。authenticate.jsp希望向hello.jsp傳遞當(dāng)前的用戶名字, 如何傳遞這一數(shù)據(jù)呢?先在authenticate.jsp中調(diào)用setAttribute()方法:
<%
String username=request.getParameter("username");
request.setAttribute("username",username);
%>
<jsp:forward page="hello.jsp" />
在hello.jsp中通過getAttribute()方法獲得用戶名字:
<% String username=(String)request.getAttribute("username"); %>
Hello: <%=username %>
從更深的層次考慮,request.getParameter()方法傳遞的數(shù)據(jù),會(huì)從Web客戶端傳到Web服務(wù)器端,代表HTTP請(qǐng)求數(shù)據(jù)。request.getParameter()方法返回String類型的數(shù)據(jù)。
request.setAttribute()和getAttribute()方法傳遞的數(shù)據(jù)只會(huì)存在于Web容器內(nèi)部,在具有轉(zhuǎn)發(fā)關(guān)系的Web組件之間共享。這兩個(gè)方法能夠設(shè)置Object類型的共享數(shù)據(jù)。
request.getParameter()取得是通過容器的實(shí)現(xiàn)來(lái)取得通過類似post,get等方式傳入的數(shù)據(jù)。
request.setAttribute()和getAttribute()只是在web容器內(nèi)部流轉(zhuǎn),僅僅是請(qǐng)求處理階段。
getAttribute是返回對(duì)象,getParameter返回字符串
總的來(lái)說(shuō):request.getAttribute()方法返回request范圍內(nèi)存在的對(duì)象,而request.getParameter()方法是獲取http提交過來(lái)的數(shù)據(jù)。
<%@page
language="java"
contentType="text/html;charset=GBK"
%>
<html>
<head>
<title>MyHtml.html</title>
<meta http-equiv="content-type" content="text/html; charset=gbk">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
<script language="javascript">
function JM_PowerList(colNum)
{
headEventObject=event.srcElement;//取得引發(fā)事件的對(duì)象
while(headEventObject.tagName!="TR") //不是tr行,則從底下的td冒泡上來(lái)尋找到相應(yīng)行
{
headEventObject=headEventObject.parentElement;
}
for (i=0;i<headEventObject.children.length;i++)
{ alert(headEventObject.children[i].tagName);
if (headEventObject.children[i]!=event.srcElement)//找到事件發(fā)生的td單元格
{
headEventObject.children[i].className='listTableHead';//把點(diǎn)擊的列的className屬性設(shè)為listTableHead
}
}
var tableRows=0;
trObject=clearStart.children[0].children; //取得表格中行對(duì)象, 原來(lái)這里叫DataTable, 可能是你寫錯(cuò)了吧??
for (i=0;i<trObject.length;i++)
{
Object=clearStart.children[0].children[i];//取得每行的對(duì)象
tableRows=(trObject[i].id=='ignore')?tableRows:tableRows+1;//如果不是忽略行,則行數(shù)加一
}
var trinnerHTML=new Array(tableRows);
var tdinnerHTML=new Array(tableRows);
var tdNumber=new Array(tableRows)
var i0=0
var i1=0
for (i=0;i<trObject.length;i++)
{
if (trObject[i].id!='ignore')
{
trinnerHTML[i0]=trObject[i].innerHTML;//把行放在數(shù)組里
tdinnerHTML[i0]=trObject[i].children[colNum].innerHTML;//把要排序的行中td的內(nèi)容放數(shù)組里
tdNumber[i0]=i;//行號(hào)
i0++;//加一,下個(gè)循環(huán)用
}
}
sourceHTML=clearStart.children[0].outerHTML;//取得表格中所有tr的html代碼
//對(duì)所有td中的字符串進(jìn)行排序, 算不算冒泡排序???
for (bi=0;bi<tableRows;bi++)
{
for (i=0;i<tableRows;i++)
{
if(tdinnerHTML[i]>tdinnerHTML[i+1])
{
t_s=tdNumber[i+1];
t_b=tdNumber[i];
tdNumber[i+1]=t_b;
tdNumber[i]=t_s;
temp_small=tdinnerHTML[i+1];
temp_big=tdinnerHTML[i];
tdinnerHTML[i+1]=temp_big;
tdinnerHTML[i]=temp_small;
}
}
}
var showshow='';
var numshow='';
for (i=0;i<tableRows;i++)
{
showshow=showshow+tdinnerHTML[i]+' ';//把排序好的td的內(nèi)容存在showshow字串里
numshow=numshow+tdNumber[i]+'|'; //把排序好的相應(yīng)的行號(hào)也存在numshow中
}
sourceHTML_head=sourceHTML.split("<TBODY>");//從<TBODY>截?cái)?我試了,前頭串為空
numshow=numshow.split("|");
var trRebuildHTML='';
if (event.srcElement.className=='listHeadClicked')
{//已點(diǎn)擊的列, 則逆排
for (i=0;i<tableRows;i++)
{
trRebuildHTML=trRebuildHTML+trObject[numshow[tableRows-1-i]].outerHTML;//取出排序好的tr的內(nèi)容連接起來(lái)
}
event.srcElement.className='listHeadClicked0';
}
else
{//默認(rèn)順排,新點(diǎn)擊順排
for (i=0;i<tableRows;i++)
{
trRebuildHTML=trRebuildHTML+trObject[numshow[i]].outerHTML;
}
event.srcElement.className='listHeadClicked';
}
//取得排序后的tr集合結(jié)果字符串
var DataRebuildTable='';
//把舊的表格頭和新的tr排序好的元素連接起來(lái), (修改了一下)
DataRebuildTable = "<table border=1 width=100% cellpadding=1 cellspacing=1 id='clearStart'><TBODY>"
+ trObject[0].outerHTML + trRebuildHTML + "</TBODY>" + "</table>";
clearStart.outerHTML=DataRebuildTable;//表格用新串重新寫一次
}
</script>
</head>
<table border=1 id="clearStart">
<tr bgcolor=cccccc id='ignore'>
<td onclick="JM_PowerList(0)">列一
</td>
<td onclick="JM_PowerList(1)">
列二
</td>
<td onclick="JM_PowerList(2)">
列二
</td>
</tr>
<tr>
<td>
周
</td>
<td>
公務(wù)員
</td>
<td>
22
</td>
</tr>
<tr>
<td>
張三
</td>
<td>
研究員
</td>
<td>
65
</td>
</tr>
<tr>
<td>
李思
</td>
<td>
科學(xué)家
</td>
<td>
24
</td>
</tr>
<tr>
<td>
王武
</td>
<td>
社會(huì)學(xué)家
</td>
<td>
38
</td>
</tr>
</table>
</body></html>
在基于 Java 語(yǔ)言的編程中,我們經(jīng)常碰到漢字的處理及顯示的問題。一大堆看不懂的亂碼肯定不是我們?cè)敢饪吹降娘@示效果,怎樣才能夠讓那些漢字正確顯示呢?Java語(yǔ)言默認(rèn)的編碼方式是UNICODE,而我們中國(guó)人通常使用的文件和數(shù)據(jù)庫(kù)都是基于GB2312或者BIG5等方式編碼的,怎樣才能夠恰當(dāng)?shù)剡x擇漢字編碼方式并正確地處理漢字的編碼呢?本文將從漢字編碼的常識(shí)入手,結(jié)合Java編程實(shí)例,分析以上兩個(gè)問題并提出解決它們的方案。
現(xiàn)在 Java 編程語(yǔ)言已經(jīng)廣泛應(yīng)用于互聯(lián)網(wǎng)世界,早在 Sun 公司開發(fā) Java 語(yǔ)言的時(shí)候,就已經(jīng)考慮到對(duì)非英文字符的支持了。Sun 公司公布的 Java 運(yùn)行環(huán)境(JRE)本身就分英文版和國(guó)際版,但只有國(guó)際版才支持非英文字符。不過在 Java 編程語(yǔ)言的應(yīng)用中,對(duì)中文字符的支持并非如同 Java Soft 的標(biāo)準(zhǔn)規(guī)范中所宣稱的那樣完美,因?yàn)橹形淖址恢灰粋€(gè),而且不同的操作系統(tǒng)對(duì)中文字符的支持也不盡相同,所以會(huì)有許多和漢字編碼處理有關(guān)的問題在我們進(jìn)行應(yīng)用開發(fā)中困擾著我們。有很多關(guān)于這些問題的解答,但都比較瑣碎,并不能夠滿足大家迫切解決問題的愿望,關(guān)于 Java 中文問題的系統(tǒng)研究并不多,本文從漢字編碼常識(shí)出發(fā),分析 Java 中文問題,希望對(duì)大家解決這個(gè)問題有所幫助。
漢字編碼的常識(shí)
我們知道,英文字符一般是以一個(gè)字節(jié)來(lái)表示的,最常用的編碼方法是 ASCII 。但一個(gè)字節(jié)最多只能區(qū)分256個(gè)字符,而漢字成千上萬(wàn),所以現(xiàn)在都以雙字節(jié)來(lái)表示漢字,為了能夠與英文字符分開,每個(gè)字節(jié)的最高位一定為1,這樣雙字節(jié)最多可以表示64K格字符。我們經(jīng)常碰到的編碼方式有 GB2312、BIG5、UNICODE 等。關(guān)于具體編碼方式的詳細(xì)資料,有興趣的讀者可以查閱相關(guān)資料。我膚淺談一下和我們關(guān)系密切的 GB2312 和 UNICODE。GB2312 碼,中華人民共和國(guó)國(guó)家標(biāo)準(zhǔn)漢字信息交換用編碼,是一個(gè)由中華人民共和國(guó)國(guó)家標(biāo)準(zhǔn)總局發(fā)布的關(guān)于簡(jiǎn)化漢字的編碼,通行于中國(guó)大陸地區(qū)及新加坡,簡(jiǎn)稱國(guó)標(biāo)碼。兩個(gè)字節(jié)中,第一個(gè)字節(jié)(高字節(jié))的值為區(qū)號(hào)值加32(20H),第二個(gè)字節(jié)(低字節(jié))的值為位號(hào)值加32(20H),用這兩個(gè)值來(lái)表示一個(gè)漢字的編碼。UNICODE 碼是微軟提出的解決多國(guó)字符問題的多字節(jié)等長(zhǎng)編碼,它對(duì)英文字符采取前面加“0”字節(jié)的策略實(shí)現(xiàn)等長(zhǎng)兼容。如 “A” 的 ASCII 碼為0x41,UNICODE 就為0x00,0x41。利用特殊的工具各種編碼之間可以互相轉(zhuǎn)換。
Java 中文問題的初步認(rèn)識(shí)
我們基于 Java 編程語(yǔ)言進(jìn)行應(yīng)用開發(fā)時(shí),不可避免地要處理中文。Java 編程語(yǔ)言默認(rèn)的編碼方式是 UNICODE,而我們通常使用的數(shù)據(jù)庫(kù)及文件都是基于 GB2312 編碼的,我們經(jīng)常碰到這樣的情況:瀏覽基于 JSP 技術(shù)的網(wǎng)站看到的是亂碼,文件打開后看到的也是亂碼,被 Java 修改過的數(shù)據(jù)庫(kù)的內(nèi)容在別的場(chǎng)合應(yīng)用時(shí)無(wú)法繼續(xù)正確地提供信息。
String sEnglish = “apple”;
String sChinese = “蘋果”;
String s = “蘋果 apple ”;
sEnglish 的長(zhǎng)度是5,sChinese的長(zhǎng)度是4,而 s 默認(rèn)的長(zhǎng)度是14。對(duì)于 sEnglish來(lái)說(shuō), Java 中的各個(gè)類都支持得非常好,肯定能夠正確顯示。但對(duì)于 sChinese 和 s 來(lái)說(shuō),雖然 Java Soft 聲明 Java 的基本類已經(jīng)考慮到對(duì)多國(guó)字符的支持(默認(rèn) UNICODE 編碼),但是如果操作系統(tǒng)的默認(rèn)編碼不是 UNICODE ,而是國(guó)標(biāo)碼等。從 Java 源代碼到得到正確的結(jié)果,要經(jīng)過 “Java 源代碼-> Java 字節(jié)碼-> ;虛擬機(jī)->操作系統(tǒng)->顯示設(shè)備”的過程。在上述過程中的每一步驟,我們都必須正確地處理漢字的編碼,才能夠使最終的顯示結(jié)果正確。
“ Java 源代碼-> Java 字節(jié)碼”,標(biāo)準(zhǔn)的 Java 編譯器 javac 使用的字符集是系統(tǒng)默認(rèn)的字符集,比如在中文 Windows 操作系統(tǒng)上就是 GBK ,而在 Linux 操作系統(tǒng)上就是ISO-8859-1,所以大家會(huì)發(fā)現(xiàn)在 Linux 操作系統(tǒng)上編譯的類中源文件中的中文字符都出了問題,解決的辦法就是在編譯的時(shí)候添加 encoding 參數(shù),這樣才能夠與平臺(tái)無(wú)關(guān)。用法是
javac -encoding GBK。
“ Java 字節(jié)碼->虛擬機(jī)->操作系統(tǒng)”, Java 運(yùn)行環(huán)境 (JRE)分英文版和國(guó)際版,但只有國(guó)際版才支持非英文字符。 Java 開發(fā)工具包 (JDK) 肯定支持多國(guó)字符,但并非所有的計(jì)算機(jī)用戶都安裝了 JDK 。很多操作系統(tǒng)及應(yīng)用軟件為了能夠更好的支持 Java ,都內(nèi)嵌了 JRE 的國(guó)際版本,為自己支持多國(guó)字符提供了方便。
“操作系統(tǒng)->顯示設(shè)備”,對(duì)于漢字來(lái)說(shuō),操作系統(tǒng)必須支持并能夠顯示它。英文操作系統(tǒng)如果不搭配特殊的應(yīng)用軟件的話,是肯定不能夠顯示中文的。
還有一個(gè)問題,就是在 Java 編程過程中,對(duì)中文字符進(jìn)行正確的編碼轉(zhuǎn)換。例如,向網(wǎng)頁(yè)輸出中文字符串的時(shí)候,不論你是用
out.println(string);還是用
<%=string%>,都必須作 UNICODE 到 GBK 的轉(zhuǎn)換,或者手動(dòng),或者自動(dòng)。在 JSP 1.0中,可以定義輸出字符集,從而實(shí)現(xiàn)內(nèi)碼的自動(dòng)轉(zhuǎn)換。用法是
<%@page contentType=”text/html;charset=gb2312” %>
但是在一些 JSP 版本中并沒有提供對(duì)輸出字符集的支持,(例如 JSP 0.92),這就需要手動(dòng)編碼輸出了,方法非常多。最常用的方法是
String s1 = request.getParameter(“keyword”);
String s2 = new String(s1.getBytes(“ISO-8859-1”),”GBK”);
getBytes 方法用于將中文字符以“ISO-8859-1”編碼方式轉(zhuǎn)化成字節(jié)數(shù)組,而“GBK” 是目標(biāo)編碼方式。我們從以ISO-8859-1方式編碼的數(shù)據(jù)庫(kù)中讀出中文字符串 s1 ,經(jīng)過上述轉(zhuǎn)換過程,在支持 GBK 字符集的操作系統(tǒng)和應(yīng)用軟件中就能夠正確顯示中文字符串 s2 。
Java 中文問題的表層分析及處理
背景
開發(fā)環(huán)境 JDK1.15 Vcafe2.0 JPadPro
服務(wù)器端 NT IIS Sybase System Jconnect(JDBC)
客戶端 IE5.0 Pwin98 ?span >
.CLASS 文件存放在服務(wù)器端,由客戶端的瀏覽器運(yùn)行 APPLET , APPLET 只起調(diào)入 FRAME 類等主程序的作用。界面包括 Textfield ,TextArea,List,Choice 等。
I.用 JDBC 執(zhí)行 SELECT 語(yǔ)句從服務(wù)器端讀取數(shù)據(jù)(中文)后,將數(shù)據(jù)用 APPEND 方法加到 TextArea(TA) ,不能正確顯示。但加到 List 中時(shí),大部分漢字卻可正確顯示。
將數(shù)據(jù)按“ISO-8859-1” 編碼方式轉(zhuǎn)化為字節(jié)數(shù)組,再按系統(tǒng)缺省編碼方式 (Default Character Encoding) 轉(zhuǎn)化為 STRING ,即可在 TA 和 List 中正確顯示。
程序段如下:
dbstr2 = results.getString(1);
//After reading the result from DB server,converting it to string.
dbbyte1 = dbstr2.getBytes(“iso-8859-1”);
dbstr1 = new String(dbbyte1);
在轉(zhuǎn)換字符串時(shí)不采用系統(tǒng)默認(rèn)編碼方式,而直接采用“ GBK” 或者 “GB2312” ,在 A 和 B 兩種情況下,從數(shù)據(jù)庫(kù)取數(shù)據(jù)都沒有問題。
II.處理方式與“取中文”相逆,先將 SQL 語(yǔ)句按系統(tǒng)缺省編碼方式轉(zhuǎn)化為字節(jié)數(shù)組,再按“ISO-8859-1”編碼方式轉(zhuǎn)化為 STRING ,最后送去執(zhí)行,則中文信息可正確寫入數(shù)據(jù)庫(kù)。
程序段如下:
sqlstmt = tf_input.getText();
//Before sending statement to DB server,converting it to sql statement.
dbbyte1 = sqlstmt.getBytes();
sqlstmt = newString(dbbyte1,”iso-8859-1”);
_stmt = _con.createStatement();
_stmt.executeUpdate(sqlstmt);
……
問題:如果客戶機(jī)上存在 CLASSPATH 指向 JDK 的 CLASSES.ZIP 時(shí)(稱為 A 情況),上述程序代碼可正確執(zhí)行。但是如果客戶機(jī)只有瀏覽器,而沒有 JDK 和 CLASSPATH 時(shí)(稱為 B 情況),則漢字無(wú)法正確轉(zhuǎn)換。
我們的分析:
1.經(jīng)過測(cè)試,在 A 情況下,程序運(yùn)行時(shí)系統(tǒng)的缺省編碼方式為 GBK 或者 GB2312 。在 B 情況下,程序啟動(dòng)時(shí)瀏覽器的 JAVA 控制臺(tái)中出現(xiàn)如下錯(cuò)誤信息:
Can't find resource for sun.awt.windows.awtLocalization_zh_CN
然后系統(tǒng)的缺省編碼方式為“8859-1”。
2.如果在轉(zhuǎn)換字符串時(shí)不采用系統(tǒng)缺省編碼方式,而是直接采用 “GBK” 或“GB2312”,則在 A 情況下程序仍然可正常運(yùn)行,在 B 情況下,系統(tǒng)出現(xiàn)錯(cuò)誤:
UnsupportedEncodingException。
3. 在客戶機(jī)上,把 JDK 的 CLASSES.ZIP 解壓后,放在另一個(gè)目錄中, CLASSPATH 只包含該目錄。然后一邊逐步刪除該目錄中的 .CLASS 文件,另一邊運(yùn)行測(cè)試程序,最后發(fā)現(xiàn)在一千多個(gè) CLASS 文件中,只有一個(gè)是必不可少的,該文件是:
sun.io.CharToByteDoubleByte.class。
將該文件拷到服務(wù)器端和其它的類放在一起,并在程序的開頭 IMPORT 它,在 B 情況下程序仍然無(wú)法正常運(yùn)行。
4.在 A 情況下,如果在 CLASSPTH 中去掉 sun.io.CharToByteDoubleByte.class ,則程序運(yùn)行時(shí)測(cè)得默認(rèn)編碼方式為“8859-1”,否則為 “GBK” 或 “GB2312” 。
如果 JDK 的版本為1.2以上的話,在 B 情況下遇到的問題得到了很好的解決,測(cè)試的步驟同上,有興趣的讀者可以嘗試一下。
Java 中文問題的根源分析及解決
在簡(jiǎn)體中文 MS Windows 98 + JDK 1.3 下,可以用 System.getProperties() 得到 Java 運(yùn)行環(huán)境的一些基本屬性,類 PoorChinese 可以幫助我們得到這些屬性。
類 PoorChinese 的源代碼:
public class PoorChinese {
}
執(zhí)行 java PoorChinese 后,我們會(huì)得到:
系統(tǒng)變量 file.encoding 的值為 GBK ,user.language 的值為 zh , user.region 的值為 CN ,這些系統(tǒng)變量的值決定了系統(tǒng)默認(rèn)的編碼方式是 GBK 。
在上述系統(tǒng)中,下面的代碼將 GB2312 文件轉(zhuǎn)換成 Big5 文件,它們能夠幫助我們理解 Java 中漢字編碼的轉(zhuǎn)化:
import java.io.*;
import java.util.*;
public class gb2big5 {
static int iCharNum=0;
public static void main(String[] args) {
System.out.println("Input GB2312 file, output Big5 file.");
if (args.length!=2) {
System.err.println("Usage: jview gb2big5 gbfile big5file");
System.exit(1);
String inputString = readInput(args[0]);
writeOutput(inputString,args[1]);
System.out.println("Number of Characters in file: "+iCharNum+".");
}
static void writeOutput(String str, String strOutFile) {
try {
FileOutputStream fos = new FileOutputStream(strOutFile);
Writer out = new OutputStreamWriter(fos, "Big5");
out.write(str);
out.close();
}
catch (IOException e) {
e.printStackTrace();
e.printStackTrace();
}
}
static String readInput(String strInFile) {
StringBuffer buffer = new StringBuffer();
try {
FileInputStream fis = new FileInputStream(strInFile);
InputStreamReader isr = new InputStreamReader(fis, "GB2312");
Reader in = new BufferedReader(isr);
int ch;
while ((ch = in.read()) > -1) {
iCharNum += 1;
buffer.append((char)ch);
}
in.close();
return buffer.toString();
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
編碼轉(zhuǎn)化的過程如下:
GB2312------------------>Unicode------------->Big5
執(zhí)行 java gb2big5 gb.txt big5.txt ,如果 gb.txt 的內(nèi)容是“今天星期三”,則得到的文件 big5.txt 中的字符能夠正確顯示;而如果 gb.txt 的內(nèi)容是“情人節(jié)快樂”,則得到的文件 big5.txt 中對(duì)應(yīng)于“節(jié)”和“樂”的字符都是符號(hào)“?”(0x3F),可見 sun.io.ByteToCharGB2312 和 sun.io.CharToByteBig5 這兩個(gè)基本類并沒有編好。
正如上例一樣, Java 的基本類也可能存在問題。由于國(guó)際化的工作并不是在國(guó)內(nèi)完成的,所以在這些基本類發(fā)布之前,沒有經(jīng)過嚴(yán)格的測(cè)試,所以對(duì)中文字符的支持并不像 Java Soft 所聲稱的那樣完美。前不久,我的一位技術(shù)上的朋友發(fā)信給我說(shuō),他終于找到了 Java Servlet 中文問題的根源。兩周以來(lái),他一直為 Java Servlet 的中文問題所困擾,因?yàn)槊棵鎸?duì)一個(gè)含有中文字符的字符串都必須進(jìn)行強(qiáng)制轉(zhuǎn)換才能夠得到正確的結(jié)果(這好象是大家公認(rèn)的唯一的解決辦法)。后來(lái),他確實(shí)不想如此繼續(xù)安分下去了,因?yàn)檫@樣的事情確實(shí)不應(yīng)該是高級(jí)程序員所要做的工作,他就找出 Servlet 解碼的源代碼進(jìn)行分析,因?yàn)樗麘岩蓡栴}就出在解碼這部分。經(jīng)過四個(gè)小時(shí)的奮斗,他終于找到了問題的根源所在。原來(lái)他的懷疑是正確的, Servlet 的解碼部分完全沒有考慮雙字節(jié),直接把 %XX 當(dāng)作一個(gè)字符。(原來(lái) Java Soft 也會(huì)犯這幺低級(jí)的錯(cuò)誤!)
如果你對(duì)這個(gè)問題有興趣或者遇到了同樣的煩惱的話,你可以按照他的步驟 對(duì)Servlet.jar 進(jìn)行修改:
找到源代碼 HttpUtils 中的 static private String parseName ,在返回前將 sb(StringBuffer) 復(fù)制成 byte bs[] ,然后 return new String(bs,”GB2312”)。作上述修改后就需要自己解碼了:
HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者
form=HttpUtils.parsePostData(……)
千萬(wàn)別忘了編譯后放到 Servlet.jar 里面。
關(guān)于 Java 中文問題的總結(jié)
Java 編程語(yǔ)言成長(zhǎng)于網(wǎng)絡(luò)世界,這就要求 Java 對(duì)多國(guó)字符有很好的支持。 Java 編程語(yǔ)言適應(yīng)了計(jì)算的網(wǎng)絡(luò)化的需求,為它能夠在網(wǎng)絡(luò)世界迅速成長(zhǎng)奠定了堅(jiān)實(shí)的基礎(chǔ)。 Java 的締造者 (Java Soft) 已經(jīng)考慮到 Java 編程語(yǔ)言對(duì)多國(guó)字符的支持,只是現(xiàn)在的解決方案有很多缺陷在里面,需要我們付諸一些補(bǔ)償性的措施。而世界標(biāo)準(zhǔn)化組織也在努力把人類所有的文字統(tǒng)一在一種編碼之中,其中一種方案是 ISO10646 ,它用四個(gè)字節(jié)來(lái)表示一個(gè)字符。當(dāng)然,在這種方案未被采用之前,還是希望 Java Soft 能夠嚴(yán)格地測(cè)試它的產(chǎn)品,為用戶帶來(lái)更多的方便。