應用報文摘要方法,得到單向的加密字符串
//MD5是16位,SHA是20位(這是兩種報文摘要的算法)
//MessageDigest md= MessageDigest.getInstance("MD5");
MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");
messageDigest.update(originalPwd.getBytes());
//String digestedPwdString = new String(messageDigest.digest());
String digestedPwdString = new String(Base64.encode(messageDigest.digest()));
System.out.println("pwd:" + digestedPwdString);
這樣,就得到密碼的報文摘要,把此摘要保存到數據庫,
以后用戶登陸時,用相同的算法算出摘要,和數據庫中的比較,如果一致,則密碼正確。
注意:
byte[] digest = messageDigest.digest();
得到的是個二進制byte數組,有可能某些byte是不可打印的字符。
所以用Base64.encode把它轉化成可打印字符。
也可以把digest的每個byte轉化成hex(16進制)保存。
MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");
messageDigest.update(originalPwd.getBytes());
byte[] bin = messageDigest.digest();
再調用下面的方法生產hex(16進制)保存。
二行制轉hex字符串的方法如下:
private static String byte2hex(byte[] b){
String hs="";
String stmp="";
for (int n=0; n<b.length; n++){
stmp=(java.lang.Integer.toHexString(b[n] & 0xFF));
if (stmp.length()==1) hs=hs+"0"+stmp;
else hs=hs+stmp;
}
return hs;
}
或者:
private static String byto2hex2(byte[] bin){
StringBuffer buf = new StringBuffer();
for (int i = 0; i < bin.length; ++i) {
int x = bin[i] & 0xFF, h = x >>> 4, l = x & 0x0F;
buf.append((char) (h + ((h < 10) ? '0' : 'a' - 10)));
buf.append((char) (l + ((l < 10) ? '0' : 'a' - 10)));
}
return buf.toString();
}
或者:
干脆直接用下面的方法生成,用到第三方包:
public static String encryptPwd(String pwd, String algorithm){
//String a = org.apache.catalina.realm.RealmBase.Digest(pwd,"SHA-1");
return org.apache.catalina.realm.RealmBase.Digest(pwd, algorithm);
}
int i=100; String binStr=Integer.toBinaryString(i); String otcStr=Integer.toOctalString(i); String hexStr=Integer.toHexString(i); System.out.println(binStr); System.out.println(otcStr); System.out.println(hexStr);
2,java 如何實現程序的自動更新,有例子最好了
做一個線程 過一段時間 就連接指定的遠程服務器 看最新版本號 與本地當前版本號是不是一致 是的話 就彈出窗口 提示用戶 用戶確認就 自動下載下來 然后更新原來的class 再啟動 過程就是這樣 自己寫一個小小的代碼測試一下就可以
2004 年 11 月 29 日
本文將對 Linux™ 程序員可以使用的內存管理技術進行概述,雖然關注的重點是 C 語言,但同樣也適用于其他語言。文中將為您提供如何管理內存的細節,然后將進一步展示如何手工管理內存,如何使用引用計數或者內存池來半手工地管理內存,以及如何使用垃圾收集自動管理內存。
內存管理是計算機編程最為基本的領域之一。在很多腳本語言中,您不必擔心內存是如何管理的,這并不能使得內存管理的重要性有一點點降低。對實際編程來說,理解您的內存管理器的能力與局限性至關重要。在大部分系統語言中,比如 C 和 C++,您必須進行內存管理。本文將介紹手工的、半手工的以及自動的內存管理實踐的基本概念。
追溯到在 Apple II 上進行匯編語言編程的時代,那時內存管理還不是個大問題。您實際上在運行整個系統。系統有多少內存,您就有多少內存。您甚至不必費心思去弄明白它有多少內存,因為每一臺機器的內存數量都相同。所以,如果內存需要非常固定,那么您只需要選擇一個內存范圍并使用它即可。
不過,即使是在這樣一個簡單的計算機中,您也會有問題,尤其是當您不知道程序的每個部分將需要多少內存時。如果您的空間有限,而內存需求是變化的,那么您需要一些方法來滿足這些需求:
實現這些需求的程序庫稱為 分配程序(allocators),因為它們負責分配和回收內存。程序的動態性越強,內存管理就越重要,您的內存分配程序的選擇也就更重要。讓我們來了解可用于內存管理的不同方法,它們的好處與不足,以及它們最適用的情形。
![]() ![]() |
![]()
|
C 編程語言提供了兩個函數來滿足我們的三個需求:
malloc
分配的內存片段的指針,并將其釋放,以便以后的程序或操作系統使用(實際上,一些 malloc
實現只能將內存歸還給程序,而無法將內存歸還給操作系統)。
要理解內存在程序中是如何分配的,首先需要理解如何將內存從操作系統分配給程序。計算機上的每一個進程都認為自己可以訪問所有的物理內存。顯然,由于同時在運行多個程序,所以每個進程不可能擁有全部內存。實際上,這些進程使用的是 虛擬內存。
只是作為一個例子,讓我們假定您的程序正在訪問地址為 629 的內存。不過,虛擬內存系統不需要將其存儲在位置為 629 的 RAM 中。實際上,它甚至可以不在 RAM 中 —— 如果物理 RAM 已經滿了,它甚至可能已經被轉移到硬盤上!由于這類地址不必反映內存所在的物理位置,所以它們被稱為虛擬內存。操作系統維持著一個虛擬地址到物理地址的轉換的表,以便計算機硬件可以正確地響應地址請求。并且,如果地址在硬盤上而不是在 RAM 中,那么操作系統將暫時停止您的進程,將其他內存轉存到硬盤中,從硬盤上加載被請求的內存,然后再重新啟動您的進程。這樣,每個進程都獲得了自己可以使用的地址空間,可以訪問比您物理上安裝的內存更多的內存。
在 32-位 x86 系統上,每一個進程可以訪問 4 GB 內存。現在,大部分人的系統上并沒有 4 GB 內存,即使您將 swap 也算上, 每個進程所使用的內存也肯定少于 4 GB。因此,當加載一個進程時,它會得到一個取決于某個稱為 系統中斷點(system break)的特定地址的初始內存分配。該地址之后是未被映射的內存 —— 用于在 RAM 或者硬盤中沒有分配相應物理位置的內存。因此,如果一個進程運行超出了它初始分配的內存,那么它必須請求操作系統“映射進來(map in)”更多的內存。(映射是一個表示一一對應關系的數學術語 —— 當內存的虛擬地址有一個對應的物理地址來存儲內存內容時,該內存將被映射。)
基于 UNIX 的系統有兩個可映射到附加內存中的基本系統調用:
brk()
是一個非常簡單的系統調用。還記得系統中斷點嗎?該位置是進程映射的內存邊界。 brk()
只是簡單地將這個位置向前或者向后移動,就可以向進程添加內存或者從進程取走內存。
mmap()
,或者說是“內存映像”,類似于 brk()
,但是更為靈活。首先,它可以映射任何位置的內存,而不單單只局限于進程。其次,它不僅可以將虛擬地址映射到物理的 RAM 或者 swap,它還可以將它們映射到文件和文件位置,這樣,讀寫內存將對文件中的數據進行讀寫。不過,在這里,我們只關心 mmap
向進程添加被映射的內存的能力。 munmap()
所做的事情與 mmap()
相反。
如您所見, brk()
或者 mmap()
都可以用來向我們的進程添加額外的虛擬內存。在我們的例子中將使用 brk()
,因為它更簡單,更通用。
如果您曾經編寫過很多 C 程序,那么您可能曾多次使用過 malloc()
和 free()
。不過,您可能沒有用一些時間去思考它們在您的操作系統中是如何實現的。本節將向您展示 malloc
和 free
的一個最簡化實現的代碼,來幫助說明管理內存時都涉及到了哪些事情。
要試著運行這些示例,需要先 復制本代碼清單,并將其粘貼到一個名為 malloc.c 的文件中。接下來,我將一次一個部分地對該清單進行解釋。
在大部分操作系統中,內存分配由以下兩個簡單的函數來處理:
void *malloc(long numbytes)
:該函數負責分配 numbytes
大小的內存,并返回指向第一個字節的指針。
void free(void *firstbyte)
:如果給定一個由先前的 malloc
返回的指針,那么該函數會將分配的空間歸還給進程的“空閑空間”。 malloc_init
將是初始化內存分配程序的函數。它要完成以下三件事:將分配程序標識為已經初始化,找到系統中最后一個有效內存地址,然后建立起指向我們管理的內存的指針。這三個變量都是全局變量:
int has_initialized = 0; void *managed_memory_start; void *last_valid_address; |
如前所述,被映射的內存的邊界(最后一個有效地址)常被稱為系統中斷點或者 當前中斷點。在很多 UNIX® 系統中,為了指出當前系統中斷點,必須使用 sbrk(0)
函數。 sbrk
根據參數中給出的字節數移動當前系統中斷點,然后返回新的系統中斷點。使用參數 0
只是返回當前中斷點。這里是我們的 malloc
初始化代碼,它將找到當前中斷點并初始化我們的變量:
/* Include the sbrk function */ #include <unistd.h> void malloc_init() { /* grab the last valid address from the OS */ last_valid_address = sbrk(0); /* we don't have any memory to manage yet, so *just set the beginning to be last_valid_address */ managed_memory_start = last_valid_address; /* Okay, we're initialized and ready to go */ has_initialized = 1; } |
現在,為了完全地管理內存,我們需要能夠追蹤要分配和回收哪些內存。在對內存塊進行了 free
調用之后,我們需要做的是諸如將它們標記為未被使用的等事情,并且,在調用 malloc
時,我們要能夠定位未被使用的內存塊。因此, malloc
返回的每塊內存的起始處首先要有這個結構:
struct mem_control_block { int is_available; int size; }; |
現在,您可能會認為當程序調用 malloc
時這會引發問題 —— 它們如何知道這個結構?答案是它們不必知道;在返回指針之前,我們會將其移動到這個結構之后,把它隱藏起來。這使得返回的指針指向沒有用于任何其他用途的內存。那樣,從調用程序的角度來看,它們所得到的全部是空閑的、開放的內存。然后,當通過 free()
將該指針傳遞回來時,我們只需要倒退幾個內存字節就可以再次找到這個結構。
在討論分配內存之前,我們將先討論釋放,因為它更簡單。為了釋放內存,我們必須要做的惟一一件事情就是,獲得我們給出的指針,回退 sizeof(struct mem_control_block)
個字節,并將其標記為可用的。這里是對應的代碼:
void free(void *firstbyte) { struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block */ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That's It! We're done. */ return; } |
如您所見,在這個分配程序中,內存的釋放使用了一個非常簡單的機制,在固定時間內完成內存釋放。分配內存稍微困難一些。以下是該算法的略述:
1. If our allocator has not been initialized, initialize it. 2. Add sizeof(struct mem_control_block) to the size requested. 3. start at managed_memory_start. 4. Are we at last_valid address? 5. If we are: A. We didn't find any existing space that was large enough -- ask the operating system for more and return that. 6. Otherwise: A. Is the current space available (check is_available from the mem_control_block)? B. If it is: i) Is it large enough (check "size" from the mem_control_block)? ii) If so: a. Mark it as unavailable b. Move past mem_control_block and return the pointer iii) Otherwise: a. Move forward "size" bytes b. Go back go step 4 C. Otherwise: i) Move forward "size" bytes ii) Go back to step 4 |
我們主要使用連接的指針遍歷內存來尋找開放的內存塊。這里是代碼:
void *malloc(long numbytes) { /* Holds where we are looking in memory */ void *current_location; /* This is the same as current_location, but cast to a * memory_control_block */ struct mem_control_block *current_location_mcb; /* This is the memory location we will return. It will * be set to 0 until we find something suitable */ void *memory_location; /* Initialize if we haven't already done so */ if(! has_initialized) { malloc_init(); } /* The memory we search for has to include the memory * control block, but the users of malloc don't need * to know this, so we'll just add it in for them. */ numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable * location */ memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_start; /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) { /* current_location and current_location_mcb point * to the same address. However, current_location_mcb * is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses. */ current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available) { if(current_location_mcb->size >= numbytes) { /* Woohoo! We've found an open, * appropriately-size location. */ /* It is no longer available */ current_location_mcb->is_available = 0; /* We own it */ memory_location = current_location; /* Leave the loop */ break; } } /* If we made it here, it's because the Current memory * block not suitable; move to the next one */ current_location = current_location + current_location_mcb->size; } /* If we still don't have a valid location, we'll * have to ask the operating system for more memory */ if(! memory_location) { /* Move the program break numbytes further */ sbrk(numbytes); /* The new memory will be where the last valid * address left off */ memory_location = last_valid_address; /* We'll move the last valid address forward * numbytes */ last_valid_address = last_valid_address + numbytes; /* We need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; } /* Now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block */ /* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_location; } |
這就是我們的內存管理器。現在,我們只需要構建它,并在程序中使用它即可。
運行下面的命令來構建 malloc
兼容的分配程序(實際上,我們忽略了 realloc()
等一些函數,不過, malloc()
和 free()
才是最主要的函數):
gcc -shared -fpic malloc.c -o malloc.so |
該程序將生成一個名為 malloc.so 的文件,它是一個包含有我們的代碼的共享庫。
在 UNIX 系統中,現在您可以用您的分配程序來取代系統的 malloc()
,做法如下:
LD_PRELOAD=/path/to/malloc.so export LD_PRELOAD |
LD_PRELOAD
環境變量使動態鏈接器在加載任何可執行程序之前,先加載給定的共享庫的符號。它還為特定庫中的符號賦予優先權。因此,從現在起,該會話中的任何應用程序都將使用我們的 malloc()
,而不是只有系統的應用程序能夠使用。有一些應用程序不使用 malloc()
,不過它們是例外。其他使用 realloc()
等其他內存管理函數的應用程序,或者錯誤地假定 malloc()
內部行為的那些應用程序,很可能會崩潰。ash shell 似乎可以使用我們的新 malloc()
很好地工作。
如果您想確保 malloc()
正在被使用,那么您應該通過向函數的入口點添加 write()
調用來進行測試。
我們的內存管理器在很多方面都還存在欠缺,但它可以有效地展示內存管理需要做什么事情。它的某些缺點包括:
mmap
一起使用。
malloc
只假定內存分配是成功的)。
realloc()
。
sbrk()
可能會交回比我們請求的更多的內存,所以在堆(heap)的末端會遺漏一些內存。
is_available
標記只包含一位信息,但它要使用完整的 4-字節 的字。
malloc()
的實現有很多,這些實現各有優點與缺點。在設計一個分配程序時,要面臨許多需要折衷的選擇,其中包括:
每一個實現都有其自身的優缺點集合。在我們的簡單的分配程序中,分配非常慢,而回收非常快。另外,由于它在使用虛擬內存系統方面較差,所以它最適于處理大的對象。
還有其他許多分配程序可以使用。其中包括:
ptmalloc
。 Doug Lea 的分配程序有著與我們的版本非常類似的基本結構,但是它加入了索引,這使得搜索速度更快,并且可以將多個沒有被使用的塊組合為一個大的塊。它還支持緩存,以便更快地再次使用最近釋放的內存。 ptmalloc
是 Doug Lea Malloc 的一個擴展版本,支持多線程。在本文后面的 參考資料部分中,有一篇描述 Doug Lea 的 Malloc 實現的文章。
眾多可用的分配程序中最有名的就是上述這些分配程序。如果您的程序有特別的分配需求,那么您可能更愿意編寫一個定制的能匹配您的程序內存分配方式的分配程序。不過,如果不熟悉分配程序的設計,那么定制分配程序通常會帶來比它們解決的問題更多的問題。要獲得關于該主題的適當的介紹,請參閱 Donald Knuth 撰寫的 The Art of Computer Programming Volume 1: Fundamental Algorithms 中的第 2.5 節“Dynamic Storage Allocation”(請參閱 參考資料中的鏈接)。它有點過時,因為它沒有考慮虛擬內存環境,不過大部分算法都是基于前面給出的函數。
在 C++ 中,通過重載 operator new()
,您可以以每個類或者每個模板為單位實現自己的分配程序。在 Andrei Alexandrescu 撰寫的 Modern C++ Design 的第 4 章(“Small Object Allocation”)中,描述了一個小對象分配程序(請參閱 參考資料中的鏈接)。
不只是我們的內存管理器有缺點,基于 malloc()
的內存管理器仍然也有很多缺點,不管您使用的是哪個分配程序。對于那些需要保持長期存儲的程序使用 malloc()
來管理內存可能會非常令人失望。如果您有大量的不固定的內存引用,經常難以知道它們何時被釋放。生存期局限于當前函數的內存非常容易管理,但是對于生存期超出該范圍的內存來說,管理內存則困難得多。而且,關于內存管理是由進行調用的程序還是由被調用的函數來負責這一問題,很多 API 都不是很明確。
因為管理內存的問題,很多程序傾向于使用它們自己的內存管理規則。C++ 的異常處理使得這項任務更成問題。有時好像致力于管理內存分配和清理的代碼比實際完成計算任務的代碼還要多!因此,我們將研究內存管理的其他選擇。
![]() ![]() |
![]()
|
引用計數是一種 半自動(semi-automated)的內存管理技術,這表示它需要一些編程支持,但是它不需要您確切知道某一對象何時不再被使用。引用計數機制為您完成內存管理任務。
在引用計數中,所有共享的數據結構都有一個域來包含當前活動“引用”結構的次數。當向一個程序傳遞一個指向某個數據結構指針時,該程序會將引用計數增加 1。實質上,您是在告訴數據結構,它正在被存儲在多少個位置上。然后,當您的進程完成對它的使用后,該程序就會將引用計數減少 1。結束這個動作之后,它還會檢查計數是否已經減到零。如果是,那么它將釋放內存。
這樣做的好處是,您不必追蹤程序中某個給定的數據結構可能會遵循的每一條路徑。每次對其局部的引用,都將導致計數的適當增加或減少。這樣可以防止在使用數據結構時釋放該結構。不過,當您使用某個采用引用計數的數據結構時,您必須記得運行引用計數函數。另外,內置函數和第三方的庫不會知道或者可以使用您的引用計數機制。引用計數也難以處理發生循環引用的數據結構。
要實現引用計數,您只需要兩個函數 —— 一個增加引用計數,一個減少引用計數并當計數減少到零時釋放內存。
一個示例引用計數函數集可能看起來如下所示:
/* Structure Definitions*/ /* Base structure that holds a refcount */ struct refcountedstruct { int refcount; } /* All refcounted structures must mirror struct * refcountedstruct for their first variables */ /* Refcount maintenance functions */ /* Increase reference count */ void REF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount++; } /* Decrease reference count */ void UNREF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount--; /* Free the structure if there are no more users */ if(rstruct->refcount == 0) { free(rstruct); } } |
REF
和 UNREF
可能會更復雜,這取決于您想要做的事情。例如,您可能想要為多線程程序增加鎖,那么您可能想擴展 refcountedstruct
,使它同樣包含一個指向某個在釋放內存之前要調用的函數的指針(類似于面向對象語言中的析構函數 —— 如果您的結構中包含這些指針,那么這是 必需的)。
當使用 REF
和 UNREF
時,您需要遵守這些指針的分配規則:
UNREF
分配前左端指針(left-hand-side pointer)指向的值。
REF
分配后左端指針(left-hand-side pointer)指向的值。
在傳遞使用引用計數的結構的函數中,函數需要遵循以下這些規則:
以下是一個使用引用計數的生動的代碼示例:
/* EXAMPLES OF USAGE */ /* Data type to be refcounted */ struct mydata { int refcount; /* same as refcountedstruct */ int datafield1; /* Fields specific to this struct */ int datafield2; /* other declarations would go here as appropriate */ }; /* Use the functions in code */ void dosomething(struct mydata *data) { REF(data); /* Process data */ /* when we are through */ UNREF(data); } struct mydata *globalvar1; /* Note that in this one, we don't decrease the * refcount since we are maintaining the reference * past the end of the function call through the * global variable */ void storesomething(struct mydata *data) { REF(data); /* passed as a parameter */ globalvar1 = data; REF(data); /* ref because of Assignment */ UNREF(data); /* Function finished */ } |
由于引用計數是如此簡單,大部分程序員都自已去實現它,而不是使用庫。不過,它們依賴于 malloc
和 free
等低層的分配程序來實際地分配和釋放它們的內存。
在 Perl 等高級語言中,進行內存管理時使用引用計數非常廣泛。在這些語言中,引用計數由語言自動地處理,所以您根本不必擔心它,除非要編寫擴展模塊。由于所有內容都必須進行引用計數,所以這會對速度產生一些影響,但它極大地提高了編程的安全性和方便性。以下是引用計數的益處:
不過,它也有其不足之處:
try
或 setjmp()
/ longjmp()
)時,您必須采取其他方法。
C++ 可以通過使用 智能指針(smart pointers)來容忍程序員所犯的一些錯誤,智能指針可以為您處理引用計數等指針處理細節。不過,如果不得不使用任何先前的不能處理智能指針的代碼(比如對 C 庫的聯接),實際上,使用它們的后果通實比不使用它們更為困難和復雜。因此,它通常只是有益于純 C++ 項目。如果您想使用智能指針,那么您實在應該去閱讀 Alexandrescu 撰寫的 Modern C++ Design 一書中的“Smart Pointers”那一章。
內存池是另一種半自動內存管理方法。內存池幫助某些程序進行自動內存管理,這些程序會經歷一些特定的階段,而且每個階段中都有分配給進程的特定階段的內存。例如,很多網絡服務器進程都會分配很多針對每個連接的內存 —— 內存的最大生存期限為當前連接的存在期。Apache 使用了池式內存(pooled memory),將其連接拆分為各個階段,每個階段都有自己的內存池。在結束每個階段時,會一次釋放所有內存。
在池式內存管理中,每次內存分配都會指定內存池,從中分配內存。每個內存池都有不同的生存期限。在 Apache 中,有一個持續時間為服務器存在期的內存池,還有一個持續時間為連接的存在期的內存池,以及一個持續時間為請求的存在期的池,另外還有其他一些內存池。因此,如果我的一系列函數不會生成比連接持續時間更長的數據,那么我就可以完全從連接池中分配內存,并知道在連接結束時,這些內存會被自動釋放。另外,有一些實現允許注冊 清除函數(cleanup functions),在清除內存池之前,恰好可以調用它,來完成在內存被清理前需要完成的其他所有任務(類似于面向對象中的析構函數)。
要在自己的程序中使用池,您既可以使用 GNU libc 的 obstack 實現,也可以使用 Apache 的 Apache Portable Runtime。GNU obstack 的好處在于,基于 GNU 的 Linux 發行版本中默認會包括它們。Apache Portable Runtime 的好處在于它有很多其他工具,可以處理編寫多平臺服務器軟件所有方面的事情。要深入了解 GNU obstack 和 Apache 的池式內存實現,請參閱 參考資料部分中指向這些實現的文檔的鏈接。
下面的假想代碼列表展示了如何使用 obstack:
#include <obstack.h> #include <stdlib.h> /* Example code listing for using obstacks */ /* Used for obstack macros (xmalloc is a malloc function that exits if memory is exhausted */ #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free /* Pools */ /* Only permanent allocations should go in this pool */ struct obstack *global_pool; /* This pool is for per-connection data */ struct obstack *connection_pool; /* This pool is for per-request data */ struct obstack *request_pool; void allocation_failed() { exit(1); } int main() { /* Initialize Pools */ global_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(global_pool); connection_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(connection_pool); request_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(request_pool); /* Set the error handling function */ obstack_alloc_failed_handler = &allocation_failed; /* Server main loop */ while(1) { wait_for_connection(); /* We are in a connection */ while(more_requests_available()) { /* Handle request */ handle_request(); /* Free all of the memory allocated * in the request pool */ obstack_free(request_pool, NULL); } /* We're finished with the connection, time * to free that pool */ obstack_free(connection_pool, NULL); } } int handle_request() { /* Be sure that all object allocations are allocated * from the request pool */ int bytes_i_need = 400; void *data1 = obstack_alloc(request_pool, bytes_i_need); /* Do stuff to process the request */ /* return */ return 0; } |
基本上,在操作的每一個主要階段結束之后,這個階段的 obstack 會被釋放。不過,要注意的是,如果一個過程需要分配持續時間比當前階段更長的內存,那么它也可以使用更長期限的 obstack,比如連接或者全局內存。傳遞給 obstack_free()
的 NULL
指出它應該釋放 obstack 的全部內容。可以用其他的值,但是它們通常不怎么實用。
使用池式內存分配的益處如下所示:
池式內存的缺點是:
![]() ![]() |
![]()
|
垃圾收集(Garbage collection)是全自動地檢測并移除不再使用的數據對象。垃圾收集器通常會在當可用內存減少到少于一個具體的閾值時運行。通常,它們以程序所知的可用的一組“基本”數據 —— 棧數據、全局變量、寄存器 —— 作為出發點。然后它們嘗試去追蹤通過這些數據連接到每一塊數據。收集器找到的都是有用的數據;它沒有找到的就是垃圾,可以被銷毀并重新使用這些無用的數據。為了有效地管理內存,很多類型的垃圾收集器都需要知道數據結構內部指針的規劃,所以,為了正確運行垃圾收集器,它們必須是語言本身的一部分。
Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因為它是免費的,而且既是保守的又是增量的,可以使用 --enable-redirect-malloc
選項來構建它,并且可以將它用作系統分配程序的簡易替代者(drop-in replacement)(用 malloc
/ free
代替它自己的 API)。實際上,如果這樣做,您就可以使用與我們在示例分配程序中所使用的相同的 LD_PRELOAD
技巧,在系統上的幾乎任何程序中啟用垃圾收集。如果您懷疑某個程序正在泄漏內存,那么您可以使用這個垃圾收集器來控制進程。在早期,當 Mozilla 嚴重地泄漏內存時,很多人在其中使用了這項技術。這種垃圾收集器既可以在 Windows® 下運行,也可以在 UNIX 下運行。
垃圾收集的一些優點:
其缺點包括:
![]() ![]() |
![]()
|
一切都需要折衷:性能、易用、易于實現、支持線程的能力等,這里只列出了其中的一些。為了滿足項目的要求,有很多內存管理模式可以供您使用。每種模式都有大量的實現,各有其優缺點。對很多項目來說,使用編程環境默認的技術就足夠了,不過,當您的項目有特殊的需要時,了解可用的選擇將會有幫助。下表對比了本文中涉及的內存管理策略。
策略 | 分配速度 | 回收速度 | 局部緩存 | 易用性 | 通用性 | 實時可用 | SMP 線程友好 |
定制分配程序 | 取決于實現 | 取決于實現 | 取決于實現 | 很難 | 無 | 取決于實現 | 取決于實現 |
簡單分配程序 | 內存使用少時較快 | 很快 | 差 | 容易 | 高 | 否 | 否 |
GNU malloc |
中 | 快 | 中 | 容易 | 高 | 否 | 中 |
Hoard | 中 | 中 | 中 | 容易 | 高 | 否 | 是 |
引用計數 | N/A | N/A | 非常好 | 中 | 中 | 是(取決于 malloc 實現) |
取決于實現 |
池 | 中 | 非常快 | 極好 | 中 | 中 | 是(取決于 malloc 實現) |
取決于實現 |
垃圾收集 | 中(進行收集時慢) | 中 | 差 | 中 | 中 | 否 | 幾乎不 |
增量垃圾收集 | 中 | 中 | 中 | 中 | 中 | 否 | 幾乎不 |
增量保守垃圾收集 | 中 | 中 | 中 | 容易 | 高 | 否 | 幾乎不 |
Web 上的文檔
malloc
實現。 mmap()
的 malloc
實現。 malloc
以及它如何與 BSD 虛擬內存交互。 ![]() |
||
|
![]() |
Jonathan Bartlett 是 Programming from the Ground Up 一書的作者,這本書介紹的是 Linux 匯編語言編程。Jonathan Bartlett 是 New Media Worx 的總開發師,負責為客戶開發 Web、視頻、kiosk 和桌面應用程序。您可以通過 johnnyb@eskimo.com 與 Jonathan 聯系。 |
??? 一。誰在做Garbage Collection?
??? 一種流行的說法:在C++里,是系統在做垃圾回收;而在Java里,是Java自身在做。
??? 在C++里,釋放內存是手動處理的,要用delete運算符來釋放分配的內存。這是流行的說法。確切地說,是應用認為不需要某實體時,就需用delete告訴系統,可以回收這塊空間了。這個要求,對編碼者來說,是件很麻煩、很難做到的事。隨便上哪個BBS,在C/C++版塊里總是有一大堆關于內存泄漏的話題。
??? Java采用一種不同的,很方便的方法:Garbage Collection.垃圾回收機制放在JVM里。JVM完全負責垃圾回收事宜,應用只在需要時申請空間,而在拋棄對象時不必關心空間回收問題。
??? 二。對象在啥時被丟棄?
??? 在C++里,當對象離開其作用域時,該對象即被應用拋棄。
??? 是對象的生命期不再與其作用域有關,而僅僅與引用有關。
??? Java的垃圾回收機制一般包含近十種算法。對這些算法中的多數,我們不必予以關心。只有其中最簡單的一個:引用計數法,與編碼有關。
??? 一個對象,可以有一個或多個引用變量指向它。當一個對象不再有任何一個引用變量指向它時,這個對象就被應用拋棄了。或者說,這個對象可以被垃圾回收機制回收了。
??? 這就是說,當不存在對某對象的任何引用時,就意味著,應用告訴JVM:我不要這個對象,你可以回收了。
??? JVM的垃圾回收機制對堆空間做實時檢測。當發現某對象的引用計數為0時,就將該對象列入待回收列表中。但是,并不是馬上予以銷毀。
??? 三。丟棄就被回收?
??? 該對象被認定為沒有存在的必要了,那么它所占用的內存就可以被釋放。被回收的內存可以用于后續的再分配。
??? 但是,并不是對象被拋棄后當即被回收的。JVM進程做空間回收有較大的系統開銷。如果每當某應用進程丟棄一個對象,就立即回收它的空間,勢必會使整個系統的運轉效率非常低下。
??? 前面說過,JVM的垃圾回收機制有多個算法。除了引用計數法是用來判斷對象是否已被拋棄外,其它算法是用來確定何時及如何做回收。JVM的垃圾回收機制要在時間和空間之間做個平衡。
??? 因此,為了提高系統效率,垃圾回收器通常只在滿足兩個條件時才運行:即有對象要回收且系統需要回收。切記垃圾回收要占用時間,因此,Java運行時系統只在需要的時候才使用它。因此你無法知道垃圾回收發生的精確時間。
??? 四。沒有引用變量指向的對象有用嗎?
??? 前面說了,沒掛上引用變量的對象是被應用丟棄的,這意味著,它在堆空間里是個垃圾,隨時可能被JVM回收。
??? 不過,這里有個不是例外的例外。對于一次性使用的對象(有些書稱之為臨時對象),可以不用引用變量指向它。舉個最簡單也最常見的例子:
??? System.out.println(“I am Java!”);
??? 就是創建了一個字符串對象后,直接傳遞給println()方法。
??? 五。應用能干預垃圾回收嗎?
??? 許多人對Java的垃圾回收不放心,希望在應用代碼里控制JVM的垃圾回收運作。這是不可能的事。對垃圾回收機制來說,應用只有兩個途徑發消息給JVM.第一個前面已經說了,就是將指向某對象的所有引用變量全部移走。這就相當于向JVM發了一個消息:這個對象不要了。第二個是調用庫方法System.gc(),多數書里說調用它讓Java做垃圾回收。
??? 第一個是一個告知,而調用System.gc()也僅僅是一個請求。JVM接受這個消息后,并不是立即做垃圾回收,而只是對幾個垃圾回收算法做了加權,使垃圾回收操作容易發生,或提早發生,或回收較多而已。
??? 希望JVM及時回收垃圾,是一種需求。其實,還有相反的一種需要:在某段時間內最好不要回收垃圾。要求運行速度最快的實時系統,特別是嵌入式系統,往往希望如此。
??? Java的垃圾回收機制是為所有Java應用進程服務的,而不是為某個特定的進程服務的。因此,任何一個進程都不能命令垃圾回收機制做什么、怎么做或做多少。
??? 六。對象被回收時要做的事
??? 一個對象在運行時,可能會有一些東西與其關連。因此,當對象即將被銷毀時,有時需要做一些善后工作。可以把這些操作寫在finalize()方法(常稱之為終止器)里。
??? protected void finalize()
??? {
??? // finalization code here
??? }
??? 這個終止器的用途類似于C++里的析構函數,而且都是自動調用的。但是,兩者的調用時機不一樣,使兩者的表現行為有重大區別。C++的析構函數總是當對象離開作用域時被調用。這就是說,C++析構函數的調用時機是確定的,且是可被應用判知的。但是,Java終止器卻是在對象被銷毀時。由上所知,被丟棄的對象何時被銷毀,應用是無法獲知的。而且,對于大多數場合,被丟棄對象在應用終止后仍未銷毀。
??? 在編碼時,考慮到這一點。譬如,某對象在運作時打開了某個文件,在對象被丟棄時不關閉它,而是把文件關閉語句寫在終止器里。這樣做對文件操作會造成問題。如果文件是獨占打開的,則其它對象將無法訪問這個文件。如果文件是共享打開的,則另一訪問該文件的對象直至應用終結仍不能讀到被丟棄對象寫入該文件的新內容。
??? 至少對于文件操作,編碼者應認清Java終止器與C++析構函數之間的差異。
??? 那么,當應用終止,會不會執行應用中的所有finalize()呢?據Bruce Eckel在Thinking in Java里的觀點:“到程序結束的時候,并非所有收尾模塊都會得到調用”。這還僅僅是指應用正常終止的場合,非正常終止呢?
??? 因此,哪些收尾操作可以放在finalize()里,是需要酌酎的。
public abstract class MultiClassLoader extends ClassLoader{ ... public synchronized Class loadClass(String s, boolean flag) throws ClassNotFoundException { /* 檢查類s是否已經在本地內存*/ Class class1 = (Class)classes.get(s); /* 類s已經在本地內存*/ if(class1 != null) return class1; try/*用默認的ClassLoader 裝入類*/ { class1 = super.findSystemClass(s); return class1; } catch(ClassNotFoundException _ex) { System.out.println(">> Not a system class."); } /* 取得類s的字節數組*/ byte abyte0[] = loadClassBytes(s); if(abyte0 == null) throw new ClassNotFoundException(); /* 將類字節數組轉換為類*/ class1 = defineClass(null, abyte0, 0, abyte0.length); if(class1 == null) throw new ClassFormatError(); if(flag) resolveClass(class1); /*解析類*/ /* 將新加載的類放入本地內存*/ classes.put(s, class1); System.out.println(">> Returning newly loaded class."); /* 返回已裝載、解析的類*/ return class1; } ... } |
![]() 圖1 Java的類裝載的體系結構 |
![]() 圖2 Java類裝載的代理結構 |
什么是UDP協議 UDP協議的全稱是用戶數據報,在網絡中它與TCP協議一樣用于處理數據包。在OSI模型中,在第四層——傳輸層,處于IP協議的上一層。UDP有不提供數據報分組、組裝和不能對數據包的排序的缺點,也就是說,當報文發送之后,是無法得知其是否安全完整到達的。 為什么要使用UDP 在選擇使用協議的時候,選擇UDP必須要謹慎。在網絡質量令人不十分滿意的環境下,UDP協議數據包丟失會比較嚴重。但是由于UDP的特性:它不屬于連接型協議,因而具有資源消耗小,處理速度快的優點,所以通常音頻、視頻和普通數據在傳送時使用UDP較多,因為它們即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。比如我們聊天用的ICQ和OICQ就是使用的UDP協議。 在Java中操縱UDP 使用位于JDK中Java.net包下的DatagramSocket和DatagramPacket類,可以非常方便地控制用戶數據報文。 在描述它們之前,必須了解位于同一個位置的InetAddress類。InetAddress實現了Java.io. Serializable接口,不允許繼承。它用于描述和包裝一個Internet IP地址,通過三個方法返回InetAddress實例: getLocalhost():返回封裝本地地址的實例。 getAllByName(String host):返回封裝Host地址的InetAddress實例數組。 getByName(String host):返回一個封裝Host地址的實例。其中,Host可以是域名或者是一個合法的IP地址。 DatagramSocket類用于創建接收和發送UDP的Socket實例。和Socket類依賴SocketImpl類一樣,DatagramSocket類的實現也依靠專門為它設計的DatagramScoketImplFactory類。DatagramSocket類有3個構建器: DatagramSocket():創建實例。這是個比較特殊的用法,通常用于客戶端編程,它并沒有特定監聽的端口,僅僅使用一個臨時的。 DatagramSocket(int port):創建實例,并固定監聽Port端口的報文。 DatagramSocket(int port, InetAddress localAddr):這是個非常有用的構建器,當一臺機器擁有多于一個IP地址的時候,由它創建的實例僅僅接收來自LocalAddr的報文。 值得注意的是,在創建DatagramSocket類實例時,如果端口已經被使用,會產生一個SocketException的異常拋出,并導致程序非法終止,這個異常應該注意捕獲。DatagramSocket類最主要的方法有4個: Receive(DatagramPacket d):接收數據報文到d中。receive方法產生一個“阻塞”。 Send(DatagramPacket d):發送報文d到目的地。 SetSoTimeout(int timeout):設置超時時間,單位為毫秒。 Close():關閉DatagramSocket。在應用程序退出的時候,通常會主動釋放資源,關閉Socket,但是由于異常地退出可能造成資源無法回收。所以,應該在程序完成時,主動使用此方法關閉Socket,或在捕獲到異常拋出后關閉Socket。 “阻塞”是一個專業名詞,它會產生一個內部循環,使程序暫停在這個地方,直到一個條件觸發。 DatagramPacket類用于處理報文,它將Byte數組、目標地址、目標端口等數據包裝成報文或者將報文拆卸成Byte數組。應用程序在產生數據包是應該注意,TCP/IP規定數據報文大小最多包含65507個,通常主機接收548個字節,但大多數平臺能夠支持8192字節大小的報文。DatagramPacket類的構建器共有4個: DatagramPacket(byte[] buf, int length, InetAddress addr, int port):從Buf數組中,取出Length長的數據創建數據包對象,目標是Addr地址,Port端口。 DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):從Buf數組中,取出Offset開始的、Length長的數據創建數據包對象,目標是Addr地址,Port端口。 DatagramPacket(byte[] buf, int offset, int length):將數據包中從Offset開始、Length長的數據裝進Buf數組。 DatagramPacket(byte[] buf, int length):將數據包中Length長的數據裝進Buf數組。 DatagramPacket類最重要的方法就是getData()了,它從實例中取得報文的Byte數組編碼。 ★簡單的實例說明 {接收數據的服務器} byte[] buf = new byte[1000]; DatagramSocket ds = new DatagramSocket(12345); //開始監視12345端口 DatagramPacket ip = new DatagramPacket(buf, buf.length); //創建接收數據報的實例 while (true) { ds.receive(ip); //阻塞,直到收到數據報后將數據裝入IP中 System.out.println(new String(buf)); } {發送數據的客戶端} InetAddress target = InetAddress.getByName(“www.xxx.com“); //得到目標機器的地址實例 DatagramSocket ds = new DatagramSocket(9999); //從9999端口發送數據報 String hello = “Hello, I am come in!”; //要發送的數據 byte[] buf = hello.getBytes(); //將數據轉換成Byte類型 op = new DatagramPacket(buf, buf.length, target, 12345); //將BUF緩沖區中的數據打包 ds.send(op); //發送數據 ds.close(); //關閉連接
public boolean setReadable(boolean readable, boolean ownerOnly) public boolean setReadable(boolean readable) public boolean setWritable(boolean writable, boolean ownerOnly) public boolean setWritable(boolean writable) public boolean setExecutable(boolean executable, boolean ownerOnly) public boolean setExecutable(boolean executable) |
命令 | 在Windows XP系統上的返回值 | 在Linux系統上的返回值 | 在solaris系統上的返回值 |
setReadable(true) | true | True(等價于chmod+r) | True(等價于chmod+r) |
setReadable(false) | False(在Windows中文件可讀性不能被設置為False) | True(等價于chmod-r) | True(等價于chmod-r) |
setWritable(true) | True(切換Windows的只讀文件屬性) | True(等價于chmod+w) | True(等價于chmod+w) |
setWritable(false) | true(切換Windows的只讀文件屬性) | True(等價于chmod-w) | True(等價于chmod-w) |
setExecutable(true) | true | True(等價于chmod+x) | True(等價于chmod+x) |
setExecutable(false) | false(在Windows中文件可執行屬性不能被設置為False) | True(等價于chmod-x) | True(等價于chmod-x) |
public boolean canRead(); public boolean canWrite(); public boolean canExecute(); |
public long getTotalSpace(); public long getFreeSpace(); public long getUsableSpace(); |
public void setTabComponentAt(int index, Component component) |
public Component getTabComponentAt(int index) |
public int indexOfTabComponent(Component tabComponent) |
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class TabbedPaneExample implements ActionListener { private JFrame frame; private JTabbedPane tabbedPane; private JButton addTabButton; private ImageIcon closeXIcon; private Dimension closeButtonSize; private int tabCounter = 0; public TabbedPaneExample() { //創建選項卡面板 tabbedPane = new JTabbedPane(); //創建一個按鈕-用戶可用來添加一個選項卡到選項卡面板 addTabButton = new JButton("Add Tab"); addTabButton.addActionListener(this); //創建一個框架來包含這個選項卡面板 frame = new JFrame(); //創建一個圖像圖標'X'以實現在每一個選項卡上的關閉功能。加載的gif是一個10x10圖形(非黑色部分是透明的) closeXIcon = new ImageIcon("C:/CloseX.gif"); //創建一個Dimension用來調整close按鈕的大小 closeButtonSize = new Dimension( closeXIcon.getIconWidth()+2, closeXIcon.getIconHeight()+2); //所選項卡面板添加到圖形中央,把"Add Tab"按鈕置于南面。然后包裝它,調整其大小并顯示它。 frame.add(tabbedPane, BorderLayout.CENTER); frame.add(addTabButton, BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setMinimumSize(new Dimension(300, 300)); frame.setVisible(true); } public void actionPerformed(ActionEvent e) { final JPanel content = new JPanel(); //創建一個描述該選項卡的面板并確保它是透明的 JPanel tab = new JPanel(); tab.setOpaque(false); //為該選項卡創建一個標簽和一個Close按鈕。一定要 //把它的尺寸設置為幾乎該圖標的大小,并且 //創建一個行為聽取器-它將定位該選項卡并且從選項卡面板上刪除它 JLabel tabLabel = new JLabel("Tab " + (++tabCounter)); JButton tabCloseButton = new JButton(closeXIcon); tabCloseButton.setPreferredSize(closeButtonSize); tabCloseButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int closeTabNumber = tabbedPane.indexOfComponent(content); tabbedPane.removeTabAt(closeTabNumber); } }); tab.add(tabLabel, BorderLayout.WEST); tab.add(tabCloseButton, BorderLayout.EAST); //把該選項卡添加到選項卡面板。注意, //第一個參數(它正常是一個描述選項卡標題的String //),為null. tabbedPane.addTab(null, content); //不是在選項卡上使用String/Icon的結合, //而是使用我們的面板。 tabbedPane.setTabComponentAt(tabbedPane.getTabCount()-1, tab); } public static void main(String[] args) { TabbedPaneExample main = new TabbedPaneExample(); } } |
![]() 圖1.一個把多個JComponent用作選項卡的JTabbedPane |
public abstract class SwingWorker<T,V> extends Object implements RunnableFuture<T> |
protected void setProgress(int progress); |
protected void process(V... chunks); protected void publish(V... chunks); |
public T get(); public T get(long timeout,TimeUnit unit); |
public final boolean cancel(boolean mayInterruptIfRunning) |
java.lang.Class klass = Myclass.class; |
log(java.lang.String.class.getClassLoader()); |
protected synchronized Class<?> loadClass (String name, boolean resolve) throws ClassNotFoundException { // First check if the class is already loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke // findClass to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } |
public class MyClassLoader extends ClassLoader { public MyClassLoader() { super(MyClassLoader.class.getClassLoader()); } } |
public class MyClassLoader extends ClassLoader { public MyClassLoader() { super(getClass().getClassLoader()); } } |
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } |
Target target3 = (Target) target2; |
圖1. 在同一個JVM中多個類加載器加載同一個目標類 |
圖2. 文件夾結構組織示例 |
public void fx(){ log("this = " + this + "; Version.fx(1)."); } |
set CLASSPATH=.;%CURRENT_ROOT%\v1;%CURRENT_ROOT%\v2 %JAVA_HOME%\bin\java Test |
圖3. 在類路徑中samepath測試排在最前面的version 1 |
set CLASSPATH=.;%CURRENT_ROOT%\v2;%CURRENT_ROOT%\v1 %JAVA_HOME%\bin\java Test |
圖4. 在類路徑中samepath測試排在最前面的version 2 |
圖5. AppClassLoader及ExtClassLoader |
static { log("client.TaskImpl.class.getClassLoader (v1) : " + TaskImpl.class.getClassLoader()); } public void execute(){ log("this = " + this + "; execute(1)"); } |
CLASSPATH=%CURRENT_ROOT%\common;%CURRENT_ROOT%\server; %CURRENT_ROOT%\client2;%CURRENT_ROOT%\client1 %JAVA_HOME%\bin\java server.Server |
圖6. 執行引擎服務器控制臺 |
圖7. 執行引擎客戶端 1控制臺 |
圖8. 執行引擎客戶端 2控制臺 |
public static void main(String[] args) {泛型類型解決了這段代碼中的顯示的類型安全問題。Java.util中的List或是其他集合類已經使用泛型重寫過了。就像前面提到的, List被重新定義為一個list,它中間的元素類型被一個類型可變的名稱為E的占位符描述。Add()方法被重新定義為期望一個類型為E的參數,用于替換以前的Object,get()方法被重新定義為返回一個E,替換了以前的Object。
????// This list is intended to hold only strings.
????// The compiler doesn't know that so we have to remember ourselves.
????List wordlist = new ArrayList();??
????// Oops! We added a String[] instead of a String.
????// The compiler doesn't know that this is an error.
????wordlist.add(args);
????// Since List can hold arbitrary objects, the get() method returns
????// Object.??Since the list is intended to hold strings, we cast the
????// return value to String but get a ClassCastException because of
????// the error above.
????String word = (String)wordlist.get(0);
}
public static void main(String[] args) {值得注意的是代碼量其實并沒有比原來那個沒有泛型的例子少多少。使用“(String)”這樣的類型轉換被替換成了類型參數“<String>”。 不同的是類型參數需要且僅需要聲明一次,而list能夠被使用任何多次,不需要類型轉換。在更長點的例子代碼中,這一點將更加明顯。即使在那些看上去泛型語法比非泛型語法要冗長的例子里,使用泛型依然是非常有價值的——額外的類型信息允許編譯器在您的代碼里執行更強的錯誤檢查。以前只能在運行起才能發現的錯誤現在能夠在編譯時就被發現。此外,以前為了處理類型轉換的異常,我們需要添加額外的代碼行。如果沒有泛型,那么當發生類型轉換異常的時候,一個ClassCastException異常就會被從實際代碼中拋出。
????// This list can only hold String objects
????List<String> wordlist = new ArrayList<String>();
????// args is a String[], not String, so the compiler won't let us do this
????wordlist.add(args);??// Compilation error!
????// We can do this, though.??
????// Notice the use of the new for/in looping statement
????for(String arg : args) wordlist.add(arg);
????// No cast is required.??List<String>.get() returns a String.
????String word = wordlist.get(0);
}
public static void main(String[] args) {象List<String>這個一個參數類型其本身也是也一個類型,也能夠被用于當作其他類型的一個類型變量值。您可能會看到這樣的代碼:
????// A map from strings to their position in the args[] array
????Map<String,Integer> map = new HashMap<String,Integer>();
????// Note that we use autoboxing to wrap i in an Integer object.
????for(int i=0; i < args.length; i++) map.put(args[i], i);??
????// Find the array index of a word.??Note no cast is required!
????Integer position = map.get("hello");
????// We can also rely on autounboxing to convert directly to an int,
????// but this throws a NullPointerException if the key does not exist
????// in the map
????int pos = map.get("world");
}
// Look at all those nested angle brackets!在上面的代碼里,java.util.List<E>和java.util.Map<K,V>的get()方法返回一個類型為E的list元素或者一個類型為V的map元素。注意,無論如何,泛型類型能夠更精密的使用他們的變量。在本書中的參考章節查看List<E>,您將會看到它的iterator( )方法被聲明為返回一個Iterator<E>。這意味著,這個方法返回一個跟list的實際的參數類型一樣的一個參數類型的實例。為了具體的說明這點,下面的例子提供了不使用get(0)方法來獲取一個List<String>的第一個元素的方法
Map<String, List<List<int[]>>> map = getWeirdMap();
// The compiler knows all the types and we can write expressions
// like this without casting.??We might still get NullPointerException
// or ArrayIndexOutOfBounds at runtime, of course.
int value = map.get(key).get(0).get(0)[0];
// Here's how we break that expression down step by step.
List<List<int[]>> listOfLists = map.get(key);
List<int[]> listOfIntArrays = listOfLists.get(0);
int[] array = listOfIntArrays.get(0);
int element = array[0];
List<String> words = // ...initialized elsewhere...。
Iterator<String> iterator = words.iterator();
String firstword = iterator.next();
List l = new ArrayList();這段代碼在java1.4下運行得很好。如果您用java5.0來編譯它,javac編譯了,但是會打印出這樣的“抱怨”:
l.add("hello");??
l.add(new Integer(123));
Object o = l.get(0);
List<Object> l = new ArrayList<Object>();參數化類型的體系
l.add("hello");??
l.add(123);??????????????// autoboxing
Object o = l.get(0);
ArrayList<Integer> l = new ArrayList<Integer>();一個List<Integer>是一個Collection<Integer>,但不是一個List<Object>。這句話不容易理解,如果您想理解為什么泛型這樣做,這段值得看一下。考察這段代碼:
List<Integer> m = l;????????????????????????????// okay
Collection<Integer> n = l;??????????????????????// okay
ArrayList<Number> o = l;????????????????????????// error
Collection<Object> p = (Collection<Object>)l;?? // error, even with cast
List<Integer> li = new ArrayList<Integer>();這就是為什么List<Integer>不是一個List<Object>的原因,雖然List<Integer>中所有的元素事實上是一個Object的實例。如果允許轉換成List<Object>,那么轉換后,理論上非整型的對象也將被允許添加到list中。
li.add(123);
// The line below will not compile.??But for the purposes of this
// thought-experiment, assume that it does compile and see how much
// trouble we get ourselves into.
List<Object> lo = li;??
// Now we can retrieve elements of the list as Object instead of Integer
Object number = lo.get(0);
// But what about this?
lo.add("hello world");
// If the line above is allowed then the line below throws ClassCastException
Integer i = li.get(1);??// Can't cast a String to Integer!
// Here's a basic parameterized list.泛型僅提供了編譯期的類型安全。如果您使用java5.0的編譯器來編譯您的代碼并且沒有得到任何警告,這些編譯器的檢查能夠確保您的代碼在運行期也是類型安全的。如果您獲得了警告或者使用了像未經處理的類型那樣修改您的集合的代碼,那么您需要增加一些步驟來確保運行期的類型安全。您可以通過使用java.util.Collections中的checkedList()和checkedMap( )方法來做到這一步。這些方法將把您的集合打包成一個wrapper集合,從而在運行時檢查確認只有正確類型的值能夠被置入集合眾。下面是一個能夠補上類型安全漏洞的一個例子:
List<Integer> li = new ArrayList<Integer>();
// It is legal to assign a parameterized type to a nonparameterized variable
List l = li;??
// This line is a bug, but it compiles and runs.
// The Java 5.0 compiler will issue an unchecked warning about it.
// If it appeared as part of a legacy class compiled with Java 1.4, however,
// then we'd never even get the warning.??
l.add("hello");
// This line compiles without warning but throws ClassCastException at runtime.
// Note that the failure can occur far away from the actual bug.
Integer i = li.get(0);
// Here's a basic parameterized list.參數化類型的數組
List<Integer> li = new ArrayList<Integer>();
// Wrap it for runtime type safety
List<Integer> cli = Collections.checkedList(li, Integer.class);
// Now widen the checked list to the raw type
List l = cli;??
// This line compiles but fails at runtime with a ClassCastException.
// The exception occurs exactly where the bug is, rather than far away
l.add("hello");
String[] words = new String[10];雖然編譯時obj是一個Object[],但是在運行時它是一個String[],它不允許被用于存放一個Integer。
Object[] objs = words;
objs[0] = 1;??// 1 autoboxed to an Integer, throws ArrayStoreException
List<String>[] wordlists = new ArrayList<String>[10];如果上面的代碼被允許,那么運行時的數組存儲檢查將會成功:沒有編譯時的類型參數,代碼簡單地存儲一個ArrayList到一個ArrayList[]數組,非常正確。既然編譯器不能阻止您通過這個方法來戰勝類型安全,那么它轉而阻止您創建一個參數化類型的數組。所以上述情節永遠不會發生,編譯器在第一行就開始拒絕編譯了。
ArrayList<Integer> ali = new ArrayList<Integer>();
ali.add(123);
Object[] objs = wordlists;
objs[0] = ali;?????????????????????? // No ArrayStoreException
String s = wordlists[0].get(0);??????// ClassCastException!
public static void printList(PrintWriter out, List list) {
????for(int i=0, n=list.size(); i < n; i++) {
????????if (i > 0) out.print(", ");
????????out.print(list.get(i).toString());
????}
}
在Java5.0中,List是一個泛型類型,如果我們試圖編譯這個方法,我們將會得到unchecked警告。為了解決這些警告,您可能需要這樣來修改這個方法:
public static void printList(PrintWriter out, List<Object> list) {這段代碼能夠編譯通過同時不會有警告,但是它并不是非常地有效,因為只有那些被聲明為List<Object>的list才會被允許使用這個方法。還記得么,類似于List<String>和List<Integer>這樣的List并不能被轉型為List<Object>。事實上我們需要一個類型安全的printList()方法,它能夠接受我們傳入的任何List,而不關心它被參數化為什么。解決辦法是使用類型參數通配符。方法可以被修改成這樣:
????for(int i=0, n=list.size(); i < n; i++) {
????????if (i > 0) out.print(", ");
????????out.print(list.get(i).toString());
????}
}
public static void printList(PrintWriter out, List<?> list) {這個版本的方法能夠被編譯過,沒有警告,而且能夠在任何我們希望使用的地方使用。通配符“?”表示一個未知類型,類型List<?>被讀作“List of unknown”
????for(int i=0, n=list.size(); i < n; i++) {
????????if (i > 0) out.print(", ");
????????Object o = list.get(i);
????????out.print(o.toString());
????}
}
List<Object> l = new ArrayList<Object>();從上面的printList()例子中,必須要搞清楚List<?>既不是List<Object>也不是一個未經處理的List。一個使用通配符的List<?>有兩個重要的特性。第一,考察類似于get()的方法,他們被聲明返回一個值,這個值的類型是類型參數中指定的。在這個例子中,類型是“unknown”,所以這些方法返回一個Object。既然我們期望的是調用這個object的toString()方法,程序能夠很好的滿足我們的意愿。
public static double sumList(List<?> list) {要修改這個方法讓它變得真正的類型安全,我們需要使用界定通配符(bounded wildcard),能夠確保List的類型參數是未知的,但又是Number或者Number的子類。下面的代碼才是我們想要的:
????double total = 0.0;
????for(Object o : list) {
????????Number n = (Number) o;??// A cast is required and may fail
????????total += n.doubleValue();
????}
????return total;
}
public static double sumList(List<? extends Number> list) {類型List<? extends Number>可以被理解為“Number未知子類的List”。理解這點非常重要,在這段文字中,Number被認為是其自身的子類。
????double total = 0.0;
????for(Number n : list) total += n.doubleValue();
????return total;
}
??? Bruce Tate并不是作為一個局外者寫就《超越Java》這邊書的。他的顧問公司專注于Java 持久化框架和輕量級開發方法,同時他也是這些流行的Java圖書的作者, Spring: A Developer's Notebook, Better, Faster, Lighter Java, 以及 Bitter Java.
??? 1,在《超越Java》中你花費了大量的時間在Ruby上面,看起來是它像在你說那些將超越Java競爭者中出類拔萃。你覺得是什么使Ruby比 PHP,Python這類語言優越?
??? 這些都是好語言,但是都有一些缺點。對大型應用,PHP和Perl不能連續地產生可讀的代碼。Lisp,Python和Smalltalk這些就缺少了偉大語言好像應該擁有的催化劑。Ruby是一種好語言,和催化劑(Rails)提供了引人注目得新價值(以效率的角度)以及還在飛速地增長。Ruby不一定是最好的語言,但是它將是我所見過最有可能的。Ruby不大可能在委員會那里超過Java.它很有可能首先在一個更小但是卻重要的環境中取得好成績。這個環境也就是一個有web UI大的胖關系數據庫。
??? 2,是否Rails就意味著Ruby?其他語言包括Java難道就不能實現同樣的思想?
??? 如今,Rails就是超過象Netscape之類語言的催化劑,具有Java一樣的功能,可通過網絡實現應用的傳送。但是我認為Rails很有可能僅僅是Ruby元編程框架浪潮的第一波。
??? 3,你的書中很多都基于典型的“將一個web接口連接到數據庫”場景,Ruby的成功案例看上去也僅僅是一兩個開發人員的小項目。但是你也承認了Java的重量級企業框架對一些項目的價值(即大型系統上的大型應用)。什么情況下一個項目對于RoR來說過于大的呢?如果一個RoR在那方面的特性發展緩慢呢?
??? 有Ruby和小團隊你可以做很多事情。基礎代碼幾乎都是一個人寫就的,但卻關乎整個公司的生計。在一些主要的公司開始進行認真的嘗試之前,我們不知道你可以利用ruby或者rails到什么程度。其中一個最吸引我的事情是經濟的規模,更小的規模。萬一生產力的數字是真實的呢?萬一確實可以得到5X的增長?那么你可以在一個部門內劃分工作,將工作劃分給團隊中的一個。交流將很少會成為問題。管理和疏忽也很少會成為問題了。我們都知道對于一間公司增長, tipping points意味著什么。因為增加溝通和管理的級別會產生很多的障礙, 所以一間公司增長要超過1,5,10,40,甚至100倍是很困難的。但是,在這一點上, Ruby on Rails的可擴展性是非常的好。
??? 4,你是否看到Java開發人員轉向Ruby嗎,還是Ruby將會給新一代的開發人員采用?
??? 我覺得兩者都有可能。有開發人員不能容忍學習servlets, Spring, XML, Hibernate, Struts 然后還要學習一些 UI 粘合的框架。在Rails中,他們將會完全給釋放出來。同時也有Java開發人員已經在尋找更加優勢的方法,他們發現了Ruby on Rails.接受了Rails的Java夢想家們的數目是令人驚愕的,他們有Thought Works,James Duncan Davidson,Stuart Halloway 更有 David Geary.
??? 5,難道Java本身就不能做一些事情來維持它的杰出地位?如果過于復雜和膨脹,什么可以阻止開發人員倒退到jdk 1.4?
??? Java將會繼續處于頂峰,并在企業應用上保持良好的表現,但是時間不會停滯不前。在某種意味上它終將會給替代。我們將需要一個更高級別的抽象。我認為我們最好的希望就是在JVM上做充足的投入,更好地支持動態語言, 擁抱新的事物,對于舊有的java代碼,則最好是保留保守的態度。
??? 6,我們應該期望Ruby在其他領域引起轟動?如果對于開發web應用它是如此不錯,假如Ruby有的可以使用的合適的UI框架,會不會在桌面應用也實用呢?
??? 現在說什么還為時過早。如今,盡管Ruby是有催化作用(Rails)的語言,但是它僅僅是一個候選。以后將會發生什么?我想誰也不知道。
??? James Duncan Davidson:嘗試新事務
??? 如果你使用Tomcat或者Ant(認真地說,什么Java開發人員什么使用過?)那么你就熟悉了James Duncan Davidson的工作了。在Sun,他致力把這些項目開源并且把他們捐獻給Apache基金會。并且他也編寫了Servlet API的最初兩個版本,還有處理XML的Java API.離開Sun之后,他做起了Mac OS 的X開發。編寫《Running Mac OS X Panther》和參與編寫了《Running Mac OS X Tiger》,《 Mac OS X Panther Hacks》,《 Cocoa in a Nutshell》和《Learning Cocoa with Objective-C, 2nd Edition》
??? 1,上一次我們見到你的時候,你還是那個《Mac desktop apps in Cocoa》家伙。而現在,我在你的blog上看到你已經深深地陷入了Rails.那是什么回事?
??? 我當時窮的要命和急切地需要錢。那時我剛剛買了一幢新房,并且抵押付款期限就快到期了――噢,等會,你想我認真點嗎?好吧,事實是我和我的幾個朋友已經一直在想一起工作一段時間了。當恰當的時機到的時候,我們給項目做了技術評估,Rails成了首選。那時我還沒用過Rails或者Ruby.但是我是不會讓小小的需要學習阻礙我去做那個項目的。今年我已經學習了三種,可能四種語言了。我不再相信一種語言可以做任何事了。如果我需要學習一些新知識去一些事情,我將全力以赴去學好它。
??? 2,你對Rails有什么看法?
??? 主要是簡單性。完成事情的容易程度。我做的那個應用的第一個項目原來是一個基于Java的web應用。每個人都知道一定會有一種更好,更快,更容易的方法的。Ruby一直都是一種好語言――并且是一種有趣的語言――因此建立于它之上的這個框架,它應得到關注。
??? 3,Ruby的晦澀和Rails的新穎對客戶來說會不會是一個問題?
??? 不全是。如今事實上恰恰相反。有太多潛在的工作, 缺并沒有足夠的人在真正地開發Ruby on Rails應用。
??? 4,為什么Ruby會如此特殊?難道Rails就不能在其他語言中實現?難道它就不能給Java實現?
??? 很少有其他語言可以完成Rails,或者像Rails那樣的。Java不在他們之列。Rails從Ruby中獲取了一些妙不可言的東西,嘗試用另一種語言復制它不僅是對Rails所做的是一個浪費,對其他語言來說也是一個浪費。但是它的概念一定會在其他非常動態的,動態類型語言中得到很好的應用。
??? 確實,我很興奮的看到其他項目正實現一些從Rails衍生的主意到其他平臺中。例如作為一個Python里的Rails版本,Django得到了一些固定的發展。但是,實際上它是Python自己的龐然大物,它如何成長將會非常有趣。
??? 現在,我已經說過了你不能用Java來實現Rails.但卻并不意味著你不能用Java做一些同樣優秀的東西。Java的力量可以以一種有趣的,神奇方式應用到一種全新的框架上。只是還沒人做那些事情。每個人都對J2EE這個糕點趨之若騖,以致于沒人以一種更加激烈,更加動態的方式來重新考慮問題。盡管有人提出一個基于Java的殺手級的框架可以與Rails做同樣多工作, 它一定也不能做的象Rails一樣。
??? 5,具有良好設計的Java應用能夠很好地支持特性的擴展――設計好你的類和包,那么你的心情將舒暢好長的一段時間。能否有團隊編寫出一個真正大型的Ruby應用?它是否具可維護性?或者還是RoR只能小打小鬧?
??? 設計良好的應用無論是以何種語言編寫的都能夠很好地支持特性的擴展。糟糕的設計無論是何種語言就不能了。同時也有了如何才是大型應用的定義的問題。我用Ruby寫的第一個rails應用部署到生產也不夠5,000行代碼,但是我之前用其他語言編寫的同樣大小的應用卻達到了50,000行代碼,所以如何定義大型是個問題。
??? 有團隊可以編寫一個可以支持大量特性,運行良好,時間上具備可維護性的Ruby on rails應用嗎?是的,毫無疑問。在使用了Ruby on Rails一段時間后,我將有信心用Rails解決任何尺寸的web應用問題。但是,那是因為我在它上面花費了一些時間,認識到編寫一個具有良好設計的應用是有可能的。
??? 也就是說,很有可能現在正有幾十個垃圾的Ruby on rails應用在編寫中。幾百或者幾千個都有可能。如果你不知道你正在做什么,你將會編寫一個垃圾的應用。
??? 6,那么我們回到了web應用,你可以在桌面上使用ruby,或者我們是否一直要用C#,面向對象C還是OS服務商支持的語言編寫UI?
??? 嗯,我的生活的一部分就是回到web應用。它對我來說是一個很好的還環境,因為自從1994年開始我就一直在做基于web的工作。但是現在我將開發基于桌面的應用。而且人們對桌面應用的需求還很大。我可不想要一個網絡的office.你也不想把一些象Aperture的東西建造成一個web應用吧。
??? 你現在可以使用Ruby去建造一個引人注目的桌面應用嗎?不,相關的工具包還不存在。但是如果存在了恰當的工具包――這是有可能的。那就沒有什么東西可以阻止它成為一個好的桌面應用語言了。那是說,我已經發現利用平臺的最好的方法就是盡量的本地化-貼近平臺,不管它是一個操作系統或者還是一個web應用框架。當我在Mac上的桌面工作,我需要寫面向對象C和Cocoa.當我用Rails的web工作,那意味著使用Ruby.而對于操作系統方面的工作,我需要用到C和shell.在這個討論中不會只有唯一的答案。
??? 我認為這就是最近對Ruby on Rails關注和屏棄以有色Java眼鏡看待世界的真正勝利。Ruby并不會成為下一個Java,完全不。而是Ruby on Rails將會幫助打破了這樣的一個觀點—— “只有一個正確的方法”,不是的。解決問題的方法有千百條。真正的,他們中沒有一個是明顯的勝者。只有解決方案有優勢的位置。
??? 我想就像我們在其中工作,吃飯和居住的建筑物一樣。一些建筑物最好是用水泥和鋼筋筑造。其他的是用砌筑。還有其他的最好是用木材,并且那樣做是有理由的。沒有人會跳起來說“所有的建筑物一定要用磚頭筑造!”,那樣太愚蠢了!同樣的道理,不是所以的應用都應該要用Ruby on Rails或者Django或者J2EE或者Perl來編寫的。對于任何一個特定的工作都有大量的工具。還有新的工具等待去發掘呢。訣竅就是決定最優秀的那個。
??? 讓我們從夸夸其談回到你的問題:在web應用的范疇,很容易出現一個新的框架的,因為你并不是與視頻卡,GUI和應用在上面跑的整個系統之類打交道。除非是你愿意開發一個自己的框架,你必須面對選擇使用哪個框架的選擇。在桌面上也是同樣的道理。你可以創建你自己的框架,做任何你想要的做的,但是該建議卻不比你自己為web創建一個新框架容易。
??? Robert Cooper:帶上利器
??? ONJava的博客Robert Cooper最近在他的blog上撰寫了“It's the End of the World as We Know It."”來回應一些“Java時代末日”的言論。Cooper是亞特蘭大地區致力于企業集成和web/web服務應用的J2EE開發人員,同時也是資訊和娛樂站點screaming-penguin.com的經營者。
??? 1,你曾經說過“長期以來‘企業級’Java一直未能逃脫的一個很悲哀的事實是500個應用才需要‘企業級’的功能。”為什么Java開發人員采用了比他們實際需要更加復雜的框架?
??? 好的,有一些因素導致了這樣現象。一個是“buzzword compliance”。 你想使用你“應該”要使用的東西。我記得在99年一些大項目采用了entity bean作為數據模型,但是我們很快發現了性能是如此的恐怖以致于我們最終又轉到了手寫的DAO層。
??? 最近對javax.persistence的修改,一定程度也表明了,EJB的失敗一直都是缺乏不同級別的支持。理想的情況是,如果我僅需要基本的,簡單的ORM-類型功能,我就能夠很快的得到。如果我想深入真正復雜的東西,給我一個“更深層”的有分布式事務的視圖。然而,盡管在那樣高的層次上,在EJB1.1/1.2的世界里,看看你需要多少行代碼1)從JNDI獲得Home存根,2)做一個find by,3)做改變,4)提交事務。對于一般的應用,答案沒有理由只應獨一無‘二’。然而更新穎的Java框架(閱讀:Spring + Hibernate)使你獲得了那一個‘二’,但是你也要做一大堆的配置。那樣,很多方面, 混淆了你的代碼。大量的因素促成我我的演說“擁有一個有效的默認配置/操作”,但是那是不同的故事了。
??? 2,你一向不屑把Ruby on Rails看作是technorati中的后起之秀。你是根本就不想接受還是只是厭惡這種夸夸其談呢?
??? 并不是我真的這樣不屑。Rails在很多重要的方面來說非常優秀。事實上,如果PHP是那要死的飛行意粉怪,并且要給Ruby替代,我想那將是一個大進步。然而,盡管Ruby確實掃除了過去的錯誤,它仍然缺乏Java那么多的功能,但是Ruby為快速開發提供了一個引人注目新的開發模型。你可能反對,這僅僅是時間問題,假以時日,它一定可以的。然而,我對Ruby/Rails有一些敵意, 是因為我一些一直都在希望java能夠擁有的特性,一直長期希望J2EE能夠擁有簡單性。
??? 3,那么是什么促使你繼續留在Java陣營,你看中它的什么呢?
??? 按照我日常的工作,在400上沒有必要使用Ruby調用PCML/RPG程序。同樣,那些大量的java擁有的 “企業級”特性很重要,更不用說它是一個統一的打包和部署的框架。
??? 4,你說“然而,Java像是變成了無所不包的了,它不是‘web的語言’,也不是桌面應用的‘一等公民’。”Java是否應該放棄一些野心,專注在一個更小應用空間集合里?如果那樣,放棄哪個?
??? 你也知道,我在我的站點上和一個紳士有很長的討論。他指出了Java在J2ME世界的成功,TME/TiVo,置頂盒——或許是下一代DVD的混戰。這些對于開發來說都是有效的領域,但是我認為如果Java變成了這樣的一個系統,那將是一個損失。
??? 使我惱怒的是Java發明的“applet”,你看看Flash(加上Flex/Laszlo),它的“Cool”(快速的用戶體驗)和“強大的”(我免費的得到數據綁定/SOAP/XML-RPC等等)使applets無地自容。“強大的”缺不需要JRE的事實立馬扼殺了applet的生存空間,如果有人能以接近數目的代碼行數向我展示Laszlo Dashboard demo,我可能已經在那個方面得到了一個核心發展。Cool是需要很大的代價的。
??? 5,Java越來越多的復雜性,越來越多的競爭框架,這是你之前批評很多次的。我們用JDK 1.2的語義編碼,手工編寫servlet是不是更好呢?
??? 我認為的復雜性是很難管理。例如,如果你從一個VB背景開始關注使用Swing,那是非常令人難受的。當你需要做一些技巧性的東西,沒有一個“簡單”接口可以Cast成更加“高級”的接口。坦白的說,最近出現的一個有用的東西就是JAX-P了。在我頭腦中,還有一些東西jre沒有的,但是卻是必須存在的。Swing存在有多久了?然而還沒有東西可以給你像VB5的數據綁定表格控制的功能。
??? 我想JDK 1.5的提升是非常顯著的。當我談到“降低復雜性”,我真正指的是A)對于一件事情, 給予更多標準的途徑來完成,因此如果我真正需要一些不同的特性,我僅僅需要一個外部的框架。B)設計更加友好的API――認真的說,看看JavaMail的JavaDoc,看看研究出如何發送一個HTML格式的email要花費你多長時間。C)增加更加通用的功能到核心運行環境,提供一個可以分別與基于Flash,RIA和桌面領域的Avalon/Sparkle相比擬的風格和性能。同樣的,我記得以前天真地從VB/VC++轉到Java世界,想道“天哪,本來一直就應該是這樣的。”幾年前,我不能說我看到任何增加到Java的東西都是和我有同樣的想法(除了將要來臨的JAX-WS API)。看看Rails,你會有同樣的感覺。看看Flex你也有同樣的感覺。看看Avalon你也有同樣的感覺。不是我不喜歡Ruby,只是看上去Java不再可以與時俱進讓我很沮喪。
??? Bill Venners:發行人的觀點
??? Artima是很多Java開發人員高度關注的站點。長久以來它的發行人Bill Venners是一個Java著作者和顧問。同時也是一個JavaWorld的專欄作家,Inside the Java Virtual Machine的作者。所以,當我們注意到Artima上的Ruby內容,我們必須找出背后的故事。
??? 1,Artima在很多人眼中一直都是作為一個Java站點,但是你剛創建一個一個新的Ruby版面, Artima當今很多的特色文章都是關于Ruby的,是什么促使了這種改變?
??? 沒有改變。Artima曾經是一個清一色的Java站點,但是幾年前我們擴展了更一般開發焦點,開始涵蓋其他語言。例如,我們開始在“Python Buzz”集成Python Blogs,在“The C++ Source”刊發C++文章。我們創建了Ruby Code & Style簡報來作為Ruby社區通過高質量,編輯的文章分享信息的地方。
??? 2,你是否認為你的Ruby報道是作為一種趨勢,或者服務已作出改變的開發人員?
??? 我們創建Ruby簡報僅僅是為了服務Ruby社區。我不知道是不是有一個趨勢,我也沒有看到很多Java開發人員轉到Ruby.人們并不只是僅僅需要用一種語言編程。我想掌握一種系統語言是有好處的,例如Java或者C++,和一種腳本語言,例如Ruby或者Python,而且能夠用兩者工作。那樣的話你就可以使用你手中最好的工具來工作了。
??? 3,你的最初少數Ruby文章幾乎沒有涉及Rails.你是否認為Rails背后有一個大的Ruby故事?你還知道有什么東西使用了Ruby?
??? 除了知道Rails在市場上很有賣點,我對Rails了解的不多。Rails商人一遍又一遍傳遞了這樣的一個信息,就是Rails能夠助你很快的創建web應用。每個人都很清楚的收到了這個信息。我認為這是一個非常好營銷工作。我也相信這個信息,但是快速的創建一個web應用不僅僅是人們所關心的。有時人們也關系與數據庫的集成,應用服務器的集群,在這種情形,其他工具可能比Rails更有效率。就Ruby而言,我認為它是一種適合腳本和創建系統的多用途的編程語言,與Python同種類別。
??? 4,即使在Rails以前,對比于其他“敏捷”語言,人們都談論到Ruby獨特的吸引Java開發人員。你認為Ruby有什么特別之處呢?為什么它對于Java移民這么好?
??? 我不相信將會有很多Java移民或者Ruby尤其適合Java程序員。現在大肆宣稱圍繞著Ruby,或許是因為Rails的買賣,所以或許你印象中的移民就是來自于那些宣稱的印象。Ruby是一種好的語言,但是Java也是,Python也是。
??? 5,你是否認為我們將會看到很多Java開發人員開始學習Ruby或者轉到Ruby,或者我們將看到一個新一代直接跳過Java而用Ruby代之?
??? Java不會離我們而去。在Artima,我們選擇了Java作為新的體系架構,而不是Ruby,或者Python,就是因為它是一個成熟的擁有免費和商用的大量工具和API的生態系統。相對于Java,是的,當使用Ruby或者Python編程的時候是有一些速度的提升,但是有了現代的像IntelliJ,Eclipse和NetBeans的 Java IDE,你可以在Java里走的更快。但是用Ruby編程是很愜意的,同時,如果有人可以從Ruby中找到他們的職業生涯,那么請全力以赴去實現。
??? 結語
??? 是否Ruby將橫掃Java?不僅僅是虔誠的Ruby狂熱者在預言這個場景。開發人員的需要觀點, 就像Venners提出的“手上對工作最優的工具”。 至關重要的是,開發人員必須對正確理解和使用這些工具負責。也就不難看出Coopper對于EJB 1.0的大肆宣稱的記憶和Davidson的預言“如今很有可能有很多垃圾的Ruby on Rails應用在編寫中”的聯系了。無視技術,讓市場的浪潮沖走是很危險的。不僅如此,很多人正在告訴我們使用Ruby會有相當大的效率提升,它確實是一個理想的工具,因此我們應該給予一定的關注。
??? 作者感謝Bruce Tate, James Duncan Davidson, Robert Cooper, 和 Bill Venners.感謝占用了他們的時間與ONJava的讀者分享他們的思想。
??? Chris Adamson是ONJava和Java.net的編輯,專攻Java,Mac Os X和多媒體開發的亞特蘭大地區的顧問。
版權聲明:任何獲得Matrix授權的網站,轉載時請務必保留以下作者信息和鏈接
原文:
http://www.onjava.com/pub/a/onjava/2005/11/16/ruby-the-rival.html
譯文:
http://www.matrix.org.cn/resource/article/44/44288_Ruby+Java.html
當JAVA程序違反了JAVA的語義規則時,JAVA虛擬機就會將發生的錯誤表示為一個異常。違反語義規則包括2種情況。一種是JAVA類庫內置的語義檢查。例如數組下標越界,會引發IndexOutOfBoundsException;訪問null的對象時會引發NullPointerException.另一種情況就是JAVA允許程序員擴展這種語義檢查,程序員可以創建自己的異常,并自由選擇在何時用throw關鍵字引發異常。所有的異常都是java.lang.Thowable的子類。
2. Java的接口和C++的虛類的相同和不同處。
由于Java不支持多繼承,而有可能某個類或對象要使用分別在幾個類或對象里面的方法或屬性,現有的單繼承機制就不能滿足要求。與繼承相比,接口有更高的靈活性,因為接口中沒有任何實現代碼。當一個類實現了接口以后,該類要實現接口里面所有的方法和屬性,并且接口里面的屬性在默認狀態下面都是public static,所有方法默認情況下是public.一個類可以實現多個接口。
3. 垃圾回收的優點和原理。并考慮2種回收機制。
Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候不再需要考慮內存管理。由于有個垃圾回收機制,Java中的對象不再有“作用域”的概念,只有對象的引用才有“作用域”。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作為一個單獨的低級別的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。回收機制有分代復制垃圾回收和標記垃圾回收,增量垃圾回收。
4. 請說出你所知道的線程同步的方法。
wait():使一個線程處于等待狀態,并且釋放所持有的對象的lock.
sleep():使一個正在運行的線程處于睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處于等待狀態的線程,注意的是在調用此方法的時候,并不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
Allnotity():喚醒所有處入等待狀態的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
5. 請講一講析構函數和虛函數的用法和作用。
6. Error與Exception有什么區別?
Error表示系統級的錯誤和程序不必處理的異常,
Exception表示需要捕捉或者需要程序進行處理的異常。
7. 在java中一個類被聲明為final類型,表示了什么意思?
表示該類不能被繼承,是頂級類。
8. 描述一下你最常用的編程風格。
9. heap和stack有什么區別。
棧是一種線形集合,其添加和刪除元素的操作應在同一段完成。棧按照后進先出的方式進行處理。
堆是棧的一個組成元素
10. 如果系統要使用超大整數(超過long長度范圍),請你設計一個數據結構來存儲這種超大型數字以及設計一種算法來實現超大整數加法運算)。
public class B{
int[] ArrOne = new ArrOne[1000];
String intString="";
public int[] Arr(String s)
{
intString = s;
for(int i=0;i {
11. 如果要設計一個圖形系統,請你設計基本的圖形元件(Point,Line,Rectangle,Triangle)的簡單實現
12,談談final, finally, finalize的區別。
final?修飾符(關鍵字)如果一個類被聲明為final,意味著它不能再派生出新的子類,不能作為父類被繼承。因此一個類不能既被聲明為 abstract的,又被聲明為final的。將變量或方法聲明為final,可以保證它們在使用中不被改變。被聲明為final的變量必須在聲明時給定初值,而在以后的引用中只能讀取,不可修改。被聲明為final的方法也同樣只能使用,不能重載。
finally?再異常處理時提供 finally 塊來執行任何清除操作。如果拋出一個異常,那么相匹配的 catch 子句就會執行,然后控制就會進入 finally 塊(如果有的話)。
finalize?方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在 Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除對象之前對這個對象調用的。
13,Anonymous Inner Class (匿名內部類) 是否可以extends(繼承)其它類,是否可以implements(實現)interface(接口)?
匿名的內部類是沒有名字的內部類。不能extends(繼承) 其它類,但一個內部類可以作為一個接口,由另一個內部類實現。
14,Static Nested Class 和 Inner Class的不同,說得越多越好(面試題有的很籠統)。
Nested Class (一般是C++的說法),Inner Class (一般是JAVA的說法)。Java內部類與C++嵌套類最大的不同就在于是否有指向外部的引用上。具體可見http: //www.frontfree.net/articles/services/view.ASP?id=704&page=1
注: 靜態內部類(Inner Class)意味著1創建一個static內部類的對象,不需要一個外部類對象,2不能從一個static內部類的一個對象訪問一個外部類對象
第四,&和&&的區別。
&是位運算符。&&是布爾邏輯運算符。
15,HashMap和Hashtable的區別。
都屬于Map接口的類,實現了將惟一鍵映射到特定的值上。
HashMap 類沒有分類或者排序。它允許一個 null 鍵和多個 null 值。
Hashtable 類似于 HashMap,但是不允許 null 鍵和 null 值。它也比 HashMap 慢,因為它是同步的。
16,Collection 和 Collections的區別。
Collections是個java.util下的類,它包含有各種有關集合操作的靜態方法。
Collection是個java.util下的接口,它是各種集合結構的父接口。
17,什么時候用assert.
斷言是一個包含布爾表達式的語句,在執行這個語句時假定該表達式為 true.如果表達式計算為 false,那么系統會報告一個 Assertionerror.它用于調試目的:
assert(a > 0); // throws an Assertionerror if a <= 0
斷言可以有兩種形式:
assert Expression1 ;
assert Expression1 : Expression2 ;
Expression1 應該總是產生一個布爾值。
Expression2 可以是得出一個值的任意表達式。這個值用于生成顯示更多調試信息的 String 消息。
斷言在默認情況下是禁用的。要在編譯時啟用斷言,需要使用 source 1.4 標記:
javac -source 1.4 Test.java
要在運行時啟用斷言,可使用 -enableassertions 或者 -ea 標記。
要在運行時選擇禁用斷言,可使用 -da 或者 -disableassertions 標記。
要系統類中啟用斷言,可使用 -esa 或者 -dsa 標記。還可以在包的基礎上啟用或者禁用斷言。
可以在預計正常情況下不會到達的任何位置上放置斷言。斷言可以用于驗證傳遞給私有方法的參數。不過,斷言不應該用于驗證傳遞給公有方法的參數,因為不管是否啟用了斷言,公有方法都必須檢查其參數。不過,既可以在公有方法中,也可以在非公有方法中利用斷言測試后置條件。另外,斷言不應該以任何方式改變程序的狀態。
18,GC是什么? 為什么要有GC? (基礎)。
GC是垃圾收集器。Java 程序員不用擔心內存管理,因為垃圾收集器會自動進行管理。要請求垃圾收集,可以調用下面的方法之一:
System.gc()
Runtime.getRuntime()。gc()
19,String s = new String("xyz");創建了幾個String Object?
兩個對象,一個是“xyx”,一個是指向“xyx”的引用對象s.
20,Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
Math.round(11.5)返回(long)12,Math.round(-11.5)返回(long)-11;
21,short s1 = 1; s1 = s1 + 1;有什么錯? short s1 = 1; s1 += 1;有什么錯?
short s1 = 1; s1 = s1 + 1;有錯,s1是short型,s1+1是int型,不能顯式轉化為short型。可修改為s1 =(short)(s1 + 1) .short s1 = 1; s1 += 1正確。
22,sleep() 和 wait() 有什么區別? 搞線程的最愛
sleep()方法是使線程停止一段時間的方法。在sleep 時間間隔期滿后,線程不一定立即恢復執行。這是因為在那個時刻,其它線程可能正在運行而且沒有被調度為放棄執行,除非(a)“醒來”的線程具有更高的優先級 (b)正在運行的線程因為其它原因而阻塞。
wait()是線程交互時,如果線程對一個同步對象x 發出一個wait()調用,該線程會暫停執行,被調對象進入等待狀態,直到被喚醒或等待時間到。
23,Java有沒有goto?
Goto?java中的保留字,現在沒有在java中使用。
24,數組有沒有length()這個方法? String有沒有length()這個方法?
數組沒有length()這個方法,有length的屬性。
String有有length()這個方法。
25,Overload和Override的區別。Overloaded的方法是否可以改變返回值的類型?
方法的重寫Overriding和重載Overloading是Java多態性的不同表現。重寫Overriding是父類與子類之間多態性的一種表現,重載Overloading是一個類中多態性的一種表現。如果在子類中定義某方法與其父類有相同的名稱和參數,我們說該方法被重寫 (Overriding)。子類的對象使用這個方法時,將調用子類中的定義,對它而言,父類中的定義如同被“屏蔽”了。如果在一個類中定義了多個同名的方法,它們或有不同的參數個數或有不同的參數類型,則稱為方法的重載(Overloading)。Overloaded的方法是可以改變返回值的類型。
26,Set里的元素是不能重復的,那么用什么方法來區分重復與否呢? 是用==還是equals()? 它們有何區別?
Set里的元素是不能重復的,那么用iterator()方法來區分重復與否。equals()是判讀兩個Set是否相等。
equals()和==方法決定引用值是否指向同一對象equals()在類中被覆蓋,為的是當兩個分離的對象的內容和類型相配的話,返回真值。
第二,Anonymous Inner Class (匿名內部類) 是否可以extends(繼承)其它類,是否可以implements(實現)interface(接口)?
匿名的內部類是沒有名字的內部類。不能extends(繼承) 其它類,但一個內部類可以作為一個接口,由另一個內部類實現。
第三,Static Nested Class 和 Inner Class的不同,說得越多越好(面試題有的很籠統)。
Nested Class (一般是C++的說法),Inner Class (一般是JAVA的說法)。Java內部類與C++嵌套類最大的不同就在于是否有指向外部的引用上。具體可見http: //www.frontfree.net/articles/services/view.asp?id=704&page=1
注: 靜態內部類(Inner Class)意味著1創建一個static內部類的對象,不需要一個外部類對象,2不能從一個static內部類的一個對象訪問一個外部類對象
第四,&和&&的區別。
&是位運算符。&&是布爾邏輯運算符。
第五,HashMap和Hashtable的區別。
都屬于Map接口的類,實現了將惟一鍵映射到特定的值上。
HashMap 類沒有分類或者排序。它允許一個 null 鍵和多個 null 值。
Hashtable 類似于 HashMap,但是不允許 null 鍵和 null 值。它也比 HashMap 慢,因為它是同步的。
第六,Collection 和 Collections的區別。
Collections是個java.util下的類,它包含有各種有關集合操作的靜態方法。
Collection是個java.util下的接口,它是各種集合結構的父接口。
第七,什么時候用assert。
斷言是一個包含布爾表達式的語句,在執行這個語句時假定該表達式為 true。如果表達式計算為 false,那么系統會報告一個 AssertionError。它用于調試目的:
assert(a > 0); // throws an AssertionError if a <= 0
斷言可以有兩種形式:
assert Expression1 ;
assert Expression1 : Expression2 ;
Expression1 應該總是產生一個布爾值。
Expression2 可以是得出一個值的任意表達式。這個值用于生成顯示更多調試信息的 String 消息。
斷言在默認情況下是禁用的。要在編譯時啟用斷言,需要使用 source 1.4 標記:
javac -source 1.4 Test.java
要在運行時啟用斷言,可使用 -enableassertions 或者 -ea 標記。
要在運行時選擇禁用斷言,可使用 -da 或者 -disableassertions 標記。
要系統類中啟用斷言,可使用 -esa 或者 -dsa 標記。還可以在包的基礎上啟用或者禁用斷言。
可以在預計正常情況下不會到達的任何位置上放置斷言。斷言可以用于驗證傳遞給私有方法的參數。不過,斷言不應該用于驗證傳遞給公有方法的參數,因為不管是否啟用了斷言,公有方法都必須檢查其參數。不過,既可以在公有方法中,也可以在非公有方法中利用斷言測試后置條件。另外,斷言不應該以任何方式改變程序的狀態。
第八,GC是什么? 為什么要有GC? (基礎)。
GC是垃圾收集器。Java 程序員不用擔心內存管理,因為垃圾收集器會自動進行管理。要請求垃圾收集,可以調用下面的方法之一:
System.gc()
Runtime.getRuntime().gc()
第九,String s = new String("xyz");創建了幾個String Object?
兩個對象,一個是“xyx”,一個是指向“xyx”的引用對象s。
第十,Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
Math.round(11.5)返回(long)12,Math.round(-11.5)返回(long)-11;
第二十一,abstract的method是否可同時是static,是否可同時是native,是否可同時是synchronized?
都不能
第二十二,接口是否可繼承接口? 抽象類是否可實現(implements)接口? 抽象類是否可繼承實體類(concrete class)?
接口可以繼承接口。抽象類可以實現(implements)接口,抽象類是否可繼承實體類,但前提是實體類必須有明確的構造函數。
第二十三,啟動一個線程是用run()還是start()?
啟動一個線程是調用start()方法,使線程所代表的虛擬處理機處于可運行狀態,這意味著它可以由JVM調度并執行。這并不意味著線程就會立即運行。run()方法可以產生必須退出的標志來停止一個線程。
第二十四,構造器Constructor是否可被override?
構造器Constructor不能被繼承,因此不能重寫Overriding,但可以被重載Overloading。
第二十五,是否可以繼承String類?
String類是final類故不可以繼承。
第二十六,當一個線程進入一個對象的一個synchronized方法后,其它線程是否可進入此對象的其它方法?
不能,一個對象的一個synchronized方法只能由一個線程訪問。
第二十七,try {}里有一個return語句,那么緊跟在這個try后的finally {}里的code會不會被執行,什么時候被執行,在return前還是后?
會執行,在return前執行。
第二十八,編程題: 用最有效率的方法算出2乘以8等於幾?
有C背景的程序員特別喜歡問這種問題。
2 << 3
第二十九,兩個對象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?
不對,有相同的hash code。
第三十,當一個對象被當作參數傳遞到一個方法后,此方法可改變這個對象的屬性,并可返回變化后的結果,那么這里到底是值傳遞還是引用傳遞?
是值傳遞。Java 編程語言只由值傳遞參數。當一個對象實例作為一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的內容可以在被調用的方法中改變,但對象的引用是永遠不會改變的。
第三十一,swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?
switch(expr1)中,expr1是一個整數表達式。因此傳遞給 switch 和 case 語句的參數應該是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。
第三十二,編程題: 寫一個Singleton出來。
Singleton模式主要作用是保證在Java應用程序中,一個類Class只有一個實例存在。
一般Singleton模式通常有幾種種形式:
第一種形式: 定義一個類,它的構造函數為private的,它有一個static的private的該類變量,在類初始化時實例話,通過一個public的getInstance方法獲取對它的引用,繼而調用其中的方法。
public class Singleton {
private Singleton(){}
//在自己內部定義自己一個實例,是不是很奇怪?
//注意這是private 只供內部調用
private static Singleton instance = new Singleton();
//這里提供了一個供外部訪問本class的靜態方法,可以直接訪問
public static Singleton getInstance() {
return instance;
}
}
第二種形式:
public class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
//這個方法比上面有所改進,不用每次都進行生成對象,只是第一次
//使用時生成實例,提高了效率!
if (instance==null)
instance=new Singleton();
return instance; }
}