ThreadLocal
類是悄悄地出現(xiàn)在 Java 平臺(tái)版本 1.2 中的。雖然支持線程局部變量早就是許多線程工具(例如 Posixpthreads
工具)的一部分,但 Java Threads API 的最初設(shè)計(jì)卻沒有這項(xiàng)有用的功能。而且,最初的實(shí)現(xiàn)也相當(dāng)?shù)托АS捎谶@些原因,ThreadLocal
極少受到關(guān)注,但對(duì)簡(jiǎn)化線程安全并發(fā)程序的開發(fā)來說,它卻是很方便的。在 輕松使用線程的第 3 部分,Java 軟件顧問 Brian Goetz 研究了ThreadLocal
并提供了一些使用技巧。參加 Brian 的 多線程 Java 編程討論論壇以獲得您工程中的線程和并發(fā)問題的幫助。
編寫線程安全類是困難的。它不但要求仔細(xì)分析在什么條件可以對(duì)變量進(jìn)行讀寫,而且要求仔細(xì)分析其它類能如何使用某個(gè)類。 有時(shí),要在不影響類的功能、易用性或性能的情況下使類成為線程安全的是很困難的。有些類保留從一個(gè)方法調(diào)用到下一個(gè)方法調(diào)用的狀態(tài)信息,要在實(shí)踐中使這樣的類成為線程安全的是困難的。
管理非線程安全類的使用比試圖使類成為線程安全的要更容易些。非線程安全類通常可以安全地在多線程程序中使用,只要您能確保一個(gè)線程所用的類的實(shí)例不被其它線程使用。例如,JDBC Connection
類是非線程安全的 — 兩個(gè)線程不能在小粒度級(jí)上安全地共享一個(gè) Connection
— 但如果每個(gè)線程都有它自己的 Connection
,那么多個(gè)線程就可以同時(shí)安全地進(jìn)行數(shù)據(jù)庫操作。
不使用 ThreadLocal
為每個(gè)線程維護(hù)一個(gè)單獨(dú)的 JDBC 連接(或任何其它對(duì)象)當(dāng)然是可能的;Thread API 給了我們把對(duì)象和線程聯(lián)系起來所需的所有工具。而 ThreadLocal 則使我們能更容易地把線程和它的每線程(per-thread)數(shù)據(jù)成功地聯(lián)系起來。
什么是線程局部變量(thread-local variable)?
線程局部變量高效地為每個(gè)使用它的線程提供單獨(dú)的線程局部變量值的副本。每個(gè)線程只能看到與自己相聯(lián)系的值,而不知道別的線程可能正在使用或修改它們自己的副本。一些編譯器(例如 Microsoft Visual C++ 編譯器或 IBM XL FORTRAN 編譯器)用存儲(chǔ)類別修飾符(像 static
或 volatile
)把對(duì)線程局部變量的支持集成到了其語言中。Java 編譯器對(duì)線程局部變量不提供特別的語言支持;相反地,它用 ThreadLocal
類實(shí)現(xiàn)這些支持, 核心 Thread
類中有這個(gè)類的特別支持。
因?yàn)榫€程局部變量是通過一個(gè)類來實(shí)現(xiàn)的,而不是作為 Java 語言本身的一部分,所以 Java 語言線程局部變量的使用語法比內(nèi)建線程局部變量語言的使用語法要笨拙一些。要?jiǎng)?chuàng)建一個(gè)線程局部變量,請(qǐng)實(shí)例化類 ThreadLocal
的一個(gè)對(duì)象。 ThreadLocal
類的行為與 java.lang.ref
中的各種 Reference
類的行為很相似; ThreadLocal
類充當(dāng)存儲(chǔ)或檢索一個(gè)值時(shí)的間接句柄。清單 1 顯示了 ThreadLocal
接口。
|
get()
訪問器檢索變量的當(dāng)前線程的值; set()
訪問器修改當(dāng)前線程的值。 initialValue()
方法是可選的,如果線程未使用過某個(gè)變量,那么您可以用這個(gè)方法來設(shè)置這個(gè)變量的初始值;它允許延遲初始化。用一個(gè)示例實(shí)現(xiàn)來說明 ThreadLocal 的工作方式是最好的方法。清單 2 顯示了 ThreadLocal
的一個(gè)實(shí)現(xiàn)方式。它不是一個(gè)特別好的實(shí)現(xiàn)(雖然它與最初實(shí)現(xiàn)非常相似),所以很可能性能不佳,但它清楚地說明了 ThreadLocal 的工作方式。
|
這個(gè)實(shí)現(xiàn)的性能不會(huì)很好,因?yàn)槊總€(gè) get()
和 set()
操作都需要 values
映射表上的同步,而且如果多個(gè)線程同時(shí)訪問同一個(gè) ThreadLocal
,那么將發(fā)生爭(zhēng)用。此外,這個(gè)實(shí)現(xiàn)也是不切實(shí)際的,因?yàn)橛?Thread
對(duì)象做 values
映射表中的關(guān)鍵字將導(dǎo)致無法在線程退出后對(duì) Thread
進(jìn)行垃圾回收,而且也無法對(duì)死線程的 ThreadLocal
的特定于線程的值進(jìn)行垃圾回收。
![]() ![]() |
![]()
|
用 ThreadLocal 實(shí)現(xiàn)每線程 Singleton
線程局部變量常被用來描繪有狀態(tài)“單子”(Singleton) 或線程安全的共享對(duì)象,或者是通過把不安全的整個(gè)變量封裝進(jìn) ThreadLocal
,或者是通過把對(duì)象的特定于線程的狀態(tài)封裝進(jìn) ThreadLocal
。例如,在與數(shù)據(jù)庫有緊密聯(lián)系的應(yīng)用程序中,程序的很多方法可能都需要訪問數(shù)據(jù)庫。在系統(tǒng)的每個(gè)方法中都包含一個(gè) Connection
作為參數(shù)是不方便的 — 用“單子”來訪問連接可能是一個(gè)雖然更粗糙,但卻方便得多的技術(shù)。然而,多個(gè)線程不能安全地共享一個(gè) JDBC Connection
。如清單 3 所示,通過使用“單子”中的 ThreadLocal
,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection
的一個(gè)引用。這樣,我們可以認(rèn)為 ThreadLocal
允許我們創(chuàng)建 每線程單子。
|
任何創(chuàng)建的花費(fèi)比使用的花費(fèi)相對(duì)昂貴些的有狀態(tài)或非線程安全的對(duì)象,例如 JDBC Connection
或正則表達(dá)式匹配器,都是可以使用每線程單子(singleton)技術(shù)的好地方。當(dāng)然,在類似這樣的地方,您可以使用其它技術(shù),例如用池,來安全地管理共享訪問。然而,從可伸縮性角度看,即使是用池也存在一些潛在缺陷。因?yàn)槌貙?shí)現(xiàn)必須使用同步,以維護(hù)池?cái)?shù)據(jù)結(jié)構(gòu)的完整性,如果所有線程使用同一個(gè)池,那么在有很多線程頻繁地對(duì)池進(jìn)行訪問的系統(tǒng)中,程序性能將因爭(zhēng)用而降低。
![]() ![]() |
![]()
|
用 ThreadLocal 簡(jiǎn)化調(diào)試日志紀(jì)錄
其它適合使用 ThreadLocal
但用池卻不能成為很好的替代技術(shù)的應(yīng)用程序包括存儲(chǔ)或累積每線程上下文信息以備稍后檢索之用這樣的應(yīng)用程序。例如,假設(shè)您想創(chuàng)建一個(gè)用于管理多線程應(yīng)用程序調(diào)試信息的工具。您可以用如清單 4 所示的 DebugLogger
類作為線程局部容器來累積調(diào)試信息。在一個(gè)工作單元的開頭,您清空容器,而當(dāng)一個(gè)錯(cuò)誤出現(xiàn)時(shí),您查詢?cè)撊萜饕詸z索這個(gè)工作單元迄今為止生成的所有調(diào)試信息。
|
在您的代碼中,您可以調(diào)用 DebugLogger.put()
來保存您的程序正在做什么的信息,而且,稍后如果有必要(例如發(fā)生了一個(gè)錯(cuò)誤),您能夠容易地檢索與某個(gè)特定線程相關(guān)的調(diào)試信息。 與簡(jiǎn)單地把所有信息轉(zhuǎn)儲(chǔ)到一個(gè)日志文件,然后努力找出哪個(gè)日志記錄來自哪個(gè)線程(還要擔(dān)心線程爭(zhēng)用日志紀(jì)錄對(duì)象)相比,這種技術(shù)簡(jiǎn)便得多,也有效得多。
ThreadLocal
在基于 servlet 的應(yīng)用程序或工作單元是一個(gè)整體請(qǐng)求的任何多線程應(yīng)用程序服務(wù)器中也是很有用的,因?yàn)樵谔幚碚?qǐng)求的整個(gè)過程中將要用到單個(gè)線程。您可以通過前面講述的每線程單子技術(shù)用 ThreadLocal
變量來存儲(chǔ)各種每請(qǐng)求(per-request)上下文信息。
![]() ![]() |
![]()
|
ThreadLocal 的線程安全性稍差的堂兄弟,InheritableThreadLocal
ThreadLocal 類有一個(gè)親戚,InheritableThreadLocal,它以相似的方式工作,但適用于種類完全不同的應(yīng)用程序。創(chuàng)建一個(gè)線程時(shí)如果保存了所有 InheritableThreadLocal
對(duì)象的值,那么這些值也將自動(dòng)傳遞給子線程。如果一個(gè)子線程調(diào)用 InheritableThreadLocal
的 get()
,那么它將與它的父線程看到同一個(gè)對(duì)象。為保護(hù)線程安全性,您應(yīng)該只對(duì)不可變對(duì)象(一旦創(chuàng)建,其狀態(tài)就永遠(yuǎn)不會(huì)被改變的對(duì)象)使用 InheritableThreadLocal
,因?yàn)閷?duì)象被多個(gè)線程共享。 InheritableThreadLocal
很合適用于把數(shù)據(jù)從父線程傳到子線程,例如用戶標(biāo)識(shí)(user id)或事務(wù)標(biāo)識(shí)(transaction id),但不能是有狀態(tài)對(duì)象,例如 JDBC Connection
。
![]() ![]() |
![]()
|
雖然線程局部變量早已赫赫有名并被包括 Posix pthreads
規(guī)范在內(nèi)的很多線程框架支持,但最初的 Java 線程設(shè)計(jì)中卻省略了它,只是在 Java 平臺(tái)的版本 1.2 中才添加上去。在很多方面, ThreadLocal
仍在發(fā)展之中;在版本 1.3 中它被重寫,版本 1.4 中又重寫了一次,兩次都專門是為了性能問題。
在 JDK 1.2 中, ThreadLocal
的實(shí)現(xiàn)方式與清單 2 中的方式非常相似,除了用同步 WeakHashMap
代替 HashMap
來存儲(chǔ) values 之外。(以一些額外的性能開銷為代價(jià),使用 WeakHashMap 解決了無法對(duì) Thread 對(duì)象進(jìn)行垃圾回收的問題。)不用說, ThreadLocal
的性能是相當(dāng)差的。
Java 平臺(tái)版本 1.3 提供的 ThreadLocal
版本已經(jīng)盡量更好了;它不使用任何同步,從而不存在可伸縮性問題,而且它也不使用弱引用。相反地,人們通過給 Thread
添加一個(gè)實(shí)例變量(該變量用于保存當(dāng)前線程的從線程局部變量到它的值的映射的 HashMap
)來修改 Thread
類以支持 ThreadLocal
。因?yàn)闄z索或設(shè)置一個(gè)線程局部變量的過程不涉及對(duì)可能被另一個(gè)線程讀寫的數(shù)據(jù)的讀寫操作,所以您可以不用任何同步就實(shí)現(xiàn) ThreadLocal.get()
和 set()
。而且,因?yàn)槊烤€程值的引用被存儲(chǔ)在自已的 Thread
對(duì)象中,所以當(dāng)對(duì) Thread
進(jìn)行垃圾回收時(shí),也能對(duì)該 Thread
的每線程值進(jìn)行垃圾回收。
不幸的是,即使有了這些改進(jìn),Java 1.3 中的 ThreadLocal
的性能仍然出奇地慢。據(jù)我的粗略測(cè)量,在雙處理器 Linux 系統(tǒng)上的 Sun 1.3 JDK 中進(jìn)行 ThreadLocal.get()
操作,所耗費(fèi)的時(shí)間大約是無爭(zhēng)用同步的兩倍。性能這么差的原因是 Thread.currentThread()
方法的花費(fèi)非常大,占了 ThreadLocal.get()
運(yùn)行時(shí)間的三分之二還多。雖然有這些缺點(diǎn),JDK 1.3 ThreadLocal.get()
仍然比爭(zhēng)用同步快得多,所以如果在任何存在嚴(yán)重爭(zhēng)用的地方(可能是有非常多的線程,或者同步塊被頻繁地執(zhí)行,或者同步塊很大), ThreadLocal
可能仍然要高效得多。
在 Java 平臺(tái)的最新版本,即版本 1.4b2 中, ThreadLocal
和 Thread.currentThread()
的性能都有了很大提高。有了這些提高, ThreadLocal
應(yīng)該比其它技術(shù),如用池,更快。由于它比其它技術(shù)更簡(jiǎn)單,也更不易出錯(cuò),人們最終將發(fā)現(xiàn)它是避免線程間出現(xiàn)不希望的交互的有效途徑。
![]() ![]() |
![]()
|
ThreadLocal
能帶來很多好處。它常常是把有狀態(tài)類描繪成線程安全的,或者封裝非線程安全類以使它們能夠在多線程環(huán)境中安全地使用的最容易的方式。使用 ThreadLocal
使我們可以繞過為實(shí)現(xiàn)線程安全而對(duì)何時(shí)需要同步進(jìn)行判斷的復(fù)雜過程,而且因?yàn)樗恍枰魏瓮剑砸哺纳屏丝缮炜s性。除簡(jiǎn)單之外,用 ThreadLocal
存儲(chǔ)每線程單子或每線程上下文信息在歸檔方面還有一個(gè)頗有價(jià)值好處 — 通過使用 ThreadLocal
,存儲(chǔ)在 ThreadLocal
中的對(duì)象都是 不被線程共享的是清晰的,從而簡(jiǎn)化了判斷一個(gè)類是否線程安全的工作。
我希望您從這個(gè)系列中得到了樂趣,也學(xué)到了知識(shí),我也鼓勵(lì)您到我的 討論論壇中來深入研究多線程問題。
Servlet是在多線程環(huán)境下的。即可能有多個(gè)請(qǐng)求發(fā)給一個(gè)servelt實(shí)例,每個(gè)請(qǐng)求是一個(gè)線程。
struts下的action也類似,同樣在多線程環(huán)境下。可以參考struts user guide: http://struts.apache.org/struts-action/userGuide/building_controller.html 中的Action Class Design Guidelines一節(jié): Write code for a multi-threaded environment - Our controller servlet creates only one instance of your Action class, and uses this one instance to service all requests. Thus, you need to write thread-safe Action classes. Follow the same guidelines you would use to write thread-safe Servlets.
譯:為多線程環(huán)境編寫代碼。我們的controller servlet指揮創(chuàng)建你的Action 類的一個(gè)實(shí)例,用此實(shí)例來服務(wù)所有的請(qǐng)求。因此,你必須編寫線程安全的Action類。遵循與寫線程安全的servlet同樣的方針。
1.什么是線程安全的代碼
在多線程環(huán)境下能正確執(zhí)行的代碼就是線程安全的。
安全的意思是能正確執(zhí)行,否則后果是程序執(zhí)行錯(cuò)誤,可能出現(xiàn)各種異常情況。
2.如何編寫線程安全的代碼
很多書籍里都詳細(xì)講解了如何這方面的問題,他們主要講解的是如何同步線程對(duì)共享資源的使用的問題。主要是對(duì)synchronized關(guān)鍵字的各種用法,以及鎖的概念。
Java1.5中也提供了如讀寫鎖這類的工具類。這些都需要較高的技巧,而且相對(duì)難于調(diào)試。
但是,線程同步是不得以的方法,是比較復(fù)雜的,而且會(huì)帶來性能的損失。等效的代碼中,不需要同步在編寫容易度和性能上會(huì)更好些。
我這里強(qiáng)調(diào)的是什么代碼是始終為線程安全的、是不需要同步的。如下:
1)常量始終是線程安全的,因?yàn)橹淮嬖谧x操作。
2)對(duì)構(gòu)造器的訪問(new 操作)是線程安全的,因?yàn)槊看味夹陆ㄒ粋€(gè)實(shí)例,不會(huì)訪問共享的資源。
3)最重要的是:局部變量是線程安全的。因?yàn)槊繄?zhí)行一個(gè)方法,都會(huì)在獨(dú)立的空間創(chuàng)建局部變量,它不是共享的資源。局部變量包括方法的參數(shù)變量。
struts user guide里有:
Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class.
譯:只使用用局部變量。--編寫線程安全的代碼最重要的原則就是,在Action類中只使用局部變量,不使用實(shí)例變量。
總結(jié):
在Java的Web服務(wù)器環(huán)境下開發(fā),要注意線程安全的問題。最簡(jiǎn)單的實(shí)現(xiàn)方式就是在Servlet和Struts Action里不要使用類變量、實(shí)例變量,但可以使用類常量和實(shí)例常量。
如果有這些變量,可以將它們轉(zhuǎn)換為方法的參數(shù)傳入,以消除它們。
注意一個(gè)容易混淆的地方:被Servlet或Action調(diào)用的類中(如值對(duì)象、領(lǐng)域模型類)中是否可以安全的使用實(shí)例變量?如果你在每次方法調(diào)用時(shí)
新建一個(gè)對(duì)象,再調(diào)用它們的方法,則不存在同步問題---因?yàn)樗鼈儾皇嵌鄠€(gè)線程共享的資源,只有共享的資源才需要同步---而Servlet和Action的實(shí)例對(duì)于多個(gè)線程是共享的。
換句話說,Servlet和Action的實(shí)例會(huì)被多個(gè)線程同時(shí)調(diào)用,而過了這一層,如果在你自己的代碼中沒有另外啟動(dòng)線程,且每次調(diào)用后續(xù)業(yè)務(wù)對(duì)象時(shí)都是先新建一個(gè)實(shí)例再調(diào)用,則都是線程安全的。
CSS全稱Cascading Style Sheet。層疊式樣式表。從三年前就開始使用CSS了,但一直以來都小看了它。CSS的出現(xiàn)其實(shí)是一次革命,它試圖將網(wǎng)站的內(nèi)容與表現(xiàn)分開。 2.外鏈?zhǔn)剑?/p> 3.導(dǎo)入式 3.屬性式: #fontRed 注明兩點(diǎn) CSS 標(biāo)簽屬性/屬性參考 CSS 標(biāo)簽屬性/屬性 行為屬性 behavior 字體和文本屬性 direction* direction 顏色和背景屬性 background-attachment 版面屬性 border 分類屬性 display list-style 定位屬性 bottom* clip 打印屬性 page** pageBreakAfter 濾鏡屬性 filter 偽類和其它屬性 :active @charset 我們知道Dreamweaver在表格制作方面做得非常出色,但是在某些時(shí)候還是必須結(jié)合css才能達(dá)到一些特定效果,下面我們先把有關(guān)表格邊框的css語法整理出來,然后另外介紹怎樣用css美化表格的邊框。 有關(guān)表格邊框的css語法 具體內(nèi)容包括:上邊框?qū)挾取⒂疫吙驅(qū)挾取⑾逻吙驅(qū)挾取⒆筮吙驅(qū)挾取⑦吙驅(qū)挾取⑦吙蝾伾⑦吙驑邮健⑸线吙颉⑾逻吙颉⒆筮吙颉⒂疫吙颉⑦吙颉挾取⒏叨取⒂嘘P(guān)標(biāo)簽等。 1.上邊框?qū)挾?/p> 語法: border-top-width: <值> 允許值: thin | medium | thick | <長(zhǎng)度> 初始值: medium 適用于: 所有對(duì)象 向下兼容: 否 上邊框?qū)挾葘傩杂糜谥付ㄒ粋€(gè)元素上邊框的寬度。值可以是三個(gè)關(guān)鍵字其中的一個(gè),都不受字體大小或長(zhǎng)度的影響,可以用于實(shí)現(xiàn)成比例的寬度。不允許使用負(fù)值。也可以用在上邊框、邊框的寬度或邊框的屬性略寫。 2.右邊框?qū)挾?/p> 語法: border-right-width: <值> 允許值: thin | medium | thick | <長(zhǎng)度> 初始值: medium 適用于: 所有對(duì)象 向下兼容: 否 右邊框?qū)挾葘傩杂糜谥付ㄔ氐挠疫吙虻膶挾取V悼梢允侨齻€(gè)關(guān)鍵字其中的一個(gè),都不受字體大小或長(zhǎng)度的影響,可以用于實(shí)現(xiàn)成比例的寬度。不允許使用負(fù)值。也可以用在右邊框、邊框的寬度或邊框的屬性略寫。 3.下邊框?qū)挾?/p> 語法: border-bottom-width: <值> 允許值: thin | medium | thick | <長(zhǎng)度> 初始值: medium 適用于: 所有對(duì)象 向下兼容: 否 下邊框?qū)挾葘傩杂糜谥付ㄔ氐南逻吙虻膶挾取V悼梢允侨齻€(gè)關(guān)鍵字其中的一個(gè),都不受字體大小或長(zhǎng)度的影響,可以用于實(shí)現(xiàn)成比例的寬度。不允許使用負(fù)值。也可以用在下邊框、邊框的寬度或邊框的屬性略寫。 4.左邊框?qū)挾?/p> 語法: border-left-width: <值> 允許值: thin | medium | thick | <長(zhǎng)度> 初始值: medium 適用于: 所有對(duì)象 向下兼容: 否 左邊框?qū)挾葘傩杂糜谥付ㄔ氐淖筮吙虻膶挾取V悼梢允侨齻€(gè)關(guān)鍵字其中的一個(gè),都不受字體大小或長(zhǎng)度的影響,可以用于實(shí)現(xiàn)成比例的寬度。不允許使用負(fù)值。也可以用在左邊框、邊框的寬度或邊框的屬性略寫。 5.邊框?qū)挾?/p> 語法: border-width: <值> 允許值: [ thin | medium | thick | <長(zhǎng)度> ]{1,4} 初始值: 未定義 適用于: 所有對(duì)象 向下兼容: 否 邊框?qū)挾葘傩杂靡坏剿膫€(gè)值來設(shè)置元素的邊界,值是一個(gè)關(guān)鍵字或長(zhǎng)度。不允許使用負(fù)值長(zhǎng)度。如果四個(gè)值都給出了,它們分別應(yīng)用于上、右、下和左邊框的式樣。如果給出一個(gè)值,它將被運(yùn)用到各邊上。如果兩個(gè)或三個(gè)值給出了,省略了的值與對(duì)邊相等。 這個(gè)屬性是上邊框?qū)挾取⒂疫吙驅(qū)挾取⑾逻吙驅(qū)挾群妥筮吙驅(qū)挾葘傩缘穆詫憽R部梢允褂寐詫懙倪吙驅(qū)傩浴?/p> 6.邊框顏色 語法: border-color: <值> 允許值: <顏色>{1,4} 初始值: 顏色屬性的值 適用于: 所有對(duì)象 向下兼容: 否 邊框顏色屬性設(shè)置一個(gè)元素的邊框顏色。可以使用一到四個(gè)關(guān)鍵字。如果四個(gè)值都給出了,它們分別應(yīng)用于上、右、下和左邊框的式樣。如果給出一個(gè)值,它將被運(yùn)用到各邊上。如果兩個(gè)或三個(gè)值給出了,省略了的值與對(duì)邊相等。也可以使用略寫的邊框?qū)傩浴?/p> 7.邊框樣式 語法: border-style: <值> 允許值: [ none | dotted | dashed | solid | double | groove | ridge | inset | outset ]{1,4} 初始值: none 適用于: 所有對(duì)象 向下兼容: 否 邊框樣式屬性用于設(shè)置一個(gè)元素邊框的樣式。這個(gè)屬性必須用于指定可見的邊框。可以使用一到四個(gè)關(guān)鍵字。如果四個(gè)值都給出了,它們分別應(yīng)用于上、右、下和左邊框的式樣。如果給出一個(gè)值,它將被運(yùn)用到各邊上。如果兩個(gè)或三個(gè)值給出了,省略了的值與對(duì)邊相等。也可以使用略寫的邊框?qū)傩浴?/p> none:無樣式; 語法: border-top: <值> 允許值: <上邊框?qū)挾?gt; || <邊框式樣> || <顏色> 初始值: 未定義 適用于: 所有對(duì)象 向下兼容: 否 上邊框?qū)傩允且粋€(gè)用于設(shè)置一個(gè)元素上邊框的寬度、式樣和顏色的略寫。注意只能給出一個(gè)邊框式樣。也可以使用略寫的邊框?qū)傩浴?/p> 9.右邊框 語法: border-right: <值> 允許值: <右邊框?qū)挾?gt; || <邊框式樣> || <顏色> 初始值: 未定義 適用于: 所有對(duì)象 向下兼容: 否 右邊框?qū)傩允且粋€(gè)用于設(shè)置一個(gè)元素右邊框的寬度、式樣、和顏色的略寫。注意只能給出一個(gè)邊框式樣。也可以使用略寫的邊框?qū)傩浴! ? 10.下邊框 語法: border-bottom: <值> 允許值: <下邊框?qū)挾?gt; || <邊框式樣> || <顏色> 初始值: 未定義 適用于: 所有對(duì)象 向下兼容: 否 下邊框?qū)傩允且粋€(gè)用于設(shè)置一個(gè)元素的下邊框的寬度、式樣和顏色的略寫。注意只能給出一個(gè)邊框式樣。也可以使用略寫的邊框?qū)傩浴?/p> 11.左邊框 語法: border-left: <值> 允許值: <左邊框?qū)挾?gt; || <邊框式樣> || <顏色> 初始值: 未定義 適用于: 所有對(duì)象 向下兼容: 否 左邊框?qū)傩允且粋€(gè)用于設(shè)置一個(gè)元素左邊框的寬度、式樣和顏色的略寫。注意只能給出一個(gè)邊框式樣。也可以使用略寫的邊框?qū)傩浴?/p> 12.邊框 語法: border: <值> 允許值: <邊框?qū)挾?gt; || <邊框式樣> || <顏色> 初始值: 未定義 適用于: 所有對(duì)象 向下兼容: 否 邊框?qū)傩允且粋€(gè)用于設(shè)置一個(gè)元素邊框的寬度、式樣和顏色的略寫。 邊框聲明的例子包括: H2 { border: groove 3em } 邊框?qū)傩灾荒茉O(shè)置四種邊框;只能給出一組邊框的寬度和式樣。為了給出一個(gè)元素的四種邊框的不同的值,網(wǎng)頁制作者必須用一個(gè)或更多的屬性,如:上邊框、右邊框、下邊框、左邊框、邊框顏色、邊框?qū)挾取⑦吙蚴綐印⑸线吙驅(qū)挾取⒂疫吙驅(qū)挾取⑾逻吙驅(qū)挾然蜃筮吙驅(qū)挾取? 13.寬度 語法: width: <值> 允許值: <長(zhǎng)度> | <百分比> | auto 初始值: auto 適用于: 塊級(jí)和替換元素 向下兼容: 否 寬度屬性的初始值為“auto”,即為該元素的原有寬度(有就是元素自己的寬度)。百分比參考上級(jí)元素的寬度。不允許使用負(fù)的長(zhǎng)度值。 14.高度 語法: height: <值> 允許值: <長(zhǎng)度> | auto 初始值: auto 適用于: 塊級(jí)和替換元素 向下兼容: 否 高度屬性的初始值為“auto”,即為該元素的原有高度(有就是元素自己的高度,)。百分比參考上級(jí)元素的寬度。不允許使用負(fù)的長(zhǎng)度值。 15.有關(guān)標(biāo)簽 table:表格標(biāo)簽,對(duì)整個(gè)表格樣式的定義要放在table中; css濾鏡 隨著網(wǎng)頁設(shè)計(jì)技術(shù)的發(fā)展,人們已經(jīng)不滿足于原有的一些HTML標(biāo)記,而是希望能夠?yàn)轫撁嫣砑右恍┒嗝襟w屬性,例如濾鏡的和漸變的效果。CSS技術(shù)的飛快發(fā)展使這些需求成為了現(xiàn)實(shí)。從現(xiàn)在開始我要為大家介紹一個(gè)新的CSS擴(kuò)展部分:CSS濾鏡屬性(Filter Properties)。使用這種技術(shù)可以把可視化的濾鏡和轉(zhuǎn)換效果添加到一個(gè)標(biāo)準(zhǔn)的HTML元素上,例如圖片、文本容器、以及其他一些對(duì)象。對(duì)于濾鏡和漸變效果,前者是基礎(chǔ),因?yàn)楹笳呔褪菫V鏡效果的不斷變化和演示更替。當(dāng)濾鏡和漸變效果結(jié)合到一個(gè)基本的SCRIPT小程序中后,網(wǎng)頁設(shè)計(jì)者就可以擁有一個(gè)建立動(dòng)態(tài)交互文檔的強(qiáng)大工具。也就是CSS FILTER+ SCRIPT, 這就說明想要建立動(dòng)態(tài)的文檔還要一些SCRIPT (腳本語言)的基礎(chǔ)。 備注:可惜只有IE4.0以上支持,如果是別的瀏覽器,那就....... 元素 說明
IE4.0以上支持的濾鏡屬性表 濾鏡效果 描述 1、Alpha 濾鏡 語法:{FILTER:ALPHA(opacity=opacity,finishopacity=finishopacity,style=style,startx=startx, "Alpha"屬性是把一個(gè)目標(biāo)元素與背景混合。設(shè)計(jì)者可以指定數(shù)值來控制混合的程度。這種“與背景混合”通俗地說就是一個(gè)元素的透明度。通過指定坐標(biāo),可以指定點(diǎn)、線、面的透明度。他們的參數(shù)含義分別如下: 效果如下: 2、Blur 濾鏡 語法:對(duì)于HTML:{ilter:blur(add=add,direction=direction,strength=strength)} 3、FlipH, FlipV 濾鏡 語法:{filter:filph} ,{filter:filpv} 分別是水平反轉(zhuǎn)和垂直反轉(zhuǎn),具體如下: 4、Chroma 濾鏡 語法:{filter:chroma(color=color)} 滴水檐坊 5、DropShadow 濾鏡 語法:{filter:dropshadow(color=color,offx=ofx,offy=offy,positive=positive)} “DropShaow"顧名思義就是添加對(duì)象的陰影效果。其工作原理是建立一個(gè)偏移量,加上較深。"Color"代表投射陰影的顏色,"offx"和"offy"分別是X方向和Y方向陰影的餓偏移量。"Positive"參數(shù)是一個(gè)布爾值,如果為“TRUE(非0)”,那么就為任何的非透明像素建立可見的投影。如果為“FASLE(0)”,那么就為透明的像素部分建立透明效果 6、Glow 濾鏡 語法:{filter:glow(color=color,strength)} 7、Gray ,Invert,Xray 濾鏡 語法:{filter:gray} ,{filter:invert},{filter:xray} Gray濾鏡是把一張圖片變成灰度圖;Invert濾鏡是把對(duì)象的可視化屬性全部翻轉(zhuǎn),包括色彩、飽和度、和亮度值;Xray濾鏡是讓對(duì)象反映出它的輪廓并把這些輪廓加亮,也就是所謂的“X”光片。 效果如下: 、Light 濾鏡 語法:Filter{light} 這個(gè)屬性模擬光源的投射效果。一旦為對(duì)象定義了“LIGHT"濾鏡屬性,那么就可以調(diào)用它的“方法(Method)"來設(shè)置或者改變屬性。“LIGHT"可用的方法有: AddAmbient 加入包圍的光源 9、Mask 濾鏡 語法:{filter:mask(color=color)} 使用"MASK"屬性可以為對(duì)象建立一個(gè)覆蓋于表面的膜,其效果就象戴者有色眼鏡看物體一樣。 10、Shadow 濾鏡 語法:{filter:shadow(color=color,direction=direction)} 利用“Shadow”屬性可以在指定的方向建立物體的投影,COLOR是投影色,DIRECTION是設(shè)置投影的方向。其中0度代表垂直向上,然后每45度為一個(gè)單位。它的默認(rèn)值是向左的270度。 filter:shadow(color=red,direction=225) 11、Wave 濾鏡 語法:{filter:wave(add=add,freq=freq,lightstrength=strength,phase=phase,strength=strength)} “FREQ”是波紋的頻率,也就是指定在對(duì)象上一共需要產(chǎn)生多少個(gè)完整的波紋, “LIGHTSTRENGTH”參數(shù)可以對(duì)于波紋增強(qiáng)光影的效果,范圍0----100, “PHASE”參數(shù)用來設(shè)置正弦波的偏移量。 “STRENGTH”代表振幅大小。 |
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ī)范的的抽象說明是一些概念的集合,它們已經(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ī)的寄存器有四種:
在上述體系結(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ī)采取如下措施:
操作數(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ī)理。
struts的validator資料見:struts validator
附一個(gè)正則表達(dá)式的資料:
正則表達(dá)式(regular expression)描述了一種字符串匹配的模式,可以用來檢查一個(gè)串是否含有某種子串、將匹配的子串做替換或者從某個(gè)串中取出符合某個(gè)條件的子串等。
列目錄時(shí), dir *.txt或ls *.txt中的*.txt就
為便于理解和記憶,先從一些概念入手,所有特殊字符或字符組合有一個(gè)總表在后面,最后一些例子供理解相應(yīng)的概念。
是由普通字符(例如字符 a 到 z)以及特殊字符(稱為元字符)組成的文字模式。正則表達(dá)式作為一個(gè)模板,將某個(gè)字符模式與所搜索的字符串進(jìn)行匹配。
可以通過在一對(duì)分隔符之間放入表達(dá)式模式的各種組件來構(gòu)造一個(gè)正則表達(dá)式,即/expression/
由所有那些未顯式指定為元字符的打印和非打印字符組成。這包括所有的大寫和小寫字母字符,所有數(shù)字,所有標(biāo)點(diǎn)符號(hào)以及一些符號(hào)。
字符 | 含義 |
\cx | 匹配由x指明的控制字符。例如, \cM 匹配一個(gè) Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個(gè)原義的 'c' 字符。 |
\f | 匹配一個(gè)換頁符。等價(jià)于 \x0c 和 \cL。 |
\n | 匹配一個(gè)換行符。等價(jià)于 \x0a 和 \cJ。 |
\r | 匹配一個(gè)回車符。等價(jià)于 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價(jià)于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等價(jià)于 [^ \f\n\r\t\v]。 |
\t | 匹配一個(gè)制表符。等價(jià)于 \x09 和 \cI。 |
\v | 匹配一個(gè)垂直制表符。等價(jià)于 \x0b 和 \cK。 |
所謂特殊字符,就是一些有特殊含義的字符,如上面說的"*.txt"中的*,簡(jiǎn)單的說就是表示任何字符串的意思。如果要查找文件名中有*的文件,則需要對(duì)*進(jìn)行轉(zhuǎn)義,即在其前加一個(gè)\。ls \*.txt。正則表達(dá)式有以下特殊字符。
特別字符 | 說明 |
$ | 匹配輸入字符串的結(jié)尾位置。如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,則 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,請(qǐng)使用 \$。 |
( ) | 標(biāo)記一個(gè)子表達(dá)式的開始和結(jié)束位置。子表達(dá)式可以獲取供以后使用。要匹配這些字符,請(qǐng)使用 \( 和 \)。 |
* | 匹配前面的子表達(dá)式零次或多次。要匹配 * 字符,請(qǐng)使用 \*。 |
+ | 匹配前面的子表達(dá)式一次或多次。要匹配 + 字符,請(qǐng)使用 \+。 |
. | 匹配除換行符 \n之外的任何單字符。要匹配 .,請(qǐng)使用 \。 |
[ | 標(biāo)記一個(gè)中括號(hào)表達(dá)式的開始。要匹配 [,請(qǐng)使用 \[。 |
? | 匹配前面的子表達(dá)式零次或一次,或指明一個(gè)非貪婪限定符。要匹配 ? 字符,請(qǐng)使用 \?。 |
\ | 將下一個(gè)字符標(biāo)記為或特殊字符、或原義字符、或向后引用、或八進(jìn)制轉(zhuǎn)義符。例如, 'n' 匹配字符 'n'。'\n' 匹配換行符。序列 '\\' 匹配 "\",而 '\(' 則匹配 "("。 |
^ | 匹配輸入字符串的開始位置,除非在方括號(hào)表達(dá)式中使用,此時(shí)它表示不接受該字符集合。要匹配 ^ 字符本身,請(qǐng)使用 \^。 |
{ | 標(biāo)記限定符表達(dá)式的開始。要匹配 {,請(qǐng)使用 \{。 |
| | 指明兩項(xiàng)之間的一個(gè)選擇。要匹配 |,請(qǐng)使用 \|。 |
構(gòu)造正則表達(dá)式的方法和創(chuàng)建數(shù)學(xué)表達(dá)式的方法一樣。也就是用多種元字符與操作符將小的表達(dá)式結(jié)合在一起來創(chuàng)建更大的表達(dá)式。正則表達(dá)式的組件可以是單個(gè)的字符、字符集合、字符范圍、字符間的選擇或者所有這些組件的任意組合。
限定符用來指定正則表達(dá)式的一個(gè)給定組件必須要出現(xiàn)多少次才能滿足匹配。有*或+或?或{n}或{n,}或{n,m}共6種。
*、+和?限定符都是貪婪的,因?yàn)樗鼈儠?huì)盡可能多的匹配文字,只有在它們的后面加上一個(gè)?就可以實(shí)現(xiàn)非貪婪或最小匹配。
正則表達(dá)式的限定符有:
字符 | 描述 |
* | 匹配前面的子表達(dá)式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價(jià)于{0,}。 |
+ | 匹配前面的子表達(dá)式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價(jià)于 {1,}。 |
? | 匹配前面的子表達(dá)式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價(jià)于 {0,1}。 |
{n} | n 是一個(gè)非負(fù)整數(shù)。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個(gè) o。 |
{n,} | n 是一個(gè)非負(fù)整數(shù)。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價(jià)于 'o+'。'o{0,}' 則等價(jià)于 'o*'。 |
{n,m} | m 和 n 均為非負(fù)整數(shù),其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 將匹配 "fooooood" 中的前三個(gè) o。'o{0,1}' 等價(jià)于 'o?'。請(qǐng)注意在逗號(hào)和兩個(gè)數(shù)之間不能有空格。 |
用來描述字符串或單詞的邊界,^和$分別指字符串的開始與結(jié)束,\b描述單詞的前或后邊界,\B表示非單詞邊界。
用圓括號(hào)將所有選擇項(xiàng)括起來,相鄰的選擇項(xiàng)之間用|分隔。但用圓括號(hào)會(huì)有一個(gè)副作用,是相關(guān)的匹配會(huì)被緩存,此時(shí)可用?:放在第一個(gè)選項(xiàng)前來消除這種副作用。
其中?:是非捕獲元之一,還有兩個(gè)非捕獲元是?=和?!,這兩個(gè)還有更多的含義,前者為正向預(yù)查,在任何開始匹配圓括號(hào)內(nèi)的正則表達(dá)式模式的位置來匹配搜索字符串,后者為負(fù)向預(yù)查,在任何開始不匹配該正則表達(dá)式模式的位置來匹配搜索字符串。
對(duì)一個(gè)正則表達(dá)式模式或部分模式兩邊添加圓括號(hào)將導(dǎo)致相關(guān)匹配存儲(chǔ)到一個(gè)臨時(shí)緩沖區(qū)中,所捕獲的每個(gè)子匹配都按照在正則表達(dá)式模式中從左至右所遇到的內(nèi)容存儲(chǔ)。存儲(chǔ)子匹配的緩沖區(qū)編號(hào)從 1 開始,連續(xù)編號(hào)直至最大 99 個(gè)子表達(dá)式。每個(gè)緩沖區(qū)都可以使用 '\n' 訪問,其中 n 為一個(gè)標(biāo)識(shí)特定緩沖區(qū)的一位或兩位十進(jìn)制數(shù)。
可以使用非捕獲元字符 '?:', '?=', or '?!' 來忽略對(duì)相關(guān)匹配的保存。
相同優(yōu)先級(jí)的從左到右進(jìn)行運(yùn)算,不同優(yōu)先級(jí)的運(yùn)算先高后低。各種操作符的優(yōu)先級(jí)從高到低如下:
操作符 | 描述 |
\ | 轉(zhuǎn)義符 |
(), (?:), (?=), [] | 圓括號(hào)和方括號(hào) |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \anymetacharacter | 位置和順序 |
| | “或”操作 |
字符 | 描述 |
\ | 將下一個(gè)字符標(biāo)記為一個(gè)特殊字符、或一個(gè)原義字符、或一個(gè) 向后引用、或一個(gè)八進(jìn)制轉(zhuǎn)義符。例如,'n' 匹配字符 "n"。'\n' 匹配一個(gè)換行符。序列 '\\' 匹配 "\" 而 "\(" 則匹配 "("。 |
^ | 匹配輸入字符串的開始位置。如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 之后的位置。 |
$ | 匹配輸入字符串的結(jié)束位置。如果設(shè)置了RegExp 對(duì)象的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 之前的位置。 |
* | 匹配前面的子表達(dá)式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價(jià)于{0,}。 |
+ | 匹配前面的子表達(dá)式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價(jià)于 {1,}。 |
? | 匹配前面的子表達(dá)式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價(jià)于 {0,1}。 |
{n} | n 是一個(gè)非負(fù)整數(shù)。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個(gè) o。 |
{n,} | n 是一個(gè)非負(fù)整數(shù)。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價(jià)于 'o+'。'o{0,}' 則等價(jià)于 'o*'。 |
{n,m} | m 和 n 均為非負(fù)整數(shù),其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 將匹配 "fooooood" 中的前三個(gè) o。'o{0,1}' 等價(jià)于 'o?'。請(qǐng)注意在逗號(hào)和兩個(gè)數(shù)之間不能有空格。 |
? | 當(dāng)該字符緊跟在任何一個(gè)其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面時(shí),匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認(rèn)的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對(duì)于字符串 "oooo",'o+?' 將匹配單個(gè) "o",而 'o+' 將匹配所有 'o'。 |
. | 匹配除 "\n" 之外的任何單個(gè)字符。要匹配包括 '\n' 在內(nèi)的任何字符,請(qǐng)使用象 '[.\n]' 的模式。 |
(pattern) | 匹配 pattern 并獲取這一匹配。所獲取的匹配可以從產(chǎn)生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中則使用 $0…$9 屬性。要匹配圓括號(hào)字符,請(qǐng)使用 '\(' 或 '\)'。 |
(?:pattern) | 匹配 pattern 但不獲取匹配結(jié)果,也就是說這是一個(gè)非獲取匹配,不進(jìn)行存儲(chǔ)供以后使用。這在使用 "或" 字符 (|) 來組合一個(gè)模式的各個(gè)部分是很有用。例如, 'industr(?:y|ies) 就是一個(gè)比 'industry|industries' 更簡(jiǎn)略的表達(dá)式。 |
(?=pattern) | 正向預(yù)查,在任何匹配 pattern 的字符串開始處匹配查找字符串。這是一個(gè)非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如,'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。預(yù)查不消耗字符,也就是說,在一個(gè)匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開始。 |
(?!pattern) | 負(fù)向預(yù)查,在任何不匹配 pattern 的字符串開始處匹配查找字符串。這是一個(gè)非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。預(yù)查不消耗字符,也就是說,在一個(gè)匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開始 |
x|y | 匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 則匹配 "zood" 或 "food"。 |
[xyz] | 字符集合。匹配所包含的任意一個(gè)字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。 |
[^xyz] | 負(fù)值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。 |
[a-z] | 字符范圍。匹配指定范圍內(nèi)的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范圍內(nèi)的任意小寫字母字符。 |
[^a-z] | 負(fù)值字符范圍。匹配任何不在指定范圍內(nèi)的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范圍內(nèi)的任意字符。 |
\b | 匹配一個(gè)單詞邊界,也就是指單詞和空格間的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
\B | 匹配非單詞邊界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
\cx | 匹配由 x 指明的控制字符。例如, \cM 匹配一個(gè) Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個(gè)原義的 'c' 字符。 |
\d | 匹配一個(gè)數(shù)字字符。等價(jià)于 [0-9]。 |
\D | 匹配一個(gè)非數(shù)字字符。等價(jià)于 [^0-9]。 |
\f | 匹配一個(gè)換頁符。等價(jià)于 \x0c 和 \cL。 |
\n | 匹配一個(gè)換行符。等價(jià)于 \x0a 和 \cJ。 |
\r | 匹配一個(gè)回車符。等價(jià)于 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價(jià)于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等價(jià)于 [^ \f\n\r\t\v]。 |
\t | 匹配一個(gè)制表符。等價(jià)于 \x09 和 \cI。 |
\v | 匹配一個(gè)垂直制表符。等價(jià)于 \x0b 和 \cK。 |
\w | 匹配包括下劃線的任何單詞字符。等價(jià)于'[A-Za-z0-9_]'。 |
\W | 匹配任何非單詞字符。等價(jià)于 '[^A-Za-z0-9_]'。 |
\xn | 匹配 n,其中 n 為十六進(jìn)制轉(zhuǎn)義值。十六進(jìn)制轉(zhuǎn)義值必須為確定的兩個(gè)數(shù)字長(zhǎng)。例如,'\x41' 匹配 "A"。'\x041' 則等價(jià)于 '\x04' & "1"。正則表達(dá)式中可以使用 ASCII 編碼。. |
\num | 匹配 num,其中 num 是一個(gè)正整數(shù)。對(duì)所獲取的匹配的引用。例如,'(.)\1' 匹配兩個(gè)連續(xù)的相同字符。 |
\n | 標(biāo)識(shí)一個(gè)八進(jìn)制轉(zhuǎn)義值或一個(gè)向后引用。如果 \n 之前至少 n 個(gè)獲取的子表達(dá)式,則 n 為向后引用。否則,如果 n 為八進(jìn)制數(shù)字 (0-7),則 n 為一個(gè)八進(jìn)制轉(zhuǎn)義值。 |
\nm | 標(biāo)識(shí)一個(gè)八進(jìn)制轉(zhuǎn)義值或一個(gè)向后引用。如果 \nm 之前至少有 nm 個(gè)獲得子表達(dá)式,則 nm 為向后引用。如果 \nm 之前至少有 n 個(gè)獲取,則 n 為一個(gè)后跟文字 m 的向后引用。如果前面的條件都不滿足,若 n 和 m 均為八進(jìn)制數(shù)字 (0-7),則 \nm 將匹配八進(jìn)制轉(zhuǎn)義值 nm。 |
\nml | 如果 n 為八進(jìn)制數(shù)字 (0-3),且 m 和 l 均為八進(jìn)制數(shù)字 (0-7),則匹配八進(jìn)制轉(zhuǎn)義值 nml。 |
\un | 匹配 n,其中 n 是一個(gè)用四個(gè)十六進(jìn)制數(shù)字表示的 Unicode 字符。例如, \u00A9 匹配版權(quán)符號(hào) (?)。 |
正則表達(dá)式 | 說明 |
/\b([a-z]+) \1\b/gi | 一個(gè)單詞連續(xù)出現(xiàn)的位置 |
/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/ | 將一個(gè)URL解析為協(xié)議、域、端口及相對(duì)路徑 |
/^(?:Chapter|Section) [1-9][0-9]{0,1}$/ | 定位章節(jié)的位置 |
/[-a-z]/ | A至z共26個(gè)字母再加一個(gè)-號(hào)。 |
/ter\b/ | 可匹配chapter,而不能terminal |
/\Bapt/ | 可匹配chapter,而不能aptitude |
/Windows(?=95 |98 |NT )/ | 可匹配Windows95或Windows98或WindowsNT,當(dāng)找到一個(gè)匹配后,從Windows后面開始進(jìn)行下一次的檢索匹配。 |