|
2010年10月8日
現有兩個Activity: Activity1,Activity2
先啟動Activity1運行順序為: Activity1 onCreate -> Activity1 onStart -> Activity1 onResume 用Intent從Activity1跳到Activity2運行順序 : Activity1 onPause -> Activity2 onCreate -> Activity2 onStart -> Activity2 onResume ->Activity1 onStop -> Activity1 onDestroy 退出應用程序: Activity2 onResume ->Activity2 onStop -> Activity2 onDestroy
本程序可以控制3個線程按順序執行, 代碼如下:
public class Test3 {
public static void main(String[] args) throws IOException { final Test obj = new Test(); new Thread() { public void run() { obj.m1(); } }.start(); new Thread() { public void run() { obj.m2(); } }.start(); new Thread() { public void run() { obj.m3(); } }.start(); }
}
class Test { static int count; volatile int target = 1; synchronized void m1() { for (int i = 0; i < 10; i++) { while (target == 2 || target == 3) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("m1() =" + i); target = 2; notifyAll(); } } synchronized void m2() { for (int i = 0; i < 10; i++) { while (target == 1 || target == 3) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("m2() =" + i); target = 3; notifyAll(); } } synchronized void m3() { for (int i = 0; i < 10; i++) { while (target == 1 || target == 2) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("m3() =" + i); target = 1; notifyAll(); } } }
摘要: 線程的同步與共享
前面程序中的線程都是獨立的、異步執行的線程。但在很多情況下,多個線程需要共享數據資源,這就涉及到線程的同步與資源共享的問題。
1 資源沖突
下面的例子說明,多個線程共享資源,如果不加以控制可能會產生沖突。
程序CounterTest.java
Code highlighting produced by Actipro CodeHighlight... 閱讀全文
1,線程的生命周期
線程從創建、運行到結束總是處于下面五個狀態之一:新建狀態、就緒狀態、運行狀態、阻塞狀態及死亡狀態。

1.新建狀態(New): 當用new操作符創建一個線程時, 例如new Thread(r),線程還沒有開始運行,此時線程處在新建狀態。 當一個線程處于新生狀態時,程序還沒有開始運行線程中的代碼
2.就緒狀態(Runnable)
一個新創建的線程并不自動開始運行,要執行線程,必須調用線程的start()方法。當線程對象調用start()方法即啟動了線程,start()方法創建線程運行的系統資源,并調度線程運行run()方法。當start()方法返回后,線程就處于就緒狀態。
處于就緒狀態的線程并不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運行線程。因為在單CPU的計算機系統中,不可能同時運行多個線程,一個時刻僅有一個線程處于運行狀態。因此此時可能有多個線程處于就緒狀態。對多個處于就緒狀態的線程是由Java運行時系統的線程調度程序(thread scheduler)來調度的。
3.運行狀態(Running)
當線程獲得CPU時間后,它才進入運行狀態,真正開始執行run()方法.
4. 阻塞狀態(Blocked)
線程運行過程中,可能由于各種原因進入阻塞狀態: 1>線程通過調用sleep方法進入睡眠狀態; 2>線程調用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調用者; 3>線程試圖得到一個鎖,而該鎖正被其他線程持有; 4>線程在等待某個觸發條件; ......
所謂阻塞狀態是正在運行的線程沒有運行結束,暫時讓出CPU,這時其他處于就緒狀態的線程就可以獲得CPU時間,進入運行狀態。
5. 死亡狀態(Dead)
有兩個原因會導致線程死亡: 1) run方法正常退出而自然死亡, 2) 一個未捕獲的異常終止了run方法而使線程猝死。 為了確定線程在當前是否存活著(就是要么是可運行的,要么是被阻塞了),需要使用isAlive方法。如果是可運行或被阻塞,這個方法返回true; 如果線程仍舊是new狀態且不是可運行的, 或者線程死亡了,則返回false.
2, 線程的優先級和調度
Java的每個線程都有一個優先級,當有多個線程處于就緒狀態時,線程調度程序根據線程的優先級調度線程運行。
可以用下面方法設置和返回線程的優先級。
· public final void setPriority(int newPriority) 設置線程的優先級。
· public final int getPriority() 返回線程的優先級。
newPriority為線程的優先級,其取值為1到10之間的整數,也可以使用Thread類定義的常量來設置線程的優先級,這些常量分別為:Thread.MIN_PRIORITY、Thread.NORM_PRIORITY、Thread.MAX_PRIORITY,它們分別對應于線程優先級的1、5和10,數值越大優先級越高。當創建Java線程時,如果沒有指定它的優先級,則它從創建該線程那里繼承優先級。
一般來說,只有在當前線程停止或由于某種原因被阻塞,較低優先級的線程才有機會運行。
前面說過多個線程可并發運行,然而實際上并不總是這樣。由于很多計算機都是單CPU的,所以一個時刻只能有一個線程運行,多個線程的并發運行只是幻覺。在單CPU機器上多個線程的執行是按照某種順序執行的,這稱為線程的調度(scheduling)。
大多數計算機僅有一個CPU,所以線程必須與其他線程共享CPU。多個線程在單個CPU是按照某種順序執行的。實際的調度策略隨系統的不同而不同,通常線程調度可以采用兩種策略調度處于就緒狀態的線程。
(1) 搶占式調度策略
Java運行時系統的線程調度算法是搶占式的 (preemptive)。Java運行時系統支持一種簡單的固定優先級的調度算法。如果一個優先級比其他任何處于可運行狀態的線程都高的線程進入就緒狀態,那么運行時系統就會選擇該線程運行。新的優先級較高的線程搶占(preempt)了其他線程。但是Java運行時系統并不搶占同優先級的線程。換句話說,Java運行時系統不是分時的(time-slice)。然而,基于Java Thread類的實現系統可能是支持分時的,因此編寫代碼時不要依賴分時。當系統中的處于就緒狀態的線程都具有相同優先級時,線程調度程序采用一種簡單的、非搶占式的輪轉的調度順序。
(2) 時間片輪轉調度策略
有些系統的線程調度采用時間片輪轉(round-robin)調度策略。這種調度策略是從所有處于就緒狀態的線程中選擇優先級最高的線程分配一定的CPU時間運行。該時間過后再選擇其他線程運行。只有當線程運行結束、放棄(yield)CPU或由于某種原因進入阻塞狀態,低優先級的線程才有機會執行。如果有兩個優先級相同的線程都在等待CPU,則調度程序以輪轉的方式選擇運行的線程。
3. 線程狀態的改變
一個線程在其生命周期中可以從一種狀態改變到另一種狀態,線程狀態的變遷如圖所示:
 1> 控制線程的啟動和結束
當一個新建的線程調用它的start()方法后即進入就緒狀態,處于就緒狀態的線程被線程調度程序選中就可以獲得CPU時間,進入運行狀態,該線程就開始運行run()方法。
控制線程的結束稍微復雜一點。如果線程的run()方法是一個確定次數的循環,則循環結束后,線程運行就結束了,線程對象即進入死亡狀態。如果run()方法是一個不確定循環,早期的方法是調用線程對象的stop()方法,然而由于該方法可能導致線程死鎖,因此從1.1版開始,不推薦使用該方法結束線程。一般是通過設置一個標志變量,在程序中改變標志變量的值實現結束線程。請看下面的例子:
程序 ThreadStop.java
import java.util.*;
class Timer implements Runnable{
boolean flag=true; public void run(){ while(flag){ System.out.print("\r\t"+new Date()+" "); try{ Thread.sleep(1000); }catch(InterruptedException e){} } System.out.println("\n"+Thread.currentThread().getName()+" Stop"); }
public void stopRun(){ flag = false; } }
public class ThreadStop{ public static void main(String args[]){ Timer timer = new Timer(); Thread thread = new Thread(timer); thread.setName("Timer"); thread.start();
for(int i=0;i<100;i++){ System.out.print("\r"+i); try{ Thread.sleep(100); }catch(InterruptedException e){} } timer.stopRun(); } }
該程序在Timer類中定義了一個布爾變量flag,同時定義了一個stopRun()方法,在其中將該變量設置為false。在主程序中通過調用該方法,從而改變該變量的值,使得run()方法的while循環條件不滿足,從而實現結束線程的運行。
說明 在Thread類中除了stop()方法被標注為不推薦(deprecated) 使用外,suspend()方法和resume()方法也被標明不推薦使用,這兩個方法原來用作線程的掛起和恢復.
2> 線程阻塞條件
處于運行狀態的線程除了可以進入死亡狀態外,還可能進入就緒狀態和阻塞狀態。下面分別討論這兩種情況:
(1) 運行狀態到就緒狀態
處于運行狀態的線程如果調用了yield()方法,那么它將放棄CPU時間,使當前正在運行的線程進入就緒狀態。這時有幾種可能的情況:如果沒有其他的線程處于就緒狀態等待運行,該線程會立即繼續運行;如果有等待的線程,此時線程回到就緒狀態狀態與其他線程競爭CPU時間,當有比該線程優先級高的線程時,高優先級的線程進入運行狀態,當沒有比該線程優先級高的線程時,但有同優先級的線程,則由線程調度程序來決定哪個線程進入運行狀態,因此線程調用yield()方法只能將CPU時間讓給具有同優先級的或高優先級的線程而不能讓給低優先級的線程。
一般來說,在調用線程的yield()方法可以使耗時的線程暫停執行一段時間,使其他線程有執行的機會。
(2) 運行狀態到阻塞狀態
有多種原因可使當前運行的線程進入阻塞狀態,進入阻塞狀態的線程當相應的事件結束或條件滿足時進入就緒狀態。使線程進入阻塞狀態可能有多種原因:
① 線程調用了sleep()方法,線程進入睡眠狀態,此時該線程停止執行一段時間。當時間到時該線程回到就緒狀態,與其他線程競爭CPU時間。
Thread類中定義了一個interrupt()方法。一個處于睡眠中的線程若調用了interrupt()方法,該線程立即結束睡眠進入就緒狀態。
② 如果一個線程的運行需要進行I/O操作,比如從鍵盤接收數據,這時程序可能需要等待用戶的輸入,這時如果該線程一直占用CPU,其他線程就得不到運行。這種情況稱為I/O阻塞。這時該線程就會離開運行狀態而進入阻塞狀態。Java語言的所有I/O方法都具有這種行為。
③ 有時要求當前線程的執行在另一個線程執行結束后再繼續執行,這時可以調用join()方法實現,join()方法有下面三種格式:
· public void join() throws InterruptedException 使當前線程暫停執行,等待調用該方法的線程結束后再執行當前線程。
· public void join(long millis) throws InterruptedException 最多等待millis毫秒后,當前線程繼續執行。
· public void join(long millis, int nanos) throws InterruptedException 可以指定多少毫秒、多少納秒后繼續執行當前線程。
上述方法使當前線程暫停執行,進入阻塞狀態,當調用線程結束或指定的時間過后,當前線程線程進入就緒狀態,例如執行下面代碼:
t.join();
將使當前線程進入阻塞狀態,當線程t執行結束后,當前線程才能繼續執行。
④ 線程調用了wait()方法,等待某個條件變量,此時該線程進入阻塞狀態。直到被通知(調用了notify()或notifyAll()方法)結束等待后,線程回到就緒狀態。
⑤ 另外如果線程不能獲得對象鎖,也進入就緒狀態。
后兩種情況在下一節討論。
好久沒搞這個了,今天把以前的筆記整理下,當復習。
Thread類和Runnable接口
多線程是一個程序中可以有多段代碼同時運行,那么這些代碼寫在哪里,如何創建線程對象呢?
首先,我們來看Java語言實現多線程編程的類和接口。在java.lang包中定義了Runnable接口和Thread類。
Runnable接口中只定義了一個方法:
· public abstract void run()
這個方法要由實現了Runnable接口的類實現。Runnable對象稱為可運行對象,一個線程的運行就是執行該對象的run()方法。
Thread類實現了Runnable接口,因此Thread對象也是可運行對象。同時Thread類也是線程類,該類的常用構造方法如下:
· public Thread()
· public Thread(Runnable target)
· public Thread(String name)
· public Thread(Runnable target, String name) target為線程運行的目標對象,即線程調用start()方法啟動后運行那個對象的run()方法,該對象的類型為Runnable,若沒有指定目標對象,則以當前類對象為目標對象,name為線程名
線程的創建
介紹下如何創建和運行線程的兩種方法。線程運行的代碼就是實現了Runnable接口的類的run()方法或者是Thread類的子類的run()方法,因此構造線程體就有兩種方法: · 繼承Thread類并覆蓋它的run()方法; · 實現Runnable接口并實現它的run()方法。
1,繼承Thread類創建線程
通過繼承Thread類,并覆蓋run()方法,這時就可以用該類的實例作為線程的目標對象。下面的程序定義了SimpleThread類,它繼承了Thread類并覆蓋了run()方法。
程序SimpleThread.java
public class SimpleThread extends Thread{
public SimpleThread(String str){
super(str);
}
public void run(){
for(int i=0; i<100; i++){
System.out.println(getName()+" = "+ i);
try{
sleep((int)(Math.random()*100));
}catch(InterruptedException e){}
}
System.out.println(getName()+ " DONE");
}
}
_____________________________________________________________________________▃
SimpleThread類繼承了Thread類,并覆蓋了run()方法,該方法就是線程體。
程序 ThreadTest.java
public class ThreadTest{
public static void main(String args[]){
Thread t1 = new SimpleThread("Runner A");
Thread t2 = new SimpleThread("Runner B");
t1.start();
t2.start();
}
}
_____________________________________________________________________________▃
在ThreadTest類的main()方法中創建了兩個SimpleThread類的線程對象并調用線程類的start()方法啟動線程。構造線程時沒有指定目標對象,所以線程啟動后執行本類的run()方法。
注意,實際上ThreadTest程序中有三個線程同時運行,在應用程序的main()方法啟動時,JVM就創建一個主線程,在主線程中可以創建其他線程。
2,實現Runnable接口創建線程
可以定義一個類實現Runnable接口,然后將該類對象作為線程的目標對象。實現Runnable接口就是實現run()方法。
下面程序通過實現Runnable接口構造線程體。
程序 ThreadTest.java
class T1 implements Runnable{
public void run(){
for(int i=0;i<15;i++)
System.out.println("Runner A="+i);
}
}
class T2 implements Runnable{
public void run(){
for(int j=0;j<15;j++)
System.out.println("Runner B="+j);
}
}
public class ThreadTest{
public static void main(String args[]){
Thread t1=new Thread(new T1(),"Thread A");
Thread t2=new Thread(new T2(),"Thread B");
t1.start();
t2.start();
}
}
_____________________________________________________________________________▃
1, R.java 是建立項目時自動生成的,只讀,用來定義該項目所有資源的索引文件。 這里面定義了很多常量, 名字與res文件夾的文件名和String.xml里的定義的常量名相同。當項目中加入了新的資源時,只需要刷新一下該項目,R.java 便自動生成了。 2, strings.xml 里面定義了字符串資源。 在類中可通過如下方式使用這些資源, Resource r = this.getContext().getResources(); String str = ((String) r.getString(R.string.name)); 在main.xml中可以 android:text="@string/name" 3, mail.xml 用來寫UI(布局,控件...) 主程序繼承Activity類,重寫了void onCreate(Bundle savedInstanceState)方法。 在方法里通過setContentView(R.layout.main)設置Activity要顯示的布局文件(\layout\main.xml) 4. AndroidManifest.xml 看下默認的:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.test" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name"> //應用程序的名字 <activity android:name=".WuActivity" //默認啟動哪個Activity android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
</application> </manifest>
用最新的就都用最新的, 不然有可能導致配置過程中出現一些問題。
我用SDK3.2和ADT 0.9.5配置, 結果Preferences->Android里設置路徑出現問題。
樹控件很適合用來顯示,導航和編輯結構化對象;但是JTree很復雜,swing中有整個一個包都是針對它的(javax.swing.tree),注意樹控件是顯示的,但是樹數據結構是一種集合接口的實現,就如同JList和java.util.List一樣,他們應用在不同層,當然你使用Jlist來顯示List接口的實現者那是很般配的。
*
**
關于樹的術語如根,節點,葉子,深度,路徑,平衡性,邊,子樹;不需要我這里過多的解釋,任何一本數據結構的書籍都會介紹他們。我這里主要是講述樹控件。
樹遍歷這個概念先提一下:遍歷即逐個訪問,對于樹而言主要有三種:前序,后序,中序遍歷樹的每個節點。遍歷一般是用遞歸算法來實現的,三種遍歷法區別于先訪問樹的那個部分。樹遍歷也是比較難的一個技術,不好掌握,我在大學時用匯編實現過這三種算法,到現在還記憶猶新(完全自己找到的規律),一下來和朋友們分享一下。
對于一個有兩個子節點的樹(或多個子節點),請你沿著樹的外圍畫一個輪廓線:
———> >——————
/ \
/ \
/_____>____\
這是大致繞樹行走的輪廓線,大家都知道(或許你還不知道)函數的調用時控制流的傳遞就是這個樣子的。(控制流是線程執行方法時的形象表述)比如一下函數: main(){
f1();
f2();
}//該函數的控制流向是:先傳給main,再由main()傳給f1,之后退回到mian(),在傳給f2()在由f2退回給main之后結束程序。異步方法調用時才會從這個封閉的輪廓中分出一個分支來。現在來談你如何設計一個樹遍歷方法:
我們來看一個函數的幾個關鍵部位,
func(){
entry入口處
中間部位
return出口處
}也許你很迷惑這與樹遍歷算法有和關系,告訴你吧這三個特殊部位就是你在設計遞歸時,遞歸函數應該出現的位置,他們出現在不同的位置就是不同的“序”,偽碼如:
先序遍歷
traversTree(Node root){
if(root !=null){
if(root.isLeaf()){//當是葉子時,
visit(root);//前序遍歷是先遍歷頁節點
}
Node[] children=root.getChildren();//獲取所有子樹
for(Node n:children){
traversTree(n);//遞歸遍歷所有子樹,注意子樹可能為空。
}
}
}
中序遍歷(亦稱廣度優先遍歷,總是先遍歷樹的根)
traversTree(Node root){
if(root !=null){
//樹非空
visit(root); //這是中序遍歷 visit出現與遞歸函數之前。
Node[] children=root.getChildren();//獲取所有子樹
for(Node n:children){
traversTree(n);//遞歸遍歷所有子樹,注意子樹可能為空。
}
}
}
后序遍歷(亦稱深度優先搜索):
traversTree(Node root){
if(root !=null){
Node[] children=root.getChildren();//獲取所有子樹
for(Node n:children){
traversTree(n);//遞歸遍歷所有子樹,注意子樹可能為空。
}
visit(root); //這是后序遍歷 visit出現在遞歸函數之后。
}
}
以上三個算法,可能有點不正確,我沒測試過,時間太久了有點忘了,總之為大家做個參考吧!
因為樹結構典型的是應用了組合設計模式,所以只要涉及到樹肯定涉及遍歷,和遞歸。所以這里羅嗦一下。 所有的樹都是節點
**
***
swing中Jtree處理樹結構是通過樹模型接口,它實現了TreeNode接口(在api文檔中竟看不到此信息!),DefaultMutableTreeNode類實現了TreeNode接口,并提供了前,中,后序的樹遍歷能力。
JTree圖形化的顯示了樹結構的每一個節點,所有的UI控件都有兩個目的,顯示和輸入(輸入包括數據的輸入如JTextField和命令輸入如菜單,按鈕等),JTree既可以用于樹結構的顯示,也可以用于命令的輸入,或使得我們編輯樹節點。
樹節點可以被選中,它由TreeSelectionModel來控制,選擇涉及到維護作為TreeNode實例的樹節點間的路徑軌跡。樹控件典型的可以激發兩類事件:TreeModelEvent和TreeExpansionEvent,當然其他Awt和Swing事件也可由樹控件激發(看其繼承層次結構即可知)比如MouseListener可用來截取鼠標事件。JTree擴展了Scrollable接口,可被放在一個滾動面板中。
JTree的構造:可使用默認的構造方法,提供一個TreeNode作為其根節點,提供一個TreeModel包含所有其它的節點,或提供一個一維數組,向量,或對象的哈希表,對于這些集合中的單個元素,如果它又是一個集合,那么他們會被解釋顯示為子樹,該功能由JTree的內部類DynamicUtilTreeNode完成。
***
****
TreeModel接口:
該接口的實現者為JTree提供顯示的數據,如果你熟悉MVC模式,你應該明白所有的swing或awt控件中模型的作用就是為相應的控件提供數據。當模型的數據結構有所變化時它會通知視圖(這里就是JTree)來更新顯示。當然模型也可以添加其他的監聽器如Jtree的addTreeModelListener,你可以實現該監聽器,來使你自己的類接收模型變化給你的通知。如果你不熟悉MVC模式,請參考POSA卷一或其他資料,順便提一下在我們學校GUI時都知道有MVC模式的應用,往往不知道那個Controller是什么類,其實就是視圖的監聽器,比如ActionListener,注意別被眾多的監聽器弄昏了,一類是模型需要添加的,一類是視圖(比如JComponent的子類)需要添加的。控制的流向或數據的流向是相反的,視圖需要添加的監聽器(我們常常實現他們)才是控制器。
因為模型和視圖都能夠觸發事件,比如視圖(JTree等控件)是觸發用戶輸入導致的事件,而模型觸發的事件是因為模型中維護的數據有所變動才觸發的(比如,樹模型中樹節點的增刪,改,或樹路徑的變動)。而他們都使用了觀察者模式,算了不多說了,到時全弄成模式了,會搞昏大家的。繼續....
JTree的setModel和getModel方法是用來更換/設置和獲取模型的方法。你可替換現有JTree的模型,或者你想這樣用,兩個模型,一個用,一個備。如果構造模型復雜耗時的話,先在后臺構造好一個在換掉原先的。就如同雙緩沖技術的思路那樣。
****
*****
雜項:
DefultTreeModel是對TreeModel接口的默認實現類,
TreeNode接口可告訴你改實現者是否為一個葉子,一個父節點等。MutalbeTreeNode接口擴展了TreeNode接口,我們可在該實現者中存放一個我們自己的類實例(setUserObject()/getUserObject);
defaultMutableTreeNode 實現了MutableTreeNode接口,children()方法返回以一維向量形式存放的直接子節點的枚舉,也可以使用getChildAt()返回特定索引位置的子節點(注意子節點完全可以是一顆子樹)該類提供了前中后序訪問樹的能力:preorderEnumeration(),,breadthFirstEnumeration(),depthFirstEnumeration()postorderEnumeration()最后兩個方法同行為,只不過是不同的稱號而已。
TreePath:該類用一系列節點表示一個從樹根到一個節點的路徑,它是只讀的,提供與其他路徑比較的能力。
TreeCellRenderrer接口:渲染tree的一個單元的組件,我們自己實現該接口并用jtree的setCellRenderer()方法替換原先的渲染器,可以是樹節點在選中,獲取焦點,不同的樹狀態(葉子或父節點,展開,或收縮)等不同的狀態下的外觀。
DefaultTreeCellRenderer類是TreeCellRenderrer接口的默認實現,它擴展了JLabel,并基于以上描述的樹狀態來渲染樹節點,其提供的屬性包括圖標,背景色,前景色等,其get和set方法是我們可以訪問的,通過這些方法你當然可以換掉樹節點的圖標了。
CellEditor接口:定義了控制何時編輯將開始,結束,提取一個新的結果,是否編輯請求改變當前組件的選擇,請參考API文檔看該接口的方法。該接口在JTree和JTable中都有用到。,該接口也可以添加監聽器,當編輯停止或取消時會激發ChangeEvents到其所有的注冊處理器哪里。
TreeCellEditor接口擴展了CellEditor接口,jtree的setCellEditor()使得我們可以用任何一個可充當編輯器的組件替換掉原來的那個。DefaultCellEditor實現了該接口,這個編輯器允許使用JTextField,JComboBox或是JCheckBox組件來編輯數據,其保護的內部類EditorDelegate會響應getCellEditorValue()方法把當前值返回。DefaultCellEditor僅基于以上三個J控件作為編輯器,其clickCountToStart方法決定鼠標單擊幾次會觸發編輯。默認對于JTextField是兩次,JComboBox和JCheckBox是一次,changeEvents會在stopCellEditing()和cancelCellEditing()時激發。
DefaultTreeCellEditor擴展了DefaultCellEditor類并且是TreeCellEditor的默認實現類,他使用JTextField來編輯節點數據,在鍵入ENTER鍵后stopCellEditing()會被調用。對于樹節點的編輯我們可添加自己的時間監聽器來處理他們。默認時編輯開始于節點被單擊三次或兩次(時間間隔在內部會用一個定時器來決定),也可以改變他們的數目setClickCountToStart();
JTree的選擇是基于行和樹路徑的,我們可以選擇使用那個。
TreeSelectionModel接口用于樹選擇模型,支持三種選擇,SINGLE_TREE_SELECTION,
DISCONTIGUOUS_TREE_SELECTION,CONTIGUOUS_TREE_SELECTION,set/getSelectionMode()可以訪選擇模型。getSelectionPath『s』()會返回一個當前選中的樹路徑。DefaultTreeSelectionModel默認實現了該接口,該類提供TreeSelectionlistener通知,當樹路徑選擇發生變化時。
TreeModelListener實現者可以偵聽模型變化,TreeSelectionListener用來偵聽視圖JTree的selection(僅有一個方法valueChanged(TreeSlectcionEvent tsEvt));
TreeExpansionListener用來對樹展開收縮進行處理。
TreeW illExpandListener在樹“將要”展開和收縮時得到通知,你可截獲處理,ExpandVetoException異常如果拋出,那么樹不會展開和收縮。
TreeModelEvent,用來通知模型的監聽器,JTree的數據部分或全部發生了變化。該事件對象封裝了源組件的引用,封裝了一個TreePath或一個用來表示路徑的數組。
TreeselectionEvent,視圖會用其通知所有視圖監聽器TreeSelectionListeners,選擇發生了變化。
TreeExpansionEvent,用來封裝相應最近或可能展開或收縮的TreePath,使用getPath()方法訪問樹路徑。
ExpandVetoException異常可由TreeWillExpandListener拋出,來否決樹路徑的展開和收縮。
JTree提供的現成方便的UI屬性:
myJTree.putClientProperty("JTree.lineStyle", "Angled");//更改線型。
如同其他Swing組件,我們也可以改變默認的用于JTree的UI資源(全局性的):
UIManager.put("Tree.hash",
new ColorUIResource(Color.lightGray));//改變渲染節點間edges邊的顏色。
UIManager.put("Tree.openIcon", new IconUIResource(
new ImageIcon("myOpenIcon.gif")));//改變一個打開的樹節點的圖標。同理可用于其它情況:Tree.leafIcon, Tree.expandedIcon,和Tree.closedIcon, Tree.collapsedIcon。
其他控制TreeUI顯示的方法:
myTree.setRowHeight()//控制樹節點的行高,
JTree的UI委托也提供了更改樹外觀的方法(相比于UIManager的方法,這里是局部的)。
BasicTreeUI basicTreeUI = (BasicTreeUI) myJTree.getUI();
basicTreeUI.setRightChildIndent(10);
basicTreeUI.setLeftChildIndent(8);
以上簡要提及了JTree的方方面面,許多的事件,將聽器模型,請仔細分析,一定要分清哪些是針對模型的那些是針對視圖的。
*****
******
簡單的示例,我這里僅用到了最簡單的樹構造方法,和一個監聽器,在
以后我的自學過程中,我會繼續試用其他的JTree知識,我的JTree學習
最終都是想實現那個GUI上的授權控制系統,請參考其他篇章,
至于這里用到的LAndFSysMenu類,在我的其他篇章中有該類的實現。
package jTreeDemo;
import java.awt.Container;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import lookAndFeelSys.*;
import userInterfaces.UIUtil;
import java.awt.*;
import java.util.Hashtable;
import java.util.Vector;
import javax.swing.tree.*;
public class JTreeTest extends JFrame{
public static void main(String[] args){
new JTreeTest("測試");
}
public JTreeTest(String title){
super(title);
biuldFrame();
}
private void biuldFrame(){
JMenuBar jmb=new JMenuBar();
JMenu jm=new LAndFSysMenu();
//JMenu jm=new JMenu("hello");
jmb.add(jm);
this.setJMenuBar(jmb);
buildFrmContent();
UIUtil.SetComponentDimension(this,0.5,0.6);
UIUtil.SetComponentToCenterOfScreen(this);
this.setVisible(true);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
private void buildFrmContent(){
Container root_c=this.getContentPane();
JTabbedPane jtp=new JTabbedPane();
Container c = new JPanel();
jtp.addTab("靜態樹組件練習",c );
jtp.addTab("事件監聽",this.treeDemo2());
root_c.add(jtp);
c.setLayout(new GridLayout(2,4));
JScrollPane jsp_1=new JScrollPane();
JScrollPane jsp_2=new JScrollPane();
JScrollPane jsp_3=new JScrollPane();
JScrollPane jsp_4=new JScrollPane();
/*為JTree準備顯示的模型*/
Object[] m1=new String[]{"節點1","節點2","節點3"};
Object[] m2=new String[][]{
{"1.1","1.2","1.3"},
{"2.1","2.2","2.3"},
{"3.1","3.2","3.3"}
};
Vector<Object> m3=new Vector<Object>();
m3.add("1");
m3.add("2");
m3.add(m1);
m3.add(m2);
Hashtable<String,Object> m4=new Hashtable<String,Object>();
m4.put("子一","葉子");
m4.put("子二", m1);
m4.put("子三",m3);
JTree jtr_1=new JTree(m1);
jsp_1.getViewport().add(jtr_1);
JTree jtr_2=new JTree(m2);
jsp_2.getViewport().add(jtr_2);
JTree jtr_3=new JTree(m3);
jsp_3.getViewport().add(jtr_3);
JTree jtr_4=new JTree(m4);
jsp_4.getViewport().add(jtr_4);
c.add(jsp_1);
c.add(jsp_2);
c.add(jsp_3);
c.add(jsp_4);
/*jsp_1.getViewport().add(jtr_1);
c.add(jsp_1);*/
}
/*<< 另一組JTree實例:*/
private JPanel treeDemo2(){
JPanel rsltPanel=new JPanel();
rsltPanel.setLayout(new BorderLayout());
JLabel jl_msg=new JLabel("此標簽用來顯示樹選擇情況");
JScrollPane jsp_1=new JScrollPane();
Object[] m=new String[]{"節點1","節點2","節點3"};
JTree jtr=new JTree(m);
jtr.getSelectionModel()
.addTreeSelectionListener(new MySelectionLstnr(
jl_msg));
jsp_1.getViewport().add(jtr);
rsltPanel.add(jsp_1,BorderLayout.CENTER);
rsltPanel.add(jl_msg,BorderLayout.SOUTH);
return rsltPanel;
}
class MySelectionLstnr implements TreeSelectionListener{
//該內部類實現樹監聽器,在樹被選中后將選中的節點
//信息打印到一個Label上
private JLabel jl_msg=null;
public MySelectionLstnr(JLabel msgLabel){
this.jl_msg=msgLabel;
}
@Override
public void valueChanged(TreeSelectionEvent e) {
// 凡是樹選擇的處理都涉及到樹路徑的處理:
TreePath path = e.getPath();
Object[] nodes = path.getPath();
//當前選中的節點是樹路徑上最后一個節點
Object selectedNode=nodes[nodes.length-1 ];
if(this.jl_msg!=null){
this.jl_msg.setText("選中的節點上的文本是:"+
selectedNode.toString());
}
}
}
/*另一組JTree實例:>>*/
}

******
參考Java Swing (Manning出版社)swing hack (orelly出版社)。
import java.awt.Dimension;
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.BoxLayout;
import javax.swing.tree.TreePath;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
/*
JTree的構造函數:
JTree()
JTree(Hashtable value)
JTree(Object[] value)//只有這個構造函數可以創建多個根結點
JTree(TreeModel newModel)
JTree(TreeNode root)
JTree(TreeNode root, boolean asksAllowsChildren)
JTree(Vector value)
*/
public class JTreeDemo
{
public static void main (String[] args)
{
// 構造函數:JTree()
JTree example1 = new JTree();
// 構造函數:JTree(Object[] value)
Object[] letters = { " a " , " b " , " c " , " d " , " e " };
JTree example2 = new JTree (letters);
// 構造函數:JTree(TreeNode root)(TreeNode空)
// 用空結點創建樹
DefaultMutableTreeNode node1 = new DefaultMutableTreeNode(); // 定義樹結點
JTree example3 = new JTree (node1); // 用此樹結點做參數調用 JTree的構造函數創建含有一個根結點的樹
// 構造函數:JTree(TreeNode root)(同上,只是TreeNode非空)
// 用一個根結點創建樹
DefaultMutableTreeNode node2 = new DefaultMutableTreeNode( " Color " );
JTree example4 = new JTree (node2); // 結點不可以顏色,默認為白面黑字
example4.setBackground (Color.lightGray);
// 構造函數:JTree(TreeNode root, boolean asksAllowsChildren)(同上,只是TreeNode又有不同)
// 使用DefaultMutableTreeNode類先用一個根結點創建樹,設置為可添加孩子結點,再添加孩子結點
DefaultMutableTreeNode color = new DefaultMutableTreeNode( " Color " , true );
DefaultMutableTreeNode gray = new DefaultMutableTreeNode ( " Gray " );
color.add (gray);
color.add ( new DefaultMutableTreeNode ( " Red " ));
gray.add ( new DefaultMutableTreeNode ( " Lightgray " ));
gray.add ( new DefaultMutableTreeNode ( " Darkgray " ));
color.add ( new DefaultMutableTreeNode ( " Green " ));
JTree example5 = new JTree (color);
// 構造函數:JTree(TreeNode root)(同上,只是TreeNode非空)
// 通過逐個添加結點創建樹
DefaultMutableTreeNode biology = new DefaultMutableTreeNode ( " Biology " );
DefaultMutableTreeNode animal = new DefaultMutableTreeNode ( " Animal " );
DefaultMutableTreeNode mammal = new DefaultMutableTreeNode ( " Mammal " );
DefaultMutableTreeNode horse = new DefaultMutableTreeNode ( " Horse " );
mammal.add (horse);
animal.add (mammal);
biology.add (animal);
JTree example6 = new JTree (biology);
horse.isLeaf();
horse.isRoot();
// 構造函數:JTree(TreeModel newModel)
// 用DefaultMutableTreeNodel類定義一個結點再用這個結點做參數定義一個用DefaultTreeMode
// 創建一個樹的模型,再用JTree的構造函數創建一個樹
DefaultMutableTreeNode root = new DefaultMutableTreeNode ( " Root1 " );
DefaultMutableTreeNode child1 = new DefaultMutableTreeNode ( " Child1 " );
DefaultMutableTreeNode child11 = new DefaultMutableTreeNode ( " Child11 " );
DefaultMutableTreeNode child111 = new DefaultMutableTreeNode ( " Child111 " );
root.add (child1); child1.add (child11); child11.add (child111);
DefaultTreeModel model = new DefaultTreeModel (root);
JTree example7 = new JTree (model);
JPanel panel = new JPanel();
panel.setLayout ( new BoxLayout (panel, BoxLayout.X_AXIS));
panel.setPreferredSize ( new Dimension ( 700 , 400 ));
panel.add ( new JScrollPane (example1)); // JTree必須放在JScrollPane上
panel.add ( new JScrollPane (example2));
panel.add ( new JScrollPane (example3));
panel.add ( new JScrollPane (example4));
panel.add ( new JScrollPane (example5));
panel.add ( new JScrollPane (example6));
panel.add ( new JScrollPane (example7));
JFrame frame = new JFrame ( " JTreeDemo " );
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setContentPane (panel);
frame.pack();
frame.show();
}
} ××××××××××××××××××××××××××××××××××××××××××××××
在實際開發過程中會經常使用JTree組件,平時會遇到這樣或那樣的問題,在此將偶得一點經驗寫下來,與大家共享,希望對大家有所幫助。
private JTree jtNetDevice;//數組件申明
private JScrollPane jspTree;//滾動面板申明
1、初始化
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("root");
jtNetDevice = new JTree(rootNode);
jtNetDevice.setAutoscrolls(true);
getTreeSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);//設置單選模式
jspTree = new JScrollPane();
jspTree.getViewport().add(jtNetDevice, null);
2、三個經常使用的取值函數
private DefaultTreeModel getTreeModel(){
return (DefaultTreeModel)jtNetDevice.getModel();
}
private DefaultMutableTreeNode getRootNode(){
return (DefaultMutableTreeNode)getTreeModel().getRoot();
}
private TreeSelectionModel getTreeSelectionModel(){
return jtNetDevice.getSelectionModel();
}
3、根據node得到path:
TreePath visiblePath = new TreePath(getTreeModel().getPathToRoot(node));
4、根據Path展開到該節點
jtNetDevice.makeVisible(visiblePath);
5、根據path設定該節點選定
jtNetDevice.setSelectionPath(visiblePath);
6、選中節點的方法
首先,根據節點得到樹路徑,其中chosen為需要選中的節點
TreePath visiblePath = new TreePath( ( (DefaultTreeModel) jtNetDevice.getModel()).
getPathToRoot(chosen));
然后根據Path選中該節點
jtNetDevice.setSelectionPath(visiblePath);
7、滾動到可見位置
jtNetDevice.scrollPathToVisible(visiblePath);
8、給JTree添加右鍵彈出菜單
void jtNetDevice_mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
jPopupMenu1.show(e.getComponent(), e.getX(), e.getY());//彈出右鍵菜單
}
}
9、關于JTree的展開
// If expand is true, expands all nodes in the tree.
// Otherwise, collapses all nodes in the tree.
public void expandAll(JTree tree, boolean expand) {
TreeNode root = (TreeNode)tree.getModel().getRoot();
// Traverse tree from root
expandAll(tree, new TreePath(root), expand);
}
private void expandAll(JTree tree, TreePath parent, boolean expand) {
// Traverse children
TreeNode node = (TreeNode)parent.getLastPathComponent();
if (node.getChildCount() >= 0) {
for (Enumeration e=node.children(); e.hasMoreElements(); ) {
TreeNode n = (TreeNode)e.nextElement();
TreePath path = parent.pathByAddingChild(n);
expandAll(tree, path, expand);
}
}
// Expansion or collapse must be done bottom-up
if (expand) {
tree.expandPath(parent);
} else {
tree.collapsePath(parent);
}
}
10、如何遍歷JTree
// 創建樹
JTree tree = new JTree();
// 添加樹節點......
// 遍歷所有節點
visitAllNodes(tree);
// 僅遍歷展開的節點
visitAllExpandedNodes(tree);
// Traverse all nodes in tree
public void visitAllNodes(JTree tree) {
TreeNode root = (TreeNode)tree.getModel().getRoot();
visitAllNodes(root);
}
public void visitAllNodes(TreeNode node) {
// node is visited exactly once
process(node);
if (node.getChildCount() >= 0) {
for (Enumeration e=node.children(); e.hasMoreElements(); ) {
TreeNode n = (TreeNode)e.nextElement();
visitAllNodes(n);
}
}
}
// Traverse all expanded nodes in tree
public void visitAllExpandedNodes(JTree tree) {
TreeNode root = (TreeNode)tree.getModel().getRoot();
visitAllExpandedNodes(tree, new TreePath(root));
}
public void visitAllExpandedNodes(JTree tree, TreePath parent) {
// Return if node is not expanded
if (!tree.isVisible(parent)) {
return;
}
// node is visible and is visited exactly once
TreeNode node = (TreeNode)parent.getLastPathComponent();
process(node);
// Visit all children
if (node.getChildCount() >= 0) {
for (Enumeration e=node.children(); e.hasMoreElements(); ) {
TreeNode n = (TreeNode)e.nextElement();
TreePath path = parent.pathByAddingChild(n);
visitAllExpandedNodes(tree, path);
}
}
}
posted on 2006-04-04 17:24 SIMONE 閱讀(9202) 評論(1) 編輯 收藏 所屬分類: JAVA
今天終于耐著性子弄懂了GridBagLayout是怎么使用的。
構造函數:
GirdBagLayout()建立一個新的GridBagLayout管理器。
GridBagConstraints()建立一個新的GridBagConstraints對象。
GridBagConstraints(int gridx,int gridy,
int gridwidth,int gridheight,
double weightx,double weighty,
int anchor,int fill, Insets insets,
int ipadx,int ipady)建立一個新的GridBagConstraints對象,并指定其參數的值。
看著這一堆的參數就快煩死了,下面就了解一下參數的意思:
參數說明:
gridx,gridy —— 設置組件的位置,
gridx設置為GridBagConstraints.RELATIVE代表此組件位于之前所加入組件的右邊。
gridy設置為GridBagConstraints.RELATIVE代表此組件位于以前所加入組件的下面。
建議定義出gridx,gridy的位置以便以后維護程序。gridx=0,gridy=0時放在0行0列。
gridwidth,gridheight —— 用來設置組件所占的單位長度與高度,默認值皆為1。
你可以使用GridBagConstraints.REMAINDER常量,代表此組件為此行或此列的最后一個組件,而且會占據所有剩余的空間。
weightx,weighty —— 用來設置窗口變大時,各組件跟著變大的比例。
當數字越大,表示組件能得到更多的空間,默認值皆為0。
anchor —— 當組件空間大于組件本身時,要將組件置于何處。
有CENTER(默認值)、NORTH、NORTHEAST、EAST、SOUTHEAST、WEST、NORTHWEST選擇。
insets —— 設置組件之間彼此的間距。
它有四個參數,分別是上,左,下,右,默認為(0,0,0,0)。
ipadx,ipady —— 設置組件間距,默認值為0。
GridBagLayout里的各種設置都必須通過GridBagConstraints,因此當我們將GridBagConstraints的參數都設置
好了之后,必須new一個GridBagConstraints的對象出來,以便GridBagLayout使用。
代碼片斷:
JButton b;
GridBagConstraints c;
int gridx,gridy,gridwidth,gridheight,anchor,fill,ipadx,ipady;
double weightx,weighty;
Insets inset;
JFrame f=new JFrame();
GridBagLayout gridbag=new GridBagLayout();
Container contentPane=f.getContentPane();
contentPane.setLayout(gridbag);
b=new JButton("first");
gridx=0;
gridy=0;
gridwidth=1;
gridheight=1;
weightx=10;
weighty=1;
anchor=GridBagConstraints.CENTER;
fill=GridBagConstraints.HORIZONTAL;
inset=new Insets(0,0,0,0);
ipadx=0;
ipady=0;
c=new GridBagConstraints(gridx,gridy,gridwidth,gridheight,weightx,weighty,anchor,fill,inset,ipadx,ipady);
gridbag.setConstraints(b,c);
contentPane.add(b);
GridBagLayout這種管理器是十分靈活的,只不過他寫起來比較麻煩,不過用了之后才發現他對界面的部署幫助很大。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/dracularking/archive/2008/04/22/2314336.aspx
下面的是這個界面的一個原始草圖:

正如你所看到的,最終的結果看上去和計劃的想法完全一樣。
你應該能看到在草圖里有一些線,這些線是用來把總界面分成若干行和列的,這樣你就很清楚每一個組件放置的格子位置。這就是GridBagLayout里"格"的那一部分,而圖上的數字就是格的號碼。
在某種意義上說, 我們可以把GridBagLayout想象成為早些年的HTML3和4,它們都是基于表的布局,Grid的概念就類似rowspan和colspan的意思,只不過換了個名字罷了。
隨著我們的界面和表格的設置完成,是時候該進行界面布局并開始寫代碼了。
工作過程
這一節我假定你已經了解了基本的窗口和組件創建知識。
通過這篇文章我們最終能在一個frame中布局組件,我們將在以后的文章對界面進行改進使它更適用。因此,為了了解這整個工作的過程,我們列出了所有的目標代碼。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GridBagWindow extends JFrame {
private JButton searchBtn;
private JComboBox modeCombo;
private JLabel tagLbl;
private JLabel tagModeLbl;
private JLabel previewLbl;
private JTable resTable;
private JTextField tagTxt;
public GridBagWindow() {
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
GridBagConstraints c = new GridBagConstraints();
//setting a default constraint value
c.fill =GridBagConstraints.HORIZONTAL;
tagLbl = new JLabel("Tags");
c.gridx = 0; //x grid position
c.gridy = 0; //y grid position
gridbag.setConstraints(tagLbl, c); //associate the label with a constraint object
contentPane.add(tagLbl); //add it to content pane
tagModeLbl = new JLabel("Tag Mode");
c.gridx = 0;
c.gridy = 1;
gridbag.setConstraints(tagModeLbl, c);
contentPane.add(tagModeLbl);
tagTxt = new JTextField("plinth");
c.gridx = 1;
c.gridy = 0;
c.gridwidth = 2;
gridbag.setConstraints(tagTxt, c);
contentPane.add(tagTxt);
String[] options = {"all", "any"};
modeCombo = new JComboBox(options);
c.gridx = 1;
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(modeCombo, c);
contentPane.add(modeCombo);
searchBtn = new JButton("Search");
c.gridx = 1;
c.gridy = 2;
gridbag.setConstraints(searchBtn, c);
contentPane.add(searchBtn);
resTable = new JTable(5,3);
c.gridx = 0;
c.gridy = 3;
c.gridwidth = 3;
gridbag.setConstraints(resTable, c);
contentPane.add(resTable);
previewLbl = new JLabel("Preview goes here");
c.gridx = 0;
c.gridy = 4;
gridbag.setConstraints(previewLbl, c);
contentPane.add(previewLbl);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[]) {
GridBagWindow window = new GridBagWindow();
window.setTitle("GridBagWindow");
window.pack();
window.setVisible(true);
}
}
構造方法前的代碼都不是很特殊,都是一些相當標準的import和變量定義。但是進入構造方法后,事情就變得有趣了。
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
我們以GridBagWindow的內容面板作為開始來創建一個GridBagLayout對象,準確地說,這個方法與過去我們所創建 GridLayout對象和BorderLayout對象的方法是一樣的。那么,現在我們就開始來設置GridBagLayout對象使它作為內容面板的 布局。
GridBagConstraints c = new GridBagConstraints();
然后我要提到這整個進程中的一個獨特的對象,那就是GridBagConstraints。這個對象在GridBagLayout中控制所 有被安置在其中組件的約束。為了把一個組件增加到你的GridBagLayout中去,你首先必須將它與一個GridBagConstraints對象建 立連接。
GridBagConstraints可以從11個方面來進行控制和操縱,也可以給你提供一些幫助。這些內容是:
- Gridx——組件的橫向坐標
- Girdy——組件的縱向坐標
- Gridwidth——組件的橫向寬度,也就是指組件占用的列數,這與HTML的colspan類似
- Gridheight——組件的縱向長度,也就是指組件占用的行數,這與HTML的rowspan類似
- Weightx——指行的權重,告訴布局管理器如何分配額外的水平空間
- Weighty——指列的權重,告訴布局管理器如何分配額外的垂直空間
- Anchor——告訴布局管理器組件在表格空間中的位置
- Fill——如果顯示區域比組件的區域大的時候,可以用來控制組件的行為。控制組件是垂直填充,還是水平填充,或者兩個方向一起填充
- Insets——指組件與表格空間四周邊緣的空白區域的大小
- Ipadx—— 組件間的橫向間距,組件的寬度就是這個組件的最小寬度加上ipadx值
- ipady—— 組件間的縱向間距,組件的高度就是這個組件的最小高度加上ipady值
可能對于一個組件的每一個實例你都需要為它建立一個單獨的GridBagConstraints;然而,這種方法我們并不推薦使用。最好的方法是,當你調用它的時候把對象設置為默認值,然后針對于每一個組件改變其相應的域。
這個方法具有通用性,因為在一些域中,比如insets、padx、pady和fill這些域,對于每一個組件來說一般都是相同的,因此這樣對一個域進行設置就會更輕松了,也能更輕松的在另外的組件中改變某些域的值。
如果在改變了某些域值之后,你想回到原始的域值的話,你應該在增加下一個組件之前進行改變。這種方法使你更容易明白你正在修改的內容,也能使你更容易明白在一連串對象中的這11個參數的作用。
也許你現在對這些內容還是一知半解,不過事實上一旦你理解了GridBagConstraints,值得安慰的是你以后做再困難的工作都會游刃有余了。
所以,如果我們已經明白了GridBagConstraints的詳細用法了,那么現在就讓我們來看看在實際應用中應該如何來實現它:
tagLbl = new JLabel("Tags");
c.gridx = 0; //x grid position
c.gridy = 0; //y grid position
gridbag.setConstraints(tagLbl, c); //設置標簽的限制
contentPane.add(tagLbl); //增加到內容面板
我們所做的是示例我們的標簽、分配給它一個格位置,將它與一個約束對象聯系起來并把它增加到我們的內容面板中。
tagModeLbl = new JLabel("Tag Mode");
c.gridx = 0;
c.gridy = 1;
gridbag.setConstraints(tagModeLbl, c);
contentPane.add(tagModeLbl);
請注意,雖然我們已經在我們的約束對象中把gridx的值設置為0,但是在這里我們仍然要對它進行重新設置——這樣做沒有其它原因,只是為了增加可讀性。
下面,我們增加一個文本域以便能存儲我們希望能搜索到的關鍵字,再增加一個組合框以便用來搜索多個關鍵字。除了我們希望的文本域有兩列之外,這個概念其他的方面都與上面所說的是相同的,所以,我們需要在增加組合框之前重新設置文本域的值。
tagTxt = new JTextField("plinth");
c.gridx = 1;
c.gridy = 0;
c.gridwidth = 2;
gridbag.setConstraints(tagTxt, c);
contentPane.add(tagTxt);
String[] options = {"all", "any"};
modeCombo = new JComboBox(options);
c.gridx = 1;
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(modeCombo, c);
contentPane.add(modeCombo);
做了這些之后,我們再在內容面板中增加一些其余的簡單組件,這時候我們就能夠瀏覽它了;其余的代碼應該不會出現任何問題了。
到這個階段,我們應該已經得到了一個類似于我們先前所設計的界面了。
7-4:JComboBox的使用:
類層次結構圖:
java.lang.Object
--java.awt.Component
--java.awt.Container
--javax.swing.JComponent
--javax.swing.JComboBox
構造函數:
JComboBox():建立一個新的JComboBox組件。
JComboBox(ComboBoxModel aModel):用ListModel建立一個新的JComboBox組件。
JComboBox(Object[] items):利用Array對象建立一個新的JComboBox組件。
JComboBox(Vector items):利用Vector對象建立一個新的JComboBox組件。
7-4-1:建立一般的JComboBox:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Vector;
public class JComboBox1{
public static void main(String[] args){
JFrame f=new JFrame("JComboBox1");
Container contentPane=f.getContentPane();
contentPane.setLayout(new GridLayout(1,2));
String[] s = {"美國","日本","大陸","英國","法國","意大利","澳洲","韓國"};
Vector v=new Vector();
v.addElement("Nokia 8850");
v.addElement("Nokia 8250");
v.addElement("Motorola v8088");
v.addElement("Motorola v3850");
v.addElement("Panasonic 8850");
v.addElement("其它");
JComboBox combo1=new JComboBox(s);
combo1.addItem("中國");//利用JComboBox類所提供的addItem()方法,加入一個項目到此JComboBox中。
combo1.setBorder(BorderFactory.createTitledBorder("你最喜歡到哪個國家玩呢?"));
JComboBox combo2=new JComboBox(v);
combo2.setBorder(BorderFactory.createTitledBorder("你最喜歡哪一種手機呢?"));
contentPane.add(combo1);
contentPane.add(combo2);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
}
7-4-2:利用ComboModel構造JComboBox:
如同JList一般,在JComboBox中也有一個構造函數是利用某種Model來構造。如下所示:
JComboBox(COmboBoxModel aModel)
ComboBoxModel是一個interface,里面定義了兩個方法,分別是setSelectedItem()與getSelectedItem().這兩個方法目的是讓用
戶選取某個項目后,可正確地顯示出用戶所選取的項目。下面是這兩個方法的詳細定義:
ComboBoxModel interface定義的方法:
Object getSelectedItem():返回所選取的項目值。
Void setSelectedItem(Object anItem):設置所選取的項目值.
與JList不同的是,JComboBox是利用ComboBoxModel,而不是ListModel.不過ComboBoxModel interface是繼承ListModel interface
,因此若我們要利用ComboBoxModel來構造JComboBox,除了要實作ComboBoxModel的兩個方法外,還必須實作ListModel的所定義的4個
方法,這樣的做法可說相當麻煩。
在介紹JList時我們曾經提到AbstractListModel這個抽象類。這個抽象類實作了ListModel interface中的addListDataListener
()、removeListDataListener()這兩個方法。因此若我們繼承AbstractListModel,則可少掉實作這兩個方法,只需要實作
getElementAt()、getSize()、setSelectedItem()與getSelectedItem()這4個方法。這樣的作法就顯得比較簡單一點.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox2{
String[] s= {"美國","日本","大陸","英國","法國","意大利","澳洲","韓國"};
public JComboBox2(){
JFrame f=new JFrame("JComboBox2");
Container contentPane=f.getContentPane();
ComboBoxModel mode=new UserDefineComboBoxModel();
JComboBox combo=new JComboBox(mode);
combo.setBorder(BorderFactory.createTitledBorder("你最喜歡到哪個國家去玩?"));
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox2();
}
class UserDefineComboBoxModel extends AbstractListModel implements ComboBoxModel{
String item=null;
public Object getElementAt(int index){
return s[index++];
}
//由于繼承AbstractListModel抽象類。因此我們分別在程序中實作了getElementAt()與getSize()方法。
public int getSize(){
return s.length;
}
//由于我們實現了ComboBoxModel interface.因此我們必須在程序中實作setSelectedItem()與getSelectedItem()方法.
public void setSelectedItem(Object anItem){
item=(String)anItem;
}
public Object getSelectedItem(){
return item;
}
}
}
當程序要show出JComboBox時,系統會先自動調用getSize()方法,看看這個JComboBox長度有多少,然后再調用getElementAt()
方法,將String Array s中的值填入JComboBox中。當用戶選擇項目時,系統會調用getSelectedItem()方法,返回所選取的項目,并
利用setSelectedItem()方法,將選取項目放在JComboBox最前端。
getElementAt()方法中的“index”參數,系統會自動由0計算,不過要自己作累加的操作,如程序中:
return s[index++];
如同JList一般,java對于JComboBox也提供了另一個類,DefaultComboBoxModel實體類。此類繼承了AbstractListModel抽象類,也
實作了ComboBoxModel interface.因此你不需要再實作getSize()、getElementAt()、setSelectedItem()與getSelectedItem()方法。
利用DefaultComboBoxModel這個類我們可以很方便地做到動態更改JComboBox的項目值。當你沒有必要自己定義特殊的ComboBoxModel
時,使用DefaultComboBoxModel就顯得非常的方便,我們來看下面的例子:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox3{
String[] s = {"美國","日本","大陸","英國","法國","意大利","澳洲","韓國"};
public JComboBox3(){
JFrame f=new JFrame("JComboBox3");
Container contentPane=f.getContentPane();
ComboBoxModel mode=new AModel();
JComboBox combo=new JComboBox(mode);
combo.setBorder(BorderFactory.createTitledBorder("您最喜歡到哪個國家玩呢?"));
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox3();
}
class AModel extends DefaultComboBoxModel{
AModel(){
for (int i=0;i<s.length;i++)
addElement(s[i]);
}
}
}
1.由于AModel繼承DefaultComboBoxModel實體類,由AModel可得到一個ComboBoxModel實體對象。
2.我們使AModel繼承DefaultComboBoxModel實體類,因此就不需要再實作getElementAt()、getSize()、setSelectedItem()與
getSelectedItem()這4個方法,直接將所要的項目用addElement()方法加入即可。系統會自動將所加入的項目放進一個Vector
中,并在輸出JComboBox時自動調用getSize()與getElementAt()方法。
7-4-3:建立有圖像的JComboBox:
在上一節中我們利用ListCellRenderer interface在JList中加入Icon圖像,而要在JComboBox中加入圖像的方法也是一樣的。
我們必須實作ListCellRenderer interface所定義的方法getListCellRendererComponent.以下為這個方法的定義:
要先了解ListCellRenderer interface.我們必須由這個interface所定義的方法,將圖像畫在JComboBox中的每個項目。
ListCellRenderer interface里只定義了一個方法,那就是getListCellRendererComponent,不過這個參數有點多,我們把它列出來
看看:
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
list:即所要畫上的圖像的JComboBox組件。
value:JComboBox項目值,如JComboBox.getModel().getElementAt(index)所返回的值。
index:為JComboBox項目的索引值,由0開始。
isSelected與cellHasFocus:判斷JComboBox中的項目是否有被選取或是有焦點置入。
上面這4個參數會在你設置JComboBox的繪圖樣式(setCellRenderer())時自動的由JComboBox組件提供,你只要關心怎么控制
getListCellRendererComponent()方法中的4個參數,而無需擔心怎么參數傳入。
要在JList中加入Icon圖像的技巧就是將JComboBox中的每一個項目當作是JLabel,因為JLabel在使用文字與圖像上非常的方便,要設置JComboBox的圖像,
必須使用setRenderer(ListCellRenderer cellRenderer){注:我們在JList中畫上圖像是利用JList所提供的setCellRenderer(ListCellRenderer
cellRenderer)方法,讀者請小心}這個方法。我們來看下面這個范例,你就能明白了!
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox4{
String[] s={"西瓜","蘋果","草莓","香蕉","葡萄"};
public JComboBox4(){
JFrame f=new JFrame("JComboBox");
Container contentPane=f.getContentPane();
JComboBox combo=new JComboBox(s);
combo.setBorder(BorderFactory.createTitledBorder("你最喜歡吃哪些水果?"));
combo.setRenderer(new ACellRenderer());
combo.setMaximumRowCount(3);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox4();
}
}
class ACellRenderer extends JLabel implements ListCellRenderer{
ACellRenderer(){
setOpaque(true);
}
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus){
if (value!=null){
setText(value.toString());
setIcon(new ImageIcon(".\\icons\\fruit"+(index+1)+".jpg"));
}
if (isSelected){
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}else{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
各們讀者在運行這個程序時會發現,即使JComboBox的選項中有圖標,但在選后圖標卻不會顯示在顯示列中,原因是在上面程序中
我們以String Array s建立JComboBox:
JComboBox combo=new JComboBox(s);
String Array s里面放的只是水果名稱,而并沒有圖標。當我們使用setRenderer()方法來JComboBox時,只會繪制JComboBox的
選項部份,而最后顯示在JComboBox上的值還是以String Array s為依據。因此JComboBox顯示列就只會顯示文字而已,而不會顯示出
圖形。要解決這個問題,我們必須改變JComboBox所傳入的參數內容,也就是將原來的String Array s更改成具有圖形的數據項。在
此我們是利用JComboBox(Object[] items)來建立有圖像的JComboBox,我們所傳進去的Object Array不應該只有文字,而必須連圖標一
并傳入。我們修改上個范例修改如下:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox5
{
String[] s = {"西瓜","蘋果","草莓","香蕉","葡萄"};
ImageIcon[] icons = new ImageIcon[5];;
public JComboBox5()
{
JFrame f = new JFrame("JComboBox");
Container contentPane = f.getContentPane();
ItemObj[] obj = new ItemObj[5];
for(int i=0; i < 5; i++)
{
icons[i] = new ImageIcon(".\\icons\\fruit"+(i+1)+".jpg");
obj[i] = new ItemObj(s[i],icons[i]);
}
JComboBox combo = new JComboBox(obj);//利用ItemObj Array obj當作是JComboBox的參數傳入,構造出JComboBox.
combo.setBorder(BorderFactory.createTitledBorder("您喜歡吃哪些水果?"));
combo.setRenderer(new ACellRenderer());
combo.setMaximumRowCount(3);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[])
{
new JComboBox5();
}
}
class ItemObj
{
String name;
ImageIcon icon;
public ItemObj(String name, ImageIcon icon){
this.name = name;
this.icon = icon;
}
}
class ACellRenderer extends JLabel implements ListCellRenderer
{
ACellRenderer()
{
setOpaque(true);
}
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (value != null)
{
setText(((ItemObj)value).name);
setIcon(((ItemObj)value).icon);
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
你可以發現,第一欄顯示有圖標顯示出來了。當然你也可以利用ComboBoxModel方式來構造出有圖標的JComboBox.我們來看下面
的例子:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox6{
String[] s={"西瓜","蘋果","草莓","香蕉","葡萄"};
ImageIcon[] icons=new ImageIcon[5];
public JComboBox6(){
JFrame f=new JFrame("JComboBox");
Container contentPane=f.getContentPane();
for(int i=0; i < 5; i++)
{
icons[i] = new ImageIcon(".\\icons\\fruit"+(i+1)+".jpg");
}
ComboBoxModel mode=new AModel();
JComboBox combo=new JComboBox(mode);
combo.setBorder(BorderFactory.createTitledBorder("您喜歡吃哪些水果?"));
combo.setRenderer(new ACellRenderer());
combo.setMaximumRowCount(3);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox6();
}
/*我們用JComboBox(ComboBoxModel aModel)來構造圖標的JComboBox,因此我們在程序中編寫一個繼承DefaultComboBoxModel的
ComboBoxModel.
*/
class AModel extends DefaultComboBoxModel{
AModel(){
for (int i=0;i<s.length;i++){
ItemObj obj=new ItemObj(s[i],icons[i]);
addElement(obj);
}
}
}
}
class ItemObj
{
String name;
ImageIcon icon;
public ItemObj(String name, ImageIcon icon){
this.name = name;
this.icon = icon;
}
}
class ACellRenderer extends JLabel implements ListCellRenderer
{
ACellRenderer()
{
setOpaque(true);
}
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (value != null)
{
setText(((ItemObj)value).name);
setIcon(((ItemObj)value).icon);
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
我們用JComboBox(ComboBoxModel aModel)來構造圖標的JComboBox,因此我們在程序中編寫一個繼承DefaultComboBoxModel的
ComboBoxModel.
7-4-4:建立可自行輸入的JComboBox:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox7
{
String[] fontsize = {"12","14","16","18","20","22","24","26","28"};
String defaultMessage = "請選擇或直接輸入文字大小!";
public JComboBox7()
{
JFrame f = new JFrame("JComboBox");
Container contentPane = f.getContentPane();
JComboBox combo = new JComboBox(fontsize);
combo.setBorder(BorderFactory.createTitledBorder("請選擇你要的文字大小"));
combo.setEditable(true);//將JComboBox設成是可編輯的.
ComboBoxEditor editor = combo.getEditor();//getEditor()方法返回ComboBoxEditor對象,如果你查看手冊,你就會發
//現ComboBoxEditor是個接口(interface),因此你可以自行實作這個接口,制作自己想要的ComboBoxEditor組件。但通常
//我們不需要這么做,因為默認的ComboBoxEditor是使用JTextField,這已經足夠應付大部份的情況了。
//configureEditor()方法會初始化JComboBox的顯示項目。例如例子中一開始就出現:"請選擇或直接輸入文字大小!"這個
//字符串。
combo.configureEditor(editor, defaultMessage);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[])
{
new JComboBox7();
}
}
7-4-5:JComboBox的事件處理:
JComboBox的事件處理亦可分為兩種,一種是取得用戶選取的項目;另一種是用戶在JComboBox上自行輸入完畢后按下[Enter]鍵,
運作相對應的工作。對于第一種事件的處理,我們使用ItemListener.對于第二種事件的處理,我們使用ActionListener.
這個范例用戶可以選取所要的字號,字號的變化會呈現在JLabel上,并可讓用戶自行輸入字體的大小。當用戶按下[Enter]鍵后
,若用戶輸入的值不在選項上時,此輸入值會增加至JComboBox中,并將輸入字體的大小顯示在JLabel上。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox8 implements ItemListener,ActionListener{
String[] fontsize={"12","14","16","18","20","22","24","26","28"};
String defaultMessage="請選擇或直接輸入文字大小!";
Font font=null;
JComboBox combo=null;
JLabel label=null;
public JComboBox8(){
JFrame f=new JFrame("JComboBox");
Container contentPane=f.getContentPane();
contentPane.setLayout(new GridLayout(2,1));
label=new JLabel("Swing",JLabel.CENTER);
font=new Font("SansSerif",Font.PLAIN,12);
label.setFont(font);
combo=new JComboBox(fontsize);
combo.setBorder(BorderFactory.createTitledBorder("請選擇你要的文字大小:"));
combo.setEditable(true);
ComboBoxEditor editor=combo.getEditor();
combo.configureEditor(editor,defaultMessage);
combo.addItemListener(this);0
combo.addActionListener(this);
contentPane.add(label);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox8();
}
public void actionPerformed(ActionEvent e){
boolean isaddItem=true;
int fontsize=0;
String tmp=(String)combo.getSelectedItem();
//判斷用戶所輸入的項目是否有重復,若有重復則不增加到JComboBox中。
try{
fontsize=Integer.parseInt(tmp);
for(int i=0;i<combo.getItemCount();i++){
if (combo.getItemAt(i).equals(tmp)){
isaddItem=false;
break;
}
}
if (isaddItem){
combo.insertItemAt(tmp,0);//插入項目tmp到0索引位置(第一列中).
}
font=new Font("SansSerif",Font.PLAIN,fontsize);
label.setFont(font);
}catch(NumberFormatException ne){
combo.getEditor().setItem("你輸入的值不是整數值,請重新輸入!");
}
}
public void itemStateChanged(ItemEvent e){//ItemListener界面只有itemStateChanged()一個方法,在此實作它。
if (e.getStateChange()==ItemEvent.SELECTED){//當用戶的選擇改變時,則在JLabel上會顯示出Swing目前字形大小信息.
int fontsize=0;
try{
fontsize=Integer.parseInt((String)e.getItem());
label.setText("Swing 目前字形大小:"+fontsize);
}catch(NumberFormatException ne){//若所輸入的值不是整數,則不作任何的操作.
}
}
}
}
HttpClient 是 Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,并且它支持 HTTP 協議最新的版本和建議。本文首先介紹 HTTPClient,然后根據作者實際工作經驗給出了一些常見問題的解決方法。 HttpClient簡介 HTTP 協議可能是現在 Internet 上使用得最多、最重要的協議了,越來越多的 Java 應用程序需要直接通過 HTTP 協議來訪問網絡資源。雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協議的基本功能,但是對于大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,并且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient,更多使用 HttpClient 的應用可以參見http://wiki.apache.org/jakarta-httpclient/HttpClientPowered。HttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 版本是在 2005.10.11 發布的 3.0 RC4 。
HttpClient 功能介紹
以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的主頁。
- 實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
- 支持自動轉向
- 支持 HTTPS 協議
- 支持代理服務器等
下面將逐一介紹怎樣使用這些功能。首先,我們必須安裝好 HttpClient。
HttpClient 基本功能的使用
GET 方法
使用 HttpClient 需要以下 6 個步驟:
1. 創建 HttpClient 的實例
2. 創建某種連接方法的實例,在這里是 GetMethod。在 GetMethod 的構造函數中傳入待連接的地址
3. 調用第一步中創建好的實例的 execute 方法來執行第二步中創建好的 method 實例
4. 讀 response
5. 釋放連接。無論執行方法是否成功,都必須釋放連接
6. 對得到后的內容進行處理
根據以上步驟,我們來編寫用GET方法來取得某網頁內容的代碼。
大部分情況下 HttpClient 默認的構造函數已經足夠使用。
HttpClient httpClient = new HttpClient();
創建GET方法的實例。在GET方法的構造函數中傳入待連接的地址即可。用GetMethod將會自動處理轉發過程,如果想要把自動處理轉發過程去掉的話,可以調用方法setFollowRedirects(false)。
GetMethod getMethod = new GetMethod("http://www.ibm.com/");
調用實例httpClient的executeMethod方法來執行getMethod。由于是執行在網絡上的程序,在運行executeMethod方法的時候,需要處理兩個異常,分別是HttpException和IOException。引起第一種異常的原因主要可能是在構造getMethod的時候傳入的協議不對,比如不小心將"http"寫成"htp",或者服務器端返回的內容不正常等,并且該異常發生是不可恢復的;第二種異常一般是由于網絡原因引起的異常,對于這種異常 (IOException),HttpClient會根據你指定的恢復策略自動試著重新執行executeMethod方法。HttpClient的恢復策略可以自定義(通過實現接口HttpMethodRetryHandler來實現)。通過httpClient的方法setParameter設置你實現的恢復策略,本文中使用的是系統提供的默認恢復策略,該策略在碰到第二類異常的時候將自動重試3次。executeMethod返回值是一個整數,表示了執行該方法后服務器返回的狀態碼,該狀態碼能表示出該方法執行是否成功、需要認證或者頁面發生了跳轉(默認狀態下GetMethod的實例是自動處理跳轉的)等。
//設置成了默認的恢復策略,在發生異常時候將自動重試3次,在這里你也可以設置成自定義的恢復策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
//執行getMethod
int statusCode = client.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + getMethod.getStatusLine());
}
在返回的狀態碼正確后,即可取得內容。取得目標地址的內容有三種方法:第一種,getResponseBody,該方法返回的是目標的二進制的byte流;第二種,getResponseBodyAsString,這個方法返回的是String類型,值得注意的是該方法返回的String的編碼是根據系統默認的編碼方式,所以返回的String值可能編碼類型有誤,在本文的"字符編碼"部分中將對此做詳細介紹;第三種,getResponseBodyAsStream,這個方法對于目標地址中有大量數據需要傳輸是最佳的。在這里我們使用了最簡單的getResponseBody方法。
byte[] responseBody = method.getResponseBody();
釋放連接。無論執行方法是否成功,都必須釋放連接
method.releaseConnection();
處理內容。在這一步中根據你的需要處理內容,在例子中只是簡單的將內容打印到控制臺
System.out.println(new String(responseBody));
import java.io.IOException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
public class GetSample{
public static void main(String[] args) {
//構造HttpClient的實例
HttpClient httpClient = new HttpClient();
//創建GET方法的實例
GetMethod getMethod = new GetMethod("http://www.ibm.com");
//使用系統提供的默認的恢復策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
try {
//執行getMethod
int statusCode = httpClient.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: "
+ getMethod.getStatusLine());
}
//讀取內容
byte[] responseBody = getMethod.getResponseBody();
//處理內容
System.out.println(new String(responseBody));
} catch (HttpException e) {
//發生致命的異常,可能是協議不對或者返回的內容有問題
System.out.println("Please check your provided http address!");
e.printStackTrace();
} catch (IOException e) {
//發生網絡異常
e.printStackTrace();
} finally {
//釋放連接
getMethod.releaseConnection();
}
}
}
POST方法
根據RFC2616,對POST的解釋如下:POST方法用來向目的服務器發出請求,要求它接受被附在請求后的實體,并把它當作請求隊列(Request-Line)中請求URI所指定資源的附加新子項。POST被設計成用統一的方法實現下列功能:
- 對現有資源的注釋(Annotation of existing resources)
- 向電子公告欄、新聞組,郵件列表或類似討論組發送消息
- 提交數據塊,如將表單的結果提交給數據處理過程
- 通過附加操作來擴展數據庫
調用HttpClient中的PostMethod與GetMethod類似,除了設置PostMethod的實例與GetMethod有些不同之外,剩下的步驟都差不多。在下面的例子中,省去了與GetMethod相同的步驟,只說明與上面不同的地方,并以登錄清華大學BBS為例子進行說明。
構造PostMethod之前的步驟都相同,與GetMethod一樣,構造PostMethod也需要一個URI參數,在本例中,登錄的地址是http://www.newsmth.net/bbslogin2.php。在創建了PostMethod的實例之后,需要給method實例填充表單的值,在BBS的登錄表單中需要有兩個域,第一個是用戶名(域名叫id),第二個是密碼(域名叫passwd)。表單中的域用類NameValuePair來表示,該類的構造函數第一個參數是域名,第二參數是該域的值;將表單所有的值設置到PostMethod中用方法setRequestBody。另外由于BBS登錄成功后會轉向另外一個頁面,但是HttpClient對于要求接受后繼服務的請求,比如POST和PUT,不支持自動轉發,因此需要自己對頁面轉向做處理。具體的頁面轉向處理請參見下面的"自動轉向"部分。代碼如下
String url = "http://www.newsmth.net/bbslogin2.php";
PostMethod postMethod = new PostMethod(url);
// 填入各個表單域的值
NameValuePair[] data = { new NameValuePair("id", "youUserName"),
new NameValuePair("passwd", "yourPwd") };
// 將表單的值放入postMethod中
postMethod.setRequestBody(data);
// 執行postMethod
int statusCode = httpClient.executeMethod(postMethod);
// HttpClient對于要求接受后繼服務的請求,象POST和PUT等不能自動處理轉發
// 301或者302
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
// 從頭中取出轉向的地址
Header locationHeader = postMethod.getResponseHeader("location");
String location = null;
if (locationHeader != null) {
location = locationHeader.getValue();
System.out.println("The page was redirected to:" + location);
} else {
System.err.println("Location field value is null.");
}
return;
}
字符編碼
某目標頁的編碼可能出現在兩個地方,第一個地方是服務器返回的http頭中,另外一個地方是得到的html/xml頁面中。
在http頭的Content-Type字段可能會包含字符編碼信息。例如可能返回的頭會包含這樣子的信息:Content-Type: text/html; charset=UTF-8。這個頭信息表明該頁的編碼是UTF-8,但是服務器返回的頭信息未必與內容能匹配上。比如對于一些雙字節語言國家,可能服務器返回的編碼類型是UTF-8,但真正的內容卻不是UTF-8編碼的,因此需要在另外的地方去得到頁面的編碼信息;但是如果服務器返回的編碼不是UTF-8,而是具體的一些編碼,比如gb2312等,那服務器返回的可能是正確的編碼信息。通過method對象的getResponseCharSet()方法就可以得到http頭中的編碼信息。
對于象xml或者html這樣的文件,允許作者在頁面中直接指定編碼類型。比如在html中會有<meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>這樣的標簽;或者在xml中會有<?xml version="1.0" encoding="gb2312"?>這樣的標簽,在這些情況下,可能與http頭中返回的編碼信息沖突,需要用戶自己判斷到底那種編碼類型應該是真正的編碼。
自動轉向
根據RFC2616中對自動轉向的定義,主要有兩種:301和302。301表示永久的移走(Moved Permanently),當返回的是301,則表示請求的資源已經被移到一個固定的新地方,任何向該地址發起請求都會被轉到新的地址上。302表示暫時的轉向,比如在服務器端的servlet程序調用了sendRedirect方法,則在客戶端就會得到一個302的代碼,這時服務器返回的頭信息中location的值就是sendRedirect轉向的目標地址。
HttpClient支持自動轉向處理,但是象POST和PUT方式這種要求接受后繼服務的請求方式,暫時不支持自動轉向,因此如果碰到POST方式提交后返回的是301或者302的話需要自己處理。就像剛才在POSTMethod中舉的例子:如果想進入登錄BBS后的頁面,必須重新發起登錄的請求,請求的地址可以在頭字段location中得到。不過需要注意的是,有時候location返回的可能是相對路徑,因此需要對location返回的值做一些處理才可以發起向新地址的請求。
另外除了在頭中包含的信息可能使頁面發生重定向外,在頁面中也有可能會發生頁面的重定向。引起頁面自動轉發的標簽是:<meta http-equiv="refresh" content="5; url=http://www.ibm.com/us">。如果你想在程序中也處理這種情況的話得自己分析頁面來實現轉向。需要注意的是,在上面那個標簽中url的值也可以是一個相對地址,如果是這樣的話,需要對它做一些處理后才可以轉發
處理HTTPS協議
HttpClient提供了對SSL的支持,在使用SSL之前必須安裝JSSE。在Sun提供的1.4以后的版本中,JSSE已經集成到JDK中,如果你使用的是JDK1.4以前的版本則必須安裝JSSE。JSSE不同的廠家有不同的實現。下面介紹怎么使用HttpClient來打開Https連接。這里有兩種方法可以打開https連接,第一種就是得到服務器頒發的證書,然后導入到本地的keystore中;另外一種辦法就是通過擴展HttpClient的類來實現自動接受證書。
方法1,取得證書,并導入本地的keystore:
- 安裝JSSE (如果你使用的JDK版本是1.4或者1.4以上就可以跳過這一步)。本文以IBM的JSSE為例子說明。先到IBM網站上下載JSSE的安裝包。然后解壓開之后將ibmjsse.jar包拷貝到<java-home>\lib\ext\目錄下。
- 取得并且導入證書。證書可以通過IE來獲得:
1. 用IE打開需要連接的https網址,會彈出如下對話框:

2. 單擊"View Certificate",在彈出的對話框中選擇"Details",然后再單擊"Copy to File",根據提供的向導生成待訪問網頁的證書文件

3. 向導第一步,歡迎界面,直接單擊"Next",

4. 向導第二步,選擇導出的文件格式,默認,單擊"Next",

5. 向導第三步,輸入導出的文件名,輸入后,單擊"Next",

6. 向導第四步,單擊"Finish",完成向導

7. 最后彈出一個對話框,顯示導出成功

-
用keytool工具把剛才導出的證書倒入本地keystore。Keytool命令在<java-home>\bin\下,打開命令行窗口,并到<java-home>\lib\security\目錄下,運行下面的命令:
- keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourEntry1 -file your.cer
其中參數alias后跟的值是當前證書在keystore中的唯一標識符,但是大小寫不區分;參數file后跟的是剛才通過IE導出的證書所在的路徑和文件名;如果你想刪除剛才導入到keystore的證書,可以用命令:
- keytool -delete -keystore cacerts -storepass changeit -alias yourEntry1
- 寫程序訪問https地址。如果想測試是否能連上https,只需要稍改一下GetSample例子,把請求的目標變成一個https地址。
GetMethod getMethod = new GetMethod(https://www.yourdomain.com);
運行該程序可能出現的問題:
-
1. 拋出異常java.net.SocketException: Algorithm SSL not available。出現這個異常可能是因為沒有加JSSEProvider,如果用的是IBM的JSSE Provider,在程序中加入這樣的一行:
if(Security.getProvider("com.ibm.jsse.IBMJSSEProvider") == null)
Security.addProvider(new IBMJSSEProvider());
或者也可以打開<java-home>\lib\security\java.security,在行
-
security.provider.1=sun.security.provider.Sun
security.provider.2=com.ibm.crypto.provider.IBMJCE
后面加入security.provider.3=com.ibm.jsse.IBMJSSEProvider
2. 拋出異常java.net.SocketException: SSL implementation not available。出現這個異常可能是你沒有把ibmjsse.jar拷貝到<java-home>\lib\ext\目錄下。
3. 拋出異常javax.net.ssl.SSLHandshakeException: unknown certificate。出現這個異常表明你的JSSE應該已經安裝正確,但是可能因為你沒有把證書導入到當前運行JRE的keystore中,請按照前面介紹的步驟來導入你的證書。
方法2,擴展HttpClient類實現自動接受證書
因為這種方法自動接收所有證書,因此存在一定的安全問題,所以在使用這種方法前請仔細考慮您的系統的安全需求。具體的步驟如下:
- 提供一個自定義的socket factory(test.MySecureProtocolSocketFactory)。這個自定義的類必須實現接口org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory,在實現接口的類中調用自定義的X509TrustManager(test.MyX509TrustManager),這兩個類可以在隨本文帶的附件中得到
- 創建一個org.apache.commons.httpclient.protocol.Protocol的實例,指定協議名稱和默認的端口號
Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory (), 443);
- 注冊剛才創建的https協議對象
Protocol.registerProtocol("https ", myhttps);
-
- 然后按照普通編程方式打開https的目標地址,代碼請參見test.NoCertificationHttpsGetSample
處理代理服務器
HttpClient中使用代理服務器非常簡單,調用HttpClient中setProxy方法就可以,方法的第一個參數是代理服務器地址,第二個參數是端口號。另外HttpClient也支持SOCKS代理。
httpClient.getHostConfiguration().setProxy(hostName,port);
結論
從上面的介紹中,可以知道HttpClient對http協議支持非常好,使用起來很簡單,版本更新快,功能也很強大,具有足夠的靈活性和擴展性。對于想在Java應用中直接訪問http資源的編程人員來說,HttpClient是一個不可多得的好工具。
package client.test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.AndFilter;
import org.htmlparser.filters.HasAttributeFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
public class TIPOTest {
private static String mainPageUrl = "http://twpat.tipo.gov.tw/twcgi/ttsweb?@0:0:1:twpat2@@"
+ Math.random();
public static String strEncoding = "utf-8"; //strEncoding = "iso-8859-1";
final static String userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)";
public static void main(String[] args) throws Exception {
// HttpClient client = new HttpClient();
// client.getHostConfiguration().setProxy("10.41.16.98", 808);
// client.getHostConfiguration().setHost(
// "http://twpat3.tipo.gov.tw/tipotwoc", 80, "http");
// String strLegalStatus = null;
String mainPage = doGetMethod(mainPageUrl);
String searchPageUrl = parserMainPage(mainPage);
String searchPage = doGetMethod(searchPageUrl);
String queryPageUrl = getQueryPageUrl(searchPage);
String queryPage = doGetMethod(queryPageUrl);
String searchResultUrl = parserPageForPostURL(queryPage);
Map parameterMap = parseParameters(queryPage);
System.out.println("searchResultUrl : " + searchResultUrl);
String response = getResultPage(searchResultUrl, "087121847",
parameterMap);
// HttpMethod method = getPostMethod(); // 使用 POST 方式提交數據
// // HttpMethod method = getGetMethod(); // 使用 GET 方式提交數據
// client.executeMethod(method); // 打印服務器返回的狀態
// System.out.println(method.getStatusLine()); // 打印結果頁面
// String response = new
// String(method.getResponseBodyAsString().getBytes(
// "8859_1"));
// 打印返回的信息
System.out.println(response);
// 釋放連接
// method.releaseConnection();
}
private static String doGetMethod(String url) throws Exception {
try {
HttpClient httpClient = new HttpClient();
HostConfiguration hc = httpClient.getHostConfiguration();
hc.setProxy("10.41.16.98", 808);
httpClient.getState().setAuthenticationPreemptive(true);
GetMethod getMethod = new GetMethod(url);
getMethod.setRequestCharSet(strEncoding);
getMethod.setResponseCharSet(strEncoding);
getMethod.setRequestHeader("User-Agent", userAgent);
getMethod.setTimeout(1000 * 60 * 2);
int StatusCode = httpClient.executeMethod(getMethod);
if (StatusCode >= 400) {
getMethod.getConnection().close();
throw new Exception("Can't get information.");
}
String str = getMethod.getResponseBodyAsString();
getMethod.releaseConnection();
writeStrToTxt(str);// wuwen add for test
return str;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
static int i = 1;
// wuwen add for test
public static void writeStrToTxt(String str) {
i++;
BufferedWriter output;
try {
output = new BufferedWriter(new FileWriter(
"C:\\Documents and Settings\\F3223114\\桌面\\" + i + ".txt"));
output.write(str);
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static String parserMainPage(String body) {
String parserSearchUrl = "";
/*
* String regEx = "href=(.+?)\\s*class=menu"; Pattern pattern =
* Pattern.compile(regEx, Pattern.CASE_INSENSITIVE); Matcher matcher =
* pattern.matcher(body); while (matcher.find()) { if
* (matcher.group(1).contains(":80:::0")) { parserSearchUrl =
* matcher.group(1); } }
*/
int i = body.indexOf("http");
if (i != -1) {
parserSearchUrl = body.substring(i, body.indexOf(">", i));
}
return parserSearchUrl;
}
public String getMidString(String sInput, String sStartTag, String sEndTag) {
int i, j = -1;
if ((i = sInput.indexOf(sStartTag)) < 0) {
return null;
} else {
if ((j = sInput.indexOf(sEndTag, i + sStartTag.length())) < 0) {
return null;
} else {
if (i + sStartTag.length() == j) {
return null;
} else {
return sInput.substring(i + sStartTag.length(), j);
}
}
}
}
private static String doPostMethod(String url, NameValuePair[] nameValuePair)
throws Exception {
try {
HttpClient httpClient = new HttpClient();
HostConfiguration hc = httpClient.getHostConfiguration();
hc.setProxy("10.41.16.98", 808);
httpClient.getState().setAuthenticationPreemptive(true);
PostMethod postMethod = new PostMethod(url);
postMethod.setRequestCharSet(strEncoding);
postMethod.setResponseCharSet(strEncoding);
postMethod.setRequestHeader("User-Agent", userAgent);
postMethod.setTimeout(1000 * 60 * 2);
postMethod.setRequestBody(nameValuePair);
int StatusCode = httpClient.executeMethod(postMethod);
if (StatusCode >= 400) {
postMethod.getConnection().close();
throw new Exception("Can't get information.");
}
String str = postMethod.getResponseBodyAsString();
postMethod.releaseConnection();
return str;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
// modify by zhuoluo 2008.12.09
private static String getResultPage(String url, String strAppNo,
Map parameterMap) throws Exception {
try {
NameValuePair _0_1_T = new NameValuePair("@_0_1_T", "T_UID");
_0_1_T.setHeadNeedEncode(false);
NameValuePair _0_1_T_1 = new NameValuePair("_0_1_T", "");
_0_1_T_1.setHeadNeedEncode(false);
NameValuePair _0_2_P = new NameValuePair("@_0_2_P", "P_PWD");
_0_2_P.setHeadNeedEncode(false);
NameValuePair _0_2_P_1 = new NameValuePair("_0_2_P", "");
_0_2_P_1.setHeadNeedEncode(false);
NameValuePair _20_0_T = new NameValuePair("@_20_0_T", "T_AN");
_20_0_T.setHeadNeedEncode(false);
NameValuePair appNo = new NameValuePair("_20_0_T", strAppNo);
appNo.setHeadNeedEncode(false);
NameValuePair _20_1_K = new NameValuePair("@_20_1_K", "K_NE");
_20_1_K.setHeadNeedEncode(false);
NameValuePair _20_1_K_1 = new NameValuePair("_20_1_K", "");
_20_1_K_1.setHeadNeedEncode(false);
NameValuePair JPAGE = new NameValuePair("JPAGE", "");
JPAGE.setHeadNeedEncode(false);
NameValuePair INFO = new NameValuePair("INFO", parameterMap.get(
"INFO").toString());
INFO.setHeadNeedEncode(false);
// NameValuePair x = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2.x",
// "39");
// x.setHeadNeedEncode(false);
// NameValuePair y = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2.y",
// "10");
// y.setHeadNeedEncode(false);
NameValuePair x = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2%25z.x",
"39");
x.setHeadNeedEncode(false);
NameValuePair y = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2%25z.y",
"10");
y.setHeadNeedEncode(false);
NameValuePair[] nameValuePair = new NameValuePair[] { _0_1_T,
_0_1_T_1, _0_2_P, _0_2_P_1, _20_0_T, appNo, _20_1_K,
_20_1_K_1, x, y , INFO };
// NameValuePair[] nameValuePair = new NameValuePair[] { _0_1_T,
// _0_1_T_1, _0_2_P, _0_2_P_1, _20_0_T, appNo, _20_1_K,
// _20_1_K_1, JPAGE, INFO };//x, y ,
String str = doPostMethod(url, nameValuePair);
writeStrToTxt(str);// wuwen add for test
return str;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private static String getQueryPageUrl(String body) {
Parser parser = Parser.createParser(body, "utf-8");
NodeFilter filter_constellation_summart = new AndFilter(
(new TagNameFilter("a")), (new HasAttributeFilter("class",
"menu")));
NodeList nodeList = null;
String href = "";
try {
nodeList = parser
.extractAllNodesThatMatch(filter_constellation_summart);
Node node = null;
node = nodeList.elementAt(3);
href = ((TagNode) node).getAttribute("href");
} catch (ParserException ex) {
}
return href;
}
private static String parserPageForPostURL(String body)
throws ParserException {
String searchResultUrl = "";
NodeFilter tagFilter = new TagNameFilter("FORM");
Parser parser = Parser.createParser(body, "UTF-8");
NodeList list = parser.extractAllNodesThatMatch(tagFilter);
for (int i = 0; i < list.size(); i++) {
Node node = list.elementAt(i);
if (node instanceof TagNode) {
String methodValue = ((TagNode) node).getAttribute("METHOD");
if (methodValue != null) {
String actionPath = ((TagNode) node).getAttribute("ACTION");
searchResultUrl = actionPath;
}
}
}
return searchResultUrl;
}
private static Map<String, String> parseParameters(String pageBody)
throws ParserException {
Map<String, String> parameterMap = new HashMap<String, String>();
NodeFilter tagFilter = new TagNameFilter("input");
Parser parser = Parser.createParser(pageBody, "UTF-8");
NodeList list = parser.extractAllNodesThatMatch(tagFilter);
for (int i = 0; i < list.size(); i++) {
Node node = list.elementAt(i);
if (node instanceof TagNode) {
String nodeName = ((TagNode) node).getAttribute("name");
String nodeValue = ((TagNode) node).getAttribute("value");
if (nodeName != null) {
if (nodeName.equals("INFO")) {
if (nodeValue == null) {
nodeValue = "";
}
parameterMap.put(nodeName, nodeValue);
}
}
}
}
return parameterMap;
}
}
TRUNC函數用于對值進行截斷。
用法有兩種:TRUNC(NUMBER)表示截斷數字,TRUNC(date)表示截斷日期。
(1)截斷數字:
格式:TRUNC(n1,n2),n1表示被截斷的數字,n2表示要截斷到那一位。n2可以是負數,表示截斷小數點前。注意,TRUNC截斷不是四舍五入。
SQL> select TRUNC(15.79) from dual;
TRUNC(15.79)
------------
15
SQL> select TRUNC(15.79,1) from dual;
TRUNC(15.79,1)
--------------
15.7
SQL> select trunc(15.79,-1) from dual;
TRUNC(15.79,-1)
---------------
10
(2)截斷日期:
先執行命令:alter session set nls_date_format='yyyy-mm-dd hh24:mi:hh';
截取今天:
SQL> select sysdate,trunc(sysdate,'dd') from dual;
SYSDATE TRUNC(SYSDATE,'DD')
------------------- -------------------
2009-03-24 21:31:17 2009-03-24 00:00:00
截取本周第一天:
SQL> select sysdate,trunc(sysdate,'d') from dual;
SYSDATE TRUNC(SYSDATE,'D')
------------------- -------------------
2009-03-24 21:29:32 2009-03-22 00:00:00
截取本月第一天:
SQL> select sysdate,trunc(sysdate,'mm') from dual;
SYSDATE TRUNC(SYSDATE,'MM')
------------------- -------------------
2009-03-24 21:30:30 2009-03-01 00:00:00
截取本年第一天:
SQL> select sysdate,trunc(sysdate,'y') from dual;
SYSDATE TRUNC(SYSDATE,'Y')
------------------- -------------------
2009-03-24 21:31:57 2009-01-01 00:00:00
截取到小時:
SQL> select sysdate,trunc(sysdate,'hh') from dual;
SYSDATE TRUNC(SYSDATE,'HH')
------------------- -------------------
2009-03-24 21:32:59 2009-03-24 21:00:00
截取到分鐘:
SQL> select sysdate,trunc(sysdate,'mi') from dual;
SYSDATE TRUNC(SYSDATE,'MI')
------------------- -------------------
2009-03-24 21:33:32 2009-03-24 21:33:00
獲取上月第一天:
SQL> select TRUNC(add_months(SYSDATE,-1),'MM') from dual
RMI原理及實現
簡介
RMI是遠程方法調用的簡稱,象其名稱暗示的那樣,它能夠幫助我們查找并執行遠程對象的方法。通俗地說,遠程調用就象將一個class放在A機器上,然后在B機器中調用這
個class的方法。
我個人認為,盡管RMI不是唯一的企業級遠程對象訪問方案,但它卻是最容易實現的。與能夠使不同編程語言開發的CORBA不同的是,RMI是一種純Java解決方案。在RMI
中,程序的所有部分都由Java編寫。
在看本篇文章時,我假定讀者都已經具備了較扎實的Java基礎知識,在這方面有欠缺的讀者請自行閱讀有關資料。
概念
我在前面已經提到,RMI是一種遠程方法調用機制,其過程對于最終用戶是透明的:在進行現場演示時,如果我不說它使用了RNI,其他人不可能知道調用的方法存儲在其他
機器上。當然了,二臺機器上必須都安裝有Java虛擬機(JVM)。
其他機器需要調用的對象必須被導出到遠程注冊服務器,這樣才能被其他機器調用。因此,如果機器A要調用機器B上的方法,則機器B必須將該對象導出到其遠程注冊服務器
。注冊服務器是服務器上運行的一種服務,它幫助客戶端遠程地查找和訪問服務器上的對象。一個對象只有導出來后,然后才能實現RMI包中的遠程接口。例如,如果想使機器A
中的Xyz對象能夠被遠程調用,它就必須實現遠程接口。
RMI需要使用占位程序和框架,占位程序在客戶端,框架在服務器端。在調用遠程方法時,我們無需直接面對存儲有該方法的機器。
在進行數據通訊前,還必須做一些準備工作。占位程序就象客戶端機器上的一個本機對象,它就象服務器上的對象的代理,向客戶端提供能夠被服務器調用的方法。然
后,Stub就會向服務器端的Skeleton發送方法調用,Skeleton就會在服務器端執行接收到的方法。
Stub和Skeleton之間通過遠程調用層進行相互通訊,遠程調用層遵循TCP/IP協議收發數據。下面我們來大致了解一種稱為為“綁定”的技術。
客戶端無論何時要調用服務器端的對象,你可曾想過他是如何告訴服務器他想創建什么樣的對象嗎?這正是“綁定”的的用武之地。在服務器端,我們將一個字符串變量與一個
對象聯系在一起(可以通過方法來實現),客戶端通過將那個字符串傳遞給服務器來告訴服務器它要創建的對象,這樣服務器就可以準確地知道客戶端需要使用哪一個對象了。所
有這些字符串和對象都存儲在的遠程注冊服務器中。
在編程中需要解決的問題
在研究代碼之前,我們來看看必須編寫哪些代碼:
遠程對象:這個接口只定義了一個方法。我們應當明白的是,這個接口并非總是不包括方法的代碼而只包括方法的定義。遠程對象包含要導出的每個方法的定義,它還實
現Java.rmi中的遠程接口。
遠程對象實現:這是一個實現遠程對象的類。如果實現了遠程對象,就能夠覆蓋該對象中的所有方法,因此,遠程對象的實現類將真正包含我們希望導出的方法的代碼。
遠程服務器:這是一個作為服務器使用的類,它是相對于要訪問遠程方法的客戶端而言的。它存儲著綁定的字符串和對象。
遠程客戶端:這是一個幫助我們訪問遠程方法提供幫助的類,它也是最終用戶。我們將使用查找和調用遠程方法的方法在該類中調用遠程方法。
編程
我們將首先編寫遠程對象,并將代碼保存為名字為AddServer.Java的文件:
import Java.rmi.*;
public interface AddServer extends Remote {
public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException;
}
我們來看看上面的代碼。首先,為了使用其內容,我們導入rmi包。然后,我們創建一個擴展了Java.rmi中遠程接口的接口。所有的遠程對象必須擴展該遠程接口,我們將該
遠程接口稱為AddServer。在該遠程對象中,有一個名字為AddNumbers的方法,客戶端可以調用這一方法。我們必須記住的是,所有的遠程方法都需要啟
動RemoteException方法,有錯誤發生時就會調用該方法。
下面我們開始編寫遠程對象的實現。這是一個實現遠程對象并包含有所有方法代碼的類,將下面的代碼保存為名字為AddServerImpl.Java的文件:
import Java.rmi.*;
public class AddServerImpl extends UnicastRemoteObject implements AddServer {
public AddServerImpl() {
super();
}
public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException {
return firstnumber + secondnumber;
}
}
首先,我們導入rmi包,然后創建一個擴展UnicastRemoteObject和實現創建的遠程對象的類;其次,我們可以為類創建一個缺省的構建器。我們還了解了AddNumbers方
法的代碼,它啟動RemoteException。這樣我們就覆蓋了創建的遠程對象中的方法。AddNumbers方法的代碼非常好理解,它接受2個整型參數,然后相加并返回它們的和。
至此,我們已經有了二個Java文件:遠程對象和遠程對象的實現。下面我們將使用Javac命令編譯這二個文件:
編譯遠程對象:
C:\jdk\bin\Javac workingdir\AddServer.Java
編譯遠程對象實現:
C:\jdk\bin\Javac workingdir\AddServerImpl.Java 這樣,就會達到二個Java文件和二個類文件,下面我們將創建stub和skeleton。為了創建stub和skeleton文件,
我們必須使用rmic編譯器編譯遠程對象實現文件。
用Rmic編譯遠程對象實現文件:
C:\jdk\bin\rmic workingdir\AddServerImpl.Java 然后,我們就會發現多了2個新建的類文件,它們分別是AddServerImpl_Stub.class
和AddServerImpl_Skel.class 。
The Coding (Contd.)
我們已經編譯了所有的源代碼,下面我們來創建客戶端和服務器端,將下面的代碼保存為名字為RmiServer.Java的文件:
import Java.rmi.*;
import Java.net.*;
public class RmiServer {
public static void main (String args[]) throws RemoteException, MalformedURLException {
AddServerImpl add = new AddServerImpl();
Naming.rebind("addnumbers",add);
}
}
首先,我們導入Java.rmi包和Java.net包。另外,我們還使用throws從句捕獲任何異常。我們從對象中得出遠程對象實現,使用rebind方法將字符串addnumbers與該對
象綁定。下面的例子顯示了綁定的含義:
從現在開始,無論何時客戶端要調用遠程對象,使用字符串addnumbers就可以實現。rebind方法有二個參數:第一個參數是字符串變量,第二個參數是遠程對象實現類的
對象。
下面我們來創建客戶端,將下面的代碼保存為名字為RmiClient.Java的文件:
import Java.rmi.*;
import Java.net.*;
public class RmiClient {
public static void main(String args[]) throws RemoteException, MalformedURLException {
String url="rmi://127.0.0.1/addnumbers";
AddServer add;
add = (AddServer)Naming.lookup(url);
int result = add.AddNumbers(10,5);
System.out.println(result);
}
}
首先,我們導入Java.rmi包和Java.net包,并使用throws從句捕獲所有必要的異常。然后通過利用Naming類中的靜態lookup方法從遠程對象中得到一個對象。(這也是我
們無需從Naming類中得到一個對象并調用它。而只使用類名字的原因。)
lookup方法接受遠程對象的完整的URL名字,該URL由完整的機器IP地址以及與對象綁定的字符串(也誻對象的綁定名)組成。在調用遠程對象時,我們使用了RMI協
議。lookup方法向我們返回一個對象,在能夠使用它前,我們必須將它的數據類型轉換為與遠程對象的數據類型一致。
Since we have both our server and client source ready, let's compile them both:
至此,我們已經有了服務器端和客戶端的源代碼,下面我們來編譯這二個源文件:
編譯遠程服務器:
C:\jdk\bin\Javac workingdir\RmiServer.Java
編譯遠程客戶端:
C:\jdk\bin\Javac workingdir\RmiClient.Java
在對我們的代碼進行測試前,還必須首先啟動RMI Registry。RMI Registry存儲有所有綁定的數據,沒有它,RMI就不能正常地運行!
啟動Rmi Registry服務器:
C:\jdk\bin\start rmiregistry
我們會注意到,這時會出現一個空白的DOS提示符窗口,這表明Rmi Registry服務器在運行,注意不要關閉該窗口。然后,我們首先在一個DOS提示符窗口中運行Rmi服務
器,然后在另一個DOS提示符窗口中運行Rmi客戶端。
啟動RMI服務器:
C:\jdk\bin\Java workingdir\RmiServer
啟動RMI客戶端:
C:\jdk\bin\Java workingdir\RmiClient
如果一切正常,我們應該能夠得到15這個輸出。我們向AddNumbers方法輸入10和5二個數字,該方法將這二者加起來,并將其和15返回給我們。如果得到了15這個輸出,
說明我們已經成功地執行了一個遠程方法。當然,在這里,我們并沒有執行真正意義上的遠程方法,因為我們的計算機既是服務器,又是客戶機。如果有計算機網絡,我們就可以
方便地進行執行遠程方法的試驗了。
將JTable的單元格設置為不可編輯,有兩種方法。
一種是自己寫一個MyTable類繼承DefaultTableModel,重寫其中的isCellEditable方法;
還有一種是在創建JTable對象時, JTable treeTable = new JTable(tableModel){ public boolean isCellEditable(int row, int column) { return false; }};
一.創建表格控件的各種方式:
1) 調用無參構造函數.
JTable table = new JTable();
2) 以表頭和表數據創建表格.
Object[][] cellData = {{"row1-col1", "row1-col2"},{"row2-col1", "row2-col2"}};
String[] columnNames = {"col1", "col2"};
JTable table = new JTable(cellData, columnNames);
3) 以表頭和表數據創建表格,并且讓表單元格不可改.
String[] headers = { "表頭一", "表頭二", "表頭三" };
Object[][] cellData = null;
DefaultTableModel model = new DefaultTableModel(cellData, headers) {
public boolean isCellEditable(int row, int column) {
return false;
}
};
table = new JTable(model);
二.對表格列的控制
0)獲取JTable中特定單元格的位置
table.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
int row = jt.rowAtPoint(e.getPoint());
int col = jt.columnAtPoint(e.getPoint());
}
});
1) 設置列不可隨容器組件大小變化自動調整寬度.
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
2) 限制某列的寬度.
TableColumn firsetColumn = table.getColumnModel().getColumn(0);
firsetColumn.setPreferredWidth(30);
firsetColumn.setMaxWidth(30);
firsetColumn.setMinWidth(30);
3) 設置當前列數.
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int count=5;
tableModel.setColumnCount(count);
4) 取得表格列數
int cols = table.getColumnCount();
5) 添加列
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.addColumn("新列名");
6) 刪除列
table.removeColumn(table.getColumnModel().getColumn(columnIndex));// columnIndex是要刪除的列序號
三.對表格行的控制
1) 設置行高
table.setRowHeight(20);
2) 設置當前航數
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int n=5;
tableModel.setRowCount(n);
3) 取得表格行數
int rows = table.getRowCount();
4) 添加表格行
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.addRow(new Object[]{"sitinspring", "35", "Boss"});
5) 刪除表格行
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
model.removeRow(rowIndex);// rowIndex是要刪除的行序號
四.存取表格單元格的數據
1) 取單元格數據
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
String cellValue=(String) tableModel.getValueAt(row, column);// 取單元格數據,row是行號,column是列號
2) 填充數據到表格.
注:數據是Member類型的鏈表,Member類如下:
public class Member{
// 名稱
private String name;
// 年齡
private String age;
// 職務
private String title;
}
填充數據的代碼:
public void fillTable(List<Member> members){
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.setRowCount(0);// 清除原有行
// 填充數據
for(Member member:members){
String[] arr=new String[3];
arr[0]=member.getName();
arr[1]=member.getAge();
arr[2]=member.getTitle();
// 添加數據到表格
tableModel.addRow(arr);
}
// 更新表格
table.invalidate();
}
2) 取得表格中的數據
public List<Member> getShowMembers(){
List<Member> members=new ArrayList<Member>();
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int rowCount=tableModel.getRowCount();
for(int i=0;i<rowCount;i++){
Member member=new Member();
member.setName((String)tableModel.getValueAt(i, 0));// 取得第i行第一列的數據
member.setAge((String)tableModel.getValueAt(i, 1));// 取得第i行第二列的數據
member.setTitle((String)tableModel.getValueAt(i, 2));// 取得第i行第三列的數據
members.add(member);
}
return members;
}
五.取得用戶所選的行
1) 取得用戶所選的單行
int selectRows=table.getSelectedRows().length;// 取得用戶所選行的行數
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
if(selectRows==1){
int selectedRowIndex = table.getSelectedRow(); // 取得用戶所選單行
.// 進行相關處理
}
2) 取得用戶所選的多行
int selectRows=table.getSelectedRows().length;// 取得用戶所選行的行數
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
if(selectRows>1)
int[] selRowIndexs=table.getSelectedRows();// 用戶所選行的序列
for(int i=0;i<selRowIndexs.length;i++){
// 用tableModel.getValueAt(row, column)取單元格數據
String cellValue=(String) tableModel.getValueAt(i, 1);
}
}
六.添加表格的事件處理
view.getTable().addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) {
// 鼠標按下時的處理
}
public void mouseReleased(MouseEvent e) {
// 鼠標松開時的處理
}
public void mouseEntered(MouseEvent e) {
// 鼠標進入表格時的處理
}
public void mouseExited(MouseEvent e) {
// 鼠標退出表格時的處理
}
public void mouseClicked(MouseEvent e) {
// 鼠標點擊時的處理
}
});
|