??? --sunfruit

上圖求一筆畫的路徑,利用圖論的相關(guān)知識(shí)可以得到程序如下:
public class OnePath {
??? private static int[][]
??????????? links = { {0,1,1,0,0,0,1,0}, {1,0,0,1,0,0,0,1}, {1,0,0,1,1,1,0,0},
??????????? {0,1,1,0,1,1,0,0}, {0,0,1,1,0,1,1,0}, {0,0,1,1,1,0,0,1}, {1,0,0,0,1,0,0,0}, {0,1,0,0,0,1,0,0}
??? };
??? public OnePath() {
??????? int sum = 0;
??????? //存放每個(gè)點(diǎn)的度
??????? int[] point = new int[links[0].length];
??????? for (int i = 0; i < links[0].length; i++) {
??????????? int[] templink = links[i];
??????????? for (int j = 0; j < links[0].length; j++) {
??????????????? point[i] += templink[j];
??????????? }
??????????? sum += point[i];
??????? }
??????? //計(jì)算度數(shù)是奇數(shù)點(diǎn)的個(gè)數(shù),如果大于2則不能一筆畫
??????? int odt = 0;
??????? int start = -1;
??????? for (int i = 0; i < point.length; i++) {
??????????? int mod = point[i] % 2;
??????????? if (mod > 0) {
??????????????? //if(start==-1)
??????????????????? start = i;
??????????????? odt++;
??????????? }
??????? }
??????? if(odt>2)
??????? {
????????? System.out.println("該圖不能一筆畫");
????????? return;
??????? }
??????? int r = 0;
??????? //從一個(gè)奇數(shù)點(diǎn)開始計(jì)算
??????? int nowd=start;
??????? System.out.print(nowd+1);
??????? while (sum > 0) {
??????????? r=0;
??????????? //對(duì)于起點(diǎn)nowd 檢查當(dāng)前的點(diǎn)r 是否合適
??????????? //links[nowd][r]==0 判斷是否有可以走的沒有用過的線路
??????????? //(point[r]<=1 && sum!=2) 判斷是否是最后一次,如果不是最后一次,那么避開度數(shù)是1的點(diǎn)
??????????? while (links[nowd][r]==0 || (point[r]<=1 && sum!=2)) {
??????????????? r++;
??????????? }
??????????? links[nowd][r]=0; //已經(jīng)用過的線路
??????????? links[r][nowd]=0; //已經(jīng)用過的線路 links[nowd][r] links[r][nowd]互為往返路線,用過1->2那么2->1也作廢了
??????????? sum=sum-2; //總度數(shù)減2 因?yàn)閺?->2 消耗了1的度和2的度
??????????? point[nowd]--; //起點(diǎn)和終點(diǎn)的度都減1 1->2 那么1的度和2的度都減1
??????????? point[r]--; //起點(diǎn)和終點(diǎn)的度都減1 1->2 那么1的度和2的度都減1
??????????? nowd =r; //設(shè)置新的起點(diǎn)
??????????? System.out.print("->"+(r+1));
??????? }
??? }
??? public static void main(String[] args) {
??????? new OnePath();
??? }
}
??? --sunfruit
???很多時(shí)候需要上傳附件到服務(wù)器,一般采用在頁面放置<input type="file" name="upload" value=""> 的方式讓用戶選擇要上傳的文件進(jìn)行上傳,使用的是HTTP協(xié)議,這樣的方式很方便開發(fā)也簡(jiǎn)單,不過如果上傳的附件比較大的時(shí)候,會(huì)出現(xiàn)IE響應(yīng)很慢的情況,如果用戶急性子,多點(diǎn)幾下上傳的按鈕,那么就會(huì)導(dǎo)致IE不響應(yīng)的情況,這個(gè)時(shí)候如果在文件上傳得過程中,給用戶一個(gè)動(dòng)態(tài)的提示甚至是一個(gè)上傳的進(jìn)度條,效果就會(huì)好多了,這樣就會(huì)用到Ajax技術(shù)了,讓Ajax以一個(gè)固定的間隔時(shí)間檢查上傳情況然后在頁面以文字或是圖片的方式體現(xiàn)出來就行了。
???在使用Ajax進(jìn)行附件上傳進(jìn)度查詢的時(shí)候也想過,直接使用Ajax進(jìn)行附件上傳,在實(shí)現(xiàn)過程中發(fā)現(xiàn)問題比較多,所以就使用了變通的方式:使用標(biāo)準(zhǔn)的附件上傳方式,結(jié)合Ajax進(jìn)行上傳的進(jìn)度檢查
???主要的代碼如下:
???Ajax的封裝
???/**
? * 創(chuàng)建 XMLHttpRequest 對(duì)象
? */
? function getXMLHttpRequest()
? {
??? var http_request;
??? if (window.XMLHttpRequest) {
????? //非IE瀏覽器框架創(chuàng)建 XMLHttpRequest 對(duì)象
????? http_request = new XMLHttpRequest();
????? if(http_request.overrideMimeType)
????? {
??????? http_request.overrideMimeType('text/xml');
????? }
??? }else if (window.ActiveXObject){
????? // 創(chuàng)建 XMLHttpRequest 對(duì)象
????? try {
??????? http_request = new ActiveXObject("Msxml2.XMLHTTP");
????? } catch (e1) {
??????? try {
????????? http_request = new ActiveXObject("Microsoft.XMLHTTP");
??????? } catch (e2) {
????????? // 不能創(chuàng)建 XMLHttpRequest 對(duì)象
??????? }
????? }
??? }
??? return http_request;
? }
/**
?? * Get請(qǐng)求
?? */
? function sendGetDictate(http_request,url)
? {
??? req.open("GET", url, true);
??? http_request.send(null);
? }
以上是Ajax的的基礎(chǔ)部分,下面說文件上傳部分的檢查部分,文件上傳本身的流程不變,只是在提交上傳以后,需要執(zhí)行setTimeout(checkupload,500); 這樣的方法 checkupload 方法要自己編寫,例如
function checkupload()
? {
??? req=getXMLHttpRequest();
??? req.onreadystatechange = setActiveContent;
??? sendGetDictate(req,"/manager/servlet/imageservlet?tag=ajaxuploadfilecheck&WARE_ID=609187669&nocache="+Math.random(),"name=111");
? }
然后需要編寫setActiveContent方法,例如
var count=0; //防止無限循環(huán),并且在頁面提交上傳得時(shí)候設(shè)置為0
function setActiveContent()
? {
??? if (req.readyState == 4) {
????? if (req.status == 200) {
??????? var rettext=req.responseText; //這里是servlet返回的內(nèi)容,檢查上傳得狀態(tài),可以在javabean或是servlet里面設(shè)置全局的靜態(tài)變量來表明上傳狀態(tài)
??????? if(rettext=="-1")
??????? {
????????? //停止循環(huán)
????????? alert("服務(wù)器更新錯(cuò)誤");
??????? }
??????? else if(rettext=="0")
??????? {
????????? //繼續(xù)循環(huán)檢查
????????? if(count<6)
????????? {
??????????? setTimeout("checkupload()",500);
??????????? count++;
????????? }
????????? else
????????? {
??????????? alert("上傳失敗");
????????? }
??????? }
????????else if(rettext=="1")
??????? {
????????? alert("文件上傳成功");
??????? }
????? }
??? }
? }
?
基本流程就是這樣了,至于文字表現(xiàn)上傳過程還是進(jìn)度條表現(xiàn),就看自己了
Painting in AWT and Swing
Good Painting Code Is the Key to App Performance
By Amy Fowler
在圖形系統(tǒng)中, 窗口工具包(windowing toolkit)通常提供一個(gè)框架以便相對(duì)容易地創(chuàng)建一個(gè)圖形用戶接口(GUI),在正確的時(shí)間、正確的屏幕位置顯示一個(gè)正確的圖像位。
AWT (abstract windowing toolkit,抽象窗口工具包) 和Swing都提供這種框架。但是實(shí)現(xiàn)這種框架的APIs對(duì)一些開發(fā)人員來講不是很好理解 -- 這就導(dǎo)致一些程序的運(yùn)行達(dá)不到預(yù)期的效果。
本文詳細(xì)地解釋AWT和Swing的繪畫機(jī)制,目的是幫助開發(fā)人員寫出正確的和高率的GUI繪畫代碼。然而,這篇文章只包括一般的畫圖機(jī)制(即,在什么地方和什么時(shí)間去呈現(xiàn)),而不介紹Swing的圖形API怎樣去呈現(xiàn)圖形。想學(xué)習(xí)怎樣去顯示漂亮的圖形,請(qǐng)?jiān)L問Java 2D 網(wǎng)站。
繪畫系統(tǒng)的演變
當(dāng)最初的、為JDK1.0使用的AWT API發(fā)布時(shí),只有重量級(jí)(heavyweight)部件("重量級(jí)" 的意思是說該部件有它自己的、遮光(opaque)的、與生俱來的窗體)。這樣就使得AWT在很大程度上依賴于本地平臺(tái)的繪畫系統(tǒng)。這樣的安排需要開發(fā)人員寫代碼的時(shí)候要考慮到很多細(xì)節(jié)問題,象重畫檢測(cè)(damage detection)、剪切(clip)計(jì)算、以及Z軸次序等。隨著JDK 1.1中輕量級(jí)(lightweight)部件的引入("輕量級(jí)" 部件重用了與它最接近的重量級(jí)祖先的本地窗體),需要AWT能在共享的代碼里為輕量級(jí)部件實(shí)現(xiàn)繪畫處理。因此,重量級(jí)和輕量級(jí)部件在它們各自的繪畫處理方法有著微妙的差別。
在JDK 1.1之后,當(dāng)發(fā)布了Swing工具的時(shí)候,引入了它自己的繪畫風(fēng)格。Swing的繪畫機(jī)制在很大程度上類似并且依賴于AWT,但是,也有它自己的觀點(diǎn),還帶來了新的API,使得應(yīng)用程序可以容易地定制繪畫工作。
在AWT中繪畫
去理解AWT繪畫API怎樣工作,有助于我們搞明白是什么觸發(fā)了窗口環(huán)境中的繪畫操作。AWT中有兩種繪畫操作:系統(tǒng)觸發(fā)的繪畫,和程序觸發(fā)的繪畫
系統(tǒng)觸發(fā)的繪畫操作
在系統(tǒng)觸發(fā)的繪畫操作中,系統(tǒng)需要一個(gè)部件顯示它的內(nèi)容,通常是由于下列中的原因:
部件第一次在屏幕上顯示
部件的大小改變了
部件顯示的內(nèi)容受損需要維護(hù)。(比如,先前擋住部件的其它物體移走了,于是部件被擋住的部分曝露出來。
程序觸發(fā)的繪畫操作
在程序觸發(fā)的繪畫操作,是部件自己決定要更新自身的內(nèi)容,因?yàn)椴考?nèi)部的狀態(tài)改變了。(比如,監(jiān)測(cè)到鼠標(biāo)按鈕已經(jīng)按下,那么它就需要去畫出按鈕"被按下"時(shí)的樣子>
畫圖的方法
不管是誰觸發(fā)了畫圖請(qǐng)求,AWT都是利用"回調(diào)"機(jī)制來實(shí)現(xiàn)繪畫,這個(gè)機(jī)制對(duì)于“重量級(jí)”和“輕量級(jí)”的部件都是相同的。這就意味著程序應(yīng)該在一個(gè)特定的可覆蓋的方法中放置那些表現(xiàn)部件自身的的代碼,并且在需要繪畫的時(shí)候,工具包就會(huì)調(diào)用這個(gè)方法。這個(gè)可覆蓋的方法在java.awt.Component中聲明:
??? public void paint(Graphics g)
當(dāng)AWT調(diào)用這個(gè)方法時(shí),作為參數(shù)的、負(fù)責(zé)在這個(gè)特定的部件上繪畫的Graphics對(duì)象是在之前已經(jīng)配置了的,擁有恰當(dāng)?shù)臓顟B(tài)值。
Graphics的顏色 值被設(shè)置為部件的前景。
Graphics的字體 設(shè)置為部件的字體。
Graphics的平移(translation) 也給設(shè)定,使用坐標(biāo)(0,0)定位部件的左上角。
Graphics的裁剪框(clip rectangle)設(shè)置為部件需要畫圖的區(qū)域。
程序必須使用這個(gè)Graphics(或者其派生類)對(duì)象來呈現(xiàn)繪畫,并且可以根據(jù)自己的需要任意改變Graphics對(duì)象的屬性值。
這里是一個(gè)回調(diào)繪畫的簡(jiǎn)單例子,在部件的范圍內(nèi)呈現(xiàn)一個(gè)實(shí)體園:
??? public void paint(Graphics g) {
??????? // 根據(jù)部件的范圍,動(dòng)態(tài)計(jì)算圓的尺寸信息。
??????? Dimension size = getSize();
??????? // 直徑
??????? int d = Math.min(size.width, size.height);
??????? int x = (size.width - d)/2;
??????? int y = (size.height - d)/2;
??????? // 畫圓(顏色已經(jīng)預(yù)先設(shè)置為部件的前景顏色)
??????? g.fillOval(x, y, d, d);
??????? g.setColor(Color.black);
??????? g.drawOval(x, y, d, d);
??? }
初次接觸AWT的開發(fā)人員可以看看PaintDemo example,那里介紹了一個(gè)在AWT程序中怎樣使用畫圖回調(diào)方法的例子。
一般情況下,程序應(yīng)該避免把繪畫代碼放置在回調(diào)方法paint()的范圍之外。為什么呢?因?yàn)閜aint方法之外的繪畫代碼可能會(huì)在不適合畫圖的時(shí)候被調(diào)用 -- 例如,在部件變?yōu)榭梢娭盎蛘咭呀?jīng)在使用一個(gè)有效的Graphics。同時(shí),不推薦在程序中直接調(diào)用paint()。
為了使能夠由程序觸發(fā)繪畫操作,AWT提供了下面的java.awt.Component的方法,這樣程序就可以提出一個(gè)異步的繪畫請(qǐng)求:
??? public void repaint()
??? public void repaint(long tm)
??? public void repaint(int x, int y, int width, int height)
??? public void repaint(long tm, int x, int y,
?????????????????? int width, int height)
下面的代碼顯示了一個(gè)簡(jiǎn)單的鼠標(biāo)監(jiān)聽器的例子,當(dāng)鼠標(biāo)按下和抬起的時(shí)候,使用repaint()來觸發(fā)“假想按鈕”的更新操作。
MouseListener l = new MouseAdapter() {
??????????? public void mousePressed(MouseEvent e) {
??????????????? MyButton b = (MyButton)e.getSource();
??????????????? b.setSelected(true);
??????????????? b.repaint();????????????
??????????? }
??????????? public void mouseReleased(MouseEvent e) {
??????????????? MyButton b = (MyButton)e.getSource();
??????????????? b.setSelected(false);
??????????????? b.repaint();????????????
??????????? }
??????? };
如果部件要呈現(xiàn)復(fù)雜的圖形,就應(yīng)該使用帶參數(shù)的repaint()方法,通過參數(shù)來指定需要更新的區(qū)域。一個(gè)比較常見的錯(cuò)誤是總是調(diào)用無參數(shù)的repaint()來提出重畫請(qǐng)求,這個(gè)方法會(huì)重畫整個(gè)部件,經(jīng)常導(dǎo)致一些不必要的畫圖處理。
paint() vs. update()
為什么我們要區(qū)分繪畫操作是"系統(tǒng)觸發(fā)" 還是"程序觸發(fā)"呢?因?yàn)樵凇爸亓考?jí)”部件上,AWT對(duì)這兩種請(qǐng)求的在處理上稍有不同(“輕量級(jí)”的情況將在后面介紹),并且不幸的是與此相關(guān)的代碼非常復(fù)雜,難以更改。
對(duì)于“重量級(jí)”部件,這兩種方式的繪畫產(chǎn)生于兩條不同的途徑,取決于是“系統(tǒng)觸發(fā)”還是“程序觸發(fā)”。
系統(tǒng)觸發(fā)的繪畫
下面介紹“系統(tǒng)觸發(fā)”的繪畫操作是怎么產(chǎn)生的:
AWT確定是一部分還是整個(gè)部件需要繪畫。
AWT促使事件分派線程調(diào)用部件的paint()方法。
程序觸發(fā)的繪畫
由程序觸發(fā)的繪畫的產(chǎn)生如下所示:
程序確定是一部分還是全部部件需要重畫以對(duì)應(yīng)內(nèi)部狀態(tài)的改變。
?
程序調(diào)用部件的repaint(),該方法向AWT登記了一個(gè)異步的請(qǐng)求 -- 當(dāng)前部件需要重畫。
?
AWT促使事件分派線程去調(diào)用部件的update() 方法。
注意: 在最初的重畫請(qǐng)求處理完成之前,如果在該部件上有多次對(duì)repaint()的調(diào)用,那么這些調(diào)用可以被合并成對(duì)update()的一次調(diào)用。決定什么時(shí)候應(yīng)該合并多次請(qǐng)求的運(yùn)算法則取決于具體的實(shí)現(xiàn)。如果多次請(qǐng)求被合并,最終被更新的區(qū)域?qū)⑹撬羞@些請(qǐng)求所要求更新的區(qū)域的聯(lián)合(union)。
?
如果部件沒有覆蓋(override)update()方法,update()的默認(rèn)實(shí)現(xiàn)會(huì)清除部件背景(如果部件不是“輕量級(jí)”),然后只是簡(jiǎn)單地調(diào)用paint()方法。
因?yàn)樽鳛槟J(rèn)的最終結(jié)果都是一樣的(paint()方法被調(diào)用),很多開發(fā)人員完全不知道一個(gè)分離的update() 方法的意義。確實(shí),默認(rèn)的update()的實(shí)現(xiàn)最終會(huì)轉(zhuǎn)回到對(duì)paint()方法的調(diào)用,然而,如果需要,這個(gè)更新操作的 "鉤子(hook)"可以使程根據(jù)不同的情況來處理程序觸發(fā)的繪畫。程序必須這么設(shè)想,對(duì)paint()的調(diào)用意味著Graphics的裁剪區(qū)"損壞"了并且必須全部重畫;然而對(duì)update()的調(diào)用沒有這種含義,它使程序做增量的繪畫。
如果程序希望只把要增加的內(nèi)容敷蓋于已存在于該部件的像素位之上,那么就使用增量畫圖操作。UpdateDemo example 示范了一個(gè)利用update()的優(yōu)點(diǎn)做增量繪畫的程序。
事實(shí)上,大多數(shù)GUI部件不需要增量繪畫,所有大部分程序可以忽略u(píng)pdate()方法,并且簡(jiǎn)單地覆蓋(override)paint()來呈現(xiàn)部件的當(dāng)前狀態(tài)。這就意味著不管“系統(tǒng)觸發(fā)”還是“程序觸發(fā)”,在大多數(shù)部件上的表現(xiàn)從其本質(zhì)上講是是等價(jià)的。
繪畫與輕量級(jí)部件
從應(yīng)用開發(fā)人員的觀點(diǎn)看,“輕量級(jí)”的繪畫API基本上和“重量級(jí)”一樣(即,你只需要覆蓋paint()方法,同樣,調(diào)用repaint()方法去觸發(fā)繪圖更新)。然而,因?yàn)锳WT的“輕量級(jí)”部件的框架全部使用普通Java代碼實(shí)現(xiàn),在輕量級(jí)部件上繪畫機(jī)制的實(shí)現(xiàn)方式有一些微妙的不同。
?
“輕量級(jí)”部件是怎樣被繪制的
“輕量級(jí)”部件需要一個(gè)處在容器體系上的“重量級(jí)”部件提供進(jìn)行繪畫的場(chǎng)所。當(dāng)這個(gè)“重量級(jí)”的“祖宗”被告知要繪制自身的窗體時(shí),它必須把這個(gè)繪畫的請(qǐng)求轉(zhuǎn)化為對(duì)其所有子孫的繪畫請(qǐng)求。這是由java.awt.Container的paint()方法處理的,該方法調(diào)用包容于其內(nèi)的所有可見的、并且與繪畫區(qū)相交的輕量級(jí)部件的paint()方法。因此對(duì)于所有覆蓋了paint()方法的Container子類(“輕量級(jí)”或“重量級(jí)”)需要立刻做下面的事情:
?? public class MyContainer extends Container {
??????? public void paint(Graphics g) {
??? // paint my contents first...
??? // then, make sure lightweight children paint
??? super.paint(g);
??????? }
??? }
如果沒有super.paint(),那么容器(container)的輕量級(jí)子孫類就不會(huì)顯示出來(這是一個(gè)非常普遍的問題,自從JDK1.1初次引進(jìn)“輕量級(jí)”部件之后)。
這種情況相當(dāng)于注釋掉了默認(rèn)的Container.update()方法的執(zhí)行,從而不能 使用遞歸去調(diào)用其輕量級(jí)子孫類的update()或者paint()方法。這就意味著任何使用update()方法實(shí)現(xiàn)增量繪畫的重量級(jí)Container子類必須確保其輕量級(jí)子孫在需要時(shí),能夠被它的遞歸操作所調(diào)用從而實(shí)現(xiàn)重畫。幸運(yùn)的是,只有少數(shù)幾個(gè)重量級(jí)的容器(Container)需要增量繪圖,所以這個(gè)問題沒有影響到大多數(shù)的程序。
?
輕量級(jí)與系統(tǒng)觸發(fā)型的畫圖
為輕量級(jí)部件實(shí)現(xiàn)窗體行為(顯示、隱藏、移動(dòng)、改變大小等)的輕量級(jí)框架的代碼全部用Java代碼寫成。經(jīng)常的,在這些功能的Java實(shí)現(xiàn)中,AWT必須明確地吩咐各個(gè)輕量級(jí)部件執(zhí)行繪畫(實(shí)質(zhì)上講這也是系統(tǒng)觸發(fā)的繪畫,盡管它不是源于本地的 操作系統(tǒng))。而輕量級(jí)框架使用repaint()方法來吩咐部件執(zhí)行繪畫,這是我們前面解釋過的,將導(dǎo)致一個(gè)update()的調(diào)用而不是直接地對(duì)paint()的調(diào)用。因此,對(duì)于輕量級(jí),系統(tǒng)觸發(fā)型的畫圖操作可以遵循下面的兩種途徑:
系統(tǒng)觸發(fā)的繪畫要求產(chǎn)生于本地系統(tǒng)(例如,輕量級(jí)的重量級(jí)祖先第一次現(xiàn)身的時(shí)候),這導(dǎo)致對(duì)paint()的直接調(diào)用。
?
系統(tǒng)觸發(fā)型的繪圖要求產(chǎn)生于輕量框架(例如,輕量級(jí)部件的尺寸改變了),這導(dǎo)致對(duì)update()的調(diào)用,該方法進(jìn)而默認(rèn)地調(diào)用paint()。
簡(jiǎn)單地講,這意味著輕量級(jí)部件在update()和paint()之間沒有實(shí)質(zhì)的差別,進(jìn)一步講這又意味著“增量的繪圖技術(shù)”不能用到輕量級(jí)部件上。
輕量級(jí)部件與透明
因?yàn)檩p量級(jí)部件"借用"了本屬于其“重量級(jí)”祖先的屏幕,所以它們支持“透明”的特征。這樣做是因?yàn)檩p量級(jí)部件是從底往上繪畫,因此如果輕量級(jí)部件遺留一些或者全部它們祖先的像素位而沒有畫,底層的部件就會(huì)"直接顯示。"出來。這也是對(duì)于輕量級(jí)部件,update()方法的在默認(rèn)實(shí)現(xiàn)將不再清除背景的原因。
LightweightDemo 例程示范了輕量級(jí)部件的透明特征。
"靈活巧妙地"繪畫方法
當(dāng)AWT嘗試著使呈現(xiàn)部件的處理盡可能高效率時(shí),部件自身paint()的實(shí)現(xiàn)可能對(duì)整體性能有重大的影響。影響這個(gè)處理過程的兩個(gè)關(guān)鍵領(lǐng)域是:
使用裁剪區(qū)來縮小需要呈現(xiàn)的范圍。
應(yīng)用內(nèi)部的版面布局信息來縮小對(duì)子部件的籠罩范圍(僅適用于輕量級(jí)).。
如果你的部件很簡(jiǎn)單 -- 比如,如果是一個(gè)按鈕 -- 那么就不值得花費(fèi)氣力去改善它的呈現(xiàn)屬性,使它僅僅去繪畫與修剪區(qū)相交的部分;不理會(huì)Graphics的裁剪區(qū)直接繪制整個(gè)部件反而更劃算。然而,如果你創(chuàng)建的部件界面很復(fù)雜,比如文本部件,那么迫切需要你的代碼使用裁剪信息來縮小需要繪圖的范圍。
更進(jìn)一步講,如果你寫了一個(gè)容納了很多部件的復(fù)雜的輕量級(jí)容器,其中的部件和容器的布局管理器,或者只是容器的布局管理器擁有布局的信息,那么就值得使用所知道的布局信息來更靈活地確定哪個(gè)子部件需要繪畫。Container.paint()的默認(rèn)實(shí)現(xiàn)只是簡(jiǎn)單地按順序遍歷子部件,檢查它是否可見、是否與重?fù)Q區(qū)域相交 -- 對(duì)于某幾個(gè)布局管理這種操作就顯得不必要的羅嗦。比如,如果容器在100*100的格子里布置部件,那么格子的信息就可以用來更快得確定這10,000個(gè)部件中哪個(gè)與裁剪框相交,哪個(gè)就確實(shí)需要繪制。
AWT繪畫準(zhǔn)則
AWT為繪制部件提供了一個(gè)簡(jiǎn)單的回調(diào)API。當(dāng)你使用它是,要遵循下面的原則:
對(duì)于大多數(shù)程序,所有的客戶區(qū)繪畫代碼應(yīng)該被放置在部件的paint()方法中。
?
通過調(diào)用repaint()方法,程序可以觸發(fā)一個(gè)將來執(zhí)行的paint()調(diào)用,不能直接調(diào)用paint()方法。
?
對(duì)于界面復(fù)雜的部件,應(yīng)該觸發(fā)帶參數(shù)的repaint()方法,使用參數(shù)定義實(shí)際需要更新的區(qū)域;而不帶參數(shù)調(diào)用會(huì)導(dǎo)致整個(gè)部件被重畫。
?
因?yàn)閷?duì)repaint()的調(diào)用會(huì)首先導(dǎo)致update()的調(diào)用,默認(rèn)地會(huì)促成paint()的調(diào)用,所以重量級(jí)部件應(yīng)該覆蓋update()方法以實(shí)現(xiàn)增量繪制,如果需要的話(輕量級(jí)部件不支持增量繪制) 。
?
覆蓋了paint()方法的java.awt.Container子類應(yīng)當(dāng)在paint()方法中調(diào)用super.paint()以保證子部件能被繪制。
?
界面復(fù)雜的部件應(yīng)該靈活地使用裁剪區(qū)來把繪畫范圍縮小到只包括與裁剪區(qū)相交的范圍。
在Swing中的繪畫
Swing起步于AWT基本繪畫模式,并且作了進(jìn)一步的擴(kuò)展以獲得最大化的性能以及改善可擴(kuò)展性能。象AWT一樣,Swing支持回調(diào)繪畫以及使用repaint()促使部件更新。另外,Swing提供了內(nèi)置的雙緩沖(double-buffering)并且作了改變以支持Swing的其它結(jié)構(gòu)(象邊框(border)和UI代理)。最后,Swing為那些想更進(jìn)一步定制繪畫機(jī)制的程序提供了RepaintManager API。
對(duì)雙緩沖的支持
Swing的最引人注目的特性之一就是把對(duì)雙緩沖的支持整個(gè)兒的內(nèi)置到工具包。通過設(shè)置javax.swing.JComponent的"doubleBuffered"屬性就可以使用雙緩沖:
???? public boolean isDoubleBuffered()
??? public void setDoubleBuffered(boolean o)
當(dāng)緩沖激活的時(shí)候,Swing的雙緩沖機(jī)制為每個(gè)包容層次(通常是每個(gè)最高層的窗體)準(zhǔn)備一個(gè)單獨(dú)的屏外緩沖。并且,盡管這個(gè)屬性可以基于部件而設(shè)置,對(duì)一個(gè)特定的容器上設(shè)置這個(gè)屬性,將會(huì)影響到這個(gè)容器下面的所有輕量級(jí)部件把自己的繪畫提交給屏外緩沖,而不管它們各自的"雙緩沖"屬性值
默認(rèn)地,所有Swing部件的該屬性值為true。不過對(duì)于JRootPane這種設(shè)置確實(shí)有些問題,因?yàn)檫@樣就使所有位于這個(gè)上層Swing部件下面的所有部件都使用了雙緩沖。對(duì)于大多數(shù)的Swing程序,不需要作任何特別的事情就可以使用雙緩沖,除非你要決定這個(gè)屬性是開還是關(guān)(并且為了使GUI能夠平滑呈現(xiàn),你需要打開這個(gè)屬性)。Swing保證會(huì)有適宜的Graphics對(duì)象(或者是為雙緩沖使用的屏外映像的Graphics,或者是正規(guī)的Graphics)傳遞給部件的繪畫回調(diào)函數(shù),所以,部件需要做的所有事情僅僅就是使用這個(gè)Graphics畫圖。本文的后面,在繪制的處理過程這一章會(huì)詳細(xì)解釋這個(gè)機(jī)制。
其他的繪畫屬性
為了改善內(nèi)部的繪畫算法性能,Swing另外引進(jìn)了幾個(gè)JComponent的相互有關(guān)聯(lián)的屬性。引入這些屬性為的是處理下面兩個(gè)問題,這兩個(gè)問題有可能導(dǎo)致輕量級(jí)部件的繪畫成本過高:
透明(Transparency): 當(dāng)一個(gè)輕量級(jí)部件的繪畫結(jié)束時(shí),如果該部件的一部分或者全部透明,那么它就可能不會(huì)把所有與其相關(guān)的像素位都涂上顏色;這就意味著不管它什么時(shí)候重畫,它底層的部件必須首先重畫。這個(gè)技術(shù)需要系統(tǒng)沿著部件的包容層次去找到最底層的重量級(jí)祖先,然后從它開始、從后向前地執(zhí)行繪畫。
重疊的部件(Overlapping components): 當(dāng)一個(gè)輕量級(jí)部件的繪畫結(jié)束是,如果有一些其他的輕量級(jí)部件部分地疊加在它的上方;就是說,不管最初的輕量級(jí)部件什么時(shí)候畫完,只要有疊加在它上面的其它部件(裁剪區(qū)與疊加區(qū)相交),這些疊加的部件必須也要部分地重畫。這需要系統(tǒng)在每次繪畫時(shí)要遍歷大量的包容層次,以檢查與之重疊的部件。
遮光性
?
在一般情況下部件是不透明的,為了提高改善性能,Swing增加了讀寫javax.swing.JComponent的遮光(opaque)屬性的操作:
??? public boolean isOpaque()
??? public void setOpaque(boolean o)
這些設(shè)置是:
true:部件同意在它的矩形范圍包含的里所有像素位上繪畫。
false:部件不保證其矩形范圍內(nèi)所有像素位上繪畫。
遮光(opaque)屬性允許Swing的繪圖系統(tǒng)去檢測(cè)是否一個(gè)對(duì)指定部件的重畫請(qǐng)求會(huì)導(dǎo)致額外的對(duì)其底層祖先的重畫。每個(gè)標(biāo)準(zhǔn)Swing部件的默認(rèn)(遮光)opaque屬性值由當(dāng)前的視-感UI對(duì)象設(shè)定。而對(duì)于大多數(shù)部件,該值為true。
部件實(shí)現(xiàn)中的一個(gè)最常見的錯(cuò)誤是它們?cè)试S遮光(opaque)屬性保持其默認(rèn)值true,卻又不完全地呈現(xiàn)它們所轄的區(qū)域,其結(jié)果就是沒有呈現(xiàn)的部分有時(shí)會(huì)造成屏幕垃圾。當(dāng)一個(gè)部件設(shè)計(jì)完畢,應(yīng)該仔細(xì)的考慮所控制的遮光(opaque)屬性,既要確保透的使用是明智的,因?yàn)樗鼤?huì)花費(fèi)更多的繪畫時(shí)間,又要確保與繪畫系統(tǒng)之間的協(xié)約履行。
遮光(opaque)屬性的意義經(jīng)常被誤解。有時(shí)候被用來表示“使部件的背景透明”。然而這不是Swing對(duì)遮光的精確解釋。一些部件,比如按鈕,為了給部件一個(gè)非矩形的外形可能會(huì)把“遮光”設(shè)置為false,或者為了短時(shí)間的視覺效果使用一個(gè)矩形框圍住部件,例如焦點(diǎn)指示框。在這些情況下,部件不遮光,但是其背景的主要部分仍然需要填充。
如先前的定義,遮光屬性的本質(zhì)是一個(gè)與負(fù)責(zé)重畫的系統(tǒng)之間訂立的契約。如果一個(gè)部件使用遮光屬性去定義怎樣使部件的外觀透明,那么該屬性的這種使用就應(yīng)該備有證明文件。(一些部件可能更合適于定義額外的屬性控制外觀怎樣怎樣增加透明度。例如,javax.swing.AbstractButton提供ContentAreaFilled屬性就是為了達(dá)到這個(gè)目的。)
另一個(gè)毫無價(jià)值的問題是遮光屬性與Swing部件的邊框(border)屬性有多少聯(lián)系。在一個(gè)部件上,由Border對(duì)象呈現(xiàn)的區(qū)域從幾何意義上講仍是部件的一部分。就是說如果部件遮光,它就有責(zé)任去填充邊框所占用的空間。(然后只需要把邊框放到該不透明的部件之上就可以了)。
如果你想使一個(gè)部件允許其底層部件能透過它的邊框范圍而顯示出來 -- 即,通過isBorderOpaque()判斷border是否支持透明而返回值為false -- 那么部件必須定義自身的遮光屬性為false并且確保它不在邊框的范圍內(nèi)繪圖。
"最佳的"繪畫方案
部件重疊的問題有些棘手。即使沒有直接的兄弟部件疊加在該部件之上,也總是可能有非直系繼承關(guān)系(比如"堂兄妹"或者"姑嬸")的部件會(huì)與它交疊。這樣的情況下,處于一個(gè)復(fù)雜層次中的每個(gè)部件的重畫工作都需要一大堆的樹遍歷來確保'正確地'繪畫。為了減少不必要的遍歷,Swing為javax.swing.JComponent增加一個(gè)只讀的isOptimizedDrawingEnabled屬性:
??? public boolean isOptimizedDrawingEnabled()
這些設(shè)置是:
true:部件指示沒有直接的子孫與其重疊。
false: 部件不保證有沒有直接的子孫與之交疊。
通過檢查isOptimizedDrawingEnabled屬性,Swing在重畫時(shí)可以快速減少對(duì)交疊部件的搜索。
因?yàn)閕sOptimizedDrawingEnabled屬性是只讀的,于是部件改變默認(rèn)值的唯一方法是在其子類覆蓋(override)這個(gè)方法來返回所期望的值。除了JLayeredPane,JDesktopPane,和JViewPort外,所有標(biāo)準(zhǔn)Swing部件對(duì)這個(gè)屬性返回true。
繪畫方法
適應(yīng)于AWT的輕量級(jí)部件的規(guī)則同樣也適用于Swing部件 -- 舉一個(gè)例子,在部件需要呈現(xiàn)的時(shí)候就會(huì)調(diào)用paint() -- 只是Swing更進(jìn)一步地把paint()的調(diào)用分解為3個(gè)分立的方法,以下列順序依次執(zhí)行:
???? protected void paintComponent(Graphics g)
??? protected void paintBorder(Graphics g)
??? protected void paintChildren(Graphics g)
Swing程序應(yīng)該覆蓋paintComponent()而不是覆蓋paint()。雖然API允許這樣做,但通常沒有理由去覆蓋paintBorder()或者paintComponents()(如果你這么做了,請(qǐng)確認(rèn)你知道你到底在做什么!)。這個(gè)分解使得編程變得更容易,程序可以只覆蓋它們需要擴(kuò)展的一部分繪畫。例如,這樣就解決先前在AWT中提到的問題,因?yàn)檎{(diào)用super.paint()失敗而使得所有輕量級(jí)子孫都不能顯示。
SwingPaintDemo例子程序舉例說明了Swing的paintComponent()回調(diào)方法的簡(jiǎn)單應(yīng)用。
繪畫與UI代理
大多數(shù)標(biāo)準(zhǔn)Swing部件擁有它們自己的、由分離的觀-感(look-and-feel)對(duì)象(叫做"UI代理")實(shí)現(xiàn)的觀-感。這意味著標(biāo)準(zhǔn)部件把大多數(shù)或者所有的繪畫委派給UI代理,并且出現(xiàn)在下面的途徑:
paint()觸發(fā)paintComponent()方法。
如果ui屬性為non-null,paintComponent()觸發(fā)ui.update()。
如果部件的遮光屬性為true,ui.udpate()方法使用背景顏色填充部件的背景并且觸發(fā)ui.paint()。
ui.paint()呈現(xiàn)部件的內(nèi)容。
這意味著Swing部件的擁有UI代理的子類(相對(duì)于JComponent的直系子類),應(yīng)該在它們所覆蓋的paintComponent方法中觸發(fā)super.paintComponent()。
??? public class MyPanel extends JPanel {
??????? protected void paintComponent(Graphics g) {
??? // Let UI delegate paint first
??? // (including background filling, if I'm opaque)
??? super.paintComponent(g);
??? // paint my contents next....
??????? }
??? }
如果因?yàn)槟承┰虿考臄U(kuò)展類不允許UI代理去執(zhí)行繪畫(是如果,例如,完全更換了部件的外觀),它可以忽略對(duì)super.paintComponent()的調(diào)用,但是它必須負(fù)責(zé)填充自己的背景,如果遮光(opaque)屬性為true的話,如前面在遮光(opaque)屬性一章講述的。
繪畫的處理過程
Swing處理"repaint"請(qǐng)求的方式與AWT有稍微地不同,雖然對(duì)于應(yīng)用開發(fā)人員來講其本質(zhì)是相同的 -- 同樣是觸發(fā)paint()。Swing這么做是為了支持它的RepaintManager API (后面介紹),就象改善繪畫性能一樣。在Swing里的繪畫可以走兩條路,如下所述:
(A) 繪畫需求產(chǎn)生于第一個(gè)重量級(jí)祖先(通常是JFrame、JDialog、JWindow或者JApplet):
事件分派線程調(diào)用其祖先的paint()
?
Container.paint()的默認(rèn)實(shí)現(xiàn)會(huì)遞歸地調(diào)用任何輕量級(jí)子孫的paint()方法。
?
當(dāng)?shù)竭_(dá)第一個(gè)Swing部件時(shí),JComponent.paint()的默認(rèn)執(zhí)行做下面的步驟:
如果部件的雙緩沖屬性為true并且部件的RepaintManager上的雙緩沖已經(jīng)激活,將把Graphics對(duì)象轉(zhuǎn)換為一個(gè)合適的屏外Graphics。
調(diào)用paintComponent()(如果使用雙緩沖就把屏外Graphics傳遞進(jìn)去)。
調(diào)用paintChildren()(如果使用雙緩沖就把屏外Graphics傳遞進(jìn)去),該方法使用裁剪并且遮光和optimizedDrawingEnabled等屬性來嚴(yán)密地判定要遞歸地調(diào)用哪些子孫的paint()。
如果部件的雙緩沖屬性為true并且在部件的RepaintManager上的雙緩沖已經(jīng)激活,使用最初的屏幕Graphics對(duì)象把屏外映像拷貝到部件上。
注意:JComponent.paint()步驟#1和#5在對(duì)paint()的遞歸調(diào)用中被忽略了(由于paintChildren(),在步驟#4中介紹了),因?yàn)樗性趕wing窗體層次中的輕量級(jí)部件將共享同一個(gè)用于雙緩沖的屏外映像。
(B) 繪畫需求從一個(gè)javax.swing.JComponent擴(kuò)展類的repaint()調(diào)用上產(chǎn)生:
JComponent.repaint()注冊(cè)一個(gè)針對(duì)部件的RepaintManager的異步的重畫需求,該操作使用invokeLater()把一個(gè)Runnable加入事件隊(duì)列以便稍后執(zhí)行在事件分派線程上的需求。
?
該Runnable在事件分派線程上執(zhí)行并且導(dǎo)致部件的RepaintManager調(diào)用該部件上paintImmediately(),該方法執(zhí)行下列步驟:
使用裁剪框以及遮光和optimizedDrawingEnabled屬性確定“根”部件,繪畫一定從這個(gè)部件開始(處理透明以及潛在的重疊部件)。
如果根部件的雙緩沖屬性為true,并且根部件的RepaintManager上的雙緩沖已激活,將轉(zhuǎn)換Graphics對(duì)象到適當(dāng)?shù)钠镣釭raphics。
調(diào)用根部件(該部件執(zhí)行上述(A)中的JComponent.paint()步驟#2-4)上的paint(),導(dǎo)致根部件之下的、與裁剪框相交的所有部件被繪制。
如果根部件的doubleBuffered屬性為true并且根部件的RepaintManager上的雙緩沖已經(jīng)激活,使用原始的Graphics把屏外映像拷貝到部件。
注意:如果在重畫沒有完成之前,又有發(fā)生多起對(duì)部件或者任何一個(gè)其祖先的repaint()調(diào)用,所有這些調(diào)用會(huì)被折疊到一個(gè)單一的調(diào)用 回到paintImmediately() on topmostSwing部件 on which 其repaint()被調(diào)用。例如,如果一個(gè)JTabbedPane包含了一個(gè)JTable并且在其包容層次中的現(xiàn)有的重畫需求完成之前兩次發(fā)布對(duì)repaint()的調(diào)用,其結(jié)果將變成對(duì)該JTabbedPane部件的paintImmediately()方法的單一調(diào)用,會(huì)觸發(fā)兩個(gè)部件的paint()的執(zhí)行。
這意味著對(duì)于Swing部件來說,update()不再被調(diào)用。
雖然repaint()方法導(dǎo)致了對(duì)paintImmediately()的調(diào)用,它不考慮"回調(diào)"繪圖,并且客戶端的繪畫代碼也不會(huì)放置到paintImmediately()方法里面。實(shí)際上,除非有特殊的原因,根本不需要超載paintImmediately()方法。
同步繪圖
象我們?cè)谇懊嬲鹿?jié)所講述的,paintImmediately()表現(xiàn)為一個(gè)入口,用來通知Swing部件繪制自身,確認(rèn)所有需要的繪畫都能適當(dāng)?shù)禺a(chǎn)生。這個(gè)方法也可能用來安排同步的繪圖需求,就象它的名字所暗示的,即一些部件有時(shí)候需要保證它們的外觀實(shí)時(shí)地與其內(nèi)部狀態(tài)保持一致(例如,在JScrollPane執(zhí)行滾定操作的時(shí)候確實(shí)需要這樣并且也做到了)。
程序不應(yīng)該直接調(diào)用這個(gè)方法,除非有合理實(shí)時(shí)繪畫需要。這是因?yàn)楫惒降膔epaint()可以使多個(gè)重復(fù)的需求得到有效的精簡(jiǎn),反之直接調(diào)用paintImmediately()則做不到這點(diǎn)。另外,調(diào)用這個(gè)方法的規(guī)則是它必須由事件分派線程中的進(jìn)程調(diào)用;它也不是為能以多線程運(yùn)行你的繪畫代碼而設(shè)計(jì)的。關(guān)于Swing單線程模式的更多信息,參考一起歸檔的文章"Threads and Swing."
RepaintManager
Swing的RepaintManager類的目的是最大化地提高Swing包容層次上的重畫執(zhí)行效率,同時(shí)也實(shí)現(xiàn)了Swing的'重新生效'機(jī)制(作為一個(gè)題目,將在其它文章里介紹)。它通過截取所有Swing部件的重畫需求(于是它們不再需要經(jīng)由AWT處理)實(shí)現(xiàn)了重畫機(jī)制,并且在需要更新的情況下維護(hù)其自身的狀態(tài)(我們已經(jīng)知道的"dirty regions")。最后,它使用invokeLater()去處理事件分派線程中的未決需求,如同在"Repaint Processing"一節(jié)中描述的那樣(B選項(xiàng)).
對(duì)于大多數(shù)程序來講,RepaintManager可以看做是Swing的內(nèi)部系統(tǒng)的一部分,并且甚至可以被忽略。然而,它的API為程序能更出色地控制繪畫中的幾個(gè)要素提供了選擇。
?
"當(dāng)前的"RepaintManager
RepaintManager設(shè)計(jì) is designed to be dynamically plugged, 雖然 有一個(gè)單獨(dú)的接口。下面的靜態(tài)方法允許程序得到并且設(shè)置"當(dāng)前的"RepaintManager:
???? public static RepaintManager currentManager(Component c)
??? public static RepaintManager currentManager(JComponent c)
??? public static void
???????? setCurrentManager(RepaintManager aRepaintManager)
更換"當(dāng)前的"RepaintManager
總的說來,程序通過下面的步驟可能會(huì)擴(kuò)展并且更換RepaintManager:
??? RepaintManager.setCurrentManager(new MyRepaintManager());
你也可以參考RepaintManagerDemo ,這是個(gè)簡(jiǎn)單的舉例說明RepaintManager加載的例子,該例子將把有關(guān)正在執(zhí)行重畫的部件的信息打印出來。
擴(kuò)展和替換RepaintManager的一個(gè)更有趣的動(dòng)機(jī)是可以改變對(duì)重畫的處理方式。當(dāng)前,默認(rèn)的重畫實(shí)現(xiàn)所使用的來跟蹤dirty regions的內(nèi)部狀態(tài)值是包內(nèi)私有的并且因此不能被繼承類訪問。然而,程序可以實(shí)現(xiàn)它們自己的跟蹤dirty regions的機(jī)制并且通過超載下面的方法對(duì)重畫需求的縮減:
???? public synchronized void
????? addDirtyRegion(JComponent c, int x, int y, int w, int h)
??? public Rectangle getDirtyRegion(JComponent aComponent)
??? public void markCompletelyDirty(JComponent aComponent)
??? public void markCompletelyClean(JComponent aComponent) {
addDirtyRegion()方法是在調(diào)用Swing部件的repaint()的之后被調(diào)用的,因此可以用作鉤子來捕獲所有的重畫需求。如果程序超載了這個(gè)方法(并且不調(diào)用super.addDirtyRegion()),那么它改變了它的職責(zé),而使用invokeLater()把Runnable放置到EventQueue ,該隊(duì)列將在合適的部件上調(diào)用paintImmediately()(translation: not for the faint of heart).
從全局控制雙緩沖
RepaintManager提供了從全局中激活或者禁止雙緩沖的API:
???? public void setDoubleBufferingEnabled(boolean aFlag)
??? public boolean isDoubleBufferingEnabled()
這個(gè)屬性在繪畫處理的時(shí)候,在JComponent的內(nèi)部檢查過以確定是否使用屏外緩沖顯示部件。這個(gè)屬性默認(rèn)為true,但是如果程序希望在全局范圍為所有Swing部件關(guān)閉雙緩沖的使用,可以按照下面的步驟做:
??? RepaintManager.currentManager(mycomponent).
????????????????? setDoubleBufferingEnabled(false);
注意:因?yàn)镾wing的默認(rèn)實(shí)現(xiàn)要初始化一個(gè)單獨(dú)的RepaintManager實(shí)例,mycomponent參數(shù)與此不相關(guān)。
Swing繪畫準(zhǔn)則
Swing開發(fā)人員在寫繪畫代碼時(shí)應(yīng)該理解下面的準(zhǔn)則:
對(duì)于Swing部件,不管是系統(tǒng)-觸發(fā)還是程序-觸發(fā)的請(qǐng)求,總會(huì)調(diào)用paint()方法;而update()不再被Swing部件調(diào)用。
?
程序可以通過repaint()觸發(fā)一個(gè)異步的paint()調(diào)用,但是不能直接調(diào)用paint()。
?
對(duì)于復(fù)雜的界面,應(yīng)該調(diào)用帶參數(shù)的repaint(),這樣可以僅僅更新由該參數(shù)定義的區(qū)域;而不要調(diào)用無參數(shù)的repaint(),導(dǎo)致整個(gè)部件重畫。
?
Swing中實(shí)現(xiàn)paint()的3個(gè)要素是調(diào)用3個(gè)分離的回調(diào)方法:
paintComponent()
paintBorder()
paintChildren()
Swing部件的子類,如果想執(zhí)行自己的繪畫代碼,應(yīng)該把自己的繪畫代碼放在paintComponent()方法的范圍之內(nèi)。(不要放在paint()里面)。
?
Swing引進(jìn)了兩個(gè)屬性來最大化的改善繪畫的性能:
opaque: 部件是否要重畫它所占據(jù)范圍中的所有像素位?
optimizedDrawingEnabled: 是否有這個(gè)部件的子孫與之交疊?
?
如故Swing部件的(遮光)opaque屬性設(shè)置為true,那就表示它要負(fù)責(zé)繪制它所占據(jù)的范圍內(nèi)的所有像素位(包括在paintComponent()中清除它自己的背景),否則會(huì)造成屏幕垃圾。
把一個(gè)部件設(shè)置為遮光(opaque)同時(shí)又把它的optimizedDrawingEnabled屬性設(shè)置為false,將導(dǎo)致在每個(gè)繪畫操作中要執(zhí)行更多的處理,因此我們推薦的明智的方法是同時(shí)使用透明并且交疊部件。
使用UI代理(包括JPanel)的Swing部件的擴(kuò)展類的典型作法是在它們自己的paintComponent()的實(shí)現(xiàn)中調(diào)用super.paintComponent()。因?yàn)閁I代理可以負(fù)責(zé)清除一個(gè)遮光部件的背景,這將照顧到#5.
Swing通過JComponent的doubleBuffered屬性支持內(nèi)置的雙緩沖,所有的Swing部件該屬性默認(rèn)值是true,然而把Swing容器的遮光設(shè)置為true有一個(gè)整體的構(gòu)思,把該容器上的所有輕量級(jí)子孫的屬性打開,不管它們各自的設(shè)定。
強(qiáng)烈建議為所有的Swing部件使用雙緩沖。
界面復(fù)雜的部件應(yīng)該靈活地運(yùn)用剪切框來,只對(duì)那些與剪切框相交的區(qū)域進(jìn)行繪畫操作,從而減少工作量。
總結(jié)
不管AWT還是Swing都提供了方便的編程手段使得部件內(nèi)容能夠正確地顯示到屏幕上。雖然對(duì)于大多數(shù)的GUI需要我們推薦使用Swing,但是理解AWT的繪畫機(jī)制也會(huì)給我們帶來幫助,因?yàn)镾wing建立在它的基礎(chǔ)上。
關(guān)于AWT和Sing的特點(diǎn)就介紹到這里,應(yīng)用開發(fā)人員應(yīng)該盡力按照本文中介紹的準(zhǔn)則來撰寫代碼,充分發(fā)揮這些API功能,使自己的程序獲得最佳性能。
?
--sunfruit
空間數(shù)據(jù)庫Oracle Spatial的建立過程如下:
-- 創(chuàng)建最基本的個(gè)人興趣點(diǎn)表結(jié)構(gòu)
drop table poi;
create table poi (gid?INTEGER,
??opid?INTEGER,
??gname?VARCHAR2(256),
??gshape?MDSYS.SDO_GEOMETRY);
-- 更新用戶空間數(shù)據(jù)對(duì)象視圖(建立索引依賴她)
delete from USER_SDO_GEOM_METADATA where TABLE_NAME='POI' and COLUMN_NAME='GSHAPE';
insert into USER_SDO_GEOM_METADATA values ('poi',
?????'gshape',
?????MDSYS.SDO_DIM_ARRAY(MDSYS.SDO_DIM_ELEMENT('lon',?-- lontitude
??????????-64800000,?-- min(china 26430867 73.41907434101486)
??????????64800000,?-- max(china 49679991 137.99997381765377)
??????????1),??-- scale (china abs 23249124)
???????MDSYS.SDO_DIM_ELEMENT('lat',??-- latitude
??????????-32400000,?-- min(china -1677502 -4.6597267116858045)
??????????32400000,?-- max(china 21571819 59.92171939467364)
??????????1)),??-- scale (china abs 23249321)
?????NULL);
-- 插入一個(gè)個(gè)人興趣點(diǎn)的SQL語句,使用標(biāo)準(zhǔn)點(diǎn)地物空間數(shù)據(jù)類型
delete from POI;
insert into POI values (20010001,
???1,
???'我的家',
???MDSYS.SDO_GEOMETRY(2001,?-- SDO_GTYPE
?????NULL,??-- SDO_SRID
?????SDO_POINT_TYPE(41884696, 14377039, NULL), NULL, NULL));
-- 插入一個(gè)個(gè)人興趣點(diǎn)的SQL語句,使用另一種點(diǎn)地物空間數(shù)據(jù)組織結(jié)構(gòu)
delete from POI;
insert into POI values (20010001,
???1,
???'我的家',
???MDSYS.SDO_GEOMETRY(2001,?-- SDO_GTYPE
?????NULL,??-- SDO_SRID
?????NULL,??-- SDO_POINT
?????MDSYS.SDO_ELEM_INFO_ARRAY (1,?-- SDO_STARTING_OFFSET
????????1,?-- SDO_ETYPE
????????1),?-- SDO_INTERPRETATION
?????MDSYS.SDO_ORDINATE_ARRAY (41884696,14377039)));
-- 創(chuàng)建缺省的R-tree空間索引
drop index POI_IDX;
CREATE INDEX POI_IDX on poi(gshape)
?INDEXTYPE is MDSYS.SPATIAL_INDEX;
--?PARAMETERS('SDO_LEVEL=10000');?-- 180*60*60*1000*2/100/100*90*60*60*1000*2/100/100 = 8398080000
-- 索引粗濾矩形窗口選擇SQL語句(對(duì)于點(diǎn)地物對(duì)象,索引粗濾的結(jié)果是精確的)
SELECT * FROM POI P
?WHERE sdo_filter(P.gshape,
??mdsys.sdo_geometry(2003,NULL,NULL,
????mdsys.sdo_elem_info_array(1,1003,3),
????mdsys.sdo_ordinate_array(41883696,14376039, 41885696,14378039)),
??'querytype=window') = 'TRUE';
-- 精確匹配矩形窗口選擇SQL語句(計(jì)算非常耗時(shí))
SELECT * FROM POI P
?WHERE sdo_relate(P.gshape,
??mdsys.sdo_geometry(2003,NULL,NULL,
????mdsys.sdo_elem_info_array(1,1003,3),
????mdsys.sdo_ordinate_array(41883696,14376039, 41885696,14378039)),
??'mask=INSIDE querytype=window') = 'TRUE';
?
Java虛擬機(jī)的深入研究
作者:劉學(xué)超
1??Java技術(shù)與Java虛擬機(jī)
說起Java,人們首先想到的是Java編程語言,然而事實(shí)上,Java是一種技術(shù),它由四方面組成: Java編程語言、Java類文件格式、Java虛擬機(jī)和Java應(yīng)用程序接口(Java API)。它們的關(guān)系如下圖所示:
圖1??Java四個(gè)方面的關(guān)系
運(yùn)行期環(huán)境代表著Java平臺(tái),開發(fā)人員編寫Java代碼(.java文件),然后將之編譯成字節(jié)碼(.class文件)。最后字節(jié)碼被裝入內(nèi)存,一旦字節(jié)碼進(jìn)入虛擬機(jī),它就會(huì)被解釋器解釋執(zhí)行,或者是被即時(shí)代碼發(fā)生器有選擇的轉(zhuǎn)換成機(jī)器碼執(zhí)行。從上圖也可以看出Java平臺(tái)由Java虛擬機(jī)和Java應(yīng)用程序接口搭建,Java語言則是進(jìn)入這個(gè)平臺(tái)的通道,用Java語言編寫并編譯的程序可以運(yùn)行在這個(gè)平臺(tái)上。這個(gè)平臺(tái)的結(jié)構(gòu)如下圖所示:
在Java平臺(tái)的結(jié)構(gòu)中, 可以看出,Java虛擬機(jī)(JVM) 處在核心的位置,是程序與底層操作系統(tǒng)和硬件無關(guān)的關(guān)鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統(tǒng), 其中依賴于平臺(tái)的部分稱為適配器;JVM 通過移植接口在具體的平臺(tái)和操作系統(tǒng)上實(shí)現(xiàn);在JVM 的上方是Java的基本類庫和擴(kuò)展類庫以及它們的API, 利用Java API編寫的應(yīng)用程序(application) 和小程序(Java applet) 可以在任何Java平臺(tái)上運(yùn)行而無需考慮底層平臺(tái), 就是因?yàn)橛蠮ava虛擬機(jī)(JVM)實(shí)現(xiàn)了程序與操作系統(tǒng)的分離,從而實(shí)現(xiàn)了Java 的平臺(tái)無關(guān)性。
那么到底什么是Java虛擬機(jī)(JVM)呢?通常我們談?wù)揓VM時(shí),我們的意思可能是:
- 對(duì)JVM規(guī)范的的比較抽象的說明;
- 對(duì)JVM的具體實(shí)現(xiàn);
- 在程序運(yùn)行期間所生成的一個(gè)JVM實(shí)例。
對(duì)JVM規(guī)范的的抽象說明是一些概念的集合,它們已經(jīng)在書《The Java Virtual Machine Specification》(《Java虛擬機(jī)規(guī)范》)中被詳細(xì)地描述了;對(duì)JVM的具體實(shí)現(xiàn)要么是軟件,要么是軟件和硬件的組合,它已經(jīng)被許多生產(chǎn)廠商所實(shí)現(xiàn),并存在于多種平臺(tái)之上;運(yùn)行Java程序的任務(wù)由JVM的運(yùn)行期實(shí)例單個(gè)承擔(dān)。在本文中我們所討論的Java虛擬機(jī)(JVM)主要針對(duì)第三種情況而言。它可以被看成一個(gè)想象中的機(jī)器,在實(shí)際的計(jì)算機(jī)上通過軟件模擬來實(shí)現(xiàn),有自己想象中的硬件,如處理器、堆棧、寄存器等,還有自己相應(yīng)的指令系統(tǒng)。
JVM在它的生存周期中有一個(gè)明確的任務(wù),那就是運(yùn)行Java程序,因此當(dāng)Java程序啟動(dòng)的時(shí)候,就產(chǎn)生JVM的一個(gè)實(shí)例;當(dāng)程序運(yùn)行結(jié)束的時(shí)候,該實(shí)例也跟著消失了。下面我們從JVM的體系結(jié)構(gòu)和它的運(yùn)行過程這兩個(gè)方面來對(duì)它進(jìn)行比較深入的研究。
2??Java虛擬機(jī)的體系結(jié)構(gòu)
剛才已經(jīng)提到,JVM可以由不同的廠商來實(shí)現(xiàn)。由于廠商的不同必然導(dǎo)致JVM在實(shí)現(xiàn)上的一些不同,然而JVM還是可以實(shí)現(xiàn)跨平臺(tái)的特性,這就要?dú)w功于設(shè)計(jì)JVM時(shí)的體系結(jié)構(gòu)了。
我們知道,一個(gè)JVM實(shí)例的行為不光是它自己的事,還涉及到它的子系統(tǒng)、存儲(chǔ)區(qū)域、數(shù)據(jù)類型和指令這些部分,它們描述了JVM的一個(gè)抽象的內(nèi)部體系結(jié)構(gòu),其目的不光規(guī)定實(shí)現(xiàn)JVM時(shí)它內(nèi)部的體系結(jié)構(gòu),更重要的是提供了一種方式,用于嚴(yán)格定義實(shí)現(xiàn)時(shí)的外部行為。每個(gè)JVM都有兩種機(jī)制,一個(gè)是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統(tǒng);另外的一個(gè)負(fù)責(zé)執(zhí)行包含在已裝載的類或接口中的指令,叫做運(yùn)行引擎。每個(gè)JVM又包括方法區(qū)、堆、Java棧、程序計(jì)數(shù)器和本地方法棧這五個(gè)部分,這幾個(gè)部分和類裝載機(jī)制與運(yùn)行引擎機(jī)制一起組成的體系結(jié)構(gòu)圖為:
圖3??JVM的體系結(jié)構(gòu)
JVM的每個(gè)實(shí)例都有一個(gè)它自己的方法域和一個(gè)堆,運(yùn)行于JVM內(nèi)的所有的線程都共享這些區(qū)域;當(dāng)虛擬機(jī)裝載類文件的時(shí)候,它解析其中的二進(jìn)制數(shù)據(jù)所包含的類信息,并把它們放到方法域中;當(dāng)程序運(yùn)行的時(shí)候,JVM把程序初始化的所有對(duì)象置于堆上;而每個(gè)線程創(chuàng)建的時(shí)候,都會(huì)擁有自己的程序計(jì)數(shù)器和Java棧,其中程序計(jì)數(shù)器中的值指向下一條即將被執(zhí)行的指令,線程的Java棧則存儲(chǔ)為該線程調(diào)用Java方法的狀態(tài);本地方法調(diào)用的狀態(tài)被存儲(chǔ)在本地方法棧,該方法棧依賴于具體的實(shí)現(xiàn)。
下面分別對(duì)這幾個(gè)部分進(jìn)行說明。
執(zhí)行引擎處于JVM的核心位置,在Java虛擬機(jī)規(guī)范中,它的行為是由指令集所決定的。盡管對(duì)于每條指令,規(guī)范很詳細(xì)地說明了當(dāng)JVM執(zhí)行字節(jié)碼遇到指令時(shí),它的實(shí)現(xiàn)應(yīng)該做什么,但對(duì)于怎么做卻言之甚少。Java虛擬機(jī)支持大約248個(gè)字節(jié)碼。每個(gè)字節(jié)碼執(zhí)行一種基本的CPU運(yùn)算,例如,把一個(gè)整數(shù)加到寄存器,子程序轉(zhuǎn)移等。Java指令集相當(dāng)于Java程序的匯編語言。
Java指令集中的指令包含一個(gè)單字節(jié)的操作符,用于指定要執(zhí)行的操作,還有0個(gè)或多個(gè)操作數(shù),提供操作所需的參數(shù)或數(shù)據(jù)。許多指令沒有操作數(shù),僅由一個(gè)單字節(jié)的操作符構(gòu)成。
虛擬機(jī)的內(nèi)層循環(huán)的執(zhí)行過程如下:
do{
取一個(gè)操作符字節(jié);
根據(jù)操作符的值執(zhí)行一個(gè)動(dòng)作;
}while(程序未結(jié)束)
由于指令系統(tǒng)的簡(jiǎn)單性,使得虛擬機(jī)執(zhí)行的過程十分簡(jiǎn)單,從而有利于提高執(zhí)行的效率。指令中操作數(shù)的數(shù)量和大小是由操作符決定的。如果操作數(shù)比一個(gè)字節(jié)大,那么它存儲(chǔ)的順序是高位字節(jié)優(yōu)先。例如,一個(gè)16位的參數(shù)存放時(shí)占用兩個(gè)字節(jié),其值為:
第一個(gè)字節(jié)*256+第二個(gè)字節(jié)字節(jié)碼。
指令流一般只是字節(jié)對(duì)齊的。指令tableswitch和lookup是例外,在這兩條指令內(nèi)部要求強(qiáng)制的4字節(jié)邊界對(duì)齊。
對(duì)于本地方法接口,實(shí)現(xiàn)JVM并不要求一定要有它的支持,甚至可以完全沒有。Sun公司實(shí)現(xiàn)Java本地接口(JNI)是出于可移植性的考慮,當(dāng)然我們也可以設(shè)計(jì)出其它的本地接口來代替Sun公司的JNI。但是這些設(shè)計(jì)與實(shí)現(xiàn)是比較復(fù)雜的事情,需要確保垃圾回收器不會(huì)將那些正在被本地方法調(diào)用的對(duì)象釋放掉。
Java的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類的實(shí)例(對(duì)象)從中分配空間,它的管理是由垃圾回收來負(fù)責(zé)的:不給程序員顯式釋放對(duì)象的能力。Java不規(guī)定具體使用的垃圾回收算法,可以根據(jù)系統(tǒng)的需求使用各種各樣的算法。
Java方法區(qū)與傳統(tǒng)語言中的編譯后代碼或是Unix進(jìn)程中的正文段類似。它保存方法代碼(編譯后的java代碼)和符號(hào)表。在當(dāng)前的Java實(shí)現(xiàn)中,方法代碼不包括在垃圾回收堆中,但計(jì)劃在將來的版本中實(shí)現(xiàn)。每個(gè)類文件包含了一個(gè)Java類或一個(gè)Java界面的編譯后的代碼。可以說類文件是Java語言的執(zhí)行代碼文件。為了保證類文件的平臺(tái)無關(guān)性,Java虛擬機(jī)規(guī)范中對(duì)類文件的格式也作了詳細(xì)的說明。其具體細(xì)節(jié)請(qǐng)參考Sun公司的Java虛擬機(jī)規(guī)范。
Java虛擬機(jī)的寄存器用于保存機(jī)器的運(yùn)行狀態(tài),與微處理器中的某些專用寄存器類似。Java虛擬機(jī)的寄存器有四種:
- pc: Java程序計(jì)數(shù)器;
- optop: 指向操作數(shù)棧頂端的指針;
- frame: 指向當(dāng)前執(zhí)行方法的執(zhí)行環(huán)境的指針;。
- vars: 指向當(dāng)前執(zhí)行方法的局部變量區(qū)第一個(gè)變量的指針。
在上述體系結(jié)構(gòu)圖中,我們所說的是第一種,即程序計(jì)數(shù)器,每個(gè)線程一旦被創(chuàng)建就擁有了自己的程序計(jì)數(shù)器。當(dāng)線程執(zhí)行Java方法的時(shí)候,它包含該線程正在被執(zhí)行的指令的地址。但是若線程執(zhí)行的是一個(gè)本地的方法,那么程序計(jì)數(shù)器的值就不會(huì)被定義。
Java虛擬機(jī)的棧有三個(gè)區(qū)域:局部變量區(qū)、運(yùn)行環(huán)境區(qū)、操作數(shù)區(qū)。
局部變量區(qū)
每個(gè)Java方法使用一個(gè)固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的。長(zhǎng)整數(shù)和雙精度浮點(diǎn)數(shù)占據(jù)了兩個(gè)局部變量的空間,卻按照第一個(gè)局部變量的索引來尋址。(例如,一個(gè)具有索引n的局部變量,如果是一個(gè)雙精度浮點(diǎn)數(shù),那么它實(shí)際占據(jù)了索引n和n+1所代表的存儲(chǔ)空間)虛擬機(jī)規(guī)范并不要求在局部變量中的64位的值是64位對(duì)齊的。虛擬機(jī)提供了把局部變量中的值裝載到操作數(shù)棧的指令,也提供了把操作數(shù)棧中的值寫入局部變量的指令。
運(yùn)行環(huán)境區(qū)
在運(yùn)行環(huán)境中包含的信息用于動(dòng)態(tài)鏈接,正常的方法返回以及異常捕捉。
動(dòng)態(tài)鏈接
運(yùn)行環(huán)境包括對(duì)指向當(dāng)前類和當(dāng)前方法的解釋器符號(hào)表的指針,用于支持方法代碼的動(dòng)態(tài)鏈接。方法的class文件代碼在引用要調(diào)用的方法和要訪問的變量時(shí)使用符號(hào)。動(dòng)態(tài)鏈接把符號(hào)形式的方法調(diào)用翻譯成實(shí)際方法調(diào)用,裝載必要的類以解釋還沒有定義的符號(hào),并把變量訪問翻譯成與這些變量運(yùn)行時(shí)的存儲(chǔ)結(jié)構(gòu)相應(yīng)的偏移地址。動(dòng)態(tài)鏈接方法和變量使得方法中使用的其它類的變化不會(huì)影響到本程序的代碼。
正常的方法返回
如果當(dāng)前方法正常地結(jié)束了,在執(zhí)行了一條具有正確類型的返回指令時(shí),調(diào)用的方法會(huì)得到一個(gè)返回值。執(zhí)行環(huán)境在正常返回的情況下用于恢復(fù)調(diào)用者的寄存器,并把調(diào)用者的程序計(jì)數(shù)器增加一個(gè)恰當(dāng)?shù)臄?shù)值,以跳過已執(zhí)行過的方法調(diào)用指令,然后在調(diào)用者的執(zhí)行環(huán)境中繼續(xù)執(zhí)行下去。
異常捕捉
異常情況在Java中被稱作Error(錯(cuò)誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動(dòng)態(tài)鏈接錯(cuò),如無法找到所需的class文件。②運(yùn)行時(shí)錯(cuò),如對(duì)一個(gè)空指針的引用。程序使用了throw語句。
當(dāng)異常發(fā)生時(shí),Java虛擬機(jī)采取如下措施:
- 檢查與當(dāng)前方法相聯(lián)系的catch子句表。每個(gè)catch子句包含其有效指令范圍,能夠處理的異常類型,以及處理異常的代碼塊地址。
- 與異常相匹配的catch子句應(yīng)該符合下面的條件:造成異常的指令在其指令范圍之內(nèi),發(fā)生的異常類型是其能處理的異常類型的子類型。如果找到了匹配的catch子句,那么系統(tǒng)轉(zhuǎn)移到指定的異常處理塊處執(zhí)行;如果沒有找到異常處理塊,重復(fù)尋找匹配的catch子句的過程,直到當(dāng)前方法的所有嵌套的catch子句都被檢查過。
- 由于虛擬機(jī)從第一個(gè)匹配的catch子句處繼續(xù)執(zhí)行,所以catch子句表中的順序是很重要的。因?yàn)镴ava代碼是結(jié)構(gòu)化的,因此總可以把某個(gè)方法的所有的異常處理器都按序排列到一個(gè)表中,對(duì)任意可能的程序計(jì)數(shù)器的值,都可以用線性的順序找到合適的異常處理塊,以處理在該程序計(jì)數(shù)器值下發(fā)生的異常情況。
- 如果找不到匹配的catch子句,那么當(dāng)前方法得到一個(gè)"未截獲異常"的結(jié)果并返回到當(dāng)前方法的調(diào)用者,好像異常剛剛在其調(diào)用者中發(fā)生一樣。如果在調(diào)用者中仍然沒有找到相應(yīng)的異常處理塊,那么這種錯(cuò)誤將被傳播下去。如果錯(cuò)誤被傳播到最頂層,那么系統(tǒng)將調(diào)用一個(gè)缺省的異常處理塊。
操作數(shù)棧區(qū)
機(jī)器指令只從操作數(shù)棧中取操作數(shù),對(duì)它們進(jìn)行操作,并把結(jié)果返回到棧中。選擇棧結(jié)構(gòu)的原因是:在只有少量寄存器或非通用寄存器的機(jī)器(如Intel486)上,也能夠高效地模擬虛擬機(jī)的行為。操作數(shù)棧是32位的。它用于給方法傳遞參數(shù),并從方法接收結(jié)果,也用于支持操作的參數(shù),并保存操作的結(jié)果。例如,iadd指令將兩個(gè)整數(shù)相加。相加的兩個(gè)整數(shù)應(yīng)該是操作數(shù)棧頂?shù)膬蓚€(gè)字。這兩個(gè)字是由先前的指令壓進(jìn)堆棧的。這兩個(gè)整數(shù)將從堆棧彈出、相加,并把結(jié)果壓回到操作數(shù)棧中。
每個(gè)原始數(shù)據(jù)類型都有專門的指令對(duì)它們進(jìn)行必須的操作。每個(gè)操作數(shù)在棧中需要一個(gè)存儲(chǔ)位置,除了long和double型,它們需要兩個(gè)位置。操作數(shù)只能被適用于其類型的操作符所操作。例如,壓入兩個(gè)int類型的數(shù),如果把它們當(dāng)作是一個(gè)long類型的數(shù)則是非法的。在Sun的虛擬機(jī)實(shí)現(xiàn)中,這個(gè)限制由字節(jié)碼驗(yàn)證器強(qiáng)制實(shí)行。但是,有少數(shù)操作(操作符dupe和swap),用于對(duì)運(yùn)行時(shí)數(shù)據(jù)區(qū)進(jìn)行操作時(shí)是不考慮類型的。
本地方法棧,當(dāng)一個(gè)線程調(diào)用本地方法時(shí),它就不再受到虛擬機(jī)關(guān)于結(jié)構(gòu)和安全限制方面的約束,它既可以訪問虛擬機(jī)的運(yùn)行期數(shù)據(jù)區(qū),也可以使用本地處理器以及任何類型的棧。例如,本地棧是一個(gè)C語言的棧,那么當(dāng)C程序調(diào)用C函數(shù)時(shí),函數(shù)的參數(shù)以某種順序被壓入棧,結(jié)果則返回給調(diào)用函數(shù)。在實(shí)現(xiàn)Java虛擬機(jī)時(shí),本地方法接口使用的是C語言的模型棧,那么它的本地方法棧的調(diào)度與使用則完全與C語言的棧相同。
3??Java虛擬機(jī)的運(yùn)行過程
上面對(duì)虛擬機(jī)的各個(gè)部分進(jìn)行了比較詳細(xì)的說明,下面通過一個(gè)具體的例子來分析它的運(yùn)行過程。
虛擬機(jī)通過調(diào)用某個(gè)指定類的方法main啟動(dòng),傳遞給main一個(gè)字符串?dāng)?shù)組參數(shù),使指定的類被裝載,同時(shí)鏈接該類所使用的其它的類型,并且初始化它們。例如對(duì)于程序:
class HelloApp
{
public static void main(String[] args)
{
System.out.println("Hello World!");
for (int i = 0; i < args.length; i++ )
{
System.out.println(args[i]);
}
}
}
編譯后在命令行模式下鍵入: java HelloApp run virtual machine
將通過調(diào)用HelloApp的方法main來啟動(dòng)java虛擬機(jī),傳遞給main一個(gè)包含三個(gè)字符串"run"、"virtual"、"machine"的數(shù)組。現(xiàn)在我們略述虛擬機(jī)在執(zhí)行HelloApp時(shí)可能采取的步驟。
開始試圖執(zhí)行類HelloApp的main方法,發(fā)現(xiàn)該類并沒有被裝載,也就是說虛擬機(jī)當(dāng)前不包含該類的二進(jìn)制代表,于是虛擬機(jī)使用ClassLoader試圖尋找這樣的二進(jìn)制代表。如果這個(gè)進(jìn)程失敗,則拋出一個(gè)異常。類被裝載后同時(shí)在main方法被調(diào)用之前,必須對(duì)類HelloApp與其它類型進(jìn)行鏈接然后初始化。鏈接包含三個(gè)階段:檢驗(yàn),準(zhǔn)備和解析。檢驗(yàn)檢查被裝載的主類的符號(hào)和語義,準(zhǔn)備則創(chuàng)建類或接口的靜態(tài)域以及把這些域初始化為標(biāo)準(zhǔn)的默認(rèn)值,解析負(fù)責(zé)檢查主類對(duì)其它類或接口的符號(hào)引用,在這一步它是可選的。類的初始化是對(duì)類中聲明的靜態(tài)初始化函數(shù)和靜態(tài)域的初始化構(gòu)造方法的執(zhí)行。一個(gè)類在初始化之前它的父類必須被初始化。整個(gè)過程如下:
圖4:虛擬機(jī)的運(yùn)行過程
4??結(jié)束語
本文通過對(duì)JVM的體系結(jié)構(gòu)的深入研究以及一個(gè)Java程序執(zhí)行時(shí)虛擬機(jī)的運(yùn)行過程的詳細(xì)分析,意在剖析清楚Java虛擬機(jī)的機(jī)理。