<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    江蘇520

    江蘇520

    常用鏈接

    統(tǒng)計(jì)

    最新評(píng)論

    2009年5月14日 #

    字符,字節(jié)和編碼三者的關(guān)系

    1. 編碼問題的由來,相關(guān)概念的理解
    1.1 字符與編碼的發(fā)展
    從計(jì)算機(jī)對多國語言的支持角度看,大致可以分為三個(gè)階段:

      系統(tǒng)內(nèi)碼 說明 系統(tǒng)
    階段一 ASCII 計(jì)算機(jī)剛開始只支持英語,其它語言不能夠在計(jì)算機(jī)上存儲(chǔ)和顯示。 英文 DOS
    階段二 ANSI編碼
    (本地化) 為使計(jì)算機(jī)支持更多語言,通常使用 0x80~0xFF 范圍的 2 個(gè)字節(jié)來表示 1 個(gè)字符。比如:漢字 '中' 在中文操作系統(tǒng)中,使用 [0xD6,0xD0] 這兩個(gè)字節(jié)存儲(chǔ)。

    不同的國家和地區(qū)制定了不同的標(biāo)準(zhǔn),由此產(chǎn)生了 GB2312, BIG5, JIS 等各自的編碼標(biāo)準(zhǔn)。這些使用 2 個(gè)字節(jié)來代表一個(gè)字符的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文系統(tǒng)下,ANSI 編碼代表 GB2312 編碼,在日文操作系統(tǒng)下,ANSI 編碼代表 JIS 編碼。

    不同 ANSI 編碼之間互不兼容,當(dāng)信息在國際間交流時(shí),無法將屬于兩種語言的文字,存儲(chǔ)在同一段 ANSI 編碼的文本中。 中文 DOS,中文 Windows 95/98,日文 Windows 95/98
    階段三 UNICODE
    (國際化) 為了使國際間信息交流更加方便,國際組織制定了 UNICODE 字符集,為各種語言中的每一個(gè)字符設(shè)定了統(tǒng)一并且唯一的數(shù)字編號(hào),以滿足跨語言、跨平臺(tái)進(jìn)行文本轉(zhuǎn)換、處理的要求。 Windows NT/2000/XP,Linux,Java

    字符串在內(nèi)存中的存放方法:

    在 ASCII 階段,單字節(jié)字符串使用一個(gè)字節(jié)存放一個(gè)字符(SBCS)。比如,"Bob123" 在內(nèi)存中為:

    42 6F 62 31 32 33 00
          
    B o b 1 2 3 \0

    在使用 ANSI 編碼支持多種語言階段,每個(gè)字符使用一個(gè)字節(jié)或多個(gè)字節(jié)來表示(MBCS),因此,這種方式存放的字符也被稱作多字節(jié)字符。比如,"中文123" 在中文 Windows 95 內(nèi)存中為7個(gè)字節(jié),每個(gè)漢字占2個(gè)字節(jié),每個(gè)英文和數(shù)字字符占1個(gè)字節(jié):

    D6 D0 CE C4 31 32 33 00
         
    中 文 1 2 3 \0

    在 UNICODE 被采用之后,計(jì)算機(jī)存放字符串時(shí),改為存放每個(gè)字符在 UNICODE 字符集中的序號(hào)。目前計(jì)算機(jī)一般使用 2 個(gè)字節(jié)(16 位)來存放一個(gè)序號(hào)(DBCS),因此,這種方式存放的字符也被稱作寬字節(jié)字符。比如,字符串 "中文123" 在 Windows 2000 下,內(nèi)存中實(shí)際存放的是 5 個(gè)序號(hào):

    2D 4E 87 65 31 00 32 00 33 00 00 00      ← 在 x86 CPU 中,低字節(jié)在前
          
    中 文 1 2 3 \0  

    一共占 10 個(gè)字節(jié)。

     
     回頁首
     
     
     

    1.2 字符,字節(jié),字符串
    理解編碼的關(guān)鍵,是要把字符的概念和字節(jié)的概念理解準(zhǔn)確。這兩個(gè)概念容易混淆,我們在此做一下區(qū)分:

      概念描述 舉例
    字符 人們使用的記號(hào),抽象意義上的一個(gè)符號(hào)。 '1', '中', 'a', '$', '¥', ……
    字節(jié) 計(jì)算機(jī)中存儲(chǔ)數(shù)據(jù)的單元,一個(gè)8位的二進(jìn)制數(shù),是一個(gè)很具體的存儲(chǔ)空間。 0x01, 0x45, 0xFA, ……
    ANSI
    字符串 在內(nèi)存中,如果“字符”是以 ANSI 編碼形式存在的,一個(gè)字符可能使用一個(gè)字節(jié)或多個(gè)字節(jié)來表示,那么我們稱這種字符串為 ANSI 字符串或者多字節(jié)字符串。 "中文123"
    (占7字節(jié))
    UNICODE
    字符串 在內(nèi)存中,如果“字符”是以在 UNICODE 中的序號(hào)存在的,那么我們稱這種字符串為 UNICODE 字符串或者寬字節(jié)字符串。 L"中文123"
    (占10字節(jié))

    由于不同 ANSI 編碼所規(guī)定的標(biāo)準(zhǔn)是不相同的,因此,對于一個(gè)給定的多字節(jié)字符串,我們必須知道它采用的是哪一種編碼規(guī)則,才能夠知道它包含了哪些“字符”。而對于 UNICODE 字符串來說,不管在什么環(huán)境下,它所代表的“字符”內(nèi)容總是不變的。

     
     回頁首
     
     
     

    1.3 字符集與編碼
    各個(gè)國家和地區(qū)所制定的不同 ANSI 編碼標(biāo)準(zhǔn)中,都只規(guī)定了各自語言所需的“字符”。比如:漢字標(biāo)準(zhǔn)(GB2312)中沒有規(guī)定韓國語字符怎樣存儲(chǔ)。這些 ANSI 編碼標(biāo)準(zhǔn)所規(guī)定的內(nèi)容包含兩層含義:

    使用哪些字符。也就是說哪些漢字,字母和符號(hào)會(huì)被收入標(biāo)準(zhǔn)中。所包含“字符”的集合就叫做“字符集”。
    規(guī)定每個(gè)“字符”分別用一個(gè)字節(jié)還是多個(gè)字節(jié)存儲(chǔ),用哪些字節(jié)來存儲(chǔ),這個(gè)規(guī)定就叫做“編碼”。
    各個(gè)國家和地區(qū)在制定編碼標(biāo)準(zhǔn)的時(shí)候,“字符的集合”和“編碼”一般都是同時(shí)制定的。因此,平常我們所說的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”這層含義外,同時(shí)也包含了“編碼”的含義。

    “UNICODE 字符集”包含了各種語言中使用到的所有“字符”。用來給 UNICODE 字符集編碼的標(biāo)準(zhǔn)有很多種,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。

      

    1.4 常用的編碼簡介
    簡單介紹一下常用的編碼規(guī)則,為后邊的章節(jié)做一個(gè)準(zhǔn)備。在這里,我們根據(jù)編碼規(guī)則的特點(diǎn),把所有的編碼分成三類:

    分類 編碼標(biāo)準(zhǔn) 說明
    單字節(jié)字符編碼 ISO-8859-1 最簡單的編碼規(guī)則,每一個(gè)字節(jié)直接作為一個(gè) UNICODE 字符。比如,[0xD6, 0xD0] 這兩個(gè)字節(jié),通過 iso-8859-1 轉(zhuǎn)化為字符串時(shí),將直接得到 [0x00D6, 0x00D0] 兩個(gè) UNICODE 字符,即 "ÖÐ"。

    反之,將 UNICODE 字符串通過 iso-8859-1 轉(zhuǎn)化為字節(jié)串時(shí),只能正常轉(zhuǎn)化 0~255 范圍的字符。
    ANSI 編碼 GB2312,
    BIG5,
    Shift_JIS,
    ISO-8859-2 …… 把 UNICODE 字符串通過 ANSI 編碼轉(zhuǎn)化為“字節(jié)串”時(shí),根據(jù)各自編碼的規(guī)定,一個(gè) UNICODE 字符可能轉(zhuǎn)化成一個(gè)字節(jié)或多個(gè)字節(jié)。

    反之,將字節(jié)串轉(zhuǎn)化成字符串時(shí),也可能多個(gè)字節(jié)轉(zhuǎn)化成一個(gè)字符。比如,[0xD6, 0xD0] 這兩個(gè)字節(jié),通過 GB2312 轉(zhuǎn)化為字符串時(shí),將得到 [0x4E2D] 一個(gè)字符,即 '中' 字。

    “ANSI 編碼”的特點(diǎn):
    1. 這些“ANSI 編碼標(biāo)準(zhǔn)”都只能處理各自語言范圍之內(nèi)的 UNICODE 字符。
    2. “UNICODE 字符”與“轉(zhuǎn)換出來的字節(jié)”之間的關(guān)系是人為規(guī)定的。
    UNICODE 編碼 UTF-8,
    UTF-16, UnicodeBig …… 與“ANSI 編碼”類似的,把字符串通過 UNICODE 編碼轉(zhuǎn)化成“字節(jié)串”時(shí),一個(gè) UNICODE 字符可能轉(zhuǎn)化成一個(gè)字節(jié)或多個(gè)字節(jié)。

    與“ANSI 編碼”不同的是:
    1. 這些“UNICODE 編碼”能夠處理所有的 UNICODE 字符。
    2. “UNICODE 字符”與“轉(zhuǎn)換出來的字節(jié)”之間是可以通過計(jì)算得到的。

    我們實(shí)際上沒有必要去深究每一種編碼具體把某一個(gè)字符編碼成了哪幾個(gè)字節(jié),我們只需要知道“編碼”的概念就是把“字符”轉(zhuǎn)化成“字節(jié)”就可以了。對于“UNICODE 編碼”,由于它們是可以通過計(jì)算得到的,因此,在特殊的場合,我們可以去了解某一種“UNICODE 編碼”是怎樣的規(guī)則。

     

    2. 字符與編碼在程序中的實(shí)現(xiàn)
    2.1 程序中的字符與字節(jié)
    在 C++ 和 Java 中,用來代表“字符”和“字節(jié)”的數(shù)據(jù)類型,以及進(jìn)行編碼的方法:

    類型或操作 C++ Java
    字符 wchar_t char
    字節(jié) char byte
    ANSI 字符串 char[] byte[]
    UNICODE 字符串 wchar_t[] String
    字節(jié)串→字符串 mbstowcs(), MultiByteToWideChar() string = new String(bytes, "encoding")
    字符串→字節(jié)串 wcstombs(), WideCharToMultiByte() bytes = string.getBytes("encoding")

    以上需要注意幾點(diǎn):

    Java 中的 char 代表一個(gè)“UNICODE 字符(寬字節(jié)字符)”,而 C++ 中的 char 代表一個(gè)字節(jié)。
    MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函數(shù)。 

      

    2.2 C++ 中相關(guān)實(shí)現(xiàn)方法
    聲明一段字符串常量:

    // ANSI 字符串,內(nèi)容長度 7 字節(jié)
    char     sz[20] = "中文123";

    // UNICODE 字符串,內(nèi)容長度 5 個(gè) wchar_t(10 字節(jié))
    wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033";

    UNICODE 字符串的 I/O 操作,字符與字節(jié)的轉(zhuǎn)換操作:

    // 運(yùn)行時(shí)設(shè)定當(dāng)前 ANSI 編碼,VC 格式
    setlocale(LC_ALL, ".936");

    // GCC 中格式
    setlocale(LC_ALL, "zh_CN.GBK");

    // Visual C++ 中使用小寫 %s,按照 setlocale 指定編碼輸出到文件
    // GCC 中使用大寫 %S
    fwprintf(fp, L"%s\n", wsz);

    // 把 UNICODE 字符串按照 setlocale 指定的編碼轉(zhuǎn)換成字節(jié)
    wcstombs(sz, wsz, 20);
    // 把字節(jié)串按照 setlocale 指定的編碼轉(zhuǎn)換成 UNICODE 字符串
    mbstowcs(wsz, sz, 20);

    在 Visual C++ 中,UNICODE 字符串常量有更簡單的表示方法。如果源程序的編碼與當(dāng)前默認(rèn) ANSI 編碼不符,則需要使用 #pragma setlocale,告訴編譯器源程序使用的編碼:

    // 如果源程序的編碼與當(dāng)前默認(rèn) ANSI 編碼不一致,
    // 則需要此行,編譯時(shí)用來指明當(dāng)前源程序使用的編碼
    #pragma setlocale(".936")

    // UNICODE 字符串常量,內(nèi)容長度 10 字節(jié)
    wchar_t wsz[20] = L"中文123";

    以上需要注意 #pragma setlocale 與 setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在編譯時(shí)起作用,setlocale() 在運(yùn)行時(shí)起作用。

     
      

    2.3 Java 中相關(guān)實(shí)現(xiàn)方法
    字符串類 String 中的內(nèi)容是 UNICODE 字符串:

    // Java 代碼,直接寫中文
    String string = "中文123";//come from http://www.bt285.cn  http://www.5a520.cn

    // 得到長度為 5,因?yàn)槭?5 個(gè)字符
    System.out.println(string.length());

    字符串 I/O 操作,字符與字節(jié)轉(zhuǎn)換操作。在 Java 包 java.io.* 中,以“Stream”結(jié)尾的類一般是用來操作“字節(jié)串”的類,以“Reader”,“Writer”結(jié)尾的類一般是用來操作“字符串”的類。

    // 字符串與字節(jié)串間相互轉(zhuǎn)化

    // 按照 GB2312 得到字節(jié)(得到多字節(jié)字符串)
    byte [] bytes = string.getBytes("GB2312");

    // 從字節(jié)按照 GB2312 得到 UNICODE 字符串
    string = new String(bytes, "GB2312");

    // 要將 String 按照某種編碼寫入文本文件,有兩種方法:

    // 第一種辦法:用 Stream 類寫入已經(jīng)按照指定編碼轉(zhuǎn)化好的字節(jié)串
    OutputStream os = new FileOutputStream("1.txt");
    os.write(bytes);
    os.close();

    // 第二種辦法:構(gòu)造指定編碼的 Writer 來寫入字符串
    Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312");
    ow.write(string);
    ow.close();

    /* 最后得到的 1.txt 和 2.txt 都是 7 個(gè)字節(jié) */

    如果 java 的源程序編碼與當(dāng)前默認(rèn) ANSI 編碼不符,則在編譯的時(shí)候,需要指明一下源程序的編碼。比如:

    E:\>javac -encoding BIG5 Hello.java

    以上需要注意區(qū)分源程序的編碼與 I/O 操作的編碼,前者是在編譯時(shí)起作用,后者是在運(yùn)行時(shí)起作用。

     

     

    3. 幾種誤解,以及亂碼產(chǎn)生的原因和解決辦法
    3.1 容易產(chǎn)生的誤解
      對編碼的誤解
    誤解一 在將“字節(jié)串”轉(zhuǎn)化成“UNICODE 字符串”時(shí),比如在讀取文本文件時(shí),或者通過網(wǎng)絡(luò)傳輸文本時(shí),容易將“字節(jié)串”簡單地作為單字節(jié)字符串,采用每“一個(gè)字節(jié)”就是“一個(gè)字符”的方法進(jìn)行轉(zhuǎn)化。

    而實(shí)際上,在非英文的環(huán)境中,應(yīng)該將“字節(jié)串”作為 ANSI 字符串,采用適當(dāng)?shù)木幋a來得到 UNICODE 字符串,有可能“多個(gè)字節(jié)”才能得到“一個(gè)字符”。

    通常,一直在英文環(huán)境下做開發(fā)的程序員們,容易有這種誤解。
    誤解二 在 DOS,Windows 98 等非 UNICODE 環(huán)境下,字符串都是以 ANSI 編碼的字節(jié)形式存在的。這種以字節(jié)形式存在的字符串,必須知道是哪種編碼才能被正確地使用。這使我們形成了一個(gè)慣性思維:“字符串的編碼”。

    當(dāng) UNICODE 被支持后,Java 中的 String 是以字符的“序號(hào)”來存儲(chǔ)的,不是以“某種編碼的字節(jié)”來存儲(chǔ)的,因此已經(jīng)不存在“字符串的編碼”這個(gè)概念了。只有在“字符串”與“字節(jié)串”轉(zhuǎn)化時(shí),或者,將一個(gè)“字節(jié)串”當(dāng)成一個(gè) ANSI 字符串時(shí),才有編碼的概念。

    不少的人都有這個(gè)誤解。

    第一種誤解,往往是導(dǎo)致亂碼產(chǎn)生的原因。第二種誤解,往往導(dǎo)致本來容易糾正的亂碼問題變得更復(fù)雜。

    在這里,我們可以看到,其中所講的“誤解一”,即采用每“一個(gè)字節(jié)”就是“一個(gè)字符”的轉(zhuǎn)化方法,實(shí)際上也就等同于采用 iso-8859-1 進(jìn)行轉(zhuǎn)化。因此,我們常常使用 bytes = string.getBytes("iso-8859-1") 來進(jìn)行逆向操作,得到原始的“字節(jié)串”。然后再使用正確的 ANSI 編碼,比如 string = new String(bytes, "GB2312"),來得到正確的“UNICODE 字符串”。

     

    3.2 非 UNICODE 程序在不同語言環(huán)境間移植時(shí)的亂碼
    非 UNICODE 程序中的字符串,都是以某種 ANSI 編碼形式存在的。如果程序運(yùn)行時(shí)的語言環(huán)境與開發(fā)時(shí)的語言環(huán)境不同,將會(huì)導(dǎo)致 ANSI 字符串的顯示失敗。

    比如,在日文環(huán)境下開發(fā)的非 UNICODE 的日文程序界面,拿到中文環(huán)境下運(yùn)行時(shí),界面上將顯示亂碼。如果這個(gè)日文程序界面改為采用 UNICODE 來記錄字符串,那么當(dāng)在中文環(huán)境下運(yùn)行時(shí),界面上將可以顯示正常的日文。

    由于客觀原因,有時(shí)候我們必須在中文操作系統(tǒng)下運(yùn)行非 UNICODE 的日文軟件,這時(shí)我們可以采用一些工具,比如,南極星,AppLocale 等,暫時(shí)的模擬不同的語言環(huán)境。

     

    3.3 網(wǎng)頁提交字符串
    當(dāng)頁面中的表單提交字符串時(shí),首先把字符串按照當(dāng)前頁面的編碼,轉(zhuǎn)化成字節(jié)串。然后再將每個(gè)字節(jié)轉(zhuǎn)化成 "%XX" 的格式提交到 Web 服務(wù)器。比如,一個(gè)編碼為 GB2312 的頁面,提交 "中" 這個(gè)字符串時(shí),提交給服務(wù)器的內(nèi)容為 "%D6%D0"。

    在服務(wù)器端,Web 服務(wù)器把收到的 "%D6%D0" 轉(zhuǎn)化成 [0xD6, 0xD0] 兩個(gè)字節(jié),然后再根據(jù) GB2312 編碼規(guī)則得到 "中" 字。

    在 Tomcat 服務(wù)器中,request.getParameter() 得到亂碼時(shí),常常是因?yàn)榍懊嫣岬降?#8220;誤解一”造成的。默認(rèn)情況下,當(dāng)提交 "%D6%D0" 給 Tomcat 服務(wù)器時(shí),request.getParameter() 將返回 [0x00D6, 0x00D0] 兩個(gè) UNICODE 字符,而不是返回一個(gè) "中" 字符。因此,我們需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字節(jié)串,再用 string = new String(bytes, "GB2312") 重新得到正確的字符串 "中"。


     3.4 從數(shù)據(jù)庫讀取字符串
    通過數(shù)據(jù)庫客戶端(比如 ODBC 或 JDBC)從數(shù)據(jù)庫服務(wù)器中讀取字符串時(shí),客戶端需要從服務(wù)器獲知所使用的 ANSI 編碼。當(dāng)數(shù)據(jù)庫服務(wù)器發(fā)送字節(jié)流給客戶端時(shí),客戶端負(fù)責(zé)將字節(jié)流按照正確的編碼轉(zhuǎn)化成 UNICODE 字符串。

    如果從數(shù)據(jù)庫讀取字符串時(shí)得到亂碼,而數(shù)據(jù)庫中存放的數(shù)據(jù)又是正確的,那么往往還是因?yàn)榍懊嫣岬降?#8220;誤解一”造成的。解決的辦法還是通過 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字節(jié)串,再重新使用正確的編碼轉(zhuǎn)化成字符串。

     

     

    3.5 電子郵件中的字符串
    當(dāng)一段 Text 或者 HTML 通過電子郵件傳送時(shí),發(fā)送的內(nèi)容首先通過一種指定的字符編碼轉(zhuǎn)化成“字節(jié)串”,然后再把“字節(jié)串”通過一種指定的傳輸編碼(Content-Transfer-Encoding)進(jìn)行轉(zhuǎn)化得到另一串“字節(jié)串”。比如,打開一封電子郵件源代碼,可以看到類似的內(nèi)容:

    Content-Type: text/plain;
            charset="gb2312"
    Content-Transfer-Encoding: base64

    sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==

    最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 兩種。在對二進(jìn)制文件或者中文文本進(jìn)行轉(zhuǎn)化時(shí),Base64 得到的“字節(jié)串”比 Quoted-Printable 更短。在對英文文本進(jìn)行轉(zhuǎn)化時(shí),Quoted-Printable 得到的“字節(jié)串”比 Base64 更短。

    郵件的標(biāo)題,用了一種更簡短的格式來標(biāo)注“字符編碼”和“傳輸編碼”。比如,標(biāo)題內(nèi)容為 "中",則在郵件源代碼中表示為:

    // 正確的標(biāo)題格式
    Subject: =?GB2312?B?1tA=?=

    其中,

    第一個(gè)“=?”與“?”中間的部分指定了字符編碼,在這個(gè)例子中指定的是 GB2312。
    “?”與“?”中間的“B”代表 Base64。如果是“Q”則代表 Quoted-Printable。
    最后“?”與“?=”之間的部分,就是經(jīng)過 GB2312 轉(zhuǎn)化成字節(jié)串,再經(jīng)過 Base64 轉(zhuǎn)化后的標(biāo)題內(nèi)容。
    如果“傳輸編碼”改為 Quoted-Printable,同樣,如果標(biāo)題內(nèi)容為 "中":

    // 正確的標(biāo)題格式
    Subject: =?GB2312?Q?=D6=D0?=

    如果閱讀郵件時(shí)出現(xiàn)亂碼,一般是因?yàn)?#8220;字符編碼”或“傳輸編碼”指定有誤,或者是沒有指定。比如,有的發(fā)郵件組件在發(fā)送郵件時(shí),標(biāo)題 "中":

    // 錯(cuò)誤的標(biāo)題格式
    Subject: =?ISO-8859-1?Q?=D6=D0?=

    這樣的表示,實(shí)際上是明確指明了標(biāo)題為 [0x00D6, 0x00D0],即 "ÖÐ",而不是 "中"。


     
     

    4. 幾種錯(cuò)誤理解的糾正
    誤解:“ISO-8859-1 是國際編碼?”
    非也。iso-8859-1 只是單字節(jié)字符集中最簡單的一種,也就是“字節(jié)編號(hào)”與“UNICODE 字符編號(hào)”一致的那種編碼規(guī)則。當(dāng)我們要把一個(gè)“字節(jié)串”轉(zhuǎn)化成“字符串”,而又不知道它是哪一種 ANSI 編碼時(shí),先暫時(shí)地把“每一個(gè)字節(jié)”作為“一個(gè)字符”進(jìn)行轉(zhuǎn)化,不會(huì)造成信息丟失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢復(fù)到原始的字節(jié)串。

    誤解:“Java 中,怎樣知道某個(gè)字符串的內(nèi)碼?”
    Java 中,字符串類 java.lang.String 處理的是 UNICODE 字符串,不是 ANSI 字符串。我們只需要把字符串作為“抽象的符號(hào)的串”來看待。因此不存在字符串的內(nèi)碼的問題。

     

     

    posted @ 2009-06-03 20:19 江蘇520| 編輯 收藏

    java異常處理筆記

         摘要:          對于一個(gè)非常熟悉 C++ 異常處理模型的程序員來說,它幾乎可以不經(jīng)任何其它培訓(xùn)和學(xué)習(xí),就可以完全接受和能夠輕松地使用 Java 語言中的異常處理編程方法。這是因?yàn)?Java 語言中的異常處理模型幾乎與 C++ 中異常處理模型有 99% 的相似度,無論是從語法規(guī)則,還是語義上來說,它們二者都幾乎完全一致...  閱讀全文

    posted @ 2009-06-02 21:39 江蘇520| 編輯 收藏

    java多線程問題及處理(筆記)

    1.死鎖
             多線程編程在實(shí)際的網(wǎng)絡(luò)程序開發(fā)中,在客戶端程序?qū)崿F(xiàn)中使用的比較簡單,但是在服務(wù)器端程序?qū)崿F(xiàn)中卻不僅是大量使用,而且會(huì)出現(xiàn)比客戶端更多的問題。

             另外一個(gè)容易在服務(wù)器端出現(xiàn)的多線程問題是——死鎖。死鎖指兩個(gè)或兩個(gè)以上的線程為了使用某個(gè)臨界資源而無限制的等待下去。還是以前面衛(wèi)生間的例子來說明死鎖,例如兩個(gè)人都同時(shí)到達(dá)衛(wèi)生間,而且兩個(gè)人都比較禮貌,第一個(gè)人和第二個(gè)人說:你先吧,第二個(gè)人和第一個(gè)人說:你先吧。這兩個(gè)人就這樣一直在互相禮讓,誰也不進(jìn)入,這種現(xiàn)象就是死鎖。這里的兩個(gè)人就好比是線程,而衛(wèi)生間在這里就是臨界資源,而由于這兩個(gè)線程在一直謙讓,誰也不使用臨界資源。

             死鎖不僅使程序無法達(dá)到預(yù)期實(shí)現(xiàn)的功能,而且浪費(fèi)系統(tǒng)的資源,所以在服務(wù)器端程序中危害比較大,在實(shí)際的服務(wù)器端程序開發(fā)中,需要注意避免死鎖。

             而死鎖的檢測比較麻煩,而且不一定每次都出現(xiàn),這就需要在測試服務(wù)器端程序時(shí),有足夠的耐心,仔細(xì)觀察程序執(zhí)行時(shí)的性能檢測,如果發(fā)現(xiàn)執(zhí)行的性能顯著降低,則很可能是發(fā)生了死鎖,然后再具體的查找死鎖出現(xiàn)的原因,并解決死鎖的問題。

             死鎖出現(xiàn)的最本質(zhì)原因還是邏輯處理不夠嚴(yán)謹(jǐn),在考慮時(shí)不是很周全,所以一般需要修改程序邏輯才能夠很好的解決死鎖。

    2. 線程優(yōu)先級(jí)
             在日常生活中,例如火車售票窗口等經(jīng)常可以看到“XXX優(yōu)先”,那么多線程編程中每個(gè)線程是否也可以設(shè)置優(yōu)先級(jí)呢?

             在多線程編程中,支持為每個(gè)線程設(shè)置優(yōu)先級(jí)。優(yōu)先級(jí)高的線程在排隊(duì)執(zhí)行時(shí)會(huì)獲得更多的CPU執(zhí)行時(shí)間,得到更快的響應(yīng)。在實(shí)際程序中,可以根據(jù)邏輯的需要,將需要得到及時(shí)處理的線程設(shè)置成較高的優(yōu)先級(jí),而把對時(shí)間要求不高的線程設(shè)置成比較低的優(yōu)先級(jí)。

             在Thread類中,總計(jì)規(guī)定了三個(gè)優(yōu)先級(jí),分別為:

    l  MAX_PRIORITY——最高優(yōu)先級(jí)

    l  NORM_PRIORITY——普通優(yōu)先級(jí),也是默認(rèn)優(yōu)先級(jí)

    l  MIN_PRIORITY——最低優(yōu)先級(jí)

    在前面創(chuàng)建的線程對象中,由于沒有設(shè)置線程的優(yōu)先級(jí),則線程默認(rèn)的優(yōu)先級(jí)是NORM_PRIORITY,在實(shí)際使用時(shí),也可以根據(jù)需要使用Thread類中的setPriority方法設(shè)置線程的優(yōu)先級(jí),該方法的聲明為:

             public final void setPriority(int newPriority)

    假設(shè)t是一個(gè)初始化過的線程對象,需要設(shè)置t的優(yōu)先級(jí)為最高,則實(shí)現(xiàn)的代碼為:

             t. setPriority(Thread. MAX_PRIORITY);

    這樣,在該線程執(zhí)行時(shí)將獲得更多的執(zhí)行機(jī)會(huì),也就是優(yōu)先執(zhí)行。如果由于安全等原因,不允許設(shè)置線程的優(yōu)先級(jí),則會(huì)拋出SecurityException異常。

    下面使用一個(gè)簡單的輸出數(shù)字的線程演示線程優(yōu)先級(jí)的使用,實(shí)現(xiàn)的示例代碼如下:

             package priority;

    /**

     * 測試線程優(yōu)先級(jí)

    * author by http://www.bt285.cn http://www.5a520.cn

     */

    public class TestPriority {

             public static void main(String[] args) {

                       PrintNumberThread p1 = new PrintNumberThread("高優(yōu)先級(jí)");

                       PrintNumberThread p2 = new PrintNumberThread("普通優(yōu)先級(jí)");

                       PrintNumberThread p3 = new PrintNumberThread("低優(yōu)先級(jí)");

                       p1.setPriority(Thread.MAX_PRIORITY);

                       p2.setPriority(Thread.NORM_PRIORITY);

                       p3.setPriority(Thread.MIN_PRIORITY);

                       p1.start();

                       p2.start();

                       p3.start();

             }

    }

    package priority;

    /**

     * 輸出數(shù)字的線程

     */

    public class PrintNumberThread extends Thread {

             String name;

             public PrintNumberThread(String name){

                       this.name = name;

             }

             public void run(){

                       try{

                                for(int i = 0;i < 10;i++){

                                         System.out.println(name + ":" + i);

                                }

                       }catch(Exception e){}

             }

    }

    程序的一種執(zhí)行結(jié)果為:

    高優(yōu)先級(jí):0

    高優(yōu)先級(jí):1

    高優(yōu)先級(jí):2

    普通優(yōu)先級(jí):0

    高優(yōu)先級(jí):3

    普通優(yōu)先級(jí):1

    高優(yōu)先級(jí):4

    普通優(yōu)先級(jí):2

    高優(yōu)先級(jí):5

    高優(yōu)先級(jí):6

    高優(yōu)先級(jí):7

    高優(yōu)先級(jí):8

    高優(yōu)先級(jí):9

    普通優(yōu)先級(jí):3

    普通優(yōu)先級(jí):4

    普通優(yōu)先級(jí):5

    普通優(yōu)先級(jí):6

    普通優(yōu)先級(jí):7

    普通優(yōu)先級(jí):8

    普通優(yōu)先級(jí):9

    低優(yōu)先級(jí):0

    低優(yōu)先級(jí):1

    低優(yōu)先級(jí):2

    低優(yōu)先級(jí):3

    低優(yōu)先級(jí):4

    低優(yōu)先級(jí):5

    低優(yōu)先級(jí):6

    低優(yōu)先級(jí):7

    低優(yōu)先級(jí):8

    低優(yōu)先級(jí):9

             在該示例程序,PrintNumberThread線程實(shí)現(xiàn)的功能是輸出數(shù)字,每次數(shù)字輸出之間沒有設(shè)置時(shí)間延遲,在測試類TestPriority中創(chuàng)建三個(gè)PrintNumberThread類型的線程對象,然后分別設(shè)置線程優(yōu)先級(jí)是最高、普通和最低,接著啟動(dòng)線程執(zhí)行程序。從執(zhí)行結(jié)果可以看出高優(yōu)先級(jí)的線程獲得了更多的執(zhí)行時(shí)間,首先執(zhí)行完成,而低優(yōu)先級(jí)的線程由于優(yōu)先級(jí)較低,所以最后一個(gè)執(zhí)行結(jié)束。

             其實(shí),對于線程優(yōu)先級(jí)的管理主要由系統(tǒng)的線程調(diào)度實(shí)現(xiàn),較高優(yōu)先級(jí)的線程優(yōu)先執(zhí)行,所以可以通過設(shè)置線程的優(yōu)先級(jí)影響線程的執(zhí)行。

    5 總結(jié)
             關(guān)于多線程的基礎(chǔ)知識(shí)就介紹這么多,在本章中介紹了線程的概念、線程的實(shí)現(xiàn)方式以及使用多線程時(shí)會(huì)遇到的問題以及解決辦法,而需要建立多線程的概念,也就是并發(fā)編程的概念還需要進(jìn)行比較多的練習(xí),理解多線程的概念并熟悉多線程的編程。

             而關(guān)于多線程編程的高級(jí)知識(shí),如線程組等則可以在熟悉了線程的基本概念以后再進(jìn)行更加深入的學(xué)習(xí)。

     

     

    posted @ 2009-05-31 20:53 江蘇520| 編輯 收藏

    從JAR與zip檔案文件中提取Java 資源

     多數(shù) java 程序員都非常清楚使用 jar 文件將組成 java 解決方案的各種資源(即 .class 文件、聲音和圖像)打包的優(yōu)點(diǎn)。剛開始使用 jar 文件的人常問的一個(gè)問題是:“如何從 jar 文件中提取圖像呢?”本文將回答這個(gè)問題,并會(huì)提供一個(gè)類,這個(gè)類使從 jar 文件中提取任何資源變得非常簡單!
    加載 gif 圖像
       
    假定我們有一個(gè) jar 文件,其中包含我們的應(yīng)用程序要使用的一組 .gif 圖像。下面就是使用 JarResources 訪問 jar 文件中的圖像文件的方法:
        JarResources JR=new JarResources(" http://www.bt285.cn /GifBundle.jar");


        Image logo=Toolkit.getDefaultToolkit().createImage(JR.getResources("logo.gif"));

        
    這段代碼說明我們可以創(chuàng)建一個(gè)JarResources對象,并將其初始化為包含我們要使用的資源的 jar 文件 -- images.jar。隨后我們使用JarResourcesgetResource()方法將來自 logo.gif 文件的原始數(shù)據(jù)提供給 awt Toolkit createImage()方法。
    命名說明
    JarResource
    是一個(gè)非常簡單的示例,它說明了如何使用 java 所提供的各種功能來處理 jar zip 檔案文件。

     
    工作方式

    JarReources類的重要數(shù)據(jù)域用來跟蹤和存儲(chǔ)指定 jar 文件的內(nèi)容:
    public final class JarResources {

       public boolean debugon=false;

       private Hashtable htsizes=new Hashtable();
       private Hashtable htjarcontents=new Hashtable();

       private String jarfilename;

    這樣,該類的實(shí)例化設(shè)置 jar 文件的名稱,然后轉(zhuǎn)到init()方法完成全部實(shí)際工作。
       public JarResources(String jarfilename) {
          this.jarfilename=jarfilename;
          init();
       }

    現(xiàn)在,init()方法只將指定 jar 文件的整個(gè)內(nèi)容加載到一個(gè) hashtable(通過資源名訪問)中。

         
    這是一個(gè)相當(dāng)有用的方法,下面我們對它作進(jìn)一步的分析。ZipFile類為我們提供了對 jar/zip 檔案頭信息的基本訪問方法。這類似于文件系統(tǒng)中的目錄信息。下面我們列出ZipFile中的所有條目,并用檔案中每個(gè)資源的大小添充 htsizes hashtable
     private void init() {
          try {
              // extracts just sizes only. 
              ZipFile zf=new ZipFile(jarFileName);
              Enumeration e=zf.entries();
              while (e.hasMoreElements()) {
                  ZipEntry ze=(ZipEntry)e.nextElement();
                  if (debugOn) {
                     System.out.println(dumpZipEntry(ze));
                  }
                  htSizes.put(ze.getName(),new Integer((int)ze.getSize()));
              }
              zf.close();
    接下來,我們使用ZipInputStream類訪問檔案。ZipInputStream類完成了全部魔術(shù),允許我們單獨(dú)讀取檔案中的每個(gè)資源。我們從檔案中讀取組成每個(gè)資源的精確字節(jié)數(shù),并將其存儲(chǔ)在 htjarcontents hashtable 中,您可以通過資源名訪問這些數(shù)據(jù):     
              // extract resources and put them into the hashtable.
              FileInputStream fis=new FileInputStream(jarFileName);
              BufferedInputStream bis=new BufferedInputStream(fis);
              ZipInputStream zis=new ZipInputStream(bis);
              ZipEntry ze=null;
              while ((ze=zis.getNextEntry())!=null) {
                 if (ze.isDirectory()) {
                    continue;////啊喲!沒有處理子目錄中的資源啊 http://www.5a520.cn  小說520網(wǎng)
                 }
                 if (debugOn) {
                    System.out.println(
                       "ze.getName()="+ze.getName()+","+"getSize()="+ze.getSize()
                       );
                 }
                 int size=(int)ze.getSize();
                 // -1 means unknown size. 
                 if (size==-1) {
                    size=((Integer)htSizes.get(ze.getName())).intValue();
                 }
                 byte[] b=new byte[(int)size];
                 int rb=0;
                 int chunk=0;
                while (((int)size - rb) > 0) {
                     chunk=zis.read(b,rb,(int)size - rb);
                     if (chunk==-1) {
                        break;
                     }
                     rb+=chunk;
                 }
                 // add to internal resource hashtable
                 htJarContents.put(ze.getName(),b);
                 if (debugOn) {
                    System.out.println(
                       ze.getName()+" rb="+rb+
                       ",size="+size+
                       ",csize="+ze.getCompressedSize()
                       );
                 }
              }
           } catch (NullPointerException e) {
              System.out.println("done.");
           } catch (FileNotFoundException e) {
              e.printStackTrace();
           } catch (IOException e) {
              e.printStackTrace();
           }
       }
    請注意,用來標(biāo)識(shí)每個(gè)資源的名稱是檔案中資源的限定路徑名,例如,不是包中的類名 -- java.util.zip 包中的ZipEntry類將被命名為 "java/util/zip/ZipEntry",而不是 "java.util.zip.ZipEntry"。

    其它方法:
        /**
        * Dumps a zip entry into a string.
        * @param ze a ZipEntry
        */
       private String dumpZipEntry(ZipEntry ze) {
           StringBuffer sb=new StringBuffer();
           if (ze.isDirectory()) {
              sb.append("d "); 
           } else {
              sb.append("f "); 
           }
           if (ze.getMethod()==ZipEntry.STORED) {
              sb.append("stored   "); 
           } else {
              sb.append("defalted ");
           }
           sb.append(ze.getName());
           sb.append("\t");
           sb.append(""+ze.getSize());
           if (ze.getMethod()==ZipEntry.DEFLATED) {
              sb.append("/"+ze.getCompressedSize());
           }
           return (sb.toString());
       }

        /**
        * Extracts a jar resource as a blob.
        * @param name a resource name.
        */
       public byte[] getResource(String name) {
          return (byte[])htJarContents.get(name);
       }
          
    代碼的最后一個(gè)重要部分是簡單的測試驅(qū)動(dòng)程序。該測試驅(qū)動(dòng)程序是一個(gè)簡單的應(yīng)用程序,它接收 jar/zip 檔案名和資源名。它試圖發(fā)現(xiàn)檔案中的資源文件,然后將成功或失敗的消息報(bào)告出來:
    public static void main(String[] args) throws IOException {
           if (args.length!=2) {
              System.err.println(
                 "usage: java JarResources < jar file name> < resource name>"
                 );
              System.exit(1);
           }
           JarResources jr=new JarResources(args[0]);
           byte[] buff=jr.getResource(args[1]);
           if (buff==null) {
              System.out.println("Could not find "+args[1]+".");
           } else {
              System.out.println("Found "+args[1]+ " (length="+buff.length+").");
           }
       }
    }              // End of JarResources class.
    您已了解了這個(gè)類。一個(gè)易于使用的類,它隱藏了使用打包在 jar 文件中的資源的全部棘手問題。
    小結(jié)
    如果您曾經(jīng)渴望知道如何從 jar 文件中提取圖像,那么您現(xiàn)在已學(xué)到了一種方法。有了本技巧提供的這個(gè)新類,您就不僅可以用 jar 文件處理圖像,而且可以將提取魔術(shù)用于 jar 文件中的任何資源。 

    posted @ 2009-05-29 19:56 江蘇520| 編輯 收藏

    JSP頁面查詢顯示常用模式

    背景
    1.    需要將數(shù)據(jù)庫查詢結(jié)果在JSP中以列表方式顯示
    2.    在一個(gè)良好的J2EE模式中數(shù)據(jù)庫查詢一般用DAO實(shí)現(xiàn)(Data Access Object), JSP僅用于顯示數(shù)據(jù)

    問題
        通過JDBC ResultSet可獲取查詢結(jié)果(存在于數(shù)據(jù)庫緩沖區(qū)內(nèi)),但在Statement、Connection關(guān)閉后ResultSet即不可用。因此需要一種方式取出所有查詢結(jié)果并傳遞至JSP頁面。

    解決方法一
        使用Value Object。將每條記錄均封裝成JavaBean對象,如:http://www.bt285.cn 把這些對象裝入Collection傳送給JSP顯示。這種方法的缺點(diǎn)是每一種查詢都需要定義一個(gè)java class,并且將記錄數(shù)據(jù)封裝成java對象時(shí)也需要很多額外的代碼。
    示例代碼:

    1. //查詢數(shù)據(jù)代碼
    2.   Connection conn = DBUtil.getConnection();
    3.   PreparedStatement pst = null;
    4.   ResultSet rs = null;
    5.   try{
    6.     String sql=“select emp_code, real_name from t_employee where organ_id=?”;
    7.     pst = conn.preparedStatement(sql);
    8.     pst.setString(1, “101”);
    9.     ResultSet rs = pst.executeQuery();
    10.     List list = new ArrayList();
    11.     Employee emp;
    12.     while (rs.next()){
    13.       emp = new Employee();
    14.       emp.setReakName(rs.getString(“real_name”));
    15.       emp.setEmpCode(rs.getString(“emp_code”));
    16.       …
    17.       list.add(emp);
    18.     }
    19.     return list;
    20.   }finally{
    21.     DBUtil.close(rs, pst ,conn);
    22.   }
    23. //jsp顯示部分代碼
    24. <%
    25.   List empList = (List)request.getAttribute(“empList”);
    26.   if (empList == null) empList = Collections.EMPTY_LIST;
    27. %>
    28. <table  cellspacing="0" width=”90%”>
    29.     <tr>  <td> http://www.5a520.cn   小說520網(wǎng) 代碼</td> <td>姓名</td>  </tr>
    30. <%
    31.   Employee emp;
    32.   for (int i=0; i< empList.size(); i++){
    33.     emp = (Employee) empList.get(i);
    34. %>
    35.     <tr>  
    36.       <td><%= emp.getEmpCode()%></td> 
    37.       <td><%= emp.getRealName()%></td>  
    38.     </tr>
    39. <%
    40.   }// end for
    41. %>
    42. </table>


    解決方法二
        遍歷ResultSet取出所有數(shù)據(jù)封裝進(jìn)Collection。
    具體做法:
    1.    生成一個(gè)List對象(List list = new ArrayList() )。
    2.    生成一個(gè)Map對象(Map map = new HashMap() )。使用Map封裝一行數(shù)據(jù),key為各字段名,value為對應(yīng)的值。(map.put(“USER_NAME”), rs.getString(“USER_NAME”))
    3.    將第2 步生成的Map對象裝入第1步的list對象中(list.add(map) )。
    4.    重復(fù)2、3步直到ResultSet遍歷完畢
    在DBUtil. resultSetToList(ResultSet rs)方法中實(shí)現(xiàn)了上述過程(所有列名均使用大寫),可參考使用。

    示例代碼

    1. //查詢數(shù)據(jù)部分代碼:
    2.   …
    3.   Connection conn = DBUtil.getConnection();
    4.   PreparedStatement pst = null;
    5.   ResultSet rs = null;
    6.   try{
    7.     String sql=“select emp_code, real_name from t_employee where organ_id=?”;
    8.     pst = conn.preparedStatement(sql);
    9.     pst.setString(1, “101”);
    10.     rs = pst.executeQuery();
    11.     List list = DBUtil. resultSetToList(ResultSet rs);
    12.     return list;
    13.   }finally{
    14.     DBUtil.close(rs, pst ,conn);
    15.   }
    16. //JSP顯示部分代碼
    17. <%
    18.   List empList = (List)request.getAttribute(“empList”);
    19.   if (empList == null) empList = Collections.EMPTY_LIST;
    20. %>
    21. <table  cellspacing="0" width=”90%”>
    22.     <tr>  <td> http://www.feng123.com 蜂蜜 代碼</td> <td>姓名</td>  </tr>
    23. <%
    24.   Map colMap;
    25.   for (int i=0; i< empList.size(); i++){
    26.     colMap = (Map) empList.get(i);
    27. %>
    28.   <tr>  
    29.     <td><%=colMap.get(“EMP_CODE”)%></td> 
    30.     <td><%=colMap.get(“REAL_NAME”)%></td>  
    31.   </tr>
    32. <%
    33.   }// end for
    34. %>
    35. </table>


    解決方法三
        使用RowSet。
    RowSet是JDBC2.0中提供的接口,Oracle對該接口有相應(yīng)實(shí)現(xiàn),其中很有用的是oracle.jdbc.rowset.OracleCachedRowSet。 OracleCachedRowSet實(shí)現(xiàn)了ResultSet中的所有方法,但與ResultSet不同的是,OracleCachedRowSet中的數(shù)據(jù)在Connection關(guān)閉后仍然有效。

    oracle的rowset實(shí)現(xiàn)在
    http://otn.oracle.com/software/content.html的jdbc下載里有,名稱是ocrs12.zip

    示例代碼

    1. //查詢數(shù)據(jù)部分代碼:
    2.   import javax.sql.RowSet;
    3.   import oracle.jdbc.rowset.OracleCachedRowSet;
    4.   …
    5.   Connection conn = DBUtil.getConnection();
    6.   PreparedStatement pst = null;
    7.   ResultSet rs = null;
    8.   try{……
    9.     String sql=“select emp_code, real_name from t_employee where organ_id=?”;
    10.     pst = conn.preparedStatement(sql);
    11.     pst.setString(1, “101”);
    12.     rs = pst.executeQuery();
    13.     OracleCachedRowSet ors = newOracleCachedRowSet();
    14.     //將ResultSet中的數(shù)據(jù)封裝到RowSet中
    15.     ors.populate(rs);
    16.     return ors;
    17.   }finally{
    18.     DBUtil.close(rs, pst, conn);
    19.   }
    20. //JSP顯示部分代碼
    21. <%
    22.   javax.sql.RowSet empRS = (javax.sql.RowSet) request.getAttribute(“empRS”);
    23. %>
    24. <table  cellspacing="0" width=”90%”>
    25.     <tr>  <td>代碼</td> <td>姓名</td>  </tr>
    26. <%
    27.   if (empRS != nullwhile (empRS.next() ) {
    28. %>
    29.   <tr>  
    30.     <td><%= empRS.get(“EMP_CODE”)%></td> 
    31.     <td><%= empRS.get(“REAL_NAME”)%></td>  
    32.   </tr>
    33. <%
    34.   }// end while
    35. %>
    36. </table>


    適用場合
      方法一使用于定制的查詢操作
      方法二適用于多條查詢語句或需要對查詢結(jié)果進(jìn)行處理的情況。
      方法三適合于單條查詢語句,適用于快速開發(fā)。

    posted @ 2009-05-27 21:36 江蘇520| 編輯 收藏

    java字符集筆記

    概述 

    本文主要包括以下幾個(gè)方面:編碼基本知識(shí),java,系統(tǒng)軟件,url,工具軟件等。 

    在下面的描述中,將以"中文"兩個(gè)字為例,經(jīng)查表可以知道其GB2312編碼是"d6d0 cec4",Unicode編碼為"4e2d 6587",UTF編碼就是"e4b8ad e69687"。注意,這兩個(gè)字沒有iso8859-1編碼,但可以用iso8859-1編碼來"表示"。 

    2. 編碼基本知識(shí) 

    最早的編碼是iso8859-1,和ascii編碼相似。但為了方便表示各種各樣的語言,逐漸出現(xiàn)了很多標(biāo)準(zhǔn)編碼,重要的有如下幾個(gè)。 

    2.1. iso8859-1 

    屬于單字節(jié)編碼,最多能表示的字符范圍是0-255,應(yīng)用于英文系列。比如,字母'a'的編碼為0x61=97。 

    很明顯,iso8859-1編碼表示的字符范圍很窄,無法表示中文字符。但是,由于是單字節(jié)編碼,和計(jì)算機(jī)最基礎(chǔ)的表示單位一致,所以很多時(shí)候,仍舊使用iso8859-1編碼來表示。而且在很多協(xié)議上,默認(rèn)使用該編碼。比如,雖然"中文"兩個(gè)字不存在iso8859-1編碼,以gb2312編碼為例,應(yīng)該是"d6d0 cec4"兩個(gè)字符,使用iso8859-1編碼的時(shí)候則將它拆開為4個(gè)字節(jié)來表示:"d6 d0 ce c4"(事實(shí)上,在進(jìn)行存儲(chǔ)的時(shí)候,也是以字節(jié)為單位處理的)。而如果是UTF編碼,則是6個(gè)字節(jié)"e4 b8 ad e6 96 87"。很明顯,這種表示方法還需要以另一種編碼為基礎(chǔ)。 

    2.2. GB2312/GBK 

    這就是漢子的國標(biāo)碼,專門用來表示漢字,是雙字節(jié)編碼,而英文字母和iso8859-1一致(兼容iso8859-1編碼)。其中g(shù)bk編碼能夠用來同時(shí)表示繁體字和簡體字,而gb2312只能表示簡體字,gbk是兼容gb2312編碼的。 

    2.3. unicode 

    這是最統(tǒng)一的編碼,可以用來表示所有語言的字符,而且是定長雙字節(jié)(也有四字節(jié)的)編碼,包括英文字母在內(nèi)。所以可以說它是不兼容iso8859-1編碼的,也不兼容任何編碼。不過,相對于iso8859-1編碼來說,uniocode編碼只是在前面增加了一個(gè)0字節(jié),比如字母'a'為"00 61"。 

    需要說明的是,定長編碼便于計(jì)算機(jī)處理(注意GB2312/GBK不是定長編碼),而unicode又可以用來表示所有字符,所以在很多軟件內(nèi)部是使用unicode編碼來處理的,比如java。 

    2.4. UTF 

    考慮到unicode編碼不兼容iso8859-1編碼,而且容易占用更多的空間:因?yàn)閷τ谟⑽淖帜福瑄nicode也需要兩個(gè)字節(jié)來表示。所以u(píng)nicode不便于傳輸和存儲(chǔ)。因此而產(chǎn)生了utf編碼,utf編碼兼容iso8859-1編碼,同時(shí)也可以用來表示所有語言的字符,不過,utf編碼是不定長編碼,每一個(gè)字符的長度從1-6個(gè)字節(jié)不等。另外,utf編碼自帶簡單的校驗(yàn)功能。一般來講,英文字母都是用一個(gè)字節(jié)表示,而漢字使用三個(gè)字節(jié)。 

    注意,雖然說utf是為了使用更少的空間而使用的,但那只是相對于unicode編碼來說,如果已經(jīng)知道是漢字,則使用GB2312/GBK無疑是最節(jié)省的。不過另一方面,值得說明的是,雖然utf編碼對漢字使用3個(gè)字節(jié),但即使對于漢字網(wǎng)頁,utf編碼也會(huì)比unicode編碼節(jié)省,因?yàn)榫W(wǎng)頁中包含了很多的英文字符。 

    3. java對字符的處理 

    在java應(yīng)用軟件中,會(huì)有多處涉及到字符集編碼,有些地方需要進(jìn)行正確的設(shè)置,有些地方需要進(jìn)行一定程度的處理。 

    3.1. getBytes(charset) 

    這是java字符串處理的一個(gè)標(biāo)準(zhǔn)函數(shù),其作用是將字符串所表示的字符按照charset編碼,并以字節(jié)方式表示。注意字符串在java內(nèi)存中總是按unicode編碼存儲(chǔ)的。比如"中文",正常情況下(即沒有錯(cuò)誤的時(shí)候)存儲(chǔ)為"4e2d 6587",如果charset為"gbk",則被編碼為"d6d0 cec4",然后返回字節(jié)"d6 d0 ce c4"。如果charset為"utf8"則最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",則由于無法編碼,最后返回 "3f 3f"(兩個(gè)問號(hào))。 

    3.2. new String(charset) 

    這是java字符串處理的另一個(gè)標(biāo)準(zhǔn)函數(shù),和上一個(gè)函數(shù)的作用相反,將字節(jié)數(shù)組按照charset編碼進(jìn)行組合識(shí)別,最后轉(zhuǎn)換為unicode存儲(chǔ)。參考上述getBytes的例子,"gbk" 和"utf8"都可以得出正確的結(jié)果"4e2d 6587",但iso8859-1最后變成了"003f 003f"(兩個(gè)問號(hào))。 

    因?yàn)閡tf8可以用來表示/編碼所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。 

    3.3. setCharacterEncoding() 

    該函數(shù)用來設(shè)置http請求或者相應(yīng)的編碼。 

    對于request,是指提交內(nèi)容的編碼,指定后可以通過getParameter()則直接獲得正確的字符串,如果不指定,則默認(rèn)使用iso8859-1編碼,需要進(jìn)一步處理。參見下述"表單輸入"。值得注意的是在執(zhí)行setCharacterEncoding()之前,不能執(zhí)行任何getParameter()。java doc上說明:This method must be called prior to reading request parameters or reading input using getReader()。而且,該指定只對POST方法有效,對GET方法無效。分析原因,應(yīng)該是在執(zhí)行第一個(gè)getParameter()的時(shí)候,java將會(huì)按照編碼分析所有的提交內(nèi)容,而后續(xù)的getParameter()不再進(jìn)行分析,所以setCharacterEncoding()無效。而對于GET方法提交表單是,提交的內(nèi)容在URL中,一開始就已經(jīng)按照編碼分析所有的提交內(nèi)容,setCharacterEncoding()自然就無效。 

    對于response,則是指定輸出內(nèi)容的編碼,同時(shí),該設(shè)置會(huì)傳遞給瀏覽器,告訴瀏覽器輸出內(nèi)容所采用的編碼。 

    3.4. 處理過程 

    下面分析兩個(gè)有代表性的例子,說明java對編碼有關(guān)問題的處理方法。 

    3.4.1. 表單輸入 

    User input  *(gbk:d6d0 cec4)  browser  *(gbk:d6d0 cec4)  web server  iso8859-1(00d6 00d 000ce 00c4)  class,需要在class中進(jìn)行處理:getbytes("iso8859-1")為d6 d0 ce c4,new String("gbk")為d6d0 cec4,內(nèi)存中以u(píng)nicode編碼則為4e2d 6587。 

    l 用戶輸入的編碼方式和頁面指定的編碼有關(guān),也和用戶的操作系統(tǒng)有關(guān),所以是不確定的,上例以gbk為例。 

    l 從browser到web server,可以在表單中指定提交內(nèi)容時(shí)使用的字符集,否則會(huì)使用頁面指定的編碼。而如果在url中直接用?的方式輸入?yún)?shù),則其編碼往往是操作系統(tǒng)本身的編碼,因?yàn)檫@時(shí)和頁面無關(guān)。上述仍舊以gbk編碼為例。 

    l Web server接收到的是字節(jié)流,默認(rèn)時(shí)(getParameter)會(huì)以iso8859-1編碼處理之,結(jié)果是不正確的,所以需要進(jìn)行處理。如 http://www.5a520.cn  小說520網(wǎng) 但如果預(yù)先設(shè)置了編碼(通過request. setCharacterEncoding ()),則能夠直接獲取到正確的結(jié)果。 

    l 在頁面中指定編碼是個(gè)好習(xí)慣,否則可能失去控制,無法指定正確的編碼。 

    3.4.2. 文件編譯 

    假設(shè)文件是gbk編碼保存的,而編譯有兩種編碼選擇:gbk或者iso8859-1,前者是中文windows的默認(rèn)編碼,后者是linux的默認(rèn)編碼,當(dāng)然也可以在編譯時(shí)指定編碼。 

    Jsp  *(gbk:d6d0 cec4)  java file  *(gbk:d6d0 cec4)  compiler read  uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  compiler write  utf(gbk: e4b8ad e69687; iso8859-1: *)  compiled file  unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  class。所以用gbk編碼保存,而用iso8859-1編譯的結(jié)果是不正確的。 

    class  unicode(4e2d 6587)  system.out / jsp.out  gbk(d6d0 cec4)  os console / browser。 

    l 文件可以以多種編碼方式保存,中文windows下,默認(rèn)為ansi/gbk。 

    l 編譯器讀取文件時(shí),需要得到文件的編碼,如果未指定,則使用系統(tǒng)默認(rèn)編碼。一般class文件,是以系統(tǒng)默認(rèn)編碼保存的,所以編譯不會(huì)出問題,但對于jsp文件,如果在中文windows下編輯保存,而部署在英文linux下運(yùn)行/編譯,則會(huì)出現(xiàn)問題。所以需要在jsp文件中用pageEncoding指定編碼。 

    l Java編譯的時(shí)候會(huì)轉(zhuǎn)換成統(tǒng)一的unicode編碼處理,最后保存的時(shí)候再轉(zhuǎn)換為utf編碼。 

    l 當(dāng)系統(tǒng)輸出字符的時(shí)候,會(huì)按指定編碼輸出,對于中文windows下,System.out將使用gbk編碼,而對于response(瀏覽器),則使用jsp文件頭指定的contentType,或者可以直接為response指定編碼。同時(shí),會(huì)告訴browser網(wǎng)頁的編碼。如果未指定,則會(huì)使用iso8859-1編碼。對于中文,應(yīng)該為browser指定輸出字符串的編碼。 

    l browser顯示網(wǎng)頁的時(shí)候,首先使用response中指定的編碼(jsp文件頭指定的contentType最終也反映在response上),如果未指定,則會(huì)使用網(wǎng)頁中meta項(xiàng)指定中的contentType。 

    3.5. 幾處設(shè)置 

    對于web應(yīng)用程序,和編碼有關(guān)的設(shè)置或者函數(shù)如下。 

    3.5.1. jsp編譯 

    指定文件的存儲(chǔ)編碼,很明顯,該設(shè)置應(yīng)該置于文件的開頭。例如:<%@page pageEncoding="GBK"%>。另外,對于一般class文件,可以在編譯的時(shí)候指定編碼。 

    3.5.2. jsp輸出 

    指定文件輸出到browser是使用的編碼,該設(shè)置也應(yīng)該置于文件的開頭。例如:<%@ page contentType="text/html; charset= GBK" %>。該設(shè)置和response.setCharacterEncoding("GBK")等效。 

    3.5.3. meta設(shè)置 

    指定網(wǎng)頁使用的編碼,該設(shè)置對靜態(tài)網(wǎng)頁尤其有作用。因?yàn)殪o態(tài)網(wǎng)頁無法采用jsp的設(shè)置,而且也無法執(zhí)行response.setCharacterEncoding()。例如:<META http-equiv="Content-Type" content="text/html; charset=GBK" /> 

    如果同時(shí)采用了jsp輸出和meta設(shè)置兩種編碼指定方式,則jsp指定的優(yōu)先。因?yàn)閖sp指定的直接體現(xiàn)在response中。 

    需要注意的是,apache有一個(gè)設(shè)置可以給無編碼指定的網(wǎng)頁指定編碼,該指定等同于jsp的編碼指定方式,所以會(huì)覆蓋靜態(tài)網(wǎng)頁中的meta指定。所以有人建議關(guān)閉該設(shè)置。 

    3.5.4. form設(shè)置 

    當(dāng)瀏覽器提交表單的時(shí)候,可以指定相應(yīng)的編碼。例如:<form accept-charset= "gb2312">。一般不必不使用該設(shè)置,瀏覽器會(huì)直接使用網(wǎng)頁的編碼。 

    4. 系統(tǒng)軟件 

    下面討論幾個(gè)相關(guān)的系統(tǒng)軟件。 

    4.1. mysql數(shù)據(jù)庫 

    很明顯,要支持多語言,應(yīng)該將數(shù)據(jù)庫的編碼設(shè)置成utf或者unicode,而utf更適合與存儲(chǔ)。但是,如果中文數(shù)據(jù)中包含的英文字母很少,其實(shí)unicode更為適合。 

    數(shù)據(jù)庫的編碼可以通過mysql的配置文件設(shè)置,例如default-character-set=utf8。還可以在數(shù)據(jù)庫鏈接URL中設(shè)置,例如: useUnicode=true&characterEncoding=UTF-8。注意這兩者應(yīng)該保持一致,在新的sql版本里,在數(shù)據(jù)庫鏈接URL里可以不進(jìn)行設(shè)置,但也不能是錯(cuò)誤的設(shè)置。 

    4.2. apache 

    appache和編碼有關(guān)的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,該功能會(huì)將所有靜態(tài)頁面的編碼設(shè)置為UTF-8,最好關(guān)閉該功能。 

    另外,apache還有單獨(dú)的模塊來處理網(wǎng)頁響應(yīng)頭,其中也可能對編碼進(jìn)行設(shè)置。 

    4.3. linux默認(rèn)編碼 

    這里所說的linux默認(rèn)編碼,是指運(yùn)行時(shí)的環(huán)境變量。兩個(gè)重要的環(huán)境變量是LC_ALL和LANG,默認(rèn)編碼會(huì)影響到j(luò)ava URLEncode的行為,下面有描述。 

    建議都設(shè)置為"zh_CN.UTF-8"。 

    4.4. 其它 

    為了支持中文文件名,linux在加載磁盤時(shí)應(yīng)該指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。 

    另外,如前所述,使用GET方法提交的信息不支持request.setCharacterEncoding(),但可以通過tomcat的配置文件指定字符集,在tomcat的server.xml文件中,形如:<Connector ... URIEncoding="GBK"/>。這種方法將統(tǒng)一設(shè)置所有請求,而不能針對具體頁面進(jìn)行設(shè)置,也不一定和browser使用的編碼相同,所以有時(shí)候并不是所期望的。 

    5. URL地址 

    URL地址中含有中文字符是很麻煩的,前面描述過使用GET方法提交表單的情況,使用GET方法時(shí),參數(shù)就是包含在URL中。 

    5.1. URL編碼 

    對于URL中的一些特殊字符,瀏覽器會(huì)自動(dòng)進(jìn)行編碼。這些字符除了"/?&"等外,還包括unicode字符,比如漢子。這時(shí)的編碼比較特殊。 

    IE有一個(gè)選項(xiàng)"總是使用UTF-8發(fā)送URL",當(dāng)該選項(xiàng)有效時(shí),IE將會(huì)對特殊字符進(jìn)行UTF-8編碼,同時(shí)進(jìn)行URL編碼。如果改選項(xiàng)無效,則使用默認(rèn)編碼"GBK",并且不進(jìn)行URL編碼。但是,對于URL后面的參數(shù),則總是不進(jìn)行編碼,相當(dāng)于UTF-8選項(xiàng)無效。比如" http://www.bt285.cn 中文.html?a=中文",當(dāng)UTF-8選項(xiàng)有效時(shí),將發(fā)送鏈接" http://www.bt285.cn %e4%b8%ad%e6%96%87.html?a=\x4e\x2d\x65\x87";而UTF-8選項(xiàng)無效時(shí),將發(fā)送鏈接"\x4e\x2d\x65\x87.html?a=\x4e\x2d\x65\x87"。注意后者前面的"中文"兩個(gè)字只有4個(gè)字節(jié),而前者卻有18個(gè)字節(jié),這主要時(shí)URL編碼的原因。 

    當(dāng)web server(tomcat)接收到該鏈接時(shí),將會(huì)進(jìn)行URL解碼,即去掉"%",同時(shí)按照ISO8859-1編碼(上面已經(jīng)描述,可以使用URLEncoding來設(shè)置成其它編碼)識(shí)別。上述例子的結(jié)果分別是" http://www.feng123.com \ue4\ub8\uad\ue6\u96\u87.html?a=\u4e\u2d\u65\u87"和" http://www.feng123.com \u4e\u2d\u65\u87.html?a=\u4e\u2d\u65\u87",注意前者前面的"中文"兩個(gè)字恢復(fù)成了6個(gè)字符。這里用"\u",表示是unicode。 

    所以,由于客戶端設(shè)置的不同,相同的鏈接,在服務(wù)器上得到了不同結(jié)果。這個(gè)問題不少人都遇到,卻沒有很好的解決辦法。所以有的網(wǎng)站會(huì)建議用戶嘗試關(guān)閉UTF-8選項(xiàng)。不過,下面會(huì)描述一個(gè)更好的處理辦法。 

    5.2. rewrite 

    熟悉的人都知道,apache有一個(gè)功能強(qiáng)大的rewrite模塊,這里不描述其功能。需要說明的是該模塊會(huì)自動(dòng)將URL解碼(去除%),即完成上述web server(tomcat)的部分功能。有相關(guān)文檔介紹說可以使用[NE]參數(shù)來關(guān)閉該功能,但我試驗(yàn)并未成功,可能是因?yàn)榘姹荆ㄎ沂褂玫氖莂pache 2.0.54)問題。另外,當(dāng)參數(shù)中含有"?& "等符號(hào)的時(shí)候,該功能將導(dǎo)致系統(tǒng)得不到正常結(jié)果。 

    rewrite本身似乎完全是采用字節(jié)處理的方式,而不考慮字符串的編碼,所以不會(huì)帶來編碼問題。 

    5.3. URLEncode.encode() 

    這是Java本身提供對的URL編碼函數(shù),完成的工作和上述UTF-8選項(xiàng)有效時(shí)瀏覽器所做的工作相似。值得說明的是,java已經(jīng)不贊成不指定編碼來使用該方法(deprecated)。應(yīng)該在使用的時(shí)候增加編碼指定。 

    當(dāng)不指定編碼的時(shí)候,該方法使用系統(tǒng)默認(rèn)編碼,這會(huì)導(dǎo)致軟件運(yùn)行結(jié)果得不確定。比如對于"中文",當(dāng)系統(tǒng)默認(rèn)編碼為"gb2312"時(shí),結(jié)果是"%4e%2d%65%87",而默認(rèn)編碼為"UTF-8",結(jié)果卻是"%e4%b8%ad%e6%96%87",后續(xù)程序?qū)㈦y以處理。另外,這兒說的系統(tǒng)默認(rèn)編碼是由運(yùn)行tomcat時(shí)的環(huán)境變量LC_ALL和LANG等決定的,曾經(jīng)出現(xiàn)過tomcat重啟后就出現(xiàn)亂碼的問題,最后才郁悶的發(fā)現(xiàn)是因?yàn)樾薷男薷牧诉@兩個(gè)環(huán)境變量。 

    建議統(tǒng)一指定為"UTF-8"編碼,可能需要修改相應(yīng)的程序。 

    5.4. 一個(gè)解決方案 

    上面說起過,因?yàn)闉g覽器設(shè)置的不同,對于同一個(gè)鏈接,web server收到的是不同內(nèi)容,而軟件系統(tǒng)有無法知道這中間的區(qū)別,所以這一協(xié)議目前還存在缺陷。 

    針對具體問題,不應(yīng)該僥幸認(rèn)為所有客戶的IE設(shè)置都是UTF-8有效的,也不應(yīng)該粗暴的建議用戶修改IE設(shè)置,要知道,用戶不可能去記住每一個(gè)web server的設(shè)置。所以,接下來的解決辦法就只能是讓自己的程序多一點(diǎn)智能:根據(jù)內(nèi)容來分析編碼是否UTF-8。 

    比較幸運(yùn)的是UTF-8編碼相當(dāng)有規(guī)律,所以可以通過分析傳輸過來的鏈接內(nèi)容,來判斷是否是正確的UTF-8字符,如果是,則以UTF-8處理之,如果不是,則使用客戶默認(rèn)編碼(比如"GBK"),下面是一個(gè)判斷是否UTF-8的例子,如果你了解相應(yīng)規(guī)律,就容易理解。 

    public static boolean isValidUtf8(byte[] b,int aMaxCount){ 

           int lLen=b.length,lCharCount=0; 

           for(int i=0;i<lLen && lCharCount<aMaxCount;++lCharCount){ 

                  byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;) 

                  if(lByte>=0) continue;//>=0 is normal ascii 

                  if(lByte<(byte)0xc0 || lByte>(byte)0xfd) return false; 

                  int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4 

                         :lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1; 

                  if(i+lCount>lLen) return false; 

                  for(int j=0;j<lCount;++j,++i) if(b[i]>=(byte)0xc0) return false; 

           } 

           return true; 



    相應(yīng)地,一個(gè)使用上述方法的例子如下: 

    public static String getUrlParam(String aStr,String aDefaultCharset) 

    throws UnsupportedEncodingException{ 

           if(aStr==null) return null; 

           byte[] lBytes=aStr.getBytes("ISO-8859-1"); 

           return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset); 



    不過,該方法也存在缺陷,如下兩方面: 

    l 沒有包括對用戶默認(rèn)編碼的識(shí)別,這可以根據(jù)請求信息的語言來判斷,但不一定正確,因?yàn)槲覀冇袝r(shí)候也會(huì)輸入一些韓文,或者其他文字。 

    l 可能會(huì)錯(cuò)誤判斷UTF-8字符,一個(gè)例子是"學(xué)習(xí)"兩個(gè)字,其GBK編碼是" \xd1\xa7\xcf\xb0",如果使用上述isValidUtf8方法判斷,將返回true。可以考慮使用更嚴(yán)格的判斷方法,不過估計(jì)效果不大。 

    有一個(gè)例子可以證明google也遇到了上述問題,而且也采用了和上述相似的處理方法,比如,如果在地址欄中輸入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=學(xué)習(xí)",google將無法正確識(shí)別,而其他漢字一般能夠正常識(shí)別。 

    最后,應(yīng)該補(bǔ)充說明一下,如果不使用rewrite規(guī)則,或者通過表單提交數(shù)據(jù),其實(shí)并不一定會(huì)遇到上述問題,因?yàn)檫@時(shí)可以在提交數(shù)據(jù)時(shí)指定希望的編碼。另外,中文文件名確實(shí)會(huì)帶來問題,應(yīng)該謹(jǐn)慎使用。 

    6. 其它 

    下面描述一些和編碼有關(guān)的其他問題。 

    6.1. SecureCRT 

    除了瀏覽器和控制臺(tái)與編碼有關(guān)外,一些客戶端也很有關(guān)系。比如在使用SecureCRT連接linux時(shí),應(yīng)該讓SecureCRT的顯示編碼(不同的session,可以有不同的編碼設(shè)置)和linux的編碼環(huán)境變量保持一致。否則看到的一些幫助信息,就可能是亂碼。 

    另外,mysql有自己的編碼設(shè)置,也應(yīng)該保持和SecureCRT的顯示編碼一致。否則通過SecureCRT執(zhí)行sql語句的時(shí)候,可能無法處理中文字符,查詢結(jié)果也會(huì)出現(xiàn)亂碼。 

    對于Utf-8文件,很多編輯器(比如記事本)會(huì)在文件開頭增加三個(gè)不可見的標(biāo)志字節(jié),如果作為mysql的輸入文件,則必須要去掉這三個(gè)字符。(用linux的vi保存可以去掉這三個(gè)字符)。一個(gè)有趣的現(xiàn)象是,在中文windows下,創(chuàng)建一個(gè)新txt文件,用記事本打開,輸入"連通"兩個(gè)字,保存,再打開,你會(huì)發(fā)現(xiàn)兩個(gè)字沒了,只留下一個(gè)小黑點(diǎn)。 

    6.2. 過濾器 

    如果需要統(tǒng)一設(shè)置編碼,則通過filter進(jìn)行設(shè)置是個(gè)不錯(cuò)的選擇。在filter class中,可以統(tǒng)一為需要的請求或者回應(yīng)設(shè)置編碼。參加上述setCharacterEncoding()。這個(gè)類apache已經(jīng)給出了可以直接使用的例子SetCharacterEncodingFilter。 

    6.3. POST和GET 

    很明顯,以POST提交信息時(shí),URL有更好的可讀性,而且可以方便的使用setCharacterEncoding()來處理字符集問題。但GET方法形成的URL能夠更容易表達(dá)網(wǎng)頁的實(shí)際內(nèi)容,也能夠用于收藏。 

    從統(tǒng)一的角度考慮問題,建議采用GET方法,這要求在程序中獲得參數(shù)是進(jìn)行特殊處理,而無法使用setCharacterEncoding()的便利,如果不考慮rewrite,就不存在IE的UTF-8問題,可以考慮通過設(shè)置URIEncoding來方便獲取URL中的參數(shù)。 

    6.4. 簡繁體編碼轉(zhuǎn)換 

    GBK同時(shí)包含簡體和繁體編碼,也就是說同一個(gè)字,由于編碼不同,在GBK編碼下屬于兩個(gè)字。有時(shí)候,為了正確取得完整的結(jié)果,應(yīng)該將繁體和簡體進(jìn)行統(tǒng)一。可以考慮將UTF、GBK中的所有繁體字,轉(zhuǎn)換為相應(yīng)的簡體字,BIG5編碼的數(shù)據(jù),也應(yīng)該轉(zhuǎn)化成相應(yīng)的簡體字。當(dāng)然,仍舊以UTF編碼存儲(chǔ)。 

    例如,對于"語言 ?言",用UTF表示為"\xE8\xAF\xAD\xE8\xA8\x80 \xE8\xAA\x9E\xE8\xA8\x80",進(jìn)行簡繁體編碼轉(zhuǎn)換后應(yīng)該是兩個(gè)相同的 "\xE8\xAF\xAD\xE8\xA8\x80>"。 

    posted @ 2009-05-25 19:03 江蘇520| 編輯 收藏

    JAVA生成高品質(zhì)縮略圖的代碼與下載

    import java.awt.image.BufferedImage;

    public class ImageScale {

    private int width;
    private int height;
    private int scaleWidth;
    double support = (double) 3.0;
    double PI = (double) 3.14159265358978;
    double[] contrib;
    double[] normContrib;
    double[] tmpContrib;
    int startContrib, stopContrib;
    int nDots;
    int nHalfDots;

    public BufferedImage imageZoomOut(BufferedImage srcBufferImage, int w, int h) {
    width = srcBufferImage.getWidth();
    height = srcBufferImage.getHeight();
    scaleWidth = w;

    if (DetermineResultSize(w, h) == 1) {
    return srcBufferImage;
    }
    CalContrib();
    BufferedImage pbOut = HorizontalFiltering(srcBufferImage, w);
    BufferedImage pbFinalOut = VerticalFiltering(pbOut, h);
    return pbFinalOut;
    }

    /**
     * 決定圖像尺寸
     */
    private int DetermineResultSize(int w, int h) {
    double scaleH, scaleV;
    scaleH = (double) w / (double) width;
    scaleV = (double) h / (double) height;
    // 需要判斷一下scaleH,scaleV,不做放大操作
    if (scaleH >= 1.0 && scaleV >= 1.0) {
    return 1;
    }
    return 0;

    } // end of DetermineResultSize()

    private double Lanczos(int i, int inWidth, int outWidth, double Support) {
    double x;

    x = (double) i * (double) outWidth / (double) inWidth;

    return Math.sin(x * PI) / (x * PI) * Math.sin(x * PI / Support)
    / (x * PI / Support);

    } // end of Lanczos()

    //
    // Assumption: same horizontal and vertical scaling factor
    //
    private void CalContrib() {
    nHalfDots = (int) ((double) width * support / (double) scaleWidth);
    nDots = nHalfDots * 2 + 1;
    try {
    contrib = new double[nDots];
    normContrib = new double[nDots];
    tmpContrib = new double[nDots];
    } catch (Exception e) {
    System.out.println("init contrib,normContrib,tmpContrib" + e);
    }

    int center = nHalfDots;
    contrib[center] = 1.0;

    double weight = 0.0;
    int i = 0;
    for (i = 1; i <= center; i++) {
    contrib[center + i] = Lanczos(i, width, scaleWidth, support);
    weight += contrib[center + i];
    }

    for (i = center - 1; i >= 0; i--) {
    contrib[i] = contrib[center * 2 - i];
    }

    weight = weight * 2 + 1.0;

    for (i = 0; i <= center; i++) {
    normContrib[i] = contrib[i] / weight;
    }

    for (i = center + 1; i < nDots; i++) {
    normContrib[i] = normContrib[center * 2 - i];
    }
    } // end of CalContrib()

    // 處理邊緣
    private void CalTempContrib(int start, int stop) {
    double weight = 0;

    int i = 0;
    for (i = start; i <= stop; i++) {
    weight += contrib[i];
    }

    for (i = start; i <= stop; i++) {
    tmpContrib[i] = contrib[i] / weight;
    }

    } // end of CalTempContrib()

    private int GetRedValue(int rgbValue) {
    int temp = rgbValue & 0x00ff0000;
    return temp >> 16;
    }

    private int GetGreenValue(int rgbValue) {
    int temp = rgbValue & 0x0000ff00;
    return temp >> 8;
    }

    private int GetBlueValue(int rgbValue) {
    return rgbValue & 0x000000ff;
    }

    private int ComRGB(int redValue, int greenValue, int blueValue) {

    return (redValue << 16) + (greenValue << 8) + blueValue;
    }

    // 行水平濾波
    private int HorizontalFilter(BufferedImage bufImg, int startX, int stopX,
    int start, int stop, int y, double[] pContrib) {
    double valueRed = 0.0;
    double valueGreen = 0.0;
    double valueBlue = 0.0;
    int valueRGB = 0;
    int i, j;

    for (i = startX, j = start; i <= stopX; i++, j++) {
    valueRGB = bufImg.getRGB(i, y);

    valueRed += GetRedValue(valueRGB) * pContrib[j];
    valueGreen += GetGreenValue(valueRGB) * pContrib[j];
    valueBlue += GetBlueValue(valueRGB) * pContrib[j];
    }

    valueRGB = ComRGB(Clip((int) valueRed), Clip((int) valueGreen),
    Clip((int) valueBlue));
    return valueRGB;

    } // end of HorizontalFilter()

    // 圖片水平濾波
    private BufferedImage HorizontalFiltering(BufferedImage bufImage, int iOutW) {
    int dwInW = bufImage.getWidth();
    int dwInH = bufImage.getHeight();
    int value = 0;
    BufferedImage pbOut = new BufferedImage(iOutW, dwInH,
    BufferedImage.TYPE_INT_RGB);

    for (int x = 0; x < iOutW; x++) {

    int startX;
    int start;
    int X = (int) (((double) x) * ((double) dwInW) / ((double) iOutW) + 0.5);
    int y = 0;

    startX = X - nHalfDots;
    if (startX < 0) {
    startX = 0;
    start = nHalfDots - X;
    } else {
    start = 0;
    }

    int stop;
    int stopX = X + nHalfDots;
    if (stopX > (dwInW - 1)) {
    stopX = dwInW - 1;
    stop = nHalfDots + (dwInW - 1 - X);
    } else {
    stop = nHalfDots * 2;
    }

    if (start > 0 || stop < nDots - 1) {
    CalTempContrib(start, stop);
    for (y = 0; y < dwInH; y++) {
    value = HorizontalFilter(bufImage, startX, stopX, start,
    stop, y, tmpContrib);
    pbOut.setRGB(x, y, value);
    }
    } else {
    for (y = 0; y < dwInH; y++) {
    value = HorizontalFilter(bufImage, startX, stopX, start,
    stop, y, normContrib);
    pbOut.setRGB(x, y, value);
    }
    }
    }

    return pbOut;

    } // end of HorizontalFiltering()

    private int VerticalFilter(BufferedImage pbInImage, int startY, int stopY,
    int start, int stop, int x, double[] pContrib) {
    double valueRed = 0.0;
    double valueGreen = 0.0;
    double valueBlue = 0.0;
    int valueRGB = 0;
    int i, j;

    for (i = startY, j = start; i <= stopY; i++, j++) {
    valueRGB = pbInImage.getRGB(x, i);

    valueRed += GetRedValue(valueRGB) * pContrib[j];
    valueGreen += GetGreenValue(valueRGB) * pContrib[j];
    valueBlue += GetBlueValue(valueRGB) * pContrib[j];
    // System.out.println(valueRed+"->"+Clip((int)valueRed)+"<-");
    //
    // System.out.println(valueGreen+"->"+Clip((int)valueGreen)+"<-");
    // System.out.println(valueBlue+"->"+Clip((int)valueBlue)+"<-"+"-->");
    }

    valueRGB = ComRGB(Clip((int) valueRed), Clip((int) valueGreen),
    Clip((int) valueBlue));
    // System.out.println(valueRGB);
    return valueRGB;

    } // end of VerticalFilter()

    private BufferedImage VerticalFiltering(BufferedImage pbImage, int iOutH) {
    int iW = pbImage.getWidth();
    int iH = pbImage.getHeight();
    int value = 0;
    BufferedImage pbOut = new BufferedImage(iW, iOutH,
    BufferedImage.TYPE_INT_RGB);

    for (int y = 0; y < iOutH; y++) {

    int startY;
    int start;
    int Y = (int) (((double) y) * ((double) iH) / ((double) iOutH) + 0.5);

    startY = Y - nHalfDots;
    if (startY < 0) {
    startY = 0;
    start = nHalfDots - Y;
    } else {
    start = 0;
    }

    int stop;
    int stopY = Y + nHalfDots;
    if (stopY > (int) (iH - 1)) {
    stopY = iH - 1;
    stop = nHalfDots + (iH - 1 - Y);
    } else {
    stop = nHalfDots * 2;
    }

    if (start > 0 || stop < nDots - 1) {
    CalTempContrib(start, stop);
    for (int x = 0; x < iW; x++) {
    value = VerticalFilter(pbImage, startY, stopY, start, stop,
    x, tmpContrib);
    pbOut.setRGB(x, y, value);
    }
    } else {
    for (int x = 0; x < iW; x++) {
    value = VerticalFilter(pbImage, startY, stopY, start, stop,
    x, normContrib);
    pbOut.setRGB(x, y, value);
    }
    }

    }

    return pbOut;

    } // end of VerticalFiltering()

    int Clip(int x) {
    if (x < 0)
    return 0;
    if (x > 255)
    return 255;
    return x;
    }

    public static void main(String[] args) {
    }
    }
    }


    -----------------------------------------------------------------------
    public BufferedImage imageZoomOut(BufferedImage srcBufferImage, int w, int h)
    我將這個(gè)方法改成了
    public BufferedImage imageZoomOut(BufferedImage srcBufferImage, float w, float h)
    為了方便同比例縮放,將w, h改為flaot型作為縮放比率,其他地方作相應(yīng)的改動(dòng)。

    如果是在網(wǎng)頁上顯示的話,需要建一個(gè)Servlet,代碼如下:
    package servlet;

    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;

    import javax.imageio.ImageIO;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import com.ImageScale;

    public class ImageServlet extends HttpServlet {

      private static final long serialVersionUID = 1L;

      public void doPost(HttpServletRequest request, HttpServletResponse response)
          throws ServletException, IOException {
        doGet(request, response);
      }

      public void doGet(HttpServletRequest request, HttpServletResponse response)
          throws ServletException, IOException {
        response.setHeader("Cache-Control", "no-store");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");
        
        // 圖片放在當(dāng)前應(yīng)用的images目錄下 如http://www.feng123.com 下的文件目錄。
        String path = getServletContext().getRealPath("images/2.jpg");    
        BufferedImage image1 = ImageIO.read(new File(path));    
        
        if(request.getParameter("x") == null) {
          ImageIO.write(image1, "jpeg", response.getOutputStream());
        }else {
          float w = Float.parseFloat(request.getParameter("x"));
          float h;
          if(request.getParameter("y") == null) {
            h = w;
          }else{
            h = Float.parseFloat(request.getParameter("y"));
          }      
          ImageScale is = new ImageScale();
          BufferedImage image2 = is.imageZoomOut(image1, w, h);
          ImageIO.write(image2, "jpeg", response.getOutputStream());
        }
      }
    }

    在web.xml中增加相應(yīng)的配置:
    <servlet>
      <servlet-name>ImageServlet</servlet-name>
      <servlet-class>servlet.ImageServlet</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>ImageServlet</servlet-name>
      <url-pattern>/imageServlet</url-pattern>
    </servlet-mapping>

    測試頁面 http://www.feng123.com 下的:
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=gbk" />
      </head>
      <body>
        圖片縮放測試http://www.bt285.cn <br/>
        <img src="imageServlet" />
        <img src="imageServlet?x=0.9&y=0.9" />
        <img src="imageServlet?x=0.8&y=0.8" />
        <img src="imageServlet?x=0.7&y=0.7" />
        <img src="imageServlet?x=0.6&y=0.6" />
        <img src="imageServlet?x=0.5&y=0.5" />
        <img src="imageServlet?x=0.4&y=0.4" />
        <img src="imageServlet?x=0.3&y=0.3" />
        <img src="imageServlet?x=0.2&y=0.2" />
        <img src="imageServlet?x=0.1&y=0.1" />
        <img src="imageServlet?x=0.08" />
        <img src="imageServlet?x=0.5&y=0.7" />
      </body>
    </html>

    posted @ 2009-05-22 22:12 江蘇520| 編輯 收藏

    java利用jmf實(shí)現(xiàn)拍照功能

    首先到SUN下載最新的JMF,然后安裝。http://java.sun.com/products/java-media/jmf/index.jsp         http://www.bt285.cn  

    然后,說一下需求

    1. 用攝像頭拍照

    2. 在文本框輸入文件名

    3. 按下拍照按鈕,獲取攝像頭內(nèi)的圖像

    4. 在拍下的照片上有一紅框截取固定大小的照片。

    5. 保存為本地圖像為jpg格式,不得壓縮畫質(zhì)

    技術(shù)關(guān)鍵,相信也是大家最感興趣的部分也就是如何讓一個(gè)攝像頭工作,并拍下一張照片了。

    利用JMF,代碼很簡單:

    //利用這三個(gè)類分別獲取攝像頭驅(qū)動(dòng),和獲取攝像頭內(nèi)的圖像流,獲取到的圖像流是一個(gè)Swing的Component組件類

     

    public static Player player = null;
                  private CaptureDeviceInfo di = null;
                  private MediaLocator ml = null;
                  String str1 = "vfw:Logitech USB Video Camera:0";
                  String str2 = "vfw:Microsoft WDM Image Capture (Win32):0";
                  di = CaptureDeviceManager.getDevice(str2);
                  ml = di.getLocator();
                  try
                  {
                  player = Manager.createRealizedPlayer(ml);
                  player.start();
                  Component comp;
                  if ((comp = player.getVisualComponent()) != null)
                  {
                  add(comp, BorderLayout.NORTH);
                  }
                  }
                  catch (Exception e)
                  {
                  e.printStackTrace();
                  }

    接下來就是點(diǎn)擊拍照,獲取攝像頭內(nèi)的當(dāng)前圖像。

    代碼也是很簡單:

     

     private JButton capture;
                  private Buffer buf = null;
                  private BufferToImage btoi = null;
                  private ImagePanel imgpanel = null;
                  private Image img = null;
                  private ImagePanel imgpanel = null;
                  JComponent c = (JComponent) e.getSource();
                  if (c == capture)//如果按下的是拍照按鈕 http://www.5a520.cn
                  {
                  FrameGrabbingControl fgc =(FrameGrabbingControl)player.getControl
                ("javax.media.control.FrameGrabbingControl");
                  buf = fgc.grabFrame(); // 獲取當(dāng)前禎并存入Buffer類 http://www.bt285.cn
                  btoi = new BufferToImage((VideoFormat) buf.getFormat());
                  img = btoi.createImage(buf); // show the image
                  imgpanel.setImage(img);
                  }

    保存圖像的就不多說了,以下為示例代碼

     

     BufferedImage bi = (BufferedImage) createImage(imgWidth, imgHeight);
                  Graphics2D g2 = bi.createGraphics();
                  g2.drawImage(img, null, null);
                  FileOutputStream out = null;
                  try
                  {
                  out = new FileOutputStream(s);
                  }
                  catch (java.io.FileNotFoundException io)
                  {
                  System.out.println("File Not Found");
                  }
                  JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
                  JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
                  param.setQuality(1f, false);//不壓縮圖像 http://www.bt285.cn
                  encoder.setJPEGEncodeParam(param);
                  try
                  {
                  encoder.encode(bi);
                  out.close();
                  }
                  catch (java.io.IOException io)
                  {
                  System.out.println("IOException");
                  }

    已經(jīng)申請將JWebCam建立為一個(gè)開源項(xiàng)目,放到GRO,大家發(fā)揮自己的想象力加入自己的代碼吧,比如拍攝視頻,添加圖像處理功能,等等

    posted @ 2009-05-21 21:03 江蘇520| 編輯 收藏

    用Tomcat 的SSO實(shí)現(xiàn)

    目標(biāo):用戶Login一次之后,可以訪問同一Server上的不同Webapp, 具體實(shí)現(xiàn)上采用Tomcat的Single Sign-On實(shí)現(xiàn). 主要分為下面幾個(gè)步驟:
    • 修改Tomcat conf/server.xml 打開SSO支持
    <Host> 節(jié)點(diǎn)下增加一個(gè)Value節(jié)點(diǎn)
    <Valve className="org.apache.catalina.authenticator.SingleSignOn"
    debug="0" requireReauthentication="false"/>
    </Host>
    • container認(rèn)證realm: user、role、server.xml的<Realm...>設(shè)置.
    tomcat的認(rèn)證機(jī)制有2個(gè)要素: user 和 role.
      • user 是區(qū)別一個(gè)個(gè)用戶的唯一識(shí)別了。
      • role 就是一些抽象的權(quán)限級(jí)別,比如“admin”、“manager”、“member”、“guest”等等,都是可以自己定義的.一個(gè)user可以擁有多種role.
    “可是tomcat怎么去拿到我的user/role信息呢?我的這些數(shù)據(jù)都在數(shù)據(jù)庫里阿?” 可以在tomcat的server.xml里用 <Realm> tag來讀取這些信息,并且tomcat提供了3、4種現(xiàn)成的Realm實(shí)現(xiàn),其中有從文件里讀的,有從JDBC讀的,有從DataSource讀的,也有從LDAP讀的。具體Realm的寫法,和提供的幾種Realm的配置方法,可以參考tomcat自己的文檔,在此不作細(xì)述。 (把tomcat自帶的webapp: tomcat-docs.war 展開,看里面的 config/realm.html) 如果連這些現(xiàn)成的配置都不能滿足你的要求的話,那也可以考慮自己寫一個(gè)Realm的實(shí)現(xiàn)類來滿足具體要求。下面舉一個(gè)JDBC的Realm的配置例子看一下:
    <Realm  className="org.apache.catalina.realm.JDBCRealm"  debug="99"
    driverName="your.jdbc.driver.here"
    connectionURL="your.jdbc.url.here"
    connectionName="test"
    connectionPassword="test"
    userTable="users"
    userNameCol="user_name"
    userCredCol="user_pass"
    userRoleTable="user_roles"
    roleNameCol="role_name" />
    • webapp使用SSO:
      • 告訴tomcat這個(gè)webapp要通過container的認(rèn)證
    具體做法: 在web.xml里面加上如下的配置:
    <security-constraint>
    <web-resource-collection>
    <web-resource-name>http://www.bt285.cn  BT下載 </web-resource-name>
    <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
    <!-- role name 指定哪個(gè)role可以訪問,可以為多個(gè)role,如兩個(gè)網(wǎng)站:http://www.5a520.cn http://www.feng123.com  -->
    <role-name>intrauser</role-name>
    </auth-constraint>
    </security-constraint>
      • 選擇一種認(rèn)證方法
    在web.xml里面加上如下的配置:
    <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>Intra Web Application</realm-name>
    </login-config>
    <security-role>
    <description>The role that is required to access intrasites</description>
    <role-name>intrauser</role-name>
    </security-role>
    這里有2個(gè)要點(diǎn):
        • auth-method
    舉例為了簡單用了最基本的一種BASIC。若使用BASIC方式,當(dāng)你去訪問受保護(hù)認(rèn)證的資源時(shí),瀏覽器會(huì)彈出一個(gè)小窗口讓你輸入用戶名和密碼。(就像我們訪問ioffice時(shí),第一次彈出來的那個(gè)認(rèn)證窗口)其他還有幾種認(rèn)證方式如:FORM、DIGEST、CLIENT-CERT。其中FORM是可以自己寫login畫面的,當(dāng)然html的form內(nèi)容有些規(guī)定(要符合j2ee和container的要求嘛)。 DIGEST是一種加密的傳輸,而CLIENT-CERT沒有查過,有興趣可以去查一下。
        • realm-name
    這個(gè)realm-name是這個(gè)webapp的認(rèn)證realm名,注意幾個(gè)處于同一SSO下的webapp,他們的realm-name要設(shè)成一樣的值。 如果不設(shè)成一樣,那么換一個(gè)webapp就要重新認(rèn)證一次,達(dá)不到SSO的效果。
      • 如何取得當(dāng)前的User信息
    原本都習(xí)慣在login以后,把一些login用戶信息放到session里面的. 現(xiàn)在認(rèn)證都交給container去做了,我們的webapp怎么拿到login用戶信息??? 確實(shí),現(xiàn)在我們的webapp能做的,只有從request里面拿到login用戶的userid了。
    String userid = request.gerRemoteUser();

    以上是在一個(gè)Tomcat Container上的SSO實(shí)現(xiàn).
    如果是不同的Container上的webapp要做SSO,這種時(shí)候一種可行的方案是,最前面架一個(gè)webserver(比如apache),在webserver這層承擔(dān)SSO的認(rèn)證任務(wù),后面內(nèi)部就可用掛多個(gè)container了. 具體都用到的時(shí)候再調(diào)查吧.

    posted @ 2009-05-19 21:21 江蘇520| 編輯 收藏

    如何用java 添加水印

    import java.awt.*
    import java.awt.event.*
    import java.io.*
    import java.awt.image.*
    import org.w3c.dom.*
    import com.sun.image.codec.jpeg.*
    import javax.imageio.*
    /**
    author by http:
    //www.5a520.cn
    */
    public class ImgBean
    public void ImgBean(){} 
    public void ImgYin(String s,String ImgName)
    try
    File _file 
    = new File(ImgName); 
    Image src 
    = ImageIO.read(_file); 
    int wideth=src.getWidth(null); 
    int height=src.getHeight(null); 
    BufferedImage image
    =new BufferedImage(wideth,height,BufferedImage.TYPE_INT_RGB); 
    Graphics g
    =image.createGraphics(); 
    g.drawImage(src,
    0,0,wideth,height,null); 
    String s
    ="我要加的水印 ,來源http://www.bt285.cn BT下載"
    g.setColor(Color.RED); 
    g.setFont(
    new Font("宋體",Font.PLAIN,20)); 
    Font aa
    =new Font("宋體",Font.PLAIN,20); 

    g.drawString(s,wideth
    -150,height-10); 
    g.dispose(); 
    FileOutputStream out
    =new FileOutputStream(ImgName); 
    JPEGImageEncoder encoder 
    = JPEGCodec.createJPEGEncoder(out); 
    encoder.encode(image); 
    out.close(); 
    }
     
    catch(Exception e)
    System.out.println(e); 
    }
     
    }
     
    }


    posted @ 2009-05-18 20:45 江蘇520| 編輯 收藏

    Tomcat 6.0.20

    Tomcat是Apache 軟件基金會(huì)(Apache Software Foundation)的Jakarta 項(xiàng)目中的一個(gè)核心項(xiàng)目,由Apache、Sun 和其他一些公司及個(gè)人共同開發(fā)而成.由于有了Sun 的參與和支持,最新的Servlet 和JSP 規(guī)范總是能在Tomcat 中得到體現(xiàn),因?yàn)門omcat 技術(shù)先進(jìn)、性能穩(wěn)定,而且免費(fèi),因而深受Java 愛好者的喜愛并得到了部分軟件開發(fā)商的認(rèn)可,成為目前比較流行的Web 應(yīng)用服務(wù)器.Tomcat 很受廣大程序員的喜歡,因?yàn)樗\(yùn)行時(shí)占用的系統(tǒng)資源小,擴(kuò)展性好,支持負(fù)載平衡與郵件服務(wù)等開發(fā)應(yīng)用系統(tǒng)常用的功能;而且它還在不斷的改進(jìn)和完善中,任何一個(gè)感興趣的程序員都可以更改它或在其中加入新的功能.

    下載:Tomcat 6.0.20

    Apache Tomcat Version 6.0.20
    Release Notes

    =============================
    KNOWN ISSUES IN THIS RELEASE:
    =============================

    * Dependency Changes
    * JNI Based Applications
    * Bundled APIs
    * Web application reloading and static fields in shared libraries
    * Tomcat on Linux
    * Enabling SSI and CGI Support
    * Security manager URLs
    * Symlinking static resources
    * Enabling invoker servlet
    * Viewing the Tomcat Change Log
    * When all else fails
    http://www.bt285.cn
    http://www.feng123.com
    ===================
    Dependency Changes:
    ===================
    Tomcat 6.0 is designed to run on JSE 5.0 and later.

    In addition, Tomcat 6.0 uses the Eclipse JDT Java compiler for compiling
    JSP pages. This means you no longer need to have the complete
    Java Development Kit (JDK) to run Tomcat, but a Java Runtime Environment
    (JRE) is sufficient. The Eclipse JDT Java compiler is bundled with the
    binary Tomcat distributions. Tomcat can also be configured to use the
    compiler from the JDK to compile JSPs, or any other Java compiler supported
    by Apache Ant.


    =======================
    JNI Based Applications:
    =======================
    Applications that require native libraries must ensure that the libraries have
    been loaded prior to use. Typically, this is done with a call like:

    static {
    System.loadLibrary("path-to-library-file");
    }

    in some class. However, the application must also ensure that the library is
    not loaded more than once. If the above code were placed in a class inside
    the web application (i.e. under /WEB-INF/classes or /WEB-INF/lib), and the
    application were reloaded, the loadLibrary() call would be attempted a second
    time.

    To avoid this problem, place classes that load native libraries outside of the
    web application, and ensure that the loadLibrary() call is executed only once
    during the lifetime of a particular JVM.


    =============
    Bundled APIs:
    =============
    A standard installation of Tomcat 6.0 makes all of the following APIs available
    for use by web applications (by placing them in "lib"):
    * annotations-api.jar (Annotations package)
    * catalina.jar (Tomcat Catalina implementation)
    * catalina-ant.jar (Tomcat Catalina Ant tasks)
    * catalina-ha.jar (High availability package)
    * catalina-tribes.jar (Group communication)
    * el-api.jar (EL 2.1 API)
    * jasper.jar (Jasper 2 Compiler and Runtime)
    * jasper-el.jar (Jasper 2 EL implementation)
    * jasper-jdt.jar (Eclipse JDT 3.3 Java compiler)
    * jsp-api.jar (JSP 2.1 API)
    * servlet-api.jar (Servlet 2.5 API)
    * tomcat-coyote.jar (Tomcat connectors and utility classes)
    * tomcat-dbcp.jar (package renamed database connection pool based on Commons DBCP)

    You can make additional APIs available to all of your web applications by
    putting unpacked classes into a "classes" directory (not created by default),
    or by placing them in JAR files in the "lib" directory.

    To override the XML parser implementation or interfaces, use the endorsed
    mechanism of the JVM. The default configuration defines JARs located in
    "endorsed" as endorsed.


    ================================================================
    Web application reloading and static fields in shared libraries:
    ================================================================
    Some shared libraries (many are part of the JDK) keep references to objects
    instantiated by the web application. To avoid class loading related problems
    (ClassCastExceptions, messages indicating that the classloader
    is stopped, etc.), the shared libraries state should be reinitialized.

    Something which might help is to avoid putting classes which would be
    referenced by a shared static field in the web application classloader,
    and putting them in the shared classloader instead (JARs should be put in the
    "lib" folder, and classes should be put in the "classes" folder).


    ================
    Tomcat on Linux:
    ================
    GLIBC 2.2 / Linux 2.4 users should define an environment variable:
    export LD_ASSUME_KERNEL=2.2.5

    Redhat Linux 9.0 users should use the following setting to avoid
    stability problems:
    export LD_ASSUME_KERNEL=2.4.1

    There are some Linux bugs reported against the NIO sendfile behavior, make sure you
    have a JDK that is up to date, or disable sendfile behavior in the Connector.<br/>
    6427312: (fc) FileChannel.transferTo() throws IOException "system call interrupted"<br/>
    5103988: (fc) FileChannel.transferTo should return -1 for EAGAIN instead throws IOException<br/>
    6253145: (fc) FileChannel.transferTo on Linux fails when going beyond 2GB boundary<br/>
    6470086: (fc) FileChannel.transferTo(2147483647, 1, channel) cause "Value too large" exception<br/>


    =============================
    Enabling SSI and CGI Support:
    =============================
    Because of the security risks associated with CGI and SSI available
    to web applications, these features are disabled by default.

    To enable and configure CGI support, please see the cgi-howto.html page.

    To enable and configue SSI support, please see the ssi-howto.html page.


    ======================
    Security manager URLs:
    ======================
    In order to grant security permissions to JARs located inside the
    web application repository, use URLs of of the following format
    in your policy file:

    file:${catalina.home}/webapps/examples/WEB-INF/lib/driver.jar

    ============================
    Symlinking static resources:
    ============================
    By default, Unix symlinks will not work when used in a web application to link
    resources located outside the web application root directory.

    This behavior is optional, and the "allowLinking" flag may be used to disable
    the check.

    =========================
    Enabling invoker servlet:
    =========================
    Starting with Tomcat 4.1.12, the invoker servlet is no longer available by
    default in all webapps. Enabling it for all webapps is possible by editing
    $CATALINA_HOME/conf/web.xml to uncomment the "/servlet/*" servlet-mapping
    definition.

    Using the invoker servlet in a production environment is not recommended and
    is unsupported. More details are available on the Tomcat FAQ at
    http://tomcat.apache.org/faq/misc.html#invoker.

    ==============================
    Viewing the Tomcat Change Log:
    ==============================
    See changelog.html in this directory.

    ====================
    When all else fails:
    ====================
    See the FAQ
    http://tomcat.apache.org/faq/

    posted @ 2009-05-17 20:12 江蘇520| 編輯 收藏

    Fire Workflow 介紹

       2009-02-02日 Fire Workflow 的Eclispe設(shè)計(jì)器插件上傳到google code ,并有較詳細(xì)的文檔(2_通過設(shè)計(jì)器和模擬器快速了解Fire Workflow.pdf,3_各種工作流模式的實(shí)現(xiàn).pdf)。

    請到http://code.google.com/p/fireflow下載。
    鏡像:http://www.bt285.cn  http://www.5a520.cn




                     Fire workflow FAQ
    1、為什么要寫Fire Workflow
           本人從事企業(yè)MIS系統(tǒng)開發(fā)很多年頭了,感覺MIS系統(tǒng)很多領(lǐng)域都有比較好的解決方案并已成為事實(shí)標(biāo)準(zhǔn),例如Spring,Hibernate等等;然而工作流還沒有令人滿意的開源產(chǎn)品。我了解過的工作流產(chǎn)品(主要是開源的,收費(fèi)產(chǎn)品沒有什么研究,僅僅看看其白皮書而已)都存在如下毛病:


            
    • 缺乏嚴(yán)密的理論做支撐,工作流模型大多千篇一律地照搬WfMC的xpdl,       
    • 因?yàn)槿狈碚撝?,所以工作流引擎的算法有點(diǎn)七拼八湊,擴(kuò)展性也比較差。       
    • 沒有好的設(shè)計(jì)器,應(yīng)用比較困難       

           最近研究并應(yīng)用了一下JBoss的Jbpm,除了其面向圖的引擎算法讓我眼前一亮外,其他的也不是令人滿意。其引擎的擴(kuò)展性不好,表結(jié)構(gòu)太復(fù)雜,在大數(shù)據(jù)量系統(tǒng)中,性能令人堪憂。
           鑒于此,我動(dòng)手寫了一個(gè)Fire Workflow,拋磚引玉。

    2、Fire Workflow的定位
           我從來不認(rèn)為工作流可以“自定義”,所以Fire Workflow是面向開發(fā)人員的。Fire Workflow和Spring、Hibernate一樣,是一個(gè)或幾個(gè)普普通通的jar包,嵌入到系統(tǒng)中,用以解決系統(tǒng)開發(fā)中工作流領(lǐng)域的問題。
           因?yàn)镕ire Workflow是面向開發(fā)人員的,所以在下面兩個(gè)方面花了較大功夫。
          

            
    • 流程設(shè)計(jì)器。Fire Workflow用于幫助開發(fā)人員解決系統(tǒng)中的流程問題,所以好的流程設(shè)計(jì)器有助于開發(fā)人員提高開發(fā)效率。Fire Workflow設(shè)計(jì)器和主流IDE緊密集成(目前有Eclipse插件和NetBeans插件),使得開發(fā)人員開發(fā)調(diào)試流程就像開發(fā)調(diào)試一個(gè)普通java類一樣簡單。       
    • 工作流引擎。Fire Workflow引擎設(shè)計(jì)充分考慮擴(kuò)展性,因?yàn)闃I(yè)務(wù)系統(tǒng)的需求五花八門,所以一個(gè)寫的很死的引擎必然沒有生命力。Fire Workflow引擎的各種服務(wù)都可以擴(kuò)展或者替換。
           Fire Workflow雖然不認(rèn)可流程“自定義”需求,但是并不否認(rèn)用戶有調(diào)整業(yè)已存在的流程的需求,而且這種需求還比較普遍。所以Fire Workflow計(jì)劃提供Web界面,讓最終用戶的系統(tǒng)管理員在一定范圍內(nèi)調(diào)整流程。

    3、Fireflow的特點(diǎn)
           理論嚴(yán)密
           Fire Workflow以Petri Net作為理論基礎(chǔ),流程的順序流轉(zhuǎn)、分支、匯聚、跳轉(zhuǎn)等算法都有定義/定理為依據(jù)。
           設(shè)計(jì)合理
           Fire workflow將工作流引擎的職責(zé)分解委派到各種服務(wù)中,每中服務(wù)都可以被擴(kuò)展或者替換。
           應(yīng)用簡單
            Fire workflow的API以及數(shù)據(jù)庫表結(jié)構(gòu)非常簡單。

           性能優(yōu)良
           Fire workflow著重在流程實(shí)例的數(shù)據(jù)量,數(shù)據(jù)庫IO等方面進(jìn)行性能優(yōu)化。


    4、Fireflow的構(gòu)成
           Fire Workflow由模型、引擎、設(shè)計(jì)器(包含模擬器)三部分組成。
          
    • 模型部分規(guī)定了流程定義文件的各種元素及其相互關(guān)系,例如流程(WorkflowProcess)、活動(dòng)(Activity)、轉(zhuǎn)移(Transition)、開始節(jié)點(diǎn)(StartNode)、結(jié)束節(jié)點(diǎn)(EndNode)、同步器(Synchronizer)。模型部分的實(shí)現(xiàn)在org-fireflow-model.jar中。
    • 引擎讀取流程定義文件并解釋執(zhí)行。引擎提供一組對象和相關(guān)的API供外部系統(tǒng)調(diào)用,如流程實(shí)例(ProcessInstance)、任務(wù)實(shí)例(TaskInstance)、工單(WorkItem)、事件等等。引擎部分的實(shí)現(xiàn)在org-fireflow-engine.jar中。
    • 設(shè)計(jì)器編輯并輸出流程定義文件。Fire Workflow的設(shè)計(jì)器附帶了強(qiáng)大的模擬器,可以在設(shè)計(jì)時(shí)模擬流程的執(zhí)行,從而檢查流程定義的正確性。

           此處附帶解釋一下我的一個(gè)觀點(diǎn):我認(rèn)為,流程定義文件和java文件一樣,是應(yīng)用系統(tǒng)源代碼的一部分。因此,流程設(shè)計(jì)器做成了當(dāng)前流行的IDE的插件,便于開發(fā)人員進(jìn)行流程開發(fā)。而且每個(gè)流程單獨(dú)一個(gè)定義文件,就像每個(gè)java類在通常情況下單獨(dú)一個(gè)文件一樣。

    5、Fire Workflow的流程定義語言為什么不使用Xpdl
           本人認(rèn)為Xpdl好看不好用。
           相較于Xpdl,F(xiàn)ire workflow 的流程定義語言主要做了如下變動(dòng)。
          
    • 廢除Package的概念:在我看來一個(gè)流程一個(gè)文件比較方便開發(fā),流程定義文件在某中程度上和java類文件一樣,是系統(tǒng)源代碼的一部分
    • 廢除全局和局部的概念:在xpdl中有全局DataField和局部DataField區(qū)分,實(shí)際上其作用不大。
    • 增加同步器節(jié)點(diǎn):Fire Workflow將流程中的節(jié)點(diǎn)分成兩類 ,即Acitivyt和Sychronizer(Start Node和End Node是synchronizer的特例)。這兩類節(jié)點(diǎn)分別代表了業(yè)務(wù)子系統(tǒng)的邏輯操作和工作流子系統(tǒng)的邏輯操作。
    • 增加Task元素:一個(gè)Activity可以包含多個(gè)Task,Task代表實(shí)際的業(yè)務(wù)邏輯。

    posted @ 2009-05-16 22:04 江蘇520| 編輯 收藏

    集成struts2 spring hibernate的實(shí)例

    集成struts,spring,hibernate時(shí),對于初學(xué)者來說最大的麻煩就其繁瑣的xml配置文件。現(xiàn)在三者都對基于注解的配置提供了良好的支持。在struts2中,使用convent plugin,得益于annotation和規(guī)約,配置過程得以大大減少。在spring2.5也可以使用@Autowired,進(jìn)行注入,使用@Controller,@Service,@Repository注解,自動(dòng)定義bean,還支持annotation風(fēng)格的聲明式事務(wù)支持,以及aspectJ類似的AOP。hibernate也可以使用JPA標(biāo)準(zhǔn)注解定義實(shí)體描述,避免使用mapping文件。

    當(dāng)然,對于annotation和xml風(fēng)格的配置,誰更好,更多依賴個(gè)人興趣。但使用annotation確實(shí)減少了很多配置工作量。本文采用annotation風(fēng)格的配置,以TaskList為例子講解struts2 spring hibernate的集成。項(xiàng)目文件見附件。

    一:配置struts2。

    首先在web.xml文件中配置filter

    <filter>  
        
    <filter-name>struts2</filter-name>  
        
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>  
    </filter>  
      
    <filter-mapping>  
        
    <filter-name>struts2</filter-name>  
        
    <url-pattern>*.action</url-pattern>  
    </filter-mapping>  

    然后在classpath中創(chuàng)建struts.xml配置文件。

    <?xml version="1.0" encoding="UTF-8"?>  
    <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"   
            " http://www.bt285.cn /dtds/struts-2.1.dtd"
    >  
    <struts>  
        
    <constant name="struts.devMode" value="true" />  
        
    <constant name="struts.convention.default.parent.package" value="default-package" />  
        
    <constant name="struts.convention.package.locators" value="action" />  
        
    <package name="default-package" extends="convention-default">  
      
            
    <default-action-ref name="index" />  
      
            
    <action name="index"  >  
                
    <result>http://www.5a520.cn /WEB-INF/content/index.jsp</result>  
            
    </action>    
      
        
    </package>  
    </struts>  

     

    struts.devMode屬性,配置啟用調(diào)試,將有更多的錯(cuò)誤信息輸出,便于排錯(cuò)。struts.convention.default.parent.package屬性,指定使用注解標(biāo)注的控制器的默認(rèn)包??梢栽谶@個(gè)默認(rèn)包中配置全局信息。

    struts.convention.package.locators屬性,為查找控制器包路徑的關(guān)鍵字。如com.mycompany.action,com.mycompany.action.user,都會(huì)被struts2掃描。里面有繼承至Action的類,或類名以Action結(jié)尾的類,都會(huì)做為Action處理。

    <default-action-ref name="index" />指定了默認(rèn)action,如果指定的action不存在則訪問該action。

    把struts2-spring-plugin-2.1.6.jar添加到classpath中,struts2會(huì)自動(dòng)掃描struts-plugin.xml文件,該文件自動(dòng)注冊了com.opensymphony.xwork2.ObjectFactory,完成和spring的集成。

    二:配置spring

    在web.xml中加入ContextLoaderListener,用以啟動(dòng)spring容器。用contextConfigLocation指定spring配置文件路徑,可以使用*通配符結(jié)尾。

     

    <listener>  
        
    <listener-class>org.springframework.web.context.ContextLoaderListener   
        
    </listener-class>  
    </listener>  
    <context-param>  
        
    <param-name>contextConfigLocation</param-name>  
        
    <param-value>http://www.bt285.cn :/applicationContext.xml</param-value>  
    </context-param> 

    配置applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>  
    <beans>  
      
        
    <context:component-scan base-package="persistence,service,action,aop"/>  
           
        
    <aop:aspectj-autoproxy  />  
      
        
    <tx:annotation-driven transaction-manager="transactionManager" />  
           
        
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" >  
            
    <property name="sessionFactory" ref="sessionFactory" />  
        
    </bean>  
      
        
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >       
            
    <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        
    </bean>  
      
        
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate" >  
            
    <property name="sessionFactory" ref="sessionFactory" />  
        
    </bean>  
           
    </beans>  

    <context:component-scan />指定Bean掃描的包,多個(gè)包逗號(hào)隔開,任何標(biāo)注了@Component,@Controller,@Service,@Repository的類,都會(huì)被自動(dòng)識(shí)別為bean。

    <aop:aspectj-autoproxy />聲明aspectj動(dòng)態(tài)代理,啟用注解驅(qū)動(dòng)的aspectj配置。

    <tx:annotation-driven />啟用注解驅(qū)動(dòng)的聲明事務(wù)支持。

    然后定義了sessionFactory和transactionManager,hibernateTemplate用來注入到Dao中,取代繼承的方式使用spring對hibernate的集成支持。

    三:hibernate配置

    hibernate配置獨(dú)立配置,方便修改。

     

    <?xml version="1.0" encoding="UTF-8"?>  
    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
    <hibernate-configuration>  
        
    <session-factory>  
            
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>  
            
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>  
            
    <property name="hibernate.connection.url">jdbc:mysql:// http://www.feng123.com :3306/sshdemo</property>  
            
    <property name="hibernate.connection.username">root</property>  
            
    <property name="hibernate.connection.password">root</property>  
            
    <property name="hibernate.hbm2ddl.auto">update</property>  
            
    <property name="hibernate.show_sql">true</property>  
            
    <property name="hibernate.format_sql">true</property>  
            
    <property name="hibernate.current_session_context_class">thread</property>         
      
            
    <!-- 最大連接數(shù) -->  
            
    <property name="hibernate.c3p0.max_size">20</property>  
            
    <!-- 最小連接數(shù) -->  
            
    <property name="hibernate.c3p0.min_size">5</property>  
            
    <!-- 獲得連接的超時(shí)時(shí)間,如果超過這個(gè)時(shí)間,會(huì)拋出異常,單位毫秒 -->  
            
    <property name="hibernate.c3p0.timeout">120</property>  
            
    <!-- 最大的PreparedStatement的數(shù)量 -->  
            
    <property name="hibernate.c3p0.max_statements">100</property>  
            
    <!-- 每隔120秒檢查連接池里的空閑連接 ,單位是秒-->  
            
    <property name="hibernate.c3p0.idle_test_period">120</property>  
            
    <!-- 當(dāng)連接池里面的連接用完的時(shí)候,C3P0一下獲取的新的連接數(shù) -->  
            
    <property name="hibernate.c3p0.acquire_increment">2</property>  
            
    <!-- 每次都驗(yàn)證連接是否可用 -->  
            
    <property name="hibernate.c3p0.validate">true</property>  
      
            
    <mapping class="domain.Task" />  
        
    </session-factory>  
    </hibernate-configuration>  


     

     

    posted @ 2009-05-14 17:40 江蘇520| 編輯 收藏

    主站蜘蛛池模板: 亚洲国产中文字幕在线观看| eeuss免费影院| 香蕉视频在线观看免费国产婷婷| 亚洲国产精品美女| 97热久久免费频精品99| 亚洲狠狠狠一区二区三区| 91精品免费久久久久久久久| 亚洲黄色在线观看视频| 久久久久免费精品国产小说| 亚洲成人免费在线| 精品无码AV无码免费专区| 久久精品国产亚洲AV嫖农村妇女| 久久免费的精品国产V∧| 亚洲精品综合久久中文字幕 | 免费无码又爽又刺激网站直播| 亚洲精品夜夜夜妓女网| 免费av片在线观看网站| 亚洲国产精品线在线观看| 久久久精品2019免费观看 | 任你躁在线精品免费| 亚洲人成电影在在线观看网色| 在线观看免费中文视频| 亚洲大片免费观看| 好吊妞在线成人免费| 精品亚洲成A人在线观看青青| 亚洲国产成人久久精品99 | 亚洲熟妇自偷自拍另欧美| 卡1卡2卡3卡4卡5免费视频| 真正全免费视频a毛片| 国产偷窥女洗浴在线观看亚洲| 两性色午夜视频免费网| 日韩精品一区二区亚洲AV观看| 精品免费久久久久久久| 亚洲精华国产精华精华液网站| 亚洲精品美女久久久久99小说| 你好老叔电影观看免费| 亚洲经典在线观看| 国内自产拍自a免费毛片| 欧洲精品码一区二区三区免费看| 亚洲精品成人片在线播放| 在线观看www日本免费网站|