藍(lán)晶石

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            10 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

          2005年12月29日 #

          一. 什么是AJAX?

            這個(gè)名字代表了異步JavaScript+XMLHTTPRequest,并且意味著你可以在基于瀏覽器的JavaScript和服務(wù)器之間建立套接字通訊。其實(shí)AJAX并不是一種新技術(shù),而是已經(jīng)成功地用于現(xiàn)代瀏覽器中的若干成功技術(shù)的可能性組合。所有的AJAX應(yīng)用程序?qū)崿F(xiàn)了一種“豐富的”UI——這是通過JavaScript操作HTML文檔對(duì)象模型并且經(jīng)由XMLHttpRequest實(shí)現(xiàn)的精確定位的數(shù)據(jù)檢索來實(shí)現(xiàn)的。典型的示例AJAX應(yīng)用程序是Google Labs(http://labs.google.com)的Google Maps和Google Suggest。這些應(yīng)用程序現(xiàn)場(chǎng)監(jiān)視用戶輸入并且提供實(shí)時(shí)的頁面更新。最重要的是,在用戶通過地圖導(dǎo)航或輸入一個(gè)查找字符串的同時(shí),這些事件不需要刷新頁面。

            事實(shí)上,支持這些令人感到驚訝的應(yīng)用的技術(shù)已經(jīng)出現(xiàn)一段時(shí)間了,盡管它們要求復(fù)雜的技能以及使用瀏覽器的技巧。一些專利產(chǎn)品就提供了相似的能力——如Macromedia Flash插件,Java Applets或.NET運(yùn)行時(shí)——在達(dá)到實(shí)用上已經(jīng)有一段時(shí)間了。把一種可與服務(wù)器通話的腳本組件引入到瀏覽器中的思想早在IE 5.0中就已經(jīng)存在。Firefox和其它流行的瀏覽器也加入到瀏覽器大軍中并以一種內(nèi)置對(duì)象形式支持XMLHTTPRequest。隨著跨平臺(tái)瀏覽器的出現(xiàn),這些技術(shù)得到了認(rèn)可并在2004年3月一家稱為Adaptive Path的公司中正式提出了AJAX。

            簡(jiǎn)而言之,由于來自于Google的支持和安裝了一點(diǎn)可用的瀏覽器技術(shù),加上為了一種"更好的用戶體驗(yàn)",每個(gè)人都在把客戶端技術(shù)添加到Web應(yīng)用程序上。

            二. AJAX與傳統(tǒng)應(yīng)用程序的區(qū)別

            一個(gè)傳統(tǒng)Web應(yīng)用程序模型實(shí)際上是一種基本的事件——用戶被迫提交表單以實(shí)現(xiàn)頁面交換。也就是說,表單提交和頁面?zhèn)魉蜔o法得到保證:還有更壞的情形——用戶需要再次點(diǎn)擊。這與AJAX截然不同-——數(shù)據(jù)跨過線路而不是完整的HTML頁面?zhèn)鬏敗_@種數(shù)據(jù)交換是經(jīng)由特定的瀏覽器對(duì)象:XMLHttpRequest實(shí)現(xiàn)的;再由適當(dāng)?shù)倪壿媮硖幚砻總€(gè)數(shù)據(jù)請(qǐng)求的結(jié)果,頁面的特定區(qū)域而不是完整的頁面被更新。結(jié)果是更快的速度,更少的擁擠和更好的信息傳送控制。

            傳統(tǒng)型"click-refresh"Web應(yīng)用程序強(qiáng)迫用戶中斷工作過程而等待頁面的重裝。通過引入AJAX技術(shù),一個(gè)客戶端腳本能夠異步地與服務(wù)器通話,而用戶仍能保持輸入數(shù)據(jù)。除了對(duì)用戶透明之外,這樣的異步意味著服務(wù)器可以有更多時(shí)間來處理請(qǐng)求。

            傳統(tǒng)Web應(yīng)用程序把所有的處理代理到服務(wù)器并且強(qiáng)迫服務(wù)器進(jìn)行狀態(tài)管理。AJAX允許靈活劃分應(yīng)用程序邏輯以及客戶和服務(wù)器之間的狀態(tài)管理。這就消除了一種"click-refresh"依賴性并且提供更好的服務(wù)器可伸縮性。當(dāng)該狀態(tài)存儲(chǔ)在客戶端,你就不必跨越服務(wù)器來維持會(huì)話或保存/結(jié)束狀態(tài)-其使用期限是由客戶端來定義的。

            三. AJAX——分布式的MVC

            盡管AJAX應(yīng)用程序依靠JavaScript來實(shí)現(xiàn)描述層,然而處理能力和知識(shí)庫(kù)仍然存在于服務(wù)器上。此時(shí),AJAX應(yīng)用程序大量的與J2EE服務(wù)器通訊——把數(shù)據(jù)輸入/輸出Web服務(wù)和servlets。具有基于AJAX的描述層的J2EE應(yīng)用程序和標(biāo)準(zhǔn)J2EE應(yīng)用程序之間的區(qū)別首先在于,MVC是通過線路分布的。通過使用AJAX,視圖是本地的,而模型和控制器是分布式的——這使得開發(fā)者能夠靈活地決定哪些部件會(huì)是基于客戶端的。具體地說,本地視圖通過巧妙地操作HTML DOM而生成圖形;控制器局部地處理用戶輸入并且根據(jù)開發(fā)者的判斷擴(kuò)展到服務(wù)器的處理——經(jīng)由HTTP請(qǐng)求(Web服務(wù),XML/RPC或其它)實(shí)現(xiàn);模型的遠(yuǎn)程部分是根據(jù)客戶端需要而下載的以達(dá)到實(shí)時(shí)更新客戶端頁面;并且狀態(tài)是在客戶端收集的。

            在以后的AJAX文章中,我們將比較深入地討論這里的每一種組件并提供有關(guān)它們聯(lián)合在一起進(jìn)行應(yīng)用的示例。現(xiàn)在,先不多說,讓我們?cè)敿?xì)地分析一個(gè)簡(jiǎn)單的AJAX示例。

            四. 郵政區(qū)號(hào)校驗(yàn)和查詢

            我們將創(chuàng)建一個(gè)包含三個(gè)INPUT字段(Zip,City和State)的HTML頁面。我們將保證,只要用戶輸入郵政區(qū)號(hào)的前三個(gè)數(shù)字,該頁面上的字段就會(huì)用第一個(gè)匹配的狀態(tài)值填充。一旦用戶輸入了所有五位郵政區(qū)號(hào)數(shù),我們將立即決定和填充相應(yīng)的城市。如果郵政區(qū)號(hào)無效(在服務(wù)器的數(shù)據(jù)庫(kù)沒有找到),那么我們將把郵政區(qū)號(hào)的邊界設(shè)置為紅色。這樣的可視化線索有助于用戶并且在現(xiàn)代瀏覽器中已經(jīng)成為一種標(biāo)準(zhǔn)(作為一實(shí)例,當(dāng)Firefox找到一個(gè)HTML頁面中的匹配關(guān)鍵字時(shí),它會(huì)高亮與你在瀏覽器查找域輸入的內(nèi)容一致的部分)。

            讓我們首先創(chuàng)建一個(gè)簡(jiǎn)單的包含三個(gè)INPUT字段的HTML:zip,city和state。請(qǐng)注意,一旦一個(gè)字符輸入進(jìn)郵政區(qū)號(hào)字段域中,即調(diào)用方法zipChanged()。JavaScript函數(shù)zipChanged()(見下)在當(dāng)zip長(zhǎng)度為3時(shí)調(diào)用函數(shù)updateState(),而在當(dāng)zip長(zhǎng)度為5時(shí)調(diào)用函數(shù)up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一個(gè)函數(shù)ask()。

          Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
          style="width:60"/>
          City: <input id="city" disabled maxlength="32" style="width:160"/>
          State:<input id="state" disabled maxlength="2" style="width:30"/>
          <script src="xmlhttp.js"></script>
          <script>
          var zipField = null;
          function zipChanged(){
          zipField = document.getElementById("zipcode")
          var zip = zipField.value;
          zip.length == 3?updateState(zip):zip.length == 5?updateCity(zip):"";
          }
          function updateState(zip) {
           var stateField = document.getElementById("state");
           ask("resolveZip.jsp?lookupType=state&zip="+zip, stateField, zipField);
          }
          function updateCity(zip) {
           var cityField = document.getElementById("city");
           ask("resolveZip.jsp? lookupType=city&zip="+zip, cityField, zipField);
          }
          </script>


              函數(shù)ask()與服務(wù)器進(jìn)行通訊并分配一個(gè)回調(diào)函數(shù)來處理服務(wù)器的響應(yīng)(見下列代碼)。后面,我們將分析具有雙重特點(diǎn)的resolveZip.jsp的內(nèi)容-它根據(jù)zip字段中的字符數(shù)查找city或state信息。重要的是,ask()使用了具有異步特點(diǎn)的XmlHttpRequest,這樣填充state和city字段或著色zip字段邊界就可以不必減慢數(shù)據(jù)入口而得以實(shí)現(xiàn)。首先,我們調(diào)用request.open()-它用服務(wù)器打開套接字頻道,使用一個(gè)HTTP動(dòng)詞(GET或POST)作為第一個(gè)參數(shù)并且以數(shù)據(jù)提供者的URL作為第二個(gè)參數(shù)。request.open()的最后一個(gè)參數(shù)被設(shè)置為true-它指示該請(qǐng)求的異步特性。注意,該請(qǐng)求還沒有被提交。隨著對(duì)request.send()的調(diào)用,開始提交-這可以為POST提供任何必要的有效載荷。在使用異步請(qǐng)求時(shí),我們必須使用request.onreadystatechanged屬性來分配請(qǐng)求的回調(diào)函數(shù)。(如果請(qǐng)求是同步的話,我們應(yīng)該能夠在調(diào)用request.send之后立即處理結(jié)果,但是我們也有可能阻斷用戶,直到該請(qǐng)求完成為止。)

          HTTPRequest = function () {
           var xmlhttp=null;
           try {
            xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
           } catch (_e) {
            try {
             xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            } catch (_E) { }
           }
           if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
            try {
             xmlhttp = new XMLHttpRequest();
            } catch (e) {
             xmlhttp = false;
            }
           }
           return xmlhttp;
          }

          function ask(url, fieldToFill, lookupField) {
           var http = new HTTPRequest();
           http.open("GET", url, true);
           http.onreadystatechange = function (){ handleHttpResponse(http, fieldToFill,lookupField)};
           http.send(null);
          }

          function handleHttpResponse(http, fieldToFill, lookupField) {
           if (http.readyState == 4) {
            result = http.responseText;
            if ( -1 != result.search("null") ) {
             lookupField.style.borderColor = "red";
             fieldToFill.value = "";
            } else {
             lookupField.style.borderColor = "";
             fieldToFill.value = result;
            }
           }
          }

            為ask()所使用的HttpRequest()函數(shù)(見上)是一跨瀏覽器的XMLHTTPRequest的一個(gè)實(shí)例的構(gòu)造器;稍后我們將分析它。到目前為止,請(qǐng)注意對(duì)于handleResponse()的調(diào)用是如何用一匿名函數(shù)包裝的-這個(gè)函數(shù)是function(){handleHttpResponse(http,fieldToFill, lookupField)}。

            該函數(shù)的代碼是動(dòng)態(tài)創(chuàng)建的并且在每次我們給http.onreadstatechange屬性賦值時(shí)被編譯。結(jié)果,JavaScript創(chuàng)建一個(gè)指向上下文(所有的變量都可以存取正在結(jié)束的方法-ask())的指針。這樣以來,匿名函數(shù)和handleResponse()就能夠被保證充分存取所有的上下文宿主的變量,直至到匿名函數(shù)的參考被垃圾回收站收集為止。換句話說,無論何時(shí)我們的匿名函數(shù)被調(diào)用,它都能無縫地參考request,fieldToFill和lookupField變量,就象它們是全局的一樣。而且,每次ask()調(diào)用都將創(chuàng)建環(huán)境的一個(gè)獨(dú)立拷貝,并且此時(shí)這些變量中保存有該函數(shù)將結(jié)束時(shí)的值。

            現(xiàn)在,讓我們分析一下函數(shù)handleResponse()。既然它能夠在請(qǐng)求處理的不同狀態(tài)下激活,那么該函數(shù)將忽略所有的情形-除了該請(qǐng)求處理完成之外-這相應(yīng)于request.readyState屬性等于4("Completed")。此時(shí),該函數(shù)讀取服務(wù)器的響應(yīng)文本。與它的名字所暗示的相反,XmlHttpRequest的輸入和輸出都不必限于XML格式。特別地,我們的resolveZip.jsp(見源碼中的列表1)返回普通文本。如果返回值為"unknown",那么該函數(shù)將假定郵政區(qū)號(hào)是無效的并且把查找字段(zip)邊界顏色置為紅色。否則,返回值被用于填充字段state或city,并且zip的邊界被賦予一種缺省顏色。

          XMLHttpRequest-傳輸對(duì)象

            讓我們返回到我們的XMLHTTPRequest的跨瀏覽器實(shí)現(xiàn)。最后一個(gè)列表包含一個(gè)HttpRequest()函數(shù)-它向上兼容于IE5.0和Mozilla 1.8/FireFox。為簡(jiǎn)化起見,我們只創(chuàng)建一個(gè)微軟XMLHTTPRequest對(duì)象,而且如果創(chuàng)建失敗,我們假定它是Firefox/Mozilla。

            該函數(shù)的核心是XMLHTTPRequest-這是一個(gè)本機(jī)瀏覽器對(duì)象,它為包括HTTP協(xié)議的任何東西與服務(wù)器之間的通訊提供方便。它允許指定任何HTTP動(dòng)詞,頭部和有效載荷,并且能夠以異步或同步方式工作。不需要下載也不需要安裝任何插件-盡管在IE的情形下,XMLHTTPRequest是一個(gè)集成到瀏覽器內(nèi)部的ActiveX。因而,"Run ActiveX Control and Plugins"默認(rèn)IE權(quán)限應(yīng)該正好適合使用它。

            最重要的是,XMLHTTPRequest允許一個(gè)到服務(wù)器的RPC風(fēng)格的編程查詢而不需要任何頁面刷新。它以一種可預(yù)測(cè)的,可控制的方式來實(shí)現(xiàn)此-提供了到HTTP協(xié)議的所有細(xì)節(jié)的完整存取-包括頭部和數(shù)據(jù)的任何定制格式。在以后的文章中,我們將向你展示其它一些業(yè)界協(xié)議-你可以在這些傳輸協(xié)議(如Web服務(wù)和XML-RPC)之上運(yùn)行-它們極大地簡(jiǎn)化大規(guī)模應(yīng)用程序的開發(fā)和維護(hù)。

            五.服務(wù)器端邏輯

            最后,服務(wù)器端的resolveZip.jsp被從函數(shù)ask()中調(diào)用(見所附源碼中的列表1)。這個(gè)resolveZip.jsp在兩種由當(dāng)前的郵政區(qū)號(hào)長(zhǎng)度所區(qū)分的獨(dú)立的場(chǎng)所下被調(diào)用(見zipChanged()函數(shù))。請(qǐng)求參數(shù)lookupType的值或者是state或者是city。為簡(jiǎn)化起見,我們將假定,兩個(gè)文件state.properties和city.properties都位于服務(wù)器中C驅(qū)動(dòng)器的根目錄下。resolveZip.jsp邏輯負(fù)責(zé)用適當(dāng)?shù)念A(yù)裝載的文件返回查找值。

            我們的支持AJAX的頁面現(xiàn)在已經(jīng)準(zhǔn)備好了。

            六.遠(yuǎn)程腳本技術(shù)-一種可選方法

            一些更舊的AJAX實(shí)現(xiàn)是基于所謂的遠(yuǎn)程腳本技術(shù)。這種思想是,用戶的行為導(dǎo)致經(jīng)由IFRAME對(duì)服務(wù)器進(jìn)行查詢,而服務(wù)器用JavaScript作出響應(yīng),該腳本一旦到達(dá)客戶端立即被執(zhí)行。這與XMLHttpRequest方法相比存在較大的區(qū)別,在后者情況下,服務(wù)器響應(yīng)數(shù)據(jù)而客戶端解釋數(shù)據(jù)。其好處是這種解決方案支持更舊的瀏覽器。

            基于IFRAME示例的HTML部分(見所附源碼中的列表2)與我們?cè)赬MLHTTPRequest場(chǎng)合下所用的極相似,但是這次我們將引入另外一個(gè)IFRAME元素-controller:

          Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
          style="width:60" size="20"/>
          City: <input id="city" disabled maxlength="32" style="width:160" size="20"/>
          State:<input id="state" disabled maxlength="2" style="width:30" size="20"/>
          <iframe id="controller" style="visibility:hidden;width:0;height:0"></iframe>

            我們保持每次擊鍵都調(diào)用zipChanged()一次,但是這一次,從zipChanged()中被調(diào)用的函數(shù)ask()(見所附源碼中的列表3)負(fù)責(zé)設(shè)置IFRAME的src屬性,而不是調(diào)用一個(gè)XMLHTTPRequest:

          function ask(url, fieldToFill, lookupField){
           var controller = document.getElementById("controller");
           controller.src= url+"&field="+fieldToFill.id+"&zip="+lookupField.id;
          }

            服務(wù)器端邏輯由一個(gè)粗略的resolveZip.jsp(見所附源碼中的列表4)所描述。它與它的XMLHTTPRequest對(duì)應(yīng)物相區(qū)別-它返回JavaScript語句,這些語句設(shè)置變量字段lookup和city的全局值,而且一旦它到達(dá)瀏覽器即從全局窗口的執(zhí)行上下文中調(diào)用函數(shù)response()。

            函數(shù)response()是一修改版本的handleResponse()-這一函數(shù)可以免于處理未完成的請(qǐng)求(詳見本文所附源碼中的列表2)。

            七. 難題

            為簡(jiǎn)化起見,讓我們"俯看"一下在我們的示例代碼中的一些重要的問題:

            1.事實(shí)-XMLHTTPRequest對(duì)象實(shí)例和回調(diào)函數(shù)調(diào)用在被使用以后并沒被破壞-在每次調(diào)用后這有可能導(dǎo)致內(nèi)存泄漏。適當(dāng)編寫的代碼應(yīng)該破壞或重用對(duì)象池中的這些實(shí)例。而且,客戶端必須使用與服務(wù)器軟件相同的對(duì)象管理技術(shù)。

            2.在大多數(shù)情況下,錯(cuò)誤往往得不到有效處理。例如,在方法ask()中對(duì)request.open()的調(diào)用可能引發(fā)一個(gè)異常,這是必須要捕獲和處理的,即使在瀏覽器中沒有設(shè)置JavaScript異常自動(dòng)捕獲功能。而handleResponse()函數(shù)又是另外一個(gè)例子。它必須要為可能的服務(wù)器端和通訊錯(cuò)誤而檢查headers和responseText值。在發(fā)生錯(cuò)誤的情況下,它必須盡力恢復(fù)并/或者報(bào)告錯(cuò)誤。正確開發(fā)的AJAX應(yīng)用程序要盡可能避免"提交"松散的數(shù)據(jù),因?yàn)橥嬖诰€路斷開和其它低級(jí)通訊的問題-所以這些程序必須建立一個(gè)強(qiáng)壯的和自恢復(fù)的框架為此提供支持。

            3.當(dāng)前服務(wù)器端框架提供相當(dāng)多的功能-它們可以與一種自由刷新方法和諧相處。例如,讓我們考慮一個(gè)定制的在指定時(shí)間內(nèi)的服務(wù)器端認(rèn)證的問題。在這種情況下,我們必須攔截到XMLHTTPRequest調(diào)用的安全系統(tǒng)響應(yīng),顯示登錄屏幕,然后在用戶被認(rèn)證后重新發(fā)出請(qǐng)求。

            所有的這些問題只是一些典型的用低級(jí)API工作的任何應(yīng)用程序代碼,而且所有這些問題都能被解決。好消息是,解決這些問題所需要的技術(shù)十分相似于大多數(shù)Java開發(fā)技術(shù),如Web服務(wù),定制標(biāo)簽和XML/XSLT。唯一的區(qū)別在于,現(xiàn)在這些技術(shù)以下列形式用于客戶端:

            ·Web服務(wù)-使用SOAP/REST/RPC等簡(jiǎn)單通訊標(biāo)準(zhǔn)

            ·客戶端定制標(biāo)簽-打包豐富的客戶端控件并集成AJAX功能

            ·數(shù)據(jù)操作-基于XML和基于XSLT技術(shù)

            八. 小結(jié)

            AJAX方法能夠向人們提供一種與桌面應(yīng)用程序相同的豐富的互聯(lián)網(wǎng)體驗(yàn)。但是,我們必須有選擇地使用AJAX技術(shù),如當(dāng)你仍在線購(gòu)物時(shí),你絕對(duì)不想讓你的信用卡通過后臺(tái)處理就悄悄地開始付款。AJAX會(huì)成為一種持續(xù)的動(dòng)力嗎?我們當(dāng)然希望這樣。在過去的五年時(shí)間內(nèi)我們一直在努力開發(fā)AJAX應(yīng)用程序并且能證明它是健全并且很有效的。然而,它要求一個(gè)開發(fā)者必須精通大量技術(shù)而不是在傳統(tǒng)的"click-refresh"Web應(yīng)用程序中所使用的那些。

          posted @ 2005-12-29 17:10 kyanite 閱讀(219) | 評(píng)論 (0)編輯 收藏

          與任何技術(shù)一樣,使用Ajax在相當(dāng)多的方面都可能范錯(cuò)誤。我在這兒討論的問題目前都缺少解決方案,并將會(huì)隨著Ajax的成熟而解決或提高。隨著開發(fā) Ajax應(yīng)用經(jīng)驗(yàn)的不斷獲取,開發(fā)者社區(qū)中將會(huì)出現(xiàn)最好的實(shí)踐經(jīng)驗(yàn)與指導(dǎo)方針。

            1、XMLHttpRequest的有效性

            Ajax開發(fā)者面對(duì)的一個(gè)最大問題是當(dāng)XMLHttpRequest不可用時(shí)如何反應(yīng)。雖然大部分現(xiàn)代瀏覽器支持XMLHttpRequest,但還是有少量的用戶,他們的瀏覽器不能支持,或由于瀏覽器安全設(shè)置而阻止對(duì)XMLHttpRequest的使用。若你的Web應(yīng)用發(fā)布于公司內(nèi)部的 Intranet上,你很可能可以指定支持哪種瀏覽器,并可以確保XMLHttpRequest是可用的。若你在公共WEB上發(fā)布,則你必須意識(shí)到由于假定XMLHttpRequest是可用的,所有就阻止了老瀏覽器、手持設(shè)備瀏覽器等等用戶來使用你的系統(tǒng)。

            然而,你應(yīng)該盡力保證應(yīng)用系統(tǒng)“正常降級(jí)”使用,在系統(tǒng)中保留適用于不支持XMLHttpRequest的瀏覽器的功能。在購(gòu)物車?yán)又校詈玫姆椒ㄊ怯幸粋€(gè)Add to Cart按鈕,可以進(jìn)行常規(guī)的提交處理,并刷新頁面來反映購(gòu)物車狀態(tài)的變化。Ajax行衛(wèi)可以在頁面被載入時(shí)通過JavaScript添加到頁面中,只在 XMLHttpRequest可用的情況下,為每個(gè)Add to Cart按鈕加上JavaScript處理函數(shù)。另一個(gè)方法是在用戶登錄時(shí)檢測(cè)XMLHttpRequest,再?zèng)Q定是提供Ajax版本還是常規(guī)基于 form提交的版本。

            2、可用性考慮

            圍繞著Ajax應(yīng)用的大部分問題都是很普通的問題。例如,讓用戶知道他們的輸入已經(jīng)被注冊(cè)并處理,是很重要的,因?yàn)樵赬MLHttpRequest處理過程中并不能提供通常的漏斗旋轉(zhuǎn)光標(biāo)。一種方法是將“確認(rèn)”按扭上的文本替換為“正在更新中…”,以避免用戶在等待響應(yīng)時(shí)多次點(diǎn)擊按鈕。

            另一個(gè)問題是,用戶可能沒有注意到他們正在觀看的頁面已經(jīng)被更新。可以通過使用各種視覺技巧來將用戶的眼光吸引到頁面的更新區(qū)域。還有一個(gè)問題是通過 Ajax更新頁面打斷了瀏覽器“退回前頁”按鈕的正常工作,地址欄中的URL不能反映頁面的全部狀態(tài),并且不能使用書簽功能。參見Resource章節(jié)中列出的網(wǎng)站地址上的文章來了解更多Ajax應(yīng)用關(guān)于可用性方面的問題。

            3、服務(wù)器負(fù)載

            使用Ajax界面代替?zhèn)鹘y(tǒng)的基于form的界面可能戲劇性地增加傳遞到服務(wù)器的請(qǐng)求數(shù)量。例如,一個(gè)普通的Google搜索給服務(wù)器造成一次命中,并在用戶確認(rèn)搜索表單時(shí)發(fā)生。然而,Google Suggest,將會(huì)試圖自動(dòng)完成你的搜索詞,在用戶打字時(shí)將會(huì)往服務(wù)器發(fā)送多個(gè)請(qǐng)求。在開發(fā)一個(gè)Ajax應(yīng)用時(shí),要注意到你將會(huì)發(fā)送多少請(qǐng)求到用戶器端,以及服務(wù)器的負(fù)載指標(biāo)。你可以通過在客戶端適當(dāng)?shù)鼐彺嬲?qǐng)求、與服務(wù)器響應(yīng)來緩減負(fù)載壓力。你也應(yīng)該在設(shè)計(jì)Ajax應(yīng)用時(shí)盡量在客戶端處理更多的邏輯,而不用與服務(wù)器端通訊。

            4、處理異步

            一定要記住,沒有任何東西可以保證XMLHttpRequest將會(huì)按照它們被發(fā)送的順序來依次結(jié)束。實(shí)際上,你在設(shè)計(jì)系統(tǒng)時(shí),腦子里應(yīng)該始終假定它們不會(huì)按原來順序結(jié)束。在購(gòu)物車?yán)又校褂昧艘粋€(gè)最后更新的時(shí)間戳來保證最新的數(shù)據(jù)不會(huì)被改寫。這個(gè)非常基本的方法可以在購(gòu)物車場(chǎng)景中工作,但可能不能在其它情況下工作。在設(shè)計(jì)時(shí)刻就要考慮你該如何處理異步服務(wù)器響應(yīng)。

            結(jié)論

            你現(xiàn)在應(yīng)該對(duì)于Ajax的基本原則有了一個(gè)良好的了解,另外,你應(yīng)該理解一些更高級(jí)的隨Ajax方法而來的設(shè)計(jì)問題。創(chuàng)建一個(gè)成功的Ajax應(yīng)用需要一系列的方法—從JavaScript UI設(shè)計(jì)到服務(wù)器端架構(gòu)—但是你現(xiàn)在應(yīng)該已經(jīng)具備了需要使用到的Ajax核心知識(shí)。

          posted @ 2005-12-29 16:01 kyanite 閱讀(172) | 評(píng)論 (0)編輯 收藏

          2005年12月27日 #

          引言

          Java的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類的實(shí)例(對(duì)象)從中分配空間。Java虛擬機(jī)(JVM)的堆中儲(chǔ)存著正在運(yùn)行的應(yīng)用程序所建立的所有對(duì)象,這些對(duì)象通過newnewarrayanewarraymultianewarray等指令建立,但是它們不需要程序代碼來顯式地釋放。一般來說,堆的是由垃圾回收 來負(fù)責(zé)的,盡管JVM規(guī)范并不要求特殊的垃圾回收技術(shù),甚至根本就不需要垃圾回收,但是由于內(nèi)存的有限性,JVM在實(shí)現(xiàn)的時(shí)候都有一個(gè)由垃圾回收所管理的堆。垃圾回收是一種動(dòng)態(tài)存儲(chǔ)管理技術(shù),它自動(dòng)地釋放不再被程序引用的對(duì)象,按照特定的垃圾收集算法來實(shí)現(xiàn)資源自動(dòng)回收的功能。

          垃圾收集的意義

          C++中,對(duì)象所占的內(nèi)存在程序結(jié)束運(yùn)行之前一直被占用,在明確釋放之前不能分配給其它對(duì)象;而在Java中,當(dāng)沒有對(duì)象引用指向原先分配給某個(gè)對(duì)象的內(nèi)存時(shí),該內(nèi)存便成為垃圾。JVM的一個(gè)系統(tǒng)級(jí)線程會(huì)自動(dòng)釋放該內(nèi)存塊。垃圾收集意味著程序不再需要的對(duì)象是"無用信息",這些信息將被丟棄。當(dāng)一個(gè)對(duì)象不再被引用的時(shí)候,內(nèi)存回收它占領(lǐng)的空間,以便空間被后來的新對(duì)象使用。事實(shí)上,除了釋放沒用的對(duì)象,垃圾收集也可以清除內(nèi)存記錄碎片。由于創(chuàng)建對(duì)象和垃圾收集器釋放丟棄對(duì)象所占的內(nèi)存空間,內(nèi)存會(huì)出現(xiàn)碎片。碎片是分配給對(duì)象的內(nèi)存塊之間的空閑內(nèi)存洞。碎片整理將所占用的堆內(nèi)存移到堆的一端,JVM將整理出的內(nèi)存分配給新的對(duì)象。

          垃圾收集能自動(dòng)釋放內(nèi)存空間,減輕編程的負(fù)擔(dān)。這使Java 虛擬機(jī)具有一些優(yōu)點(diǎn)。首先,它能使編程效率提高。在沒有垃圾收集機(jī)制的時(shí)候,可能要花許多時(shí)間來解決一個(gè)難懂的存儲(chǔ)器問題。在用Java語言編程的時(shí)候,靠垃圾收集機(jī)制可大大縮短時(shí)間。其次是它保護(hù)程序的完整性, 垃圾收集是Java語言安全性策略的一個(gè)重要部份。

          垃圾收集的一個(gè)潛在的缺點(diǎn)是它的開銷影響程序性能。Java虛擬機(jī)必須追蹤運(yùn)行程序中有用的對(duì)象, 而且最終釋放沒用的對(duì)象。這一個(gè)過程需要花費(fèi)處理器的時(shí)間。其次垃圾收集算法的不完備性,早先采用的某些垃圾收集算法就不能保證100%收集到所有的廢棄內(nèi)存。當(dāng)然隨著垃圾收集算法的不斷改進(jìn)以及軟硬件運(yùn)行效率的不斷提升,這些問題都可以迎刃而解。

          垃圾收集的算法分析

          Java語言規(guī)范沒有明確地說明JVM使用哪種垃圾回收算法,但是任何一種垃圾收集算法一般要做2件基本的事情:(1)發(fā)現(xiàn)無用信息對(duì)象;(2)回收被無用對(duì)象占用的內(nèi)存空間,使該空間可被程序再次使用。

          大多數(shù)垃圾回收算法使用了根集(root set)這個(gè)概念;所謂根集就量正在執(zhí)行的Java程序可以訪問的引用變量的集合(包括局部變量、參數(shù)、類變量),程序可以使用引用變量訪問對(duì)象的屬性和調(diào)用對(duì)象的方法。垃圾收集首選需要確定從根開始哪些是可達(dá)的和哪些是不可達(dá)的,從根集可達(dá)的對(duì)象都是活動(dòng)對(duì)象,它們不能作為垃圾被回收,這也包括從根集間接可達(dá)的對(duì)象。而根集通過任意路徑不可達(dá)的對(duì)象符合垃圾收集的條件,應(yīng)該被回收。下面介紹幾個(gè)常用的算法。

          1、  引用計(jì)數(shù)法(Reference Counting Collector)

          引用計(jì)數(shù)法是唯一沒有使用根集的垃圾回收的法,該算法使用引用計(jì)數(shù)器來區(qū)分存活對(duì)象和不再使用的對(duì)象。一般來說,堆中的每個(gè)對(duì)象對(duì)應(yīng)一個(gè)引用計(jì)數(shù)器。當(dāng)每一次創(chuàng)建一個(gè)對(duì)象并賦給一個(gè)變量時(shí),引用計(jì)數(shù)器置為1。當(dāng)對(duì)象被賦給任意變量時(shí),引用計(jì)數(shù)器每次加1當(dāng)對(duì)象出了作用域后(該對(duì)象丟棄不再使用),引用計(jì)數(shù)器減1,一旦引用計(jì)數(shù)器為0,對(duì)象就滿足了垃圾收集的條件。

          基于引用計(jì)數(shù)器的垃圾收集器運(yùn)行較快,不會(huì)長(zhǎng)時(shí)間中斷程序執(zhí)行,適宜地必須 實(shí)時(shí)運(yùn)行的程序。但引用計(jì)數(shù)器增加了程序執(zhí)行的開銷,因?yàn)槊看螌?duì)象賦給新的變量,計(jì)數(shù)器加1,而每次現(xiàn)有對(duì)象出了作用域生,計(jì)數(shù)器減1

          2tracing算法(Tracing Collector)

          tracing算法是為了解決引用計(jì)數(shù)法的問題而提出,它使用了根集的概念。基于tracing算法的垃圾收集器從根集開始掃描,識(shí)別出哪些對(duì)象可達(dá),哪些對(duì)象不可達(dá),并用某種方式標(biāo)記可達(dá)對(duì)象,例如對(duì)每個(gè)可達(dá)對(duì)象設(shè)置一個(gè)或多個(gè)位。在掃描識(shí)別過程中,基于tracing算法的垃圾收集也稱為標(biāo)記和清除(mark-and-sweep)垃圾收集器.

          3compacting算法(Compacting Collector)

          為了解決堆碎片問題,基于tracing的垃圾回收吸收了Compacting算法的思想,在清除的過程中,算法將所有的對(duì)象移到堆的一端,堆的另一端就變成了一個(gè)相鄰的空閑內(nèi)存區(qū),收集器會(huì)對(duì)它移動(dòng)的所有對(duì)象的所有引用進(jìn)行更新,使得這些引用在新的位置能識(shí)別原來 的對(duì)象。在基于Compacting算法的收集器的實(shí)現(xiàn)中,一般增加句柄和句柄表。  

          4copying算法(Coping Collector)

          該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時(shí)把堆分成 一個(gè)對(duì)象 面和多個(gè)空閑面, 程序從對(duì)象面為對(duì)象分配空間,當(dāng)對(duì)象滿了,基于coping算法的垃圾 收集就從根集中掃描活動(dòng)對(duì)象,并將每個(gè) 活動(dòng)對(duì)象復(fù)制到空閑面(使得活動(dòng)對(duì)象所占的內(nèi)存之間沒有空閑洞),這樣空閑面變成了對(duì)象面,原來的對(duì)象面變成了空閑面,程序會(huì)在新的對(duì)象面中分配內(nèi)存。

          一種典型的基于coping算法的垃圾回收是stop-and-copy算法,它將堆分成對(duì)象面和空閑區(qū)域面,在對(duì)象面與空閑區(qū)域面的切換過程中,程序暫停執(zhí)行。

          5generation算法(Generational Collector)
            stop-and-copy垃圾收集器的一個(gè)缺陷是收集器必須復(fù)制所有的活動(dòng)對(duì)象,這增加了程序等待時(shí)間,這是coping算法低效的原因。在程序設(shè)計(jì)中有這樣的規(guī)律:多數(shù)對(duì)象存在的時(shí)間比較短,少數(shù)的存在時(shí)間比較長(zhǎng)。因此,generation算法將堆分成兩個(gè)或多個(gè),每個(gè)子堆作為對(duì)象的一代(generation)。由于多數(shù)對(duì)象存在的時(shí)間比較短,隨著程序丟棄不使用的對(duì)象,垃圾收集器將從最年輕的子堆中收集這些對(duì)象。在分代式的垃圾收集器運(yùn)行后,上次運(yùn)行存活下來的對(duì)象移到下一最高代的子堆中,由于老一代的子堆不會(huì)經(jīng)常被回收,因而節(jié)省了時(shí)間。

          6adaptive算法(Adaptive Collector)

          在特定的情況下,一些垃圾收集算法會(huì)優(yōu)于其它算法。基于Adaptive算法的垃圾收集器就是監(jiān)控當(dāng)前堆的使用情況,并將選擇適當(dāng)算法的垃圾收集器。

          透視Java垃圾回收

          1、命令行參數(shù)透視垃圾收集器的運(yùn)行

          2、使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法,都可以請(qǐng)求Java的垃圾回收。在命令行中有一個(gè)參數(shù)-verbosegc可以查看Java使用的堆內(nèi)存的情況,它的格式如下:

          java -verbosegc classfile

            可以看個(gè)例子:

          class TestGC
          {
           public static void main(String[] args)
           {
            new TestGC();
            System.gc();
            System.runFinalization();
           }
          }


            在這個(gè)例子中,一個(gè)新的對(duì)象被創(chuàng)建,由于它沒有使用,所以該對(duì)象迅速地變?yōu)榭蛇_(dá),程序編譯后,執(zhí)行命令: java -verbosegc TestGC 后結(jié)果為:

          [Full GC 168K->97K(1984K), 0.0253873 secs]

            機(jī)器的環(huán)境為,Windows 2000 + JDK1.3.1,箭頭前后的數(shù)據(jù)168K97K分別表示垃圾收集GC前后所有存活對(duì)象使用的內(nèi)存容量,說明有168K-97K=71K的對(duì)象容量被回收,括號(hào)內(nèi)的數(shù)據(jù)1984K為堆內(nèi)存的總?cè)萘浚占枰臅r(shí)間是0.0253873秒(這個(gè)時(shí)間在每次執(zhí)行的時(shí)候會(huì)有所不同)。

            2finalize方法透視垃圾收集器的運(yùn)行

            在JVM垃圾收集器收集一個(gè)對(duì)象之前 ,一般要求程序調(diào)用適當(dāng)?shù)姆椒ㄡ尫刨Y源,但在沒有明確釋放資源的情況下,Java提供了缺省機(jī)制來終止化該對(duì)象心釋放資源,這個(gè)方法就是finalize()。它的原型為:

          protected void finalize() throws Throwable

            在finalize()方法返回之后,對(duì)象消失,垃圾收集開始執(zhí)行。原型中的throws Throwable表示它可以拋出任何類型的異常。

            之所以要使用finalize(),是由于有時(shí)需要采取與Java的普通方法不同的一種方法,通過分配內(nèi)存來做一些具有C風(fēng)格的事情。這主要可以通過"固有方法"來進(jìn)行,它是從Java里調(diào)用非Java方法的一種方式。CC++是目前唯一獲得固有方法支持的語言。但由于它們能調(diào)用通過其他語言編寫的子程序,所以能夠有效地調(diào)用任何東西。在非Java代碼內(nèi)部,也許能調(diào)用Cmalloc()系列函數(shù),用它分配存儲(chǔ)空間。而且除非調(diào)用了free(),否則存儲(chǔ)空間不會(huì)得到釋放,從而造成內(nèi)存"漏洞"的出現(xiàn)。當(dāng)然,free()是一個(gè)CC++函數(shù),所以我們需要在finalize()內(nèi)部的一個(gè)固有方法中調(diào)用它。也就是說我們不能過多地使用finalize(),它并不是進(jìn)行普通清除工作的理想場(chǎng)所。

            在普通的清除工作中,為清除一個(gè)對(duì)象,那個(gè)對(duì)象的用戶必須在希望進(jìn)行清除的地點(diǎn)調(diào)用一個(gè)清除方法。這與C++"破壞器"的概念稍有抵觸。在C++中,所有對(duì)象都會(huì)破壞(清除)。或者換句話說,所有對(duì)象都"應(yīng)該"破壞。若將C++對(duì)象創(chuàng)建成一個(gè)本地對(duì)象,比如在堆棧中創(chuàng)建(在Java中是不可能的),那么清除或破壞工作就會(huì)在"結(jié)束花括號(hào)"所代表的、創(chuàng)建這個(gè)對(duì)象的作用域的末尾進(jìn)行。若對(duì)象是用new創(chuàng)建的(類似于Java),那么當(dāng)程序員調(diào)用C++delete命令時(shí)(Java沒有這個(gè)命令),就會(huì)調(diào)用相應(yīng)的破壞器。若程序員忘記了,那么永遠(yuǎn)不會(huì)調(diào)用破壞器,我們最終得到的將是一個(gè)內(nèi)存"漏洞",另外還包括對(duì)象的其他部分永遠(yuǎn)不會(huì)得到清除。

            相反,Java不允許我們創(chuàng)建本地(局部)對(duì)象--無論如何都要使用new。但在Java中,沒有"delete"命令來釋放對(duì)象,因?yàn)槔占鲿?huì)幫助我們自動(dòng)釋放存儲(chǔ)空間。所以如果站在比較簡(jiǎn)化的立場(chǎng),我們可以說正是由于存在垃圾收集機(jī)制,所以Java沒有破壞器。然而,隨著以后學(xué)習(xí)的深入,就會(huì)知道垃圾收集器的存在并不能完全消除對(duì)破壞器的需要,或者說不能消除對(duì)破壞器代表的那種機(jī)制的需要(而且絕對(duì)不能直接調(diào)用finalize(),所以應(yīng)盡量避免用它)。若希望執(zhí)行除釋放存儲(chǔ)空間之外的其他某種形式的清除工作,仍然必須調(diào)用Java中的一個(gè)方法。它等價(jià)于C++的破壞器,只是沒后者方便。

            下面這個(gè)例子向大家展示了垃圾收集所經(jīng)歷的過程,并對(duì)前面的陳述進(jìn)行了總結(jié)。

          class Chair {
           static boolean gcrun = false;
           static boolean f = false;
           static int created = 0;
           static int finalized = 0;
           int i;
           Chair() {
            i = ++created;
            if(created == 47)
             System.out.println("Created 47");
           }
           protected void finalize() {
            if(!gcrun) {
             gcrun = true;
             System.out.println("Beginning to finalize after " + created + " Chairs have been created");
            }
            if(i == 47) {
             System.out.println("Finalizing Chair #47, " +"Setting flag to stop Chair creation");
             f = true;
            }
            finalized++;
            if(finalized >= created)
             System.out.println("All " + finalized + " finalized");
           }
          }

          public class Garbage {
           public static void main(String[] args) {
            if(args.length == 0) {
             System.err.println("Usage: \n" + "java Garbage before\n or:\n" + "java Garbage after");
             return;
            }
            while(!Chair.f) {
             new Chair();
             new String("To take up space");
            }
            System.out.println("After all Chairs have been created:\n" + "total created = " + Chair.created +
          ", total finalized = " + Chair.finalized);
            if(args[0].equals("before")) {
              System.out.println("gc():");
              System.gc();
              System.out.println("runFinalization():");
              System.runFinalization();
            }
            System.out.println("bye!");
            if(args[0].equals("after"))
             System.runFinalizersOnExit(true);
           }
          }


            上面這個(gè)程序創(chuàng)建了許多Chair對(duì)象,而且在垃圾收集器開始運(yùn)行后的某些時(shí)候,程序會(huì)停止創(chuàng)建Chair。由于垃圾收集器可能在任何時(shí)間運(yùn)行,所以我們不能準(zhǔn)確知道它在何時(shí)啟動(dòng)。因此,程序用一個(gè)名為gcrun的標(biāo)記來指出垃圾收集器是否已經(jīng)開始運(yùn)行。利用第二個(gè)標(biāo)記fChair可告訴main()它應(yīng)停止對(duì)象的生成。這兩個(gè)標(biāo)記都是在finalize()內(nèi)部設(shè)置的,它調(diào)用于垃圾收集期間。另兩個(gè)static變量--created以及finalized--分別用于跟蹤已創(chuàng)建的對(duì)象數(shù)量以及垃圾收集器已進(jìn)行完收尾工作的對(duì)象數(shù)量。最后,每個(gè)Chair都有它自己的(非staticint i,所以能跟蹤了解它具體的編號(hào)是多少。編號(hào)為47Chair進(jìn)行完收尾工作后,標(biāo)記會(huì)設(shè)為true,最終結(jié)束Chair對(duì)象的創(chuàng)建過程。(關(guān)于這個(gè)例子的更具體的分析和說明請(qǐng)參看《Java編程思想》的第四章)

            關(guān)于垃圾收集的幾點(diǎn)補(bǔ)充

            經(jīng)過上述的說明,可以發(fā)現(xiàn)垃圾回收有以下的幾個(gè)特點(diǎn):

            (1)垃圾收集發(fā)生的不可預(yù)知性:由于實(shí)現(xiàn)了不同的垃圾收集算法和采用了不同的收集機(jī)制,所以它有可能是定時(shí)發(fā)生,有可能是當(dāng)出現(xiàn)系統(tǒng)空閑CPU資源時(shí)發(fā)生,也有可能是和原始的垃圾收集一樣,等到內(nèi)存消耗出現(xiàn)極限時(shí)發(fā)生,這與垃圾收集器的選擇和具體的設(shè)置都有關(guān)系。

            (2)垃圾收集的精確性:主要包括2 個(gè)方面:(a)垃圾收集器能夠精確標(biāo)記活著的對(duì)象;(b)垃圾收集器能夠精確地定位對(duì)象之間的引用關(guān)系。前者是完全地回收所有廢棄對(duì)象的前提,否則就可能造成內(nèi)存泄漏。而后者則是實(shí)現(xiàn)歸并和復(fù)制等算法的必要條件。所有不可達(dá)對(duì)象都能夠可靠地得到回收,所有對(duì)象都能夠重新分配,允許對(duì)象的復(fù)制和對(duì)象內(nèi)存的縮并,這樣就有效地防止內(nèi)存的支離破碎。(3)現(xiàn)在有許多種不同的垃圾收集器,每種有其算法且其表現(xiàn)各異,既有當(dāng)垃圾收集開始時(shí)就停止應(yīng)用程序的運(yùn)行,又有當(dāng)垃圾收集開始時(shí)也允許應(yīng)用程序的線程運(yùn)行,還有在同一時(shí)間垃圾收集多線程運(yùn)行。

            (4)垃圾收集的實(shí)現(xiàn)和具體的JVM 以及JVM的內(nèi)存模型有非常緊密的關(guān)系。不同的JVM 可能采用不同的垃圾收集,而JVM 的內(nèi)存模型決定著該JVM可以采用哪些類型垃圾收集。現(xiàn)在,HotSpot 系列JVM中的內(nèi)存系統(tǒng)都采用先進(jìn)的面向?qū)ο蟮目蚣茉O(shè)計(jì),這使得該系列JVM都可以采用最先進(jìn)的垃圾收集。

            (5)隨著技術(shù)的發(fā)展,現(xiàn)代垃圾收集技術(shù)提供許多可選的垃圾收集器,而且在配置每種收集器的時(shí)候又可以設(shè)置不同的參數(shù),這就使得根據(jù)不同的應(yīng)用環(huán)境獲得最優(yōu)的應(yīng)用性能成為可能。

            針對(duì)以上特點(diǎn),我們?cè)谑褂玫臅r(shí)候要注意:

            (1)不要試圖去假定垃圾收集發(fā)生的時(shí)間,這一切都是未知的。比如,方法中的一個(gè)臨時(shí)對(duì)象在方法調(diào)用完畢后就變成了無用對(duì)象,這個(gè)時(shí)候它的內(nèi)存就可以被釋放。

            (2Java中提供了一些和垃圾收集打交道的類,而且提供了一種強(qiáng)行執(zhí)行垃圾收集的方法--調(diào)用System.gc(),但這同樣是個(gè)不確定的方法。Java 中并不保證每次調(diào)用該方法就一定能夠啟動(dòng)垃圾收集,它只不過會(huì)向JVM發(fā)出這樣一個(gè)申請(qǐng),到底是否真正執(zhí)行垃圾收集,一切都是個(gè)未知數(shù)。

            (3)挑選適合自己的垃圾收集器。一般來說,如果系統(tǒng)沒有特殊和苛刻的性能要求,可以采用JVM的缺省選項(xiàng)。否則可以考慮使用有針對(duì)性的垃圾收集器,比如增量收集器就比較適合實(shí)時(shí)性要求較高的系統(tǒng)之中。系統(tǒng)具有較高的配置,有比較多的閑置資源,可以考慮使用并行標(biāo)記/清除收集器。

            (4)關(guān)鍵的也是難把握的問題是內(nèi)存泄漏。良好的編程習(xí)慣和嚴(yán)謹(jǐn)?shù)木幊虘B(tài)度永遠(yuǎn)是最重要的,不要讓自己的一個(gè)小錯(cuò)誤導(dǎo)致內(nèi)存出現(xiàn)大漏洞。

            (5)盡早釋放無用對(duì)象的引用。大多數(shù)程序員在使用臨時(shí)變量的時(shí)候,都是讓引用變量在退出活動(dòng)域(scope)后,自動(dòng)設(shè)置為null,暗示垃圾收集器來收集該對(duì)象,還必須注意該引用的對(duì)象是否被監(jiān)聽,如果有,則要去掉監(jiān)聽器,然后再賦空值。

            結(jié)束語

            一般來說,Java開發(fā)人員可以不重視JVM中堆內(nèi)存的分配和垃圾處理收集,但是,充分理解Java的這一特性可以讓我們更有效地利用資源。同時(shí)要注意finalize()方法是Java的缺省機(jī)制,有時(shí)為確保對(duì)象資源的明確釋放,可以編寫自己的finalize方法。

          posted @ 2005-12-27 12:10 kyanite 閱讀(238) | 評(píng)論 (0)編輯 收藏

          2005年8月20日 #

                近日,使用struts 1.1,發(fā)現(xiàn)討厭的中文亂碼問題,在form的傳送過程和入庫(kù)時(shí)候出現(xiàn)。就我在網(wǎng)絡(luò)上找的方法羅列如下:
          (Tomcat 5.0.28+struts 1.1+hibernate 2.1+sqlserver2k)
          1.直接轉(zhuǎn)編碼public static String isoToGB(String src){   
          String strRet=null;   
          try{    
            strRet = new String(src.getBytes("ISO_8859_1"),"GB2312");  
            }catch(Exception e)    {         
          }    return strRet;
          }通過一個(gè)函數(shù)轉(zhuǎn)編碼,我沒有成功,不知為何!

          2.過濾filter設(shè)置法

          package yourbean;

          import javax.servlet.*;
          import javax.servlet.http.*;
          import java.io.*;
          import java.util.*;

          public class servfilter extends HttpServlet implements Filter {  private FilterConfig filterConfig;  //Handle the passed-in FilterConfig  public void init(FilterConfig filterConfig) {    this.filterConfig = filterConfig;  }  //Process the request/response pair  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {    try {      request.setCharacterEncoding("GB2312");       ((HttpServletResponse)response).setHeader("Cache-control","no-cache");      response.setHeader("Pragma","No-cache"); response.setHeader("Cache-Control","no-cache"); response.setHeader("Expires","0");       ((HttpServletResponse)response).setHeader("Pragram","no-cache");      filterChain.doFilter(request, response);    }    catch(ServletException sx) {      filterConfig.getServletContext().log(sx.getMessage());    }    catch(IOException iox) {      filterConfig.getServletContext().log(iox.getMessage());    }  }  //Clean up resources  public void destroy() {  }}下面是一個(gè)web.xml文件你用jbuilder寫上面的bean的時(shí)候會(huì)生成一個(gè)<?xml version="1.0" encoding="ISO-8859-1"?>

          <!DOCTYPE web-app    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"    "http://java.sun.com/dtd/web-app_2_3.dtd">

          <web-app>  <display-name>Welcome to Tomcat</display-name>  <description>     Welcome to Tomcat  </description>  <filter>    <filter-name>servfilter</filter-name>    <filter-class>yourbean.servfilter</filter-class>  </filter>  <filter-mapping>    <filter-name>servfilter</filter-name>    <url-pattern>/*</url-pattern>  </filter-mapping></web-app>把上面的servfilter編譯放在你的web-inf/classes/yourbean/下web.xml放在web-inf/下和classes在一個(gè)目錄下在每個(gè)jsp頁面上加上<%@page contentType="text/html;charset=GBK"%>

          也不是很方便,而且在tomcat也沒有成功,繼續(xù)郁悶!

          3.我現(xiàn)在使用方法,推薦!!

          寫一個(gè)myActionServlet來并覆蓋ActionServlet中的process()方法。

            protected void process(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException {    /**@todo Override this org.apache.struts.action.ActionServlet method*/    request.setCharacterEncoding("GB2312");//就加著一行一切都解決了    super.process(request, response);  }

          當(dāng)然別忘了改一下web.xml里面的配置  <servlet>    <servlet-name>action</servlet-name>    <servlet-class>strutsdemo.myActionServlet</servlet-class>    <init-param>      <param-name>debug</param-name>      <param-value>2</param-value>    </init-param>    <init-param>      <param-name>config</param-name>      <param-value>/WEB-INF/struts-config.xml</param-value>    </init-param>    <load-on-startup>2</load-on-startup>  </servlet>

          改一下servlet-class標(biāo)簽中的內(nèi)容就可以!

          真的可以,一勞用yi!

          具體編碼的理論就不說了,google上已經(jīng)夠多了。

          另外,如果不用struts的話,hibernate也可能碰到中文亂碼問題,只要在hibernate.cfg.xml配置中如下:

          <property name="hibernate.connection.url">   jdbc:microsoft:sqlserver://Localhost:1433;SelectMethod=cursor;characterEncoding=GBK;DatabaseName=myDatabase.  </property>

          characterEncoding=GBK!就可以了。
          posted @ 2005-08-20 13:52 kyanite 閱讀(685) | 評(píng)論 (0)編輯 收藏

          2005年8月10日 #

           最近在公司里做了一個(gè)手機(jī)的項(xiàng)目,需要JAVA程序在發(fā)送短信的時(shí)候和第三方的短信服務(wù)器連接。短信接口是用C++寫的。琢磨了三天,大致搞懂了JNI的主體部分。先將心得整理,希望各位朋友少走彎路。
              首先引用一篇文章,介紹一個(gè)簡(jiǎn)單的JNI的調(diào)用的過程。
              JAVA以其跨平臺(tái)的特性深受人們喜愛,而又正由于它的跨平臺(tái)的目的,使得它和本地機(jī)器的各種內(nèi)部聯(lián)系變得很少,約束了它的功能。解決JAVA對(duì)本地操作的一種方法就是JNI。 
              JAVA通過JNI調(diào)用本地方法,而本地方法是以庫(kù)文件的形式存放的(在WINDOWS平臺(tái)上是DLL文件形式,在UNIX機(jī)器上是SO文件形式)。通過調(diào)用本地的庫(kù)文件的內(nèi)部方法,使JAVA可以實(shí)現(xiàn)和本地機(jī)器的緊密聯(lián)系,調(diào)用系統(tǒng)級(jí)的各接口方法。 
              簡(jiǎn)單介紹及應(yīng)用如下: 
              一、JAVA中所需要做的工作 
                  在JAVA程序中,首先需要在類中聲明所調(diào)用的庫(kù)名稱,如下: 
          1.         static { 
          2.                  System.loadLibrary(“goodluck”); 
          3.                }
           
                  在這里,庫(kù)的擴(kuò)展名字可以不用寫出來,究竟是DLL還是SO,由系統(tǒng)自己判斷。 
                  還需要對(duì)將要調(diào)用的方法做本地聲明,關(guān)鍵字為native。并且只需要聲明,而不需要具 體實(shí)現(xiàn)。如下: 
                  public native static void set(int i); 
                  public native static int get(); 
                  然后編譯該JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就會(huì)生成C/C++的頭文件。 
                  例如程序testdll.java,內(nèi)容為: 
          1.         public class testdll 
          2.          { 
          3.            static 
          4.                  { 
          5.                   System.loadLibrary("goodluck"); 
          6.                  } 
          7.            public native static int get(); 
          8.            public native static void set(int i); 
          9.            public static void main(String[] args) 
          10.                 { 
          11.                   testdll test = new testdll(); 
          12.                   test.set(10); 
          13.                   System.out.println(test.get()); 
          14.                  } 
          15.            }
           
              用javac testdll.java編譯它,會(huì)生成testdll.class。 
              再用javah testdll,則會(huì)在當(dāng)前目錄下生成testdll.h文件,這個(gè)文件需要被C/C++程序調(diào)用來生成所需的庫(kù)文件。 
              二、C/C++中所需要做的工作 
              對(duì)于已生成的.h頭文件,C/C++所需要做的,就是把它的各個(gè)方法具體的實(shí)現(xiàn)。然后編譯連接成庫(kù)文件即可。再把庫(kù)文件拷貝到JAVA程序的路徑下面,就可以用JAVA調(diào)用C/C++所實(shí)現(xiàn)的功能了。 
              接上例子。我們先看一下testdll.h文件的內(nèi)容: 
              /* DO NOT EDIT THIS FILE - it is machine generated */ 
              #include <jni.h> 
              /* Header for class testdll */ 
              #ifndef _Included_testdll 
              #define _Included_testdll 
              #ifdef __cplusplus 
              extern "C" { 
              #endif 
               /* 
                * Class: testdll 
                * Method: get 
                * Signature: ()I 
                */ 
              JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 
                /* 
                 * Class: testdll 
                 * Method: set 
                 * Signature: (I)V 
                 */ 
              JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint); 
              #ifdef __cplusplus 
                          } 
              #endif 
              #endif 
              在具體實(shí)現(xiàn)的時(shí)候,我們只關(guān)心兩個(gè)函數(shù)原型 
              JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和 
              JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint); 
              這里JNIEXPORT和JNICALL都是JNI的關(guān)鍵字,表示此函數(shù)是要被JNI調(diào)用的。而jint是以JNI為中介使JAVA的int類型與本地的int溝通的一種類型,我們可以視而不見,就當(dāng)做int使用。函數(shù)的名稱是JAVA_再加上java程序的package路徑再加函數(shù)名組成的。參數(shù)中,我們也只需要關(guān)心在JAVA程序中存在的參數(shù),至于JNIEnv*和jclass我們一般沒有必要去碰它。 
              好,下面我們用testdll.cpp文件具體實(shí)現(xiàn)這兩個(gè)函數(shù): 
              #include "testdll.h" 
              int i = 0; 
              JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass) 
                { 
                 return i; 
                } 
              JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j) 
                { 
                 i = j; 
                } 
              編譯連接成庫(kù)文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名稱要與JAVA中需要調(diào)用的一致,這里就是goodluck.dll 。把goodluck.dll拷貝到testdll.class的目錄下,java testdll運(yùn)行它,就可以觀察到結(jié)果了。 
          我的項(xiàng)目比較復(fù)雜,需要調(diào)用動(dòng)態(tài)鏈接庫(kù),這樣在JNI傳送參數(shù)到C程序時(shí),需要對(duì)參數(shù)進(jìn)行處理轉(zhuǎn)換。才可以被C程序識(shí)別。
          大體程序如下:
          1. public class SendSMS {
          2. static
          3.     {
          4.       System.out.println(System.getProperty("java.library.path"));
          5.       System.loadLibrary("sms");
          6.     }
          7.     public native static int SmsInit();
          8.     public native static int SmsSend(byte[] mobileNo, byte[] smContent);
          9. }

          在這里要注意的是,path里一定要包含類庫(kù)的路徑,否則在程序運(yùn)行時(shí)會(huì)拋出異常:
          java.lang.UnsatisfiedLinkError: no sms in java.library.path
              at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
                   at java.lang.Runtime.loadLibrary0(Runtime.java:788)
                   at java.lang.System.loadLibrary(System.java:834)
                   at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
              at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
          Exception in thread "main"
          指引的路徑應(yīng)該到.dll文件的上一級(jí),如果指到.dll,則會(huì)報(bào):
          java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries
                   at java.lang.ClassLoader$NativeLibrary.load(Native Method)
          at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
                   at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
                   at java.lang.Runtime.loadLibrary0(Runtime.java:788)
                   at java.lang.System.loadLibrary(System.java:834)
                   at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
                   at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
          Exception in thread "main"
          通過編譯,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h頭文件。(建議使用Jbuilder進(jìn)行編譯,操作比較簡(jiǎn)單!)這個(gè)頭文件就是Java和C之間的紐帶。要特別注意的是方法中傳遞的參數(shù)jbyteArray,這在接下來的過程中會(huì)重點(diǎn)介紹。
          1. /* DO NOT EDIT THIS FILE - it is machine generated */
          2. #include <jni.h>
          3. /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */
          4. #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
          5. #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
          6. #ifdef __cplusplus
          7. extern "C" {
          8. #endif
          9. /*
          10.  * Class:     com_mobilesoft_sms_mobilesoftinfo_SendSMS
          11.  * Method:    SmsInit
          12.  * Signature: ()I
          13.  */
          14. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit
          15.   (JNIEnv *, jclass);
          16. /*
          17.  * Class:     com_mobilesoft_sms_mobilesoftinfo_SendSMS
          18.  * Method:    SmsSend
          19.  * Signature: ([B[B)I
          20.  */
          21. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend
          22.   (JNIEnv *, jclass, jbyteArray, jbyteArray);
          23. #ifdef __cplusplus
          24. }
          25. #endif
          26. #endif

          對(duì)于我要調(diào)用的C程序的動(dòng)態(tài)鏈接庫(kù),C程序也要提供一個(gè)頭文件,sms.h。這個(gè)文件將要調(diào)用的方法羅列了出來。
          1. /*
          2.  * SMS API 
          3.  * Author: yippit
          4.  * Date: 2004.6.8
          5.  */
          6. #ifndef MCS_SMS_H
          7. #define MCS_SMS_H
          8. #define DLLEXPORT __declspec(dllexport)
          9. /*sms storage*/
          10. #define SMS_SIM        0
          11. #define SMS_MT        1
          12. /*sms states*/
          13. #define SMS_UNREAD        0
          14. #define SMS_READ            1
          15. /*sms type*/
          16. #define SMS_NOPARSE        -1
          17. #define SMS_NORMAL        0
          18. #define SMS_FLASH            1
          19. #define SMS_MMSNOTI        2
          20. typedef struct tagSmsEntry {
          21.     int index;        /*index, start from 1*/
          22.     int status;        /*read, unread*/
          23.     int type;            /*-1-can't parser 0-normal, 1-flash, 2-mms*/
          24.     int storage;    /*SMS_SIM, SMS_MT*/
          25.     char date[24];
          26.     char number[32];
          27.     char text[144];
          28. } SmsEntry;
          29. DLLEXPORT int SmsInit(void);
          30. DLLEXPORT int SmsSend(char *phonenum, char *content);
          31. DLLEXPORT int SmsSetSCA(char *sca);
          32. DLLEXPORT int SmsGetSCA(char *sca);
          33. DLLEXPORT int SmsSetInd(int ind);
          34. DLLEXPORT int SmsGetInd(void);
          35. DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);
          36. DLLEXPORT int SmsSaveFlash(int flag);
          37. DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index); 
          38. DLLEXPORT int SmsDelete(int storage, int index);
          39. DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/
          40. #endif

          在有了這兩個(gè)頭文件之后,就可以進(jìn)行C程序的編寫了。也就是實(shí)現(xiàn)對(duì)JNI調(diào)用的兩個(gè)方法。在網(wǎng)上的資料中,由于調(diào)用的方法實(shí)現(xiàn)的都比較簡(jiǎn)單,(大多是打印字符串等)所以避開了JNI中最麻煩的部分,也是最關(guān)鍵的部分,參數(shù)的傳遞。由于Java和C的編碼是不同的,所以傳遞的參數(shù)是要進(jìn)行再處理,否則C程序是會(huì)對(duì)參數(shù)在編譯過程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。
          Sms.c的程序如下:
          1. #include "sms.h"
          2. #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"
          3. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)
          4. {
          5.     return SmsInit();
          6. }
          7. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)
          8. {
          9.     char * pSmscontent ;
          10.     //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);
          11.     jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);
          12.     char * pMobileNo = (char *)arrayBody;
          13.     printf("[%s]\n ", pMobileNo);
          14.     //jsize size = (*env)->GetArrayLength(env,smscontent);
          15.     arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);
          16.     pSmscontent = (char *)arrayBody;
          17.     printf("<%s>\n", pSmscontent);
          18.     return SmsSend(pMobileNo,pSmscontent); 
          19. }

          對(duì)于C或C++,在程序上是會(huì)有稍微的不同,這可以由讀者對(duì)其進(jìn)行適當(dāng)?shù)男薷摹_@里要注意的是GetArrayLength,GetByteArrayElements等這些JNI中已經(jīng)包含的方法,這些方法是專門對(duì)轉(zhuǎn)換參數(shù)類型而提供的。具體的方法有很多,在下一篇中會(huì)做專門的介紹。
          在完成了上述的文件后,可以對(duì)sms.c進(jìn)行編譯,生成.dll文件(建議在release中編譯,這樣動(dòng)態(tài)鏈接庫(kù)的容積會(huì)比較小!)
          完成.dll文件的編譯后,就可以在Java中調(diào)用C程序中的方法了。例如文件test.java
          1. public class test {
          2.   public test() {
          3.   }
          4.   public static void main(String[] args) {
          5.     byte[] mobileno = {
          6.         0x31, 0x33, 0x36, 0x36, 0x31, 0x36, 0x33, 0x30, 0x36, 0x36, 0x37, 0x00};
          7.     String smscontentemp = "早上好";
          8.     byte[] temp = {0};
          9.    try {
          10.       byte[] smscontentdb = smscontentemp.getBytes("gbk");
          11.       byte[] smscontent = new byte[smscontentdb.length + temp.length];
          12.       System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);
          13.       System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);
          14.       SendSMS sendSMS = new SendSMS();
          15.       sendSMS.SmsInit();
          16.       if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {
          17.         System.out.println("chenggong !");
          18.       }
          19.       else {
          20.         System.out.println("shibai !");
          21.       }
          22.     }catch (Exception ex) {}
          23.   }
          24. }

          在這個(gè)文件中要注意的有一點(diǎn),就是在傳遞字節(jié)數(shù)組到C程序中時(shí),最后的結(jié)尾一定要以0結(jié)束。這是一個(gè)偷懶的做法,不過是個(gè)有效的做法。因?yàn)榇蠖鄶?shù)情況下,接口是由第三方提供的。所以我們一般是不知道在C的方法里,具體是怎么處理參數(shù)的。而C又是要求數(shù)組是有長(zhǎng)度。所以,在Java中,如果你不想寫程序傳數(shù)組的長(zhǎng)度,那么在數(shù)組中以0結(jié)尾就是最方便的方法了。當(dāng)然,如果有更好的方法也希望大家提出。
          到這里,一個(gè)完整的Java通過JNI調(diào)用動(dòng)態(tài)鏈接庫(kù)的程序就完成了。實(shí)際上也不是很復(fù)雜。只要多注意一下細(xì)節(jié),是很容易得出來的。
          posted @ 2005-08-10 10:30 kyanite 閱讀(2807) | 評(píng)論 (0)編輯 收藏

          2005年8月1日 #

               摘要: 個(gè)人簡(jiǎn)介  個(gè)人基本簡(jiǎn)歷                                           ...  閱讀全文
          posted @ 2005-08-01 20:36 kyanite 閱讀(520) | 評(píng)論 (0)編輯 收藏

          http://www.ea.com.cn/
          http://www.novemsoft.com/
          http://www.novemsoft.com/
          http://www30.sap.com/china/index.aspx
          http://default.baosight.com/Baosight/frontpages/Main.baosight
          http://www.sunsystems.cn/
          http://www.worksoft.com.cn/
          http://www.accenture.com/xd/xd.asp?it=cnweb&xd=locations/china/china_home.xml
          http://www.altigen.com.cn/DEFAULT.ASP
          http://www.alcatel-sbell.com.cn/index.asp
          http://www.shifang.com.cn/
          https://www.ubisoft.com.cn/index/index.aspx
          http://www.sysmex.com.cn/
          http://www.symbio.com.cn/index.html
          posted @ 2005-08-01 20:35 kyanite 閱讀(197) | 評(píng)論 (0)編輯 收藏

          2005年7月1日 #

          今天一口氣,下載了三個(gè)開發(fā)jsp上傳下載的開發(fā)包,分別為jspsmart.jar
          瀟湘子的開發(fā)包和jakata 開源項(xiàng)目的fileupload開發(fā)包。由于項(xiàng)目開發(fā)需要,準(zhǔn)備花三天時(shí)間,研究這個(gè)機(jī)制,可惜沒有源代碼,呵呵,ok,好好看了
          posted @ 2005-07-01 22:09 kyanite 閱讀(715) | 評(píng)論 (5)編輯 收藏

          2005年6月25日 #

          由于最近事情比較多,換了新的環(huán)境,不過很快就適應(yīng)了!前幾天我的幾個(gè)兄弟,準(zhǔn)備去浙江慈溪,吃楊梅!去年的臨近畢業(yè)的時(shí)候,那可是每天晚上都去夜宵,很是happy。轉(zhuǎn)眼畢業(yè)一年,晚上和一幫兄弟通了電話,也許生活還是依舊!突然提到以前的那位。是啊,好久沒有聯(lián)系了,不知道她現(xiàn)在怎么樣,就在這里祝福她!通完電話,就怎么也睡不著覺了!在外面的日子很自由,但有時(shí)候也很無奈,這就是生活吧,為了夢(mèng)想......
          posted @ 2005-06-25 16:46 kyanite 閱讀(161) | 評(píng)論 (0)編輯 收藏

          2005年6月24日 #

          這段時(shí)間忙活了幾天,終于把項(xiàng)目需要用的資源申請(qǐng)到了。開始努力做項(xiàng)目,我們現(xiàn)在的項(xiàng)目準(zhǔn)備采用eclipse開發(fā),剛開始接觸,以后多來這里向牛人請(qǐng)教了!

          posted @ 2005-06-24 13:29 kyanite 閱讀(225) | 評(píng)論 (0)編輯 收藏

          僅列出標(biāo)題  
          主站蜘蛛池模板: 阿荣旗| 松溪县| 子洲县| 同江市| 闽侯县| 关岭| 石狮市| 乌什县| 象州县| 洮南市| 定安县| 永平县| 谢通门县| 新田县| 台东县| 板桥市| 大荔县| 苗栗市| 云林县| 日土县| 西平县| 南康市| 南陵县| 关岭| 收藏| 孟津县| 天祝| 拉孜县| 五大连池市| 杨浦区| 富民县| 崇文区| 钦州市| 沧源| 望城县| 永善县| 门头沟区| 松原市| 合肥市| 景德镇市| 青海省|